feat: use configuration service API as a single source of truth - #3273
feat: use configuration service API as a single source of truth#3273MartinCupela wants to merge 13 commits into
Conversation
# Conflicts: # examples/tutorial/src/3-channel-list/App.tsx # examples/tutorial/src/7-livestream/App.tsx # examples/vite/src/App.tsx # examples/vite/src/ChatLayout/SwitchableChannelNavigation.tsx # examples/vite/src/SingleChannel/SingleChannelApp.tsx # src/components/ChannelList/ChannelList.tsx # src/components/Chat/Chat.tsx # src/context/ChatContext.tsx
# Conflicts: # ai-docs/ai-migration-v14-v15.md # examples/vite/src/AppSettings/tabs/General/GeneralTab.tsx # src/components/Channel/hooks/useChannelConfig.ts # src/components/Thread/ThreadHeader.tsx
|
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: Pro Plus 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 |
`client.channelConfigsByTypeStore` is now `client.channelServerConfigs
|
Size Change: -4.05 kB (-0.49%) Total Size: 821 kB 📦 View Changed
ℹ️ View Unchanged
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## release-v15 #3273 +/- ##
==============================================
Coverage ? 84.80%
==============================================
Files ? 521
Lines ? 15442
Branches ? 4934
==============================================
Hits ? 13096
Misses ? 2346
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
1. What changes with the new configuration API
The client combines each server flag with whatever is registered through
client.config, and thatcombined value is what it enforces. Components were reading only the server's half, so a menu could
offer an action the composer had already disabled.
Components read resolved configuration
useChannelConfigreturns the channel's resolved configuration rather than the channel type'sraw server config, so field names change with it:
useAttachmentManagerStategained the composer-resolved gates —attachmentsEnabled,locationEnabled,pollsEnabled,customCdn— which letAttachmentSelectordrop a duplicateselector of its own.
Both hooks subscribe to the configuration. These values are getters on the LLC instances, so
nothing re-rendered when they changed; a
client.config.set()did not reach the screen.useMarkReadmoved from
channel.getConfig()?.read_eventsto the resolvedreadEvents.enabledfor the samereason — a plain method call sits outside React's dependency graph and cannot re-run.
After this, no component reads
channel.serverConfig.Request handlers register once on the client
doSendMessageRequest,doUpdateMessageRequest,doDeleteMessageRequestanddoMarkReadRequestare removed from
ChannelandThread, along withuseChannelRequestHandlers,useThreadRequestHandlers,useEditMessageHandler, and the ownership coordinator behind them.The props and
client.configwrote to the same slot, so the SDK carried a coordinator that trackedwhich mounted component owned each handler, restored the previous owner on unmount, and re-applied
everything whenever the LLC re-derived. All of that existed to reconcile two ways of doing one thing.
Example app
examples/vitegains a Configuration tab: the resolved tree per scope, inline editors, areference of every configurable path, and a view of what is registered versus what resolved. It was
the main tool for exercising this end to end.
2. Other changes introduced here (not already in
release-v15)Unified
channel.staterelease-v15still reads the per-domain stores (ownCapabilitiesStore,membersStore,watcherStore,readStore). The client replaced them with a singleStateStore<ChannelStateData>,so every consumer here moves to one selector over
channel.state:useChannelCapabilities,useIsDmChannel,useChannelHasMembersOnline,useChannelHeaderOnlineStatus,VirtualizedMessageList,TypingIndicator,ChannelHeader.useIsChannelMutedanduseAIStatebecome thin readers of reactive slices — dropping their ownclient.on(...)/channel.on(...)subscriptions and, for the AI indicator, gaining theconnection-loss reset the client now owns.
Channel lifecycle and reconnect
<Channel>marks its channel active while mounted (channel.activate()/deactivate(),refcounted) and calls
channel.reload()onconnection.recovered.These go together. The client deliberately skips re-seeding an active channel's message list on
channel-list hydration and reconnect, so its smaller page cannot perturb a larger scrolled-back
window — and it hands that window to the consumer. Nothing else calls
reload(), so without this thelist silently stays as it was while offline, and hard deletes made meanwhile are never reconciled
(they reach other clients via no event; only a re-query surfaces them).
Smaller fixes
useLiveLocationSharingManagerdisposes the manager on unmount.StreamedMessageTextunsubscribes fromai_indicator.stop; it previously leaked a listener onevery effect run and on unmount.
Channel/utils.ts(findInMsgSetById/findInMsgSetByDate, helpers for ajumpToFirstUnreadMessagethat exists in neither repo; not publicly exported).src/styling/dark.scssadditions.Testing
mock-builders/generator/channelState.ts—channel.stateis aStateStorenow, so a plain-objectmock crashes with
getLatestValue is not a function. This builds a real store plus the conveniencegetters the real
ChannelStateexposes.PollCreationDialog/__tests__/validationErrors.test.tsx— asserts each poll validation code rendersits own translated copy. Its
treturns a marker rather than the English default, because a testthat asserts English copy cannot tell translated output from the client's raw
message.Documentation
ai-docs/ai-migration-v14-v15.mdgains a Channel state moves to a single reactive store sectioncovering the
*Storeremoval, theAIStatesimport move,disconnected→pendingDisposal, theactivate()/reload obligation, and the test-mock changes. The configuration sections also get thetop-level heading they were missing.
Dependency
stream-chatmoves from10.0.0-rc.6to10.0.0-rc.7(peer and dev), matching the examples.The channel-state APIs above do not exist in
rc.6.3. Breaking changes not already in
release-v15BREAKING CHANGE: request-handler props are removed from
ChannelandThread.doSendMessageRequest,doUpdateMessageRequest,doDeleteMessageRequestanddoMarkReadRequestare gone, along withuseChannelRequestHandlers,useThreadRequestHandlersanduseEditMessageHandler. Register once on the client:Three differences: handlers take a single params object and return
{ message }; threadflows register under the
threadkey; and registration is per CLIENT, not per mountedsubtree — the one thing the props could do that this cannot. Per-channel behaviour now
needs a branch inside one handler on the
cidit receives.BREAKING CHANGE:
useChannelConfigreturns the channel's RESOLVED configuration insteadof the channel type's raw server config, so field names change:
channelConfig?.typing_events → channelConfig?.typingEvents.enabled
channelConfig?.read_events → channelConfig?.readEvents.enabled
channelConfig?.replies → channelConfig?.replies.enabled
channelConfig?.user_message_reminders → channelConfig?.userMessageReminders.enabled
channelConfig?.commands → channelConfig?.availableCommands
Every gate is now the server flag ANDed with what is registered through
client.config,which is what the client actually enforces. The hook also subscribes: these were getters,
so a
client.config.set()previously never reached the screen. No component readschannel.serverConfigany more.BREAKING CHANGE:
useAttachmentManagerStatereplaceshasCustomDoUploadRequestwithcustomCdn, and gainsattachmentsEnabled,locationEnabledandpollsEnabled.BREAKING CHANGE: the per-domain stores on
channel.stateare removed —readStore,typingStore,membersStore,watcherStore,ownCapabilitiesStore,mutedUsersStore.Subscribe to
channel.stateitself; the selector is unchanged, becauseChannelStateDatais flat and keeps the same top-level keys:
useStateStore(channel.state.ownCapabilitiesStore, sel) → useStateStore(channel.state, sel)
WatcherStateis renamedChannelWatchState, andchannel.disconnectedis renamedchannel.pendingDisposal(removed outright — no deprecated alias).BREAKING CHANGE:
AIStatesis no longer exported fromstream-chat-react— import itfrom
stream-chat. The values are unchanged, but it is now literal-typed, so an inferredarray of its members no longer accepts the wide
AIState:const STOPPABLE: readonly AIState[] = [AIStates.Thinking, AIStates.Generating];
BREAKING CHANGE:
<Channel>marks its channel active while mounted and reloads it onconnection.recovered. The client skips re-seeding an active channel's message list onhydration and reconnect, so the consumer owns that window. A custom channel surface that
does not render
<Channel>must callchannel.activate()/deactivate()andchannel.reload()itself, or its list goes stale after a reconnect.Note for tests (not a consumer break, but it will affect integrator suites):
channel.getConfig()is now thechannel.serverConfiggetter, sovi.spyOn(channel, 'getConfig')has no equivalent — set the channel type's config on
client.channelServerConfigs, which alsodrives the derivation so
channel.configis correct.vi.spyOn(channel, 'muteStatus')→ seedchannel.state.partialNext({ muteStatus }).channel.disconnected = true→channel.pendingDisposal = true.channel.statemock now crashes — use a realStateStore.