Conversation
stream-chat now types every server-sent date as `TimestampNS` and makes `new Date(timestamp)` a compile error (GetStream/stream-chat-js#1884). - `processMessages` `lastRead`, `MessageList` `headerPosition` / `insertIntro` and the `VirtualizedMessageList` `lastReadDate` render prop are typed `TimestampNS` - mock builders mint branded timestamps (`convertDateToTimestamp` returns `TimestampNS`) - tests brand epoch/derived literals with `asTimestampNS`; a test that passed a `Date` as `lastRead` now passes a wire timestamp, and a test no longer imports the sibling `stream-chat-js/src` checkout - the Vite example's WebSocket event templates carry `TimestampNS` - migration docs describe the brand, the Date guard and its gaps, and drop the stale `latestMessageDatesByChannels` references BREAKING CHANGE: `processMessages`' `lastRead`, `MessageList`'s `headerPosition` and the `lastReadDate` render prop are typed `TimestampNS`; a plain millisecond `number` no longer compiles.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
2 tasks
szuperaz
approved these changes
Sep 23, 2026
MartinCupela
approved these changes
Sep 24, 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
This branch has not been deployed
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 found no runtime unit bugs: every server timestamp that becomes a
Date, gets formatted, or meetsDate.now()already goes throughconvertTimestampToDate/nsToDate/nsToMs. The work is closing the type gaps so the brand, and with it the guard, reaches the public props and the tests.🛠 Implementation details
Public types (breaking)
These now take
TimestampNS, so a millisecondnumberno longer compiles:ProcessMessagesParams.lastRead(processMessages)MessageListheaderPosition/insertIntro. It already had to be in nanoseconds, but was typednumber.VirtualizedMessageListlastReadDaterender propChannelFilesView.utils's internalnormalizeTimestampTests and fixtures
mock-builders/generator/time.tsconvertDateToTimestampreturnsTimestampNS, which makes the generated fixtures assignable (about 50 test errors fixed in one place).0,NaN,now - msToNs(…)) go throughasTimestampNS.MessageList/__tests__/utils.test.tspassed aDateaslastReadthrough an untyped helper; it now passesnowNs(), and the helper is typed.NotificationAnnouncer.test.tsximported a type from the sibling../stream-chat-js/srccheckout; it now imports fromstream-chat. That removes 17 errors fromyarn types:tests.Other
TimestampNS.ai-docs/ai-migration-v14-v15.mdnow documents:ts ?? Date.now(),Math.max(…), and unit mix-ups;asTimestampNSand the updated type table.latestMessageDatesByChannelsas retyped; it now points to the section that records its removal.i18n-v15-migration.mdno longer destructures it fromuseChat.ChatContextValue.mutesis fixed.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)yarn types: no new errors. The two remaining errors predate this PR and come from other stream-chat-js changes:RetrySendMessageWithLocalUpdateParamswas removed by stream-chat-js#1882;translatorsByNotificationTypelacks the newapi:message:send:failed/update:failedtypes (bug: Channel is not directly filtered out of theChannelListwhen a member leaves the channel. #1881).yarn types:tests: no new errors versus the published rc, and 17 fewer after the import fix.yarn test: 2782 passing. The one failure,Channel.test.tsx"should eventually pass down a message when a message.new event…", also fails on a clean stream-chat-js HEAD without this work and passes on rc.12, so it comes from stream-chat-js bug: no translation key for "Maximum number of files reached" #1876 or bug: Channel is not directly filtered out of theChannelListwhen a member leaves the channel. #1881.message.newall render correct dates, with no "Invalid Date" and no console errors.🎨 UI Changes
None. Types, tests and docs only.