From 6257e5bda2ba732665d5c36c52ddd800aefdd594 Mon Sep 17 00:00:00 2001 From: Anton Arnautov Date: Tue, 4 Aug 2026 21:27:39 +0200 Subject: [PATCH 1/2] Initial commit --- src/channel.ts | 4 +-- .../MessageDeliveryReporter.ts | 23 ++++--------- .../paginators/ReminderPaginator.ts | 12 +++---- src/thread.ts | 22 ++++++------- src/types.ts | 32 ------------------- src/utils.ts | 2 +- 6 files changed, 26 insertions(+), 69 deletions(-) diff --git a/src/channel.ts b/src/channel.ts index 0a023e8ec..560571c2a 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -31,7 +31,6 @@ import type { CreateDraftResponse, DeleteMessageOptions, Event, - EventAPIResponse, EventHandler, EventPayload, EventType, @@ -39,6 +38,7 @@ import type { GetRepliesRequest, LocalMessage, MarkReadRequest, + MarkReadResponseEvent, MarkUnreadRequest, MessagePaginationOptions, MessageRequest, @@ -133,7 +133,7 @@ export type CustomDeleteMessageRequestFn = ( export type CustomMarkReadRequestFn = (params: { channel: Channel; options?: MarkReadRequest; -}) => Promise; +}) => Promise<{ event: MarkReadResponseEvent } | null>; export type ChannelInstanceConfig = { requestHandlers?: { diff --git a/src/messageDelivery/MessageDeliveryReporter.ts b/src/messageDelivery/MessageDeliveryReporter.ts index ce8a759f0..48ce22ed9 100644 --- a/src/messageDelivery/MessageDeliveryReporter.ts +++ b/src/messageDelivery/MessageDeliveryReporter.ts @@ -3,16 +3,15 @@ import { Channel } from '../channel'; import type { ThreadUserReadState } from '../thread'; import { Thread } from '../thread'; import type { - EventAPIResponse, LocalMessage, MarkDeliveredRequest, MarkReadRequest, + MarkReadResponse, StreamAPIError, StreamResponse, } from '../types'; import { throttle, userHasReadReceipts } from '../utils'; import { isAPIError, isErrorRetryable } from '../errors'; -import type { MarkReadResponse as Gen_MarkReadResponse } from '../gen/models'; const MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD = 100 as const; const MARK_AS_DELIVERED_BUFFER_TIMEOUT = 1000 as const; @@ -306,16 +305,11 @@ export class MessageDeliveryReporter { ? { ...options, thread_id: collection.id } : options; - let result: EventAPIResponse | StreamResponse | null = null; + let result: Partial> | null = null; if (isThreadCollection) { - const markReadRequestHandler = collection.configState.getLatestValue() - .requestHandlers?.markReadRequest as - | ((params: { - thread: Thread; - options?: MarkReadRequest; - }) => Promise | void) - | undefined; + const markReadRequestHandler = + collection.configState.getLatestValue().requestHandlers?.markReadRequest; result = markReadRequestHandler ? ((await markReadRequestHandler({ options: requestOptions, @@ -323,13 +317,8 @@ export class MessageDeliveryReporter { })) ?? null) : await channel.markRead(requestOptions); } else { - const markReadRequestHandler = channel.configState.getLatestValue().requestHandlers - ?.markReadRequest as - | ((params: { - channel: Channel; - options?: MarkReadRequest; - }) => Promise | void) - | undefined; + const markReadRequestHandler = + channel.configState.getLatestValue().requestHandlers?.markReadRequest; result = markReadRequestHandler ? ((await markReadRequestHandler({ channel, options: requestOptions })) ?? null) : await channel.markRead(requestOptions); diff --git a/src/pagination/paginators/ReminderPaginator.ts b/src/pagination/paginators/ReminderPaginator.ts index dd6fe21b7..73ec8ba5e 100644 --- a/src/pagination/paginators/ReminderPaginator.ts +++ b/src/pagination/paginators/ReminderPaginator.ts @@ -5,7 +5,7 @@ import type { PaginatorOptions, } from './BasePaginator'; import type { - QueryRemindersOptions, + QueryRemindersRequest, ReminderFilters, ReminderResponseData, ReminderSort, @@ -25,7 +25,7 @@ const DEFAULT_SORT: ReminderSort = [{ direction: 1, field: 'created_at' }]; export class ReminderPaginator extends BasePaginator< ReminderResponseData, - QueryRemindersOptions + QueryRemindersRequest > { private client: StreamChat; protected _filters: ReminderFilters | undefined; @@ -52,7 +52,7 @@ export class ReminderPaginator extends BasePaginator< constructor( client: StreamChat, - options?: PaginatorOptions, + options?: PaginatorOptions, ) { super({ initialCursor: ZERO_PAGE_CURSOR, @@ -83,8 +83,8 @@ export class ReminderPaginator extends BasePaginator< protected getNextQueryShape({ direction, }: Required< - Pick, 'direction'> - >): QueryRemindersOptions { + Pick, 'direction'> + >): QueryRemindersRequest { const cursor = this.cursor?.[direction]; return { filter: this.filters, @@ -96,7 +96,7 @@ export class ReminderPaginator extends BasePaginator< query = async ({ queryShape, - }: PaginationQueryParams): Promise< + }: PaginationQueryParams): Promise< PaginationQueryReturnValue > => { const { reminders: items, next, prev } = await this.client.queryReminders(queryShape); diff --git a/src/thread.ts b/src/thread.ts index 6039a5bfb..3e533f137 100644 --- a/src/thread.ts +++ b/src/thread.ts @@ -7,10 +7,10 @@ import { import { applyReactionLocally } from './entityStore'; import type { DraftResponse, - EventAPIResponse, EventType, LocalMessage, MarkReadRequest, + MarkReadResponseEvent, MessageResponse, ReactionRequest, ReadStateResponse, @@ -75,7 +75,7 @@ const DEFAULT_ITEM_ORDER: SortParamRequest[] = [{ field: 'created_at', direction export type CustomThreadMarkReadRequestFn = (params: { thread: Thread; options?: MarkReadRequest; -}) => Promise | void; +}) => Promise<{ event: MarkReadResponseEvent } | null> | void; export type ThreadInstanceConfig = { requestHandlers?: { @@ -139,7 +139,7 @@ export class Thread extends WithSubscriptions { participants: threadData.thread_participants, read: formatReadState( !threadData.read || threadData.read.length === 0 - ? getPlaceholderReadResponse(client.userID) + ? getPlaceholderReadResponse(client.userId) : threadData.read, ), // Use the parent message's reply_count, not the top-level threadData.reply_count. The @@ -180,7 +180,7 @@ export class Thread extends WithSubscriptions { isStateStale: false, parentMessage: formattedParentMessage, participants: [], - read: formatReadState(getPlaceholderReadResponse(client.userID)), + read: formatReadState(getPlaceholderReadResponse(client.userId)), replyCount: parentMessage.reply_count ?? 0, title: '', updatedAt: parentMessage.updated_at ? new Date(parentMessage.updated_at) : null, @@ -305,7 +305,7 @@ export class Thread extends WithSubscriptions { } get ownUnreadCount() { - return ownUnreadCountSelector(this.client.userID)(this.state.getLatestValue()); + return ownUnreadCountSelector(this.client.userId)(this.state.getLatestValue()); } public activate = () => { @@ -429,7 +429,7 @@ export class Thread extends WithSubscriptions { this.state.subscribeWithSelector( (nextValue) => ({ active: nextValue.active, - unreadMessageCount: ownUnreadCountSelector(this.client.userID)(nextValue), + unreadMessageCount: ownUnreadCountSelector(this.client.userId)(nextValue), }), ({ active, unreadMessageCount }) => { if (!active || !unreadMessageCount) return; @@ -452,8 +452,8 @@ export class Thread extends WithSubscriptions { const { channel } = this.state.getLatestValue(); if ( - !this.client.userID || - this.client.userID !== event.user?.id || + !this.client.userId || + this.client.userId !== event.user?.id || event.channel?.cid !== channel.cid ) { return; @@ -491,11 +491,11 @@ export class Thread extends WithSubscriptions { private subscribeNewReplies = () => this.client.on('message.new', (event) => { - if (!this.client.userID || event.message?.parent_id !== this.id) { + if (!this.client.userId || event.message?.parent_id !== this.id) { return; } - const isOwnMessage = event.message.user?.id === this.client.userID; + const isOwnMessage = event.message.user?.id === this.client.userId; const { active, read } = this.state.getLatestValue(); this.upsertReplyLocally({ @@ -526,7 +526,7 @@ export class Thread extends WithSubscriptions { user: event.user, unreadMessageCount: 0, }; - } else if (active && userId === this.client.userID) { + } else if (active && userId === this.client.userId) { // Do not increment unread count for the current user in an active thread } else { // Increment unread count for all users except the author of the new message diff --git a/src/types.ts b/src/types.ts index e679cc767..ddfdf07bf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1107,35 +1107,3 @@ export type GiphyVersions = keyof Images; export type TranslationLanguage = TranslateMessageRequest['language']; export * from './gen/models'; - -export type EventAPIResponse = APIResponse & { - event: Event; -}; - -export type PartializeKeys = Partial> & Omit; - -type ErrorResponseDetails = { - code: number; - messages: string[]; -}; - -export type APIErrorResponse = { - duration: string; - message: string; - more_info: string; - StatusCode: number; - code?: number; - details?: ErrorResponseDetails; -}; - -export type DraftMessagePayload = PartializeKeys< - Omit, - 'id' -> & { - user_id?: string; -}; - -export type QueryRemindersOptions = Pager & { - filter?: ReminderFilters; - sort?: ReminderSort; -}; diff --git a/src/utils.ts b/src/utils.ts index 48ef780c4..e750e3e5d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -802,7 +802,7 @@ export const generateChannelTempCid = (channelType: string, members: string[]) = export const isDate = (value: unknown): value is Date => !!(value as Date).getTime; export const isLocalMessage = (message: unknown): message is LocalMessage => - isDate((message as LocalMessage).created_at); + typeof (message as LocalMessage | undefined)?.status === 'string'; export const runDetached = ( callback: Promise, From 7406d6df3be561b650072c5fd1b0e109adfdd156 Mon Sep 17 00:00:00 2001 From: Zita Szupera Date: Fri, 14 Aug 2026 13:50:34 -0500 Subject: [PATCH 2/2] chore: update migration guides --- src/channel.ts | 5 +- src/thread.ts | 5 +- .../MessageDeliveryReporter.test.ts | 58 +++++++++++++++++++ v9-to-v10-migration-guide-methods.md | 58 ++++++++++++++++++- v9-to-v10-migration-guide-other.md | 15 ++--- v9-to-v10-migration-guide-type-renames.md | 49 ++++++++++++++-- 6 files changed, 173 insertions(+), 17 deletions(-) diff --git a/src/channel.ts b/src/channel.ts index 90deab7a6..716dd5a6a 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -37,7 +37,7 @@ import type { GetRepliesRequest, LocalMessage, MarkReadRequest, - MarkReadResponseEvent, + MarkReadResponse, MarkUnreadRequest, MessagePaginationOptions, MessageRequest, @@ -52,6 +52,7 @@ import type { SendMessageOptions, SendReactionRequest, SharedLocation, + StreamResponse, UnBanUserOptions, UpdateChannelPartialRequest, UpdateLiveLocationRequest, @@ -132,7 +133,7 @@ export type CustomDeleteMessageRequestFn = ( export type CustomMarkReadRequestFn = (params: { channel: Channel; options?: MarkReadRequest; -}) => Promise<{ event: MarkReadResponseEvent } | null>; +}) => Promise> | null>; export type ChannelInstanceConfig = { requestHandlers?: { diff --git a/src/thread.ts b/src/thread.ts index cd5f3e03d..95e7859a0 100644 --- a/src/thread.ts +++ b/src/thread.ts @@ -10,12 +10,13 @@ import type { EventType, LocalMessage, MarkReadRequest, - MarkReadResponseEvent, + MarkReadResponse, MessageResponse, ReactionRequest, ReadStateResponse, SendReactionRequest, SortParamRequest, + StreamResponse, ThreadStateResponse, UserResponse, } from './types'; @@ -76,7 +77,7 @@ const DEFAULT_ITEM_ORDER: SortParamRequest[] = [{ field: 'created_at', direction export type CustomThreadMarkReadRequestFn = (params: { thread: Thread; options?: MarkReadRequest; -}) => Promise<{ event: MarkReadResponseEvent } | null> | void; +}) => Promise> | null> | void; export type ThreadInstanceConfig = { requestHandlers?: { diff --git a/test/unit/messageDelivery/MessageDeliveryReporter.test.ts b/test/unit/messageDelivery/MessageDeliveryReporter.test.ts index 781d976aa..5a8384055 100644 --- a/test/unit/messageDelivery/MessageDeliveryReporter.test.ts +++ b/test/unit/messageDelivery/MessageDeliveryReporter.test.ts @@ -5,8 +5,10 @@ import { generateThreadResponse } from '../test-utils/generateThreadResponse'; import { type APIError, Channel, + type CustomMarkReadRequestFn, Event, MarkDeliveredResponse, + MarkReadResponse, StreamAPIError, StreamChat, StreamResponse, @@ -385,6 +387,62 @@ describe('MessageDeliveryReporter', () => { expect(markDeliveredSpy).not.toHaveBeenCalled(); }); + // `CustomMarkReadRequestFn` returns `Promise> | null>`. + // The `Partial<>` is what lets a handler delegate straight to `channel.markRead` — that resolves + // to `StreamResponse` whose `event` is optional, so a stricter + // `{ event: MarkReadResponseEvent }` return type would reject the delegation at compile time. + describe('custom markReadRequest handler', () => { + const markReadEvent = { + channel_id: channelId, + channel_type: channelType, + cid: `${channelType}:${channelId}`, + created_at: new Date(), + type: 'message.read', + }; + + it('accepts a handler that delegates straight to channel.markRead', async () => { + const response = { + duration: '0.1ms', + event: markReadEvent, + } as StreamResponse; + const markReadSpy = vi.spyOn(channel, 'markRead').mockResolvedValue(response); + + const markReadRequest: CustomMarkReadRequestFn = ({ channel, options }) => + channel.markRead(options); + const handler = vi.fn(markReadRequest); + channel.configState.partialNext({ requestHandlers: { markReadRequest: handler } }); + + const result = await channel.markReadViaReporter({ thread_id: 'threadId' }); + + expect(handler).toHaveBeenCalledWith({ + channel, + options: { thread_id: 'threadId' }, + }); + expect(markReadSpy).toHaveBeenCalledWith({ thread_id: 'threadId' }); + expect(result).toBe(response); + }); + + it('accepts a handler that returns only an event, without a duration', async () => { + const markReadSpy = vi.spyOn(channel, 'markRead'); + const handler = vi.fn(async () => ({ event: markReadEvent })); + channel.configState.partialNext({ requestHandlers: { markReadRequest: handler } }); + + const result = await channel.markReadViaReporter(); + + expect(handler).toHaveBeenCalled(); + // the handler replaces the request entirely — the SDK must not also issue one + expect(markReadSpy).not.toHaveBeenCalled(); + expect(result).toEqual({ event: markReadEvent }); + }); + + it('normalizes a nullish handler result to null', async () => { + const handler = vi.fn(async () => undefined); + channel.configState.partialNext({ requestHandlers: { markReadRequest: handler } }); + + await expect(channel.markReadViaReporter()).resolves.toBeNull(); + }); + }); + const receiveMessages = (count: number, startId = 0) => { // last_read < last message const channels = Array.from({ length: count }, (_, i) => { diff --git a/v9-to-v10-migration-guide-methods.md b/v9-to-v10-migration-guide-methods.md index 59a23ab2b..c60f4f2e2 100644 --- a/v9-to-v10-migration-guide-methods.md +++ b/v9-to-v10-migration-guide-methods.md @@ -793,6 +793,30 @@ channel.markReadViaReporter(data?: MarkReadRequest); // batched through Messag Migration rule: if you want to preserve the v9 batching behavior, rename `markRead` → `markReadViaReporter`. If your v9 code was calling `markAsReadRequest`, rename it to `markRead`. +**Return types changed too**, and the batched path is the one that bites: + +```ts +// v9 — both fields required +channel.markRead(...) : Promise // { duration, event } + +// v10 +channel.markRead(...) : Promise> // event? is optional +channel.markReadViaReporter(...) : Promise> | null> +``` + +On the reporter path **every** field is optional, `duration` included, because a caller-supplied `markReadRequest` handler is allowed to return a partial response. So v9 code like `const { event } = await channel.markRead(); event.cid` needs narrowing after the rename: + +```ts +const response = await channel.markReadViaReporter(); +if (response?.event) { + // … +} +``` + +`client.messageDeliveryReporter.markRead(collection, options?)` has the same return type — `MessageDeliveryReporter` is part of the public surface. + +`EventAPIResponse` itself no longer exists; see [the shape-change note](./v9-to-v10-migration-guide-type-renames.md#eventapiresponse--one-type-per-endpoint) for why `MarkReadResponseEvent` is not interchangeable with a WS `Event`. + #### `channel.markUnread` ```ts @@ -868,7 +892,7 @@ channel.deleteDraft(options?: { parent_id? }); channel.getDraft(options?: { parent_id? }); // v10 — inherited/override with generated shape -channel.createDraft(request: Gen_CreateDraftRequest); // { message: DraftPayload } +channel.createDraft(request: Gen_CreateDraftRequest); // { message: MessageRequest } channel.deleteDraft(request?: { parent_id? }); channel.getDraft(request?: { parent_id? }); // inherited unchanged channel._createDraft(request); // same shape @@ -1048,7 +1072,7 @@ from an RC rather than from v9, the change is a pure rename: | ids `ChannelPaginatorsOrchestrator:default-handler:*` | `ChannelManager:default-handler:*` | | module `stream-chat` (unchanged) | `stream-chat` (unchanged) | -Nothing else in the RC API changed, and no deprecated alias is exported — the old names are gone. +No deprecated alias is exported — the old names are gone. For the RC deltas outside `ChannelManager`, see [Coming from a v10 release candidate — removed type aliases](#coming-from-a-v10-release-candidate--removed-type-aliases) at the end of this guide. ### Removed helpers (were exported from `stream-chat`) @@ -1203,6 +1227,36 @@ logger.info(msg, extra); --- +## Coming from a v10 release candidate — removed type aliases + +Skip this section if you are upgrading from v9; everything here is already covered above. It exists for integrations pinned to the `rc` dist-tag, because `10.0.0-rc.1` / `rc.2` still exported five aliases that v10 final deletes outright. **No back-compat alias remains for any of them.** + +| Removed after `rc.2` | Replacement | Detail | +| ----------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `EventAPIResponse` | one generated type per endpoint | [shape-change note](./v9-to-v10-migration-guide-type-renames.md#eventapiresponse--one-type-per-endpoint) | +| `APIErrorResponse` | `APIError` | [shape-change note](./v9-to-v10-migration-guide-type-renames.md#apierrorresponse--apierror) — `StatusCode` → `status_code` | +| `DraftMessagePayload` | `MessageRequest` | [shape-change note](./v9-to-v10-migration-guide-type-renames.md#draftmessagepayload--messagerequest) | +| `PartializeKeys` | none | type utility; inline the built-in equivalent — see `v9-to-v10-migration-guide-other.md` | +| `QueryRemindersOptions` | `QueryRemindersRequest` | see `v9-to-v10-migration-guide-other.md` | + +`QueryRemindersOptions` is the one that moved twice: it was the full `Pager & { filter?, sort? }` shape in `rc.1`, a back-compat alias to `QueryRemindersRequest` in `rc.2`, and deleted in final. `ReminderPaginator`'s second generic parameter moved with it — `PaginatorOptions` becomes `PaginatorOptions`. + +### Custom mark-read request handlers + +`ChannelInstanceConfig.requestHandlers.markReadRequest` and its `ThreadInstanceConfig` counterpart are v10-only surface (there is nothing equivalent in v9), but their return type changed after `rc.2`: + +```ts +// rc.1 / rc.2 +type CustomMarkReadRequestFn = (params) => Promise; + +// v10 final +type CustomMarkReadRequestFn = ( + params, +) => Promise> | null>; +``` + +The `Partial<>` is deliberate: it lets a handler return just `{ event }` without fabricating a `duration`, and it means a handler can delegate straight to the SDK — `markReadRequest: ({ channel, options }) => channel.markRead(options)` — which the `rc` signature rejected because `MarkReadResponse.event` is optional. `CustomThreadMarkReadRequestFn` takes `{ thread, options? }` instead of `{ channel, options? }` and additionally permits a `void` return. + ## Logging (applies to every class) `options.logger` (function) and `client.logger(level, msg, extra?)` are gone. To capture logs in v10, configure the shared `chatLoggerSystem` before constructing the client: diff --git a/v9-to-v10-migration-guide-other.md b/v9-to-v10-migration-guide-other.md index 8118cd314..6e614d944 100644 --- a/v9-to-v10-migration-guide-other.md +++ b/v9-to-v10-migration-guide-other.md @@ -235,12 +235,13 @@ Field-name typos in a typed filter are now compile errors. If you were relying o `ChannelFilters`, `MessageFilters`, `ReactionFilters`, `ThreadFilters`, `UserFilters` still exist as convenience aliases but derive from the constrained request types. The three remaining hand-written poll/reminder filter types are now migrated the same way: -| Alias | Now derives from | -| ----------------------- | ------------------------------------------------------------------------------- | -| `QueryPollsFilters` | `NonNullable` | -| `QueryVotesFilters` | `NonNullable` | -| `ReminderFilters` | `NonNullable` | -| `QueryRemindersOptions` | `QueryRemindersRequest` (was `Pager & { filter?, sort? }` — an exact duplicate) | +| Alias | Now derives from | +| ------------------- | ---------------------------------------------- | +| `QueryPollsFilters` | `NonNullable` | +| `QueryVotesFilters` | `NonNullable` | +| `ReminderFilters` | `NonNullable` | + +`QueryRemindersOptions` is **not** in that table: unlike the aliases above it does not survive at all. v9 defined it as `Pager & { filter?: ReminderFilters; sort?: ReminderSort }`; v10 removes it with no replacement alias — use `QueryRemindersRequest` directly. (It is not a pure rename: `sort` is now `SortParamRequest[]` and `filter` is operator-constrained, per the sections above.) `ReminderPaginator`'s second generic parameter moved with it, so `PaginatorOptions` becomes `PaginatorOptions`. Beyond the three breaking effects above, the field sets shifted to match the API spec: @@ -496,7 +497,7 @@ _user?: ClientUser The following v9 helper types are removed from the public surface. They mostly served the old hand-rolled types and are no longer needed: -`Readable`, `KnownKeys`, `PartializeKeys`, `UnknownType`, `MessageResponseBase`, `LocalMessageBase`, `FormatMessageResponse`, `ChannelAPIResponse` variants, `QueryChannelsAPIResponse`, `QueryReactionsOptions`/`APIResponse`, `TranslateResponse`, `ModerationResult`, `AutomodDetails`, `FlagsResponse`, `MessageFlagsResponse`, `FlagReport(s)Response`, `ReviewFlagReportResponse`, `BannedUsersResponse`, `FutureChannelBan(s)Response`, `HookEvent(s)Response`, `CheckPush/SQS/SNSResponse`, `CommandResponse` family, `ExportChannel*`/`ExportUsers*` types, push-preference types (`ChatLevelPushPreference`, `CallLevelPushPreference`, `PushPreferenceLevel`, `ChatPreferences`, `PushPreference`). +`Readable`, `KnownKeys`, `PartializeKeys`, `UnknownType`, `MessageResponseBase`, `LocalMessageBase`, `FormatMessageResponse`, `ChannelAPIResponse` variants, `QueryChannelsAPIResponse`, `QueryReactionsOptions`/`QueryReactionsAPIResponse`, `TranslateResponse`, `ModerationResult`, `AutomodDetails`, `FlagsResponse`, `MessageFlagsResponse`, `FlagReport(s)Response`, `ReviewFlagReportResponse`, `BannedUsersResponse`, `FutureChannelBan(s)Response`, `HookEvent(s)Response`, `CheckPush/SQS/SNSResponse`, `CommandResponse` family, `ExportChannel*`/`ExportUsers*` types, push-preference types (`ChatLevelPushPreference`, `CallLevelPushPreference`, `PushPreferenceLevel`, `ChatPreferences`, `PushPreference`). For any of these that survive as a generated shape, the replacement is the generator's `Gen_*` re-export (re-exported through `./types` or `./gen/models`). For the type utilities (`Readable`, `KnownKeys`, `PartializeKeys`, `UnknownType`) there is no replacement — inline the built-in equivalent or drop the constraint. diff --git a/v9-to-v10-migration-guide-type-renames.md b/v9-to-v10-migration-guide-type-renames.md index 127b52299..9bf470c6f 100644 --- a/v9-to-v10-migration-guide-type-renames.md +++ b/v9-to-v10-migration-guide-type-renames.md @@ -4,7 +4,9 @@ > > This document is written for AI agents doing mechanical rewrites. Each entry lists the v9 name, the v10 name, and the file(s) where the type is exported from. All v10 names are still importable from the package root (`stream-chat`) or from `stream-chat/dist/types` — nothing has moved outside the package surface. > -> If a codebase imports one of these names it will fail to resolve in v10; apply the table below as a find/replace. Behavior is unchanged — the underlying type is identical to what the removed alias resolved to in v9. +> If a codebase imports one of these names it will fail to resolve in v10; apply the table below as a find/replace. For most rows behavior is unchanged — the underlying type is identical to what the removed alias resolved to in v9. +> +> **Three rows are the exception.** `APIErrorResponse`, `DraftMessagePayload`, and `EventAPIResponse` were hand-rolled object types in v9, not aliases of a generated type, and their v10 targets differ field-by-field. A mechanical find/replace on those three will compile in some places and silently change meaning in others — stop at them and read [Rows that are shape changes, not renames](#rows-that-are-shape-changes-not-renames) below. ## How to apply @@ -27,7 +29,7 @@ v10 exposes two generated types whose names collide with v9 aliases that pointed | v9 (removed) | v10 (use this) | Notes | | ------------------------------ | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `APIErrorResponse` | `APIError` | The v9 alias pointed at the generated `APIError`. Do not confuse with the local `APIError` in `src/errors.ts` — that's a different `Error & { code }` shape and is unaffected by this rename. If a file uses both, import the generated one with an `as Gen_APIError` alias. | +| `APIErrorResponse` | `APIError` | ⚠️ **Shape change, not an alias** — `StatusCode` becomes `status_code`, `code` becomes required, `details` becomes `Array`. See [below](#apierrorresponse--apierror). | | `AppSettings` | `AppResponseFields` | | | `AppSettingsAPIResponse` | `GetApplicationResponse` | Return type of `client.getAppSettings()`. | | `AutomodDetails` | `AutomodDetailsResponse` | | @@ -37,9 +39,9 @@ v10 exposes two generated types whose names collide with v9 aliases that pointed | `ChannelQueryOptions` | `ChannelGetOrCreateRequest` | Payload for `channel.watch()`, `channel.create()`, and `channel.query()`. The v9 alias masked the OpenAPI name; v10 uses the generated name directly. | | `CommandResponse` | `Command` | Slash-command descriptor — matches the shape stored under `channel.getConfig().commands`. | | `CreatePollData` | `CreatePollRequest` | Payload for `client.createPoll()` / `PollManager.createPoll()`. | -| `DraftMessagePayload` | `MessageRequest` | Trivial 1:1 alias. | +| `DraftMessagePayload` | `MessageRequest` | ⚠️ **Shape change, and the payload is now nested** — `channel.createDraft` takes `{ message: MessageRequest }`. See [below](#draftmessagepayload--messagerequest). | | `ErrorFromResponse` | `StreamAPIError` | **Runtime value**, not just a type — was `export const ErrorFromResponse = StreamAPIError;`. Rewrite `instanceof ErrorFromResponse` and `new ErrorFromResponse(...)` call sites too. | -| `EventAPIResponse` | `APIResponse` + WS `Event` | The endpoint that used to return an event over HTTP is gone. Consume the response as `APIResponse` and pick up the event from the WS stream (`Event`). | +| `EventAPIResponse` | depends on the endpoint | ⚠️ **One v9 alias became three generated response types.** HTTP endpoints still return an event — only `markDelivered` lost it. See [below](#eventapiresponse--one-type-per-endpoint). | | `EventTypes` | `EventType` | Simple singular/plural rename. | | `MarkDeliveredOptions` | `MarkDeliveredRequest` | | | `MarkReadOptions` | `MarkReadRequest` | | @@ -69,6 +71,45 @@ v10 exposes two generated types whose names collide with v9 aliases that pointed | `UpdateLocationPayload` | `UpdateLiveLocationRequest` | Payload for `channel.stopLiveLocationSharing`. | | `User_old` | `UserResponse` | Trivial 1:1 alias. | +## Rows that are shape changes, not renames + +Three entries in the table above were **hand-rolled object types** in v9 rather than aliases of a generated type. Renaming the identifier is necessary but not sufficient — the field sets differ, so read the field the call site actually touches. + +### `APIErrorResponse` → `APIError` + +| | v9 `APIErrorResponse` | v10 `APIError` | +| ----------- | ------------------------------------------------ | --------------------------------------------------------------------------- | +| status code | `StatusCode: number` | **`status_code: number`** — field renamed | +| `code` | `code?: number` | **`code: number`** — now required | +| `details` | `details?: { code: number; messages: string[] }` | **`details: Array`** — required, and a different type | +| — | — | adds `unrecoverable?: boolean`, `exception_fields?: Record` | + +The trap is `StatusCode`: it is still a live field name elsewhere in the SDK — on WS errors and on the local error type in `src/errors.ts` — so a blind `StatusCode` → `status_code` sweep will corrupt those call sites. Only rewrite it where the value came out of `err.response.data` on an HTTP error. + +There is **no name collision on the public surface**: `src/errors.ts` is not re-exported from the package root, so `import { APIError } from 'stream-chat'` unambiguously resolves to the generated model. (Inside this repo, files that need both import the generated one as `Gen_APIError`.) + +### `DraftMessagePayload` → `MessageRequest` + +v9's type was `PartializeKeys, 'id'> & { user_id?: string }`. Against `MessageRequest`: + +- `text` was **required**, and is now optional. +- `html` and `user_id` **do not exist** on `MessageRequest`. +- `MessageRequest` **adds** `pinned`, `pinned_at`, `pin_expires`, `restricted_visibility`, and narrows `type` to `'regular' | 'system'`. + +The payload is also **nested** now — `channel.createDraft` takes `CreateDraftRequest`, i.e. `{ message: MessageRequest }`, not a bare message. See `channel.createDraft` in `v9-to-v10-migration-guide-methods.md` for the call-site rewrite. + +### `EventAPIResponse` → one type per endpoint + +v9 used this one alias for three endpoints. v10 gives each its own generated response type, and **two of the three still return the event over HTTP** — the v9 advice to "pick the event up from the WS stream" applies only to `markDelivered`. + +| v9 call site | v10 return type | Event | +| ---------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------ | +| `channel.sendEvent` | `StreamResponse` | `event: WSEvent` — still required; structural match for v9 | +| `channel.markAsReadRequest` → v10 `channel.markRead` | `StreamResponse` | `event?: MarkReadResponseEvent` — now **optional** | +| `client.markChannelsDelivered` → v10 `markDelivered` | `StreamResponse` | none — `{ duration }` only. Read the event off the WS stream here. | + +Two things to watch on the `markRead` row: `event` became optional, so destructuring it needs narrowing; and `MarkReadResponseEvent` is a **narrower shape than the WS `Event` union** (`type: string` rather than the `'message.read'` literal, and none of the WS read-event extras such as `total_unread_count` / `unread_channels`). It is not assignable to `Event` — do not feed it to code that expects a WS event. + ## Types that are **not** renamed (kept as-is) These v9 names look like they'd be caught by the same rewrite pass but are **not** simple aliases — they either have hand-authored shape on top of the generated type (via `RequireLiteral`, compound intersections, etc.) or point at a locally-defined type. Some names now re-export the generated shape directly through `export * from './gen/models'`; the name is the same but the shape may have narrowed since v9. Do not rewrite these: