Skip to content

feat: migrate to instance configuration api - #3784

Merged
isekovanic merged 5 commits into
V10from
feat/migrate-to-instance-configuration-api
Aug 22, 2026
Merged

feat: migrate to instance configuration api#3784
isekovanic merged 5 commits into
V10from
feat/migrate-to-instance-configuration-api

Conversation

@isekovanic

Copy link
Copy Markdown
Contributor

🎯 Goal

Migrate the SDK onto client.config, the instance-configuration API added in stream-chat-js#1831.

Two things follow from it:

  • Feature gates must be read resolved, not raw. Six channel-type flags and four composer flags now resolve into the instance's configuration, ANDed with whatever the integrator registered. Reading channel.serverConfig?.read_events answers only the server's half, so UI gated on it offers features the client has already disabled.
  • Configuration belongs in one place. Props that duplicated what client.config can express are removed rather than kept as a second way in.

🛠 Implementation details

Resolved config replaces raw server flags

Call site Before After
useMarkRead getConfig()?.read_events channel.config.readEvents.enabled
ChannelMessagePreviewDeliveryStatus same reactive via useStateStore
ThreadMessagePreviewDeliveryStatus same reactive via useStateStore
Channel poll gate getConfig()?.polls messageComposer.config.polls.enabled
Channel commands getConfig()?.commands?.length channel.config.availableCommands
AutoCompleteInput getConfig()?.max_message_length composer.config.text.maxLengthOnSend (server-capped)
usePaginatedChannels paginator.config.x = y paginator.updateConfig({ x })

paginator.config is Readonly now — direct assignment is a compile error, and nested writes throw because defaults are deep-frozen.

Props removed

Removed Replacement
<Channel doMarkReadRequest> client.config.set({ channel: { requestHandlers: { markReadRequest } } })
<Channel doUpdateMessageRequest> …{ requestHandlers: { updateMessageRequest } }
doFileUploadRequest client.config.set({ messageComposer: { attachments: { doUploadRequest } } })
<Channel stateUpdateThrottleInterval> …{ channel: { messagePaginator: { stateThrottleMs } } }
<Channel newMessageStateUpdateThrottleInterval> same

The two throttle props had one reference each in the whole SDK — their own type declaration. Nothing read them. Deleted rather than left inert.

doSendMessageRequest stays (for now). The SDK occupies requestHandlers.sendMessageRequest unconditionally to run uploadPendingAttachments inside the send pipeline (after the optimistic ingest, before the POST), so it has to wrap an integrator handler rather than be replaced by it. TODO in useChannelRequestHandlers — it can be deleted once async uploads move to the LLC, or if the LLC exposes a next-shaped handler slot. Since we'll be moving the async uploads feature to the LLC most likely I decided it was best to wait for this and then we can probably get rid of the hook for good.

Removing doFileUploadRequest also fixed a latent bug: the image-compression branch in Channel.tsx skipped compression only when the prop was set, so a doUploadRequest registered through client.config still got compressed. It now reads resolved config.

useChannelRequestHandlers: two correctness fixes

Re-apply on re-derivation. Channel.initializeConfig replaces requestHandlers from the declarative tree, and runs on every change to channel, messagePaginator or messageOperations (the latter two are alsoWatch). Our write goes through configState.partialNext, which is not one of those layers — so any client.config.set() on those keys dropped our send handler, and with it the attachment-upload step, silently. A configState.subscribe re-apply guards it; the handler's identity is the guard, so there is no write loop.

Stopped deleting slots we don't own. The hook used to delete markReadRequest / updateMessageRequest before re-registering. Once a handler arrives declaratively the LLC resolves it onto configState, and that delete removed it — the LLC then fell back to ctx.defaults.*, an unmocked request that hangs rather than errors. Now only sendMessageRequest / retrySendMessageRequest are touched.

Other

  • channel.configState is a prototype getter now, so { ...channel } no longer carries it. Guarded in the hook.
  • initiateClientWithChannels writes client.channelServerConfigsStore instead of jest.spyOn(channel, 'getConfig')serverConfig is a getter, and going through the store also drives the channel's own derivation, so channel.config is correct too.
  • New mock-builders/event/utils.tstoChannelResponse(). Channel was structurally assignable to Partial<ChannelResponse> by accident; it isn't now that channel.config means something different.
  • SampleApp: drafts moved to client.config.set() at the client-creation site; the setup function keeps only middleware and uses config.setSetupFunction instead of the deprecated setMessageComposerSetupFunction. Dropped linkPreviews: { enabled: true } — that is the v10 default.
  • ai-docs/ai-migration-v9-to-v10.md: new §13.1 covering the whole API (registration site, request handlers, setup functions, resolved-vs-raw table, caveats), plus 9 quick-reference rows and corrections to §5, §13, §16.1, §17.3. §16.1 was telling people to re-set messagePaginator.pageSize after mount, which is an imperative patch that gets dropped on the next derivation.

Behaviour changes without a compile error

  • linkPreviews.enabled defaults to true (LLC change). Was false, and LinkPreviewsManager.enabled used to AND url_enrichment itself; that gate moved into resolved config. Link previews now appear wherever enrichment is enabled server-side.
  • attachmentManager.isUploadEnabled is now config.enabled && hasAvailableUploadSlots && (!usesStreamStorage || hasUploadPermission), with the channel type's uploads flag ANDed into config.enabled. A custom doUploadRequest no longer waives the upload-file capability — integrators uploading outside Stream need attachments: { customCdn: true }.
  • Poll-button timing. pollCreationEnabled was false until the channel query landed (raw flag, undefined -> falsy). It is now true optimistically and narrows to false if the server says no, because polls.enabled defaults to true meaning "no opinion, let the server decide".

🎨 UI Changes

No visual changes. Three behavioural ones are listed above — link previews appearing by default is the visible one.

🧪 Testing

☑️ Checklist

  • I have signed the Stream CLA (required)
  • PR targets the develop branch — targets V10
  • Documentation is updated
  • New code is tested in main example apps, including all possible scenarios
    • SampleApp iOS and Android
    • Expo iOS and Android — no changes; ExpoMessaging has no drafts or composer setup to migrate

@isekovanic
isekovanic merged commit 33aa520 into V10 Aug 22, 2026
0 of 4 checks passed
@isekovanic
isekovanic deleted the feat/migrate-to-instance-configuration-api branch August 22, 2026 00:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant