Repository navigation
feat!: adopt stream-chat's branded TimestampNS - #3822
Merged
Merged
Conversation
stream-chat now types every server-sent date as `TimestampNS` and makes `new Date(timestamp)` a compile error (GetStream/stream-chat-js#1884). - offline DB read mappers brand stored integers: `mapStorableToTimestamp` returns `TimestampNS`, and the new `mapStorableToRequiredTimestamp` replaces the `?? 0` fallbacks - `findInMessagesByDate`, `getChannelUnreadState`, `deleteMessagesForChannel` and `useIsChannelMuted` (now core's `ChannelMuteStatus`) carry `TimestampNS` - mock builders mint branded timestamps; tests brand epoch/derived literals, and the reaction-group fixtures use nanoseconds instead of ISO strings - SampleApp / ExpoMessaging timestamp props and event templates carry `TimestampNS` - remove the write-only `lastReadRef` in `Channel` and the unused `utils/date.ts` - migration doc describes the brand, the Date guard and its gaps BREAKING CHANGE: `findInMessagesByDate` takes a `TimestampNS`, and `getChannelUnreadState` / `useIsChannelMuted` return `TimestampNS` timestamps.
1 of 2 tasks
oliverlaz
requested review from
isekovanic and
szuperaz
and removed request for
szuperaz
September 23, 2026 14:07
szuperaz
approved these changes
Sep 23, 2026
oliverlaz
added a commit
to GetStream/stream-chat-js
that referenced
this pull request
Sep 24, 2026
…guard (#1884) ## CLA - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required). - [x] Code changes are tested ## Description of the changes, What, Why and How? Follow-up to GetStream/chat#17356 ([REACT-1179](https://linear.app/stream/issue/REACT-1179)), which taught the TypeScript generator to type every server-sent date as `TimestampNS`, a branded unix-nanosecond `number`, and to emit `models/timestamp-guard.d.ts`, which makes `new Date(timestamp)` a compile error. This PR regenerates the client with it and closes the gaps the brand exposed. **Why:** a nanosecond timestamp is out of `Date`'s range, so the natural `new Date(message.created_at)` silently yields an Invalid Date that throws later from `.toISOString()` or renders "Invalid Date". Until now nothing flagged it at compile time. ### Time helpers (`src/utils/time.ts`) - `nowNs`, `msToNs` and `dateToNs` return `TimestampNS`. - `nsToDate`, `nsToRfc3339` and `convertTimestampToDate` require a `TimestampNS`, so `nsToDate(Date.now())` no longer compiles instead of returning a 1970 date. - `nsToMs` keeps taking a plain `number`, because it also converts durations (the difference of two timestamps). - New `asTimestampNS(n)` brands a number that is already in nanoseconds (DB rows, fixtures, the epoch `asTimestampNS(0)`). It is exported from the root. ### Hand-written types carry the brand Plain `number` would drop the brand, so `new Date(channel.state.read[id].last_read)` would still compile. These are now `TimestampNS`: - read state (`last_read`, `last_delivered_at`), mute status, `lastRead()` / `countUnread()` - thread state, poll `lastActivityAt`, reminder state and `timeLeftMs` - the receipts tracker and delivery reporter - the paginators (`lastMessageAt`, unread snapshot, `truncate`, deletion, `findItemByTimestamp`) - the `LocalEvent` / `ConnectedEvent` timestamps, the cooldown timer, the composer audit clock, and offline `truncated_at` ### Shipping the guard to consumers The client is regenerated from GetStream/chat master, which since GetStream/chat#17460 emits the guard as `models/timestamp-guard.ts`, a module `tsc` compiles into `dist/types` like any other file. No copy step is needed. Emitting it is not enough on its own, though: a global augmentation only loads for a consumer if the entry point's type graph imports it. So: - `src/index.ts` re-exports the guard type-only (`export type {} from './gen/models/timestamp-guard'`). Declaration emit keeps it, and esbuild erases it, so there is no runtime import of an empty module. Without this line the guard is still emitted but never loads for a consumer; I checked, and the dist type check below then fails. - `test/types/timestamps.ts` holds compile-time assertions, run twice: - against `src` by `yarn types`, which **this PR adds to PR CI**; - against the built `dist`, resolved through `exports` exactly as a consumer would, at the end of `yarn build`. A release whose published types lost the guard now fails. ### Other timestamp fixes found while auditing - **`pinMessage`**: a `number` there means seconds, so passing `message.pinned_at` compiled and threw at runtime. Both parameters now reject a `TimestampNS`. - **Offline read state**: `handleRead` persisted `received_at` (the local clock at receipt) as `last_read`. For a mark-unread that is "now", which is past the messages just marked unread, so a cold start from SQLite lost the unread boundary. It now writes what `Channel` writes: `last_read_at` for a mark-unread, `created_at` otherwise. - **`rate_limit_reset`**: `new Date('1700000000')` is an Invalid Date. The header is now read as unix seconds. - The dev burst simulator and several stale test fixtures used ISO strings or `Date`s; they now use nanoseconds. ### Unrelated items in the regen These come from the current spec, not from the timestamp work: - `translateMessage` now returns `TranslateMessageResponse`. - `updatePoll` forwards `team`. - `appeal` forwards `channel_cid`. The generator's compact-interface change (#17356) also makes the `src/gen/models/index.ts` diff mostly whitespace. ### Verification - `yarn types` (src, scripts, type assertions), `yarn lint`, `yarn test` (3967 passing) and `yarn build` (including the dist type check) all pass after rebasing on `release-v10`. - I checked that the assertions aren't hollow: against a stale build, the `pinMessage` assertions fail with "Unused `@ts-expect-error`". - I ran stream-chat-react's Vite example and stream-chat-react-native's SampleApp (iOS simulator) against this build. The channel list, message list, date separators, threads, drafts and search all render correct dates. The RN offline database holds integer nanoseconds and hydrates correctly on a cold start. Related PRs: - GetStream/stream-chat-react#3297 and GetStream/stream-chat-react-native#3822 adopt this. Both need a stream-chat release that includes it. - GetStream/chat#17425 lists `asTimestampNS` in the generator template; this PR's generated file already carries the same line. ## Changelog - **BREAKING:** server-sent timestamps are typed `TimestampNS` (a branded `number`). Constructing a response-shaped object needs `nowNs()` / `msToNs()` / `dateToNs()` / `asTimestampNS()`, and `new Date(timestamp)` no longer compiles. - **BREAKING:** `nsToDate`, `nsToRfc3339` and `convertTimestampToDate` require a `TimestampNS`; `nowNs`, `msToNs` and `dateToNs` return one. - **BREAKING:** `client.pinMessage` rejects a server timestamp where a number means a seconds offset. - New `asTimestampNS` helper. - Fix: offline read state persists the server read timestamp instead of the local receipt time. - Fix: `rate_limit_reset` is parsed from unix seconds instead of producing an Invalid Date.
github-actions Bot
pushed a commit
to GetStream/stream-chat-js
that referenced
this pull request
Sep 24, 2026
## [10.0.0-rc.14](v10.0.0-rc.13...v10.0.0-rc.14) (2026-09-24) ### ⚠ BREAKING CHANGES * brand server-sent timestamps as TimestampNS and ship the Date guard (#1884) ### Features * brand server-sent timestamps as TimestampNS and ship the Date guard ([#1884](#1884)) ([e51198a](e51198a)), closes [GetStream/chat#17356](https://github.com/GetStream/chat/issues/17356) [GetStream/chat#17460](https://github.com/GetStream/chat/issues/17460) [#17356](https://github.com/GetStream/stream-chat-js/issues/17356) [GetStream/stream-chat-react#3297](GetStream/stream-chat-react#3297) [GetStream/stream-chat-react-native#3822](GetStream/stream-chat-react-native#3822) [GetStream/chat#17425](https://github.com/GetStream/chat/issues/17425)
rc.14 is the first release with TimestampNS, asTimestampNS and the published Date guard, which this SDK now imports. Refs: GetStream/stream-chat-js#1884
Contributor
SDK Size
|
github-actions Bot
pushed a commit
to GetStream/stream-chat-js
that referenced
this pull request
Nov 6, 2026
## [10.0.0](v9.55.0...v10.0.0) (2026-11-06) ### ⚠ BREAKING CHANGES * release v10 from master as latest (#2004) * `LocalAttachmentMetadata` requires only `id`. `file` and `uploadState` are optional, so code that reads them from a `LocalAttachment` must handle `undefined`. A `file` must come with its `uploadState`: `localMetadata: { id, file }` no longer compiles, and an attachment in that shape from untyped code is no longer sent. An attachment with neither field (a custom type or `giphy`) is sent as it is, takes no upload slot, and counts as content in `hasSendableData`. * CustomCommandData is no longer exported. Delete its augmentations and imports. A leftover augmentation still compiles and has no effect, so search for the name rather than relying on tsc. An app that wants a closed set of command names declares its own union. See ai-docs/v10-breaking-changes.md#custom-data-augmentation. * the client no longer dispatches capabilities.changed and the name is no longer an EventType. Subscribe to channel.state instead: channel.state.subscribeWithSelector(({ ownCapabilities }) => ({ ownCapabilities }), fn). See ai-docs/v10-breaking-changes.md#capabilities-changed-removed. * AttachmentManager.uploadAttachment() is removed. Retry a failed upload, or upload an attachment you built yourself such as a voice recording, with `attachmentManager.uploadFile(attachment)`. It keeps the attachment's id, fields and position, and runs the pre- and post-upload middleware, which uploadAttachment() skipped. uploadFile() does not check `isUploadEnabled`, so check it first to skip the upload as uploadAttachment() did. uploadFile() always resolves to an attachment: where a result was checked for `undefined`, check `result.localMetadata.uploadState` instead. See ai-docs/v10-breaking-changes.md#upload-attachment-removed. * the message composer installs the command middlewares by default: createActiveCommandGuardMiddleware and createCommandStringExtractionMiddleware in the text composer chain, createCommandInjectionMiddleware in the composition chain and createDraftCommandInjectionMiddleware in the draft composition chain. Typing `/giphy cats` activates giphy and leaves `cats` in the text, and a command picked with `textComposer.setCommand()` is sent with its name. Delete your own installs of these middlewares. A UI without a command chip removes all four by id: `stream-io/text-composer/active-command-guard` and `stream-io/text-composer/command-string-extraction` from `textComposer.middlewareExecutor`, `stream-io/message-composer-middleware/command-string-injection` from `compositionMiddlewareExecutor` and `stream-io/message-composer-middleware/draft-command-string-injection` from `draftCompositionMiddlewareExecutor`. See ai-docs/v10-breaking-changes.md#composer-command-middlewares-default. * Channel.watch() resolves with the channel instance to use (the channel, or the instance that superseded it) instead of the server response. - watch() sends no query for a channel that is already watched and loaded (watchStatus Watching, initialized, and messagePaginator.hasResults) when it passes no options or the same options as the last successful watch() during the current watch; other options send a query. It no longer refreshes a watched channel: use channel.reload(), and load other messages through channel.messagePaginator (jumpToMessage(), toTail(), toHead()). - A watch() while another with the same options is in flight joins it instead of sending a request. - Aborting requestOptions.signal ends only that caller's wait; the request is cancelled once every caller waiting for it has aborted. - Channel.ensureWatched() is removed (v10 release candidates only); call watch(). ``` ## Changelog - `Channel.watch()` resolves with the channel and sends no query for a channel already watched and loaded, unless it asks for options the current watch was not sent with. `Channel.ensureWatched()` is removed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) * remove messageSetToAddToIfDoesNotExist and let the message paginator place every page (#1926) * open api hot fixes (#1927) * `StreamChatOptions` and `client.options` are removed. `new StreamChat(key, config)` and `StreamChat.getInstance(key, config)` take the configuration tree (`DeepPartial<InstanceConfigTree>`) as their second argument; the `config` wrapper is dropped. Option moves: - `allowServerSideConnect`, `axiosRequestConfig`, `baseURL`, `browser`, `persistUserOnConnectionFailure` -> `client.<same name>` - `notifications` -> `client.notificationManager` - top-level axios fields (`timeout`, `httpsAgent`, ...) -> `client.axiosRequestConfig.<key>` Example: `new StreamChat(key, { client: { allowServerSideConnect: true } })`. `client.persistUserOnConnectionFailure` is no longer a field (use `client.config.setConfig('client', { persistUserOnConnectionFailure })`); `client.browser` and `client.node` are read-only getters. `baseURL`, `axiosRequestConfig` and `notificationManager` are read at construction, and the tree copies `axiosRequestConfig` instead of keeping your object. See v9-to-v10-migration-guide-client-construction.md. * client.activeChannels is removed; use client.channelManager.get(cid) and client.channelManager.values(). * client.hydrateActiveChannels() is renamed to client.hydrateChannels(). * client.getChannelById() and client.getChannelByMembers() are removed; use client.channel() or client.channelManager.ensure({ type, id?, data? }). * thread.deactivate() is removed; thread.activate() returns the function that ends the activation. * channel._disconnect() is renamed to channel.disconnect(). * channel.disconnected is removed; read the read-only channel.pendingDisposal instead, which only channel.disconnect() sets. * when a channel created from members gets a cid another stored instance already holds, the stored instance stays, as in v9, and the new one is marked channel.supersededBy; if the new one is open and the stored one isn't, its unsent composition moves to the stored one, so UIs holding the new instance should switch to channel.supersededBy. * channel.initialized is set by query() and create() too, not only by watch(). * channel search results are not watched; watch a result when it is opened. * MessageSearchSource no longer watches the channels it fetches for its results; watch the channel when a result is opened, e.g. with jumpToMessage(id, { watchChannel: true }). * adopt TS generator changes (#1896) * reenable WS fallback (#1889) * thread paginator and manager revamp (#1888) * **MessageDeliveryReporter:** The `isLocalUnreadCountEnabled` client option is removed. Use `channel.readEvents.localUnreadCountEnabled` in the configuration service instead (see above). * brand server-sent timestamps as TimestampNS and ship the Date guard (#1884) * `connection.changed` is removed. Connectivity is published as two state stores and nothing else: `client.networkConnection.state` for the device and `client.wsConnection.state` for this client's socket. The event was silent on `closeConnection()` and two error paths, and held a drop for five seconds; the stores are written on every transition and publish immediately. * `client.wsConnection` is now a `WSConnection` wrapper rather than the `StableWSConnection` itself, and is never null. The live socket is `client.wsConnection.connection`, replaced on every connect; read `isHealthy`, `isConnecting`, `connect()` and `disconnect()` from the wrapper. `StableWSConnection` is constructed with `{ wsConnection }` instead of `{ client }`. * `client.defaultWSTimeout` is removed, along with the `WebSocketImpl` and `wsUrlParams` client options. They move to the socket's configuration as `connectTimeoutMs`, `webSocketImpl` and `urlParams`, set with `client.config.set({ client: { wsConnection: { … } } })`. Unlike the fields and options they replace, these survive a reconnect. * `ThreadManagerState.lastConnectionDropAt` is removed. Read `client.wsConnection.state.lastUnhealthyAt`, which is written on every status transition, including the `disconnect()` path the old event was silent about. * requests that watch a channel or subscribe to presence now wait for a WebSocket connection id instead of silently returning unwatched data. The gate is in `ApiClient._doRequest`, so it covers every endpoint carrying `watch` or `presence`, plus `stopWatchingChannel` and `longPoll`. With no socket open and none being established the request rejects with "No connection id is available"; an explicit `watch: false` is still honoured, and a caller's `AbortSignal` abandons the wait. Test fixtures that fake a connected user without a live socket will now throw. * a UI that renders a "connection lost" banner must hold the drop itself. The socket's store publishes drops the moment they happen, where the old event delayed them by five seconds. `client.wsConnection.config.offlineNotificationDisplayDelayMs` (5s) is the shared value to wait for; nothing in this package acts on it. * `connection.recovered` is no longer dispatched when the socket drops while a recovery is running, because every reload in it can have failed. Work keyed off that event will correctly stop running for recoveries that recovered nothing. * openapi related clean up (#1870) * **SearchController:** remove InternalSearchControllerState (#1866) * remove response date transform (#1849) * `client.instanceConfigurationService` is replaced by `client.config`, and the exported class `InstanceConfigurationService` is renamed to `InstanceConfigurationRegistry`. * `client.setInstanceConfigurationFunction()` is removed. Register setup functions with `client.config.setSetupFunction(key, fn)`. `client.setMessageComposerSetupFunction()` is unchanged. * the per-class setup types are replaced by generic ones keyed by instance. Removed: `ChannelSetupFunction`, `ChannelSetupState`, `ChannelTearDownFunction`, `MessageComposerSetupFunction`, `MessageComposerSetupState`, `MessageComposerTearDownFunction`, `StreamChatSetupFunction`, `StreamChatSetupState`, `StreamChatTearDownFunction`, `ThreadSetupFunction`, `ThreadSetupState`, `ThreadTearDownFunction`, `SetInstanceConfigurationFunctions`, `SetInstanceConfigurationServiceStates` and `SetupFnOf`. Use `InstanceSetupFunction`, `InstanceSetupState`, `InstanceSetupTearDownFunction` and `InstanceSetupKey`. * `ChannelInstanceConfig` is renamed to `ChannelConfig` and `ThreadInstanceConfig` to `ThreadConfig`. * `Channel.getConfig()` is removed. Read the server's channel-type configuration from `channel.serverConfig`, or the resolved configuration from `channel.config`. * `client.configs` is renamed to `client.channelServerConfigs` and `client.configsStore` to `client.channelServerConfigsStore`. Keys are still cids, so lookups translate directly. Prefer `channel.serverConfig`. * `channel.config.commands` is renamed to `channel.config.availableCommands`. The server owns the list, so it is read-only and absent from the declarative configuration tree. * `applyCommandValidatorOverride` is no longer exported. Command validation is configured through the composer's configuration. * `MessageComposerConfig` gains a required `polls`; `AttachmentManagerConfig` gains required `enabled` and `customCdn`; `LocationComposerConfig` gains a required `minShareDurationMs`. A config object built literally must supply them. * `linkPreviews.enabled` now defaults to `true`, where it defaulted to `false`. It is ANDed with the channel type's `url_enrichment`, so `true` means "no opinion, let the server decide"; the old default double-gated the feature and kept it off even where the server had enabled it. * server flags gate the features they describe, ANDed with the registered value rather than the client value winning: `typingEvents` with `typing_events`, `readEvents` with `read_events`, `attachments` with `uploads`, `polls` with `polls`, `location` with `shared_locations`, and `linkPreviews` with `url_enrichment`. Code that set a flag to `true` and assumed it took effect may now find the feature off. * setters no longer discard a write when the server is masking the field. Previously the setter compared against the effective value, so the write was dropped and the earlier request was honoured once the server relented. Affects `linkPreviewsManager.enabled`, `textComposer.enabled`, `textComposer.maxLengthOnEdit`, `textComposer.maxLengthOnSend` and `attachmentManager.maxNumberOfFilesPerMessage`. * remove hand-written types and API wrappers (#1836) * **i18n:** `Streami18n.init()` now rejects when i18next fails to initialize, where it previously logged and resolved. Callers that ignore the returned promise will see an unhandled rejection. The instance remains usable in its degraded English form either way. `Streami18n.brand` is removed; nothing in either UI SDK read it, and a custom integration that recognized instances through it should compare against its own marker or accept the instance it was handed. * **i18n:** share the translation runtime as stream-chat/i18n (#1830) * `ChannelManager` keeps its name but is a different class table Co-authored-by: Ivan Sekovanikj <31964049+isekovanic@users.noreply.github.com> * paginator-based message storage replaces channel.state.messages and channel.state.last_message_at. The message list, thread replies and pinned messages are now owned by channel.messagePaginator, thread.messagePaginator and channel.pinnedMessagesPaginator. OpenAPI-generated clients replace the hand-written request/response types. See docs/breaking-changes-v14-v15.md for the full migration guide. ### Bug Fixes * accept poll.update data without id ([#1997](#1997)) ([c5863a9](c5863a9)) * add docs snippets; fix open api issues ([#1913](#1913)) ([53505e7](53505e7)) * add messages received after truncate to state ([#1950](#1950)) ([b178379](b178379)) * address code issues found while reviewing v10 source comments ([#1922](#1922)) ([2fc319e](2fc319e)) * adjust paginator offset when items are removed ([#1842](#1842)) ([61b5a1f](61b5a1f)) * apply thread reloads and queries to the registered thread ([#1929](#1929)) ([acb33c4](acb33c4)), closes [#1913](#1913) * avoid types from NodeJS namespace ([#1947](#1947)) ([68fb042](68fb042)) * bring back reaction apis to channel ([#1906](#1906)) ([d419fe3](d419fe3)), closes [#1897](#1897) [#1897](#1897) * channel list reconciliation ([#1942](#1942)) ([f7f9ec8](f7f9ec8)) * channel paginator merging bugs ([#1894](#1894)) ([dbbbd75](dbbbd75)), closes [#1865](#1865) * consolidate master and v10 ([#1957](#1957)) ([802a956](802a956)) * count hidden messages on each side of a jump page ([#1940](#1940)) ([487ac3d](487ac3d)) * **deps:** bump [@stream-io](https://github.com/stream-io) toolkit packages to the .mjs/.cjs builds ([#1971](#1971)) ([aa97cf4](aa97cf4)), closes [GetStream/js-toolkit#58](https://github.com/GetStream/js-toolkit/issues/58) * dispatch connection.recovered on reconnect when recovery is disabled ([#1994](#1994)) ([13bb533](13bb533)) * docs fixes ([#1949](#1949)) ([4c3e46d](4c3e46d)) * drop capabilities.changed; channel.state ownCapabilities is the change signal ([#1992](#1992)) ([6da7713](6da7713)) * drop server-side remnants, add WS interface ([#1819](#1819)) ([39c0c53](39c0c53)) * ensure API errors parsing works even with multiple axios copies ([#1909](#1909)) ([e0cfba9](e0cfba9)) * existing messages retry guards ([#1982](#1982)) ([c7c971f](c7c971f)) * handle tokenProvider rejection during reconnect token refresh ([#1963](#1963)) ([e22d218](e22d218)) * handle user.updated in threads ([#1979](#1979)) ([9064ffc](9064ffc)) * **i18n:** reject from init() and drop the Streami18n brand static ([#1839](#1839)) ([67bb1c7](67bb1c7)) * install the command middlewares in the composer by default ([#1985](#1985)) ([1dc25e2](1dc25e2)) * issues found by the RN e2e tests ([#1996](#1996)) ([f5d1a2d](f5d1a2d)) * jump to thread reply bug ([#1948](#1948)) ([5ec1b07](5ec1b07)) * let an edit clear all custom fields ([#1983](#1983)) ([9437b09](9437b09)) * narrow channel.state.typing to typing.start events ([#1960](#1960)) ([acb2345](acb2345)), closes [#1920](#1920) * notify channel state subscribers on watcher, ban and member removal events ([#1977](#1977)) ([3a7be78](3a7be78)) * odd edge cases for channel list recovery ([#1966](#1966)) ([dbc2468](dbc2468)) * offline db hydration race conditions ([#1865](#1865)) ([a4587e1](a4587e1)) * open api hot fixes ([#1927](#1927)) ([4fb4b52](4fb4b52)) * optimistic updates for translated messages ([#1975](#1975)) ([91fc290](91fc290)) * **pagination:** count a page set with setItems as loaded, even when empty ([#1995](#1995)) ([bfff3c8](bfff3c8)) * **pagination:** keep loaded items when filterQueryResults empties a later page ([#1986](#1986)) ([78a32c3](78a32c3)) * **paginator:** correct windows filled by hydration rather than by a query ([#1860](#1860)) ([93b6fa0](93b6fa0)) * **paginator:** do not issue the first-page query twice ([#1858](#1858)) ([6462cef](6462cef)) * pass full request options to hydration query ([#1959](#1959)) ([489774b](489774b)) * pending task folding ([#1873](#1873)) ([028f6e8](028f6e8)) * **poll:** keep translations from poll.updated in the poll state ([#1937](#1937)) ([e47b75a](e47b75a)), closes [#1878](#1878) * post-merge/OAPI-reconciliation adjustments ([#1812](#1812)) ([5073c67](5073c67)) * reconnect past connection timeout ([#1874](#1874)) ([2004a83](2004a83)), closes [#1760](#1760) * regenerate api layer (fix generated filter types) ([#1827](#1827)) ([e540d88](e540d88)) * remove CustomCommandData ([#1989](#1989)) ([f737d61](f737d61)) * respect predefined filters from db ([#1869](#1869)) ([cbc7707](cbc7707)) * restore composer custom data from drafts and edits, keep custom on edit ([#1978](#1978)) ([51ddcad](51ddcad)) * **search:** match message text by word prefix in MessageSearchSource ([#1987](#1987)) ([ef58ef5](ef58ef5)) * send the read request regardless of read receipt privacy settings in v10 ([#1855](#1855)) ([3b24387](3b24387)) * set thread cursor when initied from setItems ([#1898](#1898)) ([63bef43](63bef43)) * signal api ([#1828](#1828)) ([9032b1a](9032b1a)) * stability and perf fixes ([#1910](#1910)) ([080af2c](080af2c)) * unread snapshot count, pinned replies, joined and invite filters, read events check ([#1981](#1981)) ([1247d10](1247d10)) * v10 in depth review hotfixes ([#1920](#1920)) ([98a298d](98a298d)) * v10 regressions found in side-by-side QA ([#1965](#1965)) ([b87d598](b87d598)) ### Features * add instance configuration service ([#1831](#1831)) ([434966c](434966c)) * add message/channel paginator stack with thread minimal-init and state stores ([#1795](#1795)) ([62f0507](62f0507)) * adopt TS generator changes ([#1896](#1896)) ([82af6de](82af6de)) * brand server-sent timestamps as TimestampNS and ship the Date guard ([#1884](#1884)) ([e51198a](e51198a)), closes [GetStream/chat#17356](https://github.com/GetStream/chat/issues/17356) [GetStream/chat#17460](https://github.com/GetStream/chat/issues/17460) [#17356](https://github.com/GetStream/stream-chat-js/issues/17356) [GetStream/stream-chat-react#3297](GetStream/stream-chat-react#3297) [GetStream/stream-chat-react-native#3822](GetStream/stream-chat-react-native#3822) [GetStream/chat#17425](https://github.com/GetStream/chat/issues/17425) * channel state migration ([#1834](#1834)) ([9876add](9876add)) * connection recovery ([#1841](#1841)) ([8ec9a9d](8ec9a9d)) * destructive reconciliation ([#1822](#1822)) ([3c46fbf](3c46fbf)) * establish network connection observer services ([#1859](#1859)) ([4c46949](4c46949)) * generalize entity store in V10 ([#1814](#1814)) ([d066ba0](d066ba0)) * **i18n:** share the translation runtime as stream-chat/i18n ([#1830](#1830)) ([40cf062](40cf062)), closes [GetStream/stream-chat-react#3271](GetStream/stream-chat-react#3271) [GetStream/stream-chat-react-native#3777](GetStream/stream-chat-react-native#3777) * integrate OpenAPI generated clients ([#1760](#1760)) ([0776bc4](0776bc4)) * let AttachmentManager.uploadFile retry an existing attachment ([#1991](#1991)) ([cf8b1fa](cf8b1fa)) * let integrators send a poll with the message by removing the poll-only middleware ([#1970](#1970)) ([b515849](b515849)), closes [GetStream/stream-chat-react#3365](GetStream/stream-chat-react#3365) * message pruning ([696f56f](696f56f)) * message pruning ([#1875](#1875)) ([d4d2c6c](d4d2c6c)) * **MessageComposer:** add send and update message API ([#1881](#1881)) ([95b2502](95b2502)) * **MessageComposer:** reference the composer on upload notifications ([#1871](#1871)) ([d520fc1](d520fc1)) * **messageDelivery:** report receipt state from the furthest cursor ([#1883](#1883)) ([79f3ac1](79f3ac1)) * **MessageDeliveryReporter:** mark channel read locally when local read count tracking are enabled ([#1886](#1886)) ([439a018](439a018)) * migrate ChannelPaginatorsOrchestrator to ChannelManager ([#1813](#1813)) ([c694930](c694930)) * move to WS API v2 ([#1833](#1833)) ([d530f43](d530f43)) * **NotificationManager:** add ensureTimeout for callers that cannot know if notification was already triggered ([#1872](#1872)) ([e5d06aa](e5d06aa)) * openapi related clean up ([#1870](#1870)) ([b3fa906](b3fa906)) * optimistic reactions ([#1808](#1808)) ([54740f1](54740f1)) * poll optimistic votes v10 port ([#1904](#1904)) ([143446b](143446b)) * port search improvements ([#1829](#1829)) ([7276299](7276299)), closes [#1823](#1823) * reenable WS fallback ([#1889](#1889)) ([45841cc](45841cc)) * regenerate from latest api spec; docs sync ([#1969](#1969)) ([ae1e86a](ae1e86a)) * regenerate from latest open api spec ([#1821](#1821)) ([22344ca](22344ca)) * regenerate open api spec + improve tooling ([#1824](#1824)) ([a3198e7](a3198e7)) * release v10 from master as latest ([#2004](#2004)) ([40ead99](40ead99)), closes [#2003](#2003) [#1476](#1476) [#1938](#1938) [#1937](#1937) [#1962](#1962) [#1963](#1963) [#1944](#1944) [#1952](#1952) [#1953](#1953) [#1954](#1954) [#1957](#1957) [#1976](#1976) [#1908](#1908) [#2003](#2003) * remaining optimistic updates ([#1843](#1843)) ([808b2c8](808b2c8)) * remove hand-written types and API wrappers ([#1836](#1836)) ([dc49a78](dc49a78)) * remove handwritten upload methods; align method names ([#1832](#1832)) ([1a6bdb8](1a6bdb8)) * remove messageSetToAddToIfDoesNotExist and let the message paginator place every page ([#1926](#1926)) ([2b7960d](2b7960d)) * remove response date transform ([#1849](#1849)) ([ef5dcc6](ef5dcc6)) * replace StreamChat.activeChannels with entity item index for loaded channels ([#1901](#1901)) ([ec9178a](ec9178a)) * replace StreamChatOptions with the configuration tree ([#1911](#1911)) ([f05b861](f05b861)) * send and count attachments with nothing to upload ([#1993](#1993)) ([b2dfb8a](b2dfb8a)), closes [ai-docs/v10-breaking-changes.md#non-upload-attachments](https://github.com/ai-docs/v10-breaking-changes.md/issues/non-upload-attachments) * settle pending attachment uploads ([#1880](#1880)) ([59f5869](59f5869)) * thread paginator and manager revamp ([#1888](#1888)) ([d1562aa](d1562aa)) * update to latest api spec ([461eb24](461eb24)) * update to latest API spec ([#2005](#2005)) ([5668f20](5668f20)) * v10 ([cd3141e](cd3141e)) ### Refactors * call the paginator intervals intervals, not windows ([#1939](#1939)) ([69aa18a](69aa18a)) * **channel:** remove no-op overrides, dead branch and redundant id guards ([#1936](#1936)) ([050953e](050953e)), closes [#1870](#1870) * **i18n:** move the translation runtime to @stream-io/i18n ([#1861](#1861)) ([45f45bd](45f45bd)) * make Channel.watch() watch a channel only when it is not watched and loaded ([#1943](#1943)) ([3c363cb](3c363cb)) * **SearchController:** remove InternalSearchControllerState ([#1866](#1866)) ([fc64f7f](fc64f7f)) * simplify optimistic updates ([#1897](#1897)) ([8b84f02](8b84f02)), closes [#1882](#1882) * unify message operation apis ([#1882](#1882)) ([2ed1c75](2ed1c75)) ### Performance Improvements * store publish amplification ([#1876](#1876)) ([d93ee7d](d93ee7d))
Contributor
|
🎉 This PR is included in version 10.0.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎯 Goal
Adopt stream-chat's branded
TimestampNS(GetStream/stream-chat-js#1884, REACT-1179). stream-chat now types every server-sent date asTimestampNS, a branded unix-nanosecondnumber. Its published types also makenew Date(timestamp)a compile error, because a nanosecond value is out ofDate's range and silently becomes an Invalid Date.An audit of this SDK, the offline store and the example apps found no runtime unit bugs: every server timestamp that becomes a
Dateor meets the local clock already goes throughconvertTimestampToDate/nsToDate/nsToMs/nowNs. The work is making values from SQLite, fixtures and example code assignable to the branded types.🛠 Implementation details
Offline DB
mapStorableToTimestampreturnsTimestampNS | undefined.mapStorableToRequiredTimestampreplaces the 17mapStorableToTimestamp(x) ?? 0fallbacks across the 10 read mappers, with unchanged behaviour: an absent column still reads back as the epoch.JSON.parse(extraData), which isany; all of them are updated.schema.tsstaynumber, since they are the storage boundary.Public API (breaking)
findInMessagesByDate(messages, targetTimestamp)takes aTimestampNS(its stale JSDoc is fixed too).getChannelUnreadStatereturnslast_readasTimestampNS, falling back toasTimestampNS(0).useIsChannelMuteduses core'sChannelMuteStatusinstead of a hand-copied shape.deleteMessagesForChannel'struncated_atis aTimestampNS.Tests and fixtures
mock-builders/generator/time.tsconvertDateToTimestampreturnsTimestampNS.asTimestampNS.first_reaction_at/last_reaction_at, which is the wrong unit; they now usenowNs().Example apps
DraftsList'sdateand the WebSocket event templates'created_atareTimestampNS.end_atit reads from the route.Cleanup
lastReadRefinChannel.src/utils/date.ts(secondsUntil).Docs:
ai-docs/ai-migration-v9-to-v10.mdnow documents:ts ?? Date.now(),Math.max(…), and unit mix-ups;asTimestampNS, the updated signatures, and how the offline mappers brand rows.Verification (first against a local build of GetStream/stream-chat-js#1884, re-run against the published
stream-chat@10.0.0-rc.14with the same results)cd package && yarn typecheck(source, tests and mock builders): no new errors. The two remaining errors predate this PR and also appear on plainV10against the same build:useChannelRequestHandlersfrom stream-chat-js#1882's message-operation API change;notificationTranslationslacks the newapi:message:send:failed/update:failedtypes (Android - The pictures (available for the attachment) are disappeared suddenly. #1881).yarn test:unit: 2000 tests passing (203 suites).🎨 UI Changes
None. Types, offline-mapper typing, tests and docs only; rendering is unchanged.
🧪 Testing
cd package && yarn typecheck && yarn test:unit. For end-to-end checks, run SampleApp against a stream-chat build that includes GetStream/stream-chat-js#1884.