Support predefined filters in ChannelListQuery#4113
Conversation
# Conflicts: # CHANGELOG.md # DemoApp/StreamChat/Components/DemoChatChannelListVC.swift
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR introduces server-side predefined channel list filters with transparent query resolution. Clients create queries with a ChangesPredefined Channel List Filters
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
Tests/StreamChatTests/Workers/ChannelListUpdater_Tests.swift (1)
452-452: 💤 Low valueConsider renaming the Core Data attribute for clarity.
The predicate uses attribute name
filterHashbut queries againstquery.queryHash. This naming mismatch could cause confusion for future maintainers. If the attribute now stores query hashes rather than filter hashes, consider renaming it toqueryHashin the Core Data model for semantic clarity.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Tests/StreamChatTests/Workers/ChannelListUpdater_Tests.swift` at line 452, The test is using request.predicate = NSPredicate(format: "filterHash == %@", query.queryHash) while the attribute name is filterHash but it actually stores query hashes; update the Core Data model attribute name from filterHash to queryHash for semantic clarity, then update all references (e.g., request.predicate, fetch requests, NSManagedObject properties) to use "queryHash" (or, if renaming the model is not possible, add a migration mapping and clear comments explaining the mismatch) so code and model consistently use queryHash instead of filterHash.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Around line 6-9: Update the Upcoming changelog section to follow "Keep a
Changelog" structure: replace emoji-prefixed subsection headings like "✅ Added"
with plain "### Added" (and use "### Fixed"/"### Changed" where applicable),
ensure the existing entry mentioning
ChannelListQuery(predefinedFilter:filterValues:sortValues:) is under "##
StreamChat" and move or add empty subsections for "## StreamChatUI" and "##
StreamChatCommonUI" under the same "# Upcoming" parent so the file contains
separate "StreamChat", "StreamChatUI", and "StreamChatCommonUI" subsections with
standardized "### Added/Fixed/Changed" headings.
In
`@Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift`:
- Around line 189-192: When replacing self.query with updatedQuery you must also
update the channelListLinker so websocket/event filtering uses the new query;
after assigning self.query = updatedQuery call the code path that reconfigures
the linker (e.g. update or recreate channelListLinker with updatedQuery — for
example invoke the existing linker configuration helper or a new
configureChannelListLinker(with: updatedQuery) method) instead of only calling
updateChannelListObserver(); apply the same change to the other occurrence
around lines 225–228 so channelListLinker always reflects the current query.
In `@Sources/StreamChat/Database/DTOs/ChannelListQueryDTO.swift`:
- Around line 56-63: The current code silently swallows decode failures for
persisted query payloads (dto.filterJSONData and dto.sortJSONData) by using
try?; update the ChannelListQueryDTO -> mapping logic that sets updated.filter
and updated.sort so that decoding errors from
Filter<ChannelListFilterScope>.predefinedFilter(fromJSONData:) and
[Sorting<ChannelListSortingKey>].predefinedFilterSort(fromJSONData:) are caught
and logged (include the error and the offending data) instead of being
ignored—use a do/catch around those calls, log the error with a clear message
referencing dto.filterJSONData/dto.sortJSONData and the associated types
(Filter<ChannelListFilterScope>, Sorting<ChannelListSortingKey>), and only set
updated.filter/updated.sort on success.
In `@Sources/StreamChat/Query/ChannelListQuery`+PredefinedFilter.swift:
- Around line 34-37: The guard in ChannelListQuery+PredefinedFilter that checks
ChannelListFilterScope.predefinedFilterKeyMapping[key] logs unknown keys but
returns self, leaving the unknown leaf in the filter tree; change the
early-return behavior so the unknown key is actually removed from the returned
filter (e.g., return a new filter with that child/leaf dropped) instead of
returning self. Locate the guard (the lookup of predefinedFilterKeyMapping[key])
and replace the return self with logic that returns the filter with the
offending leaf removed (use the existing filter-tree manipulation helpers or
implement a removal of the child node for the given key) so unresolved keys
cannot leak into predicate resolution.
In `@Sources/StreamChat/StateLayer/ChannelListState`+Observer.swift:
- Around line 63-69: reload(with:) currently replaces channelListObserver but
leaves channelListLinker tied to the old query; update reload(with:) to also
rebuild or reinitialize channelListLinker using the new query (e.g., call
Self.makeChannelListLinker(for: newQuery, database: database, clientConfig:
clientConfig) or the existing factory used at init), so both channelListObserver
and channelListLinker reflect the newQuery and subsequent event-driven updates
evaluate against the refreshed criteria.
In `@TestTools/StreamChatTestTools/SpyPattern/Spy/ChannelListUpdater_Spy.swift`:
- Around line 53-60: The spy currently only treats a server-resolved change as
updatedQuery when filters differ (guard uses resolvedQuery.isFilterEqual), so
sort-only resolutions are ignored; update the logic inside update_completion
(where resolvedQuery, changedQuery and ChannelListQuery are used) to detect any
mutation of the query (not just filter changes) — for example, replace the
isFilterEqual check with a full query equality check or add a sort-comparison
(e.g., use an existing isEqual/isQueryEqual method or compare resolvedQuery.sort
to channelListQuery.sort) so that sort-only server resolutions also assign
changedQuery and propagate it into ChannelListUpdateResult.
---
Nitpick comments:
In `@Tests/StreamChatTests/Workers/ChannelListUpdater_Tests.swift`:
- Line 452: The test is using request.predicate = NSPredicate(format:
"filterHash == %@", query.queryHash) while the attribute name is filterHash but
it actually stores query hashes; update the Core Data model attribute name from
filterHash to queryHash for semantic clarity, then update all references (e.g.,
request.predicate, fetch requests, NSManagedObject properties) to use
"queryHash" (or, if renaming the model is not possible, add a migration mapping
and clear comments explaining the mismatch) so code and model consistently use
queryHash instead of filterHash.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ff35987c-ce52-43f5-afbf-f502fe762565
📒 Files selected for processing (30)
CHANGELOG.mdDemoApp/StreamChat/Components/DemoChatChannelListVC.swiftSources/StreamChat/APIClient/Endpoints/Payloads/ChannelListPayload.swiftSources/StreamChat/Controllers/ChannelListController/ChannelListController.swiftSources/StreamChat/Database/DTOs/ChannelDTO.swiftSources/StreamChat/Database/DTOs/ChannelListQueryDTO.swiftSources/StreamChat/Database/DatabaseSession.swiftSources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contentsSources/StreamChat/Query/ChannelListQuery+PredefinedFilter.swiftSources/StreamChat/Query/ChannelListQuery.swiftSources/StreamChat/Query/Filter.swiftSources/StreamChat/Query/Sorting/ChannelListSortingKey.swiftSources/StreamChat/Repositories/SyncOperations.swiftSources/StreamChat/StateLayer/ChannelList.swiftSources/StreamChat/StateLayer/ChannelListState+Observer.swiftSources/StreamChat/StateLayer/ChannelListState.swiftSources/StreamChat/Utils/Dictionary+Extensions.swiftSources/StreamChat/Workers/ChannelListUpdater.swiftStreamChat.xcodeproj/project.pbxprojTestTools/StreamChatTestTools/Mocks/StreamChat/Database/DatabaseSession_Mock.swiftTestTools/StreamChatTestTools/SpyPattern/Spy/ChannelListUpdater_Spy.swiftTests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelListPayload_Tests.swiftTests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swiftTests/StreamChatTests/Database/DTOs/ChannelListQueryDTO_Tests.swiftTests/StreamChatTests/Query/ChannelListQuery_PredefinedFilter_Tests.swiftTests/StreamChatTests/Query/ChannelListQuery_Tests.swiftTests/StreamChatTests/Query/Filter_Tests.swiftTests/StreamChatTests/Query/Sorting/ListDatabaseObserver+Sorting_Tests.swiftTests/StreamChatTests/StateLayer/ChannelList_Tests.swiftTests/StreamChatTests/Workers/ChannelListUpdater_Tests.swift
| ## StreamChat | ||
| ### ✅ Added | ||
| - Add `ChannelListQuery(predefinedFilter:filterValues:sortValues:)` for creating channel list queries with predefined filters [#4113](https://github.com/GetStream/stream-chat-swift/pull/4113) | ||
|
|
There was a problem hiding this comment.
Align Upcoming changelog structure with the required format.
Use ### Added / ### Fixed / ### Changed (without emoji), and include the ## StreamChatCommonUI subsection under # Upcoming as required.
As per coding guidelines, "Follow Keep a Changelog format with ### Added, ### Fixed, ### Changed subsections in CHANGELOG.md" and "Include separate subsections in CHANGELOG.md for StreamChat, StreamChatUI, and StreamChatCommonUI".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CHANGELOG.md` around lines 6 - 9, Update the Upcoming changelog section to
follow "Keep a Changelog" structure: replace emoji-prefixed subsection headings
like "✅ Added" with plain "### Added" (and use "### Fixed"/"### Changed" where
applicable), ensure the existing entry mentioning
ChannelListQuery(predefinedFilter:filterValues:sortValues:) is under "##
StreamChat" and move or add empty subsections for "## StreamChatUI" and "##
StreamChatCommonUI" under the same "# Upcoming" parent so the file contains
separate "StreamChat", "StreamChatUI", and "StreamChatCommonUI" subsections with
standardized "### Added/Fixed/Changed" headings.
| guard let coreDataMetadata = ChannelListFilterScope.predefinedFilterKeyMapping[key] else { | ||
| StreamCore.log.error("Unknown channel list filtering key '\(key)' - dropping from local predefined filter.") | ||
| return self | ||
| } |
There was a problem hiding this comment.
Unknown filter keys are not actually dropped.
Line 35 says unknown keys are dropped, but Line 36 returns self, so the unknown leaf remains in the filter tree. This can leak unresolved keys into local predicate resolution and cause incorrect cached/local behavior.
Proposed direction
-private func applyCoreDataFilteringKeys() -> Filter {
+private func applyCoreDataFilteringKeys() -> Filter? {
if `operator`.isGroupOperator {
guard let children = value as? [Filter] else {
- return self
+ return self
}
+ let mappedChildren = children.compactMap { $0.applyCoreDataFilteringKeys() }
return Filter(
operator: `operator`,
key: nil,
- value: children.map { $0.applyCoreDataFilteringKeys() },
+ value: mappedChildren,
isCollectionFilter: false
)
}
guard let key else { return self }
guard let coreDataMetadata = ChannelListFilterScope.predefinedFilterKeyMapping[key] else {
StreamCore.log.error("Unknown channel list filtering key '\(key)' - dropping from local predefined filter.")
- return self
+ return nil
}
return Filter(
operator: `operator`,
key: key,
value: value,
valueMapper: coreDataMetadata.valueMapper,
keyPathString: coreDataMetadata.keyPathString,
isCollectionFilter: coreDataMetadata.isCollectionFilter,
predicateMapper: coreDataMetadata.predicateMapper
)
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Sources/StreamChat/Query/ChannelListQuery`+PredefinedFilter.swift around
lines 34 - 37, The guard in ChannelListQuery+PredefinedFilter that checks
ChannelListFilterScope.predefinedFilterKeyMapping[key] logs unknown keys but
returns self, leaving the unknown leaf in the filter tree; change the
early-return behavior so the unknown key is actually removed from the returned
filter (e.g., return a new filter with that child/leaf dropped) instead of
returning self. Locate the guard (the lookup of predefinedFilterKeyMapping[key])
and replace the return self with logic that returns the filter with the
offending leaf removed (use the existing filter-tree manipulation helpers or
implement a removal of the child node for the given key) so unresolved keys
cannot leak into predicate resolution.
| let resolvedQuery = loadPredefinedFilter(for: channelListQuery) | ||
| update_completion = { result in | ||
| let changedQuery: ChannelListQuery? = { | ||
| guard let resolvedQuery, !resolvedQuery.isFilterEqual(to: channelListQuery) else { return nil } | ||
| return resolvedQuery | ||
| }() | ||
| completion?(result.map { ChannelListUpdateResult(channels: $0, updatedQuery: changedQuery) }) | ||
| } |
There was a problem hiding this comment.
Propagate sort-only query changes in spy updatedQuery.
Line [56] checks only isFilterEqual, so sort-only server resolutions won’t set updatedQuery. This can hide regressions in tests that rely on query mutation.
🛠️ Proposed fix
let changedQuery: ChannelListQuery? = {
- guard let resolvedQuery, !resolvedQuery.isFilterEqual(to: channelListQuery) else { return nil }
+ guard let resolvedQuery, resolvedQuery.queryHash != channelListQuery.queryHash else { return nil }
return resolvedQuery
}()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@TestTools/StreamChatTestTools/SpyPattern/Spy/ChannelListUpdater_Spy.swift`
around lines 53 - 60, The spy currently only treats a server-resolved change as
updatedQuery when filters differ (guard uses resolvedQuery.isFilterEqual), so
sort-only resolutions are ignored; update the logic inside update_completion
(where resolvedQuery, changedQuery and ChannelListQuery are used) to detect any
mutation of the query (not just filter changes) — for example, replace the
isFilterEqual check with a full query equality check or add a sort-comparison
(e.g., use an existing isEqual/isQueryEqual method or compare resolvedQuery.sort
to channelListQuery.sort) so that sort-only server resolutions also assign
changedQuery and propagate it into ChannelListUpdateResult.
martinmitrevski
left a comment
There was a problem hiding this comment.
looks good, but some changes at tricky places - we should do extensive testing here
| completion(error) | ||
| } | ||
| updateChannelList { result in | ||
| completion?(result.error) |
There was a problem hiding this comment.
I think I've mentioned this in the other PR as well, why not self?.callback?
There was a problem hiding this comment.
Adding back, got dropped with refactoring.
| /// - Important: Always add new sorting keys to the map. | ||
| static let predefinedSortingKeyMapping: [String: Self] = Dictionary( | ||
| uniqueKeysWithValues: [ | ||
| .cid, |
There was a problem hiding this comment.
are these documented/checked by the backend?
| return | ||
| } | ||
|
|
||
| let keys = container.allKeys |
There was a problem hiding this comment.
why are all these changes needed?
There was a problem hiding this comment.
Backend implementation can return JSON which looks like:
{ "type": "messaging", "members": { "$in": ["amy"]
} }
which should be treated as $and.
Current parsing would only pick the first node. These changes fixes it by turning that case into explicit $and. $eq has a similar case where the operator can be omitted from the JSON.
I will see how to make this code more understandable.
There was a problem hiding this comment.
OK, not really, but I did add more tests since there were some small issues with the implicit $and handling.
| private let channelListLinker: ChannelListLinker | ||
| private let channelListUpdater: ChannelListUpdater | ||
| private let database: DatabaseContainer | ||
| private let dynamicFilter: ((ChatChannel) -> Bool)? |
There was a problem hiding this comment.
no more dynamic filter?
There was a problem hiding this comment.
It is just unused variable here, it is passed into ChannelListLinker in the init method. Probably should revert this cleanup noise.
Generated by 🚫 Danger |
| if let updated = worker.loadPredefinedFilter(for: query) { | ||
| query = updated | ||
| } |
There was a problem hiding this comment.
For supporting app relaunches where API call has not finished and we load the filter from CoreData and pass it to FRC.
There was a problem hiding this comment.
I had 2 options here:
a) load it async, but then FRC needs to be recreated/reloaded anyway (2 times, one without correct local filter, second with loaded filter) (this is because only CoreData knows the fetches filter JSON)
b) block, load filter, create FRC once
This reverts commit b0a5371.
# Conflicts: # CHANGELOG.md
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@fastlane/Fastfile`:
- Around line 733-740: The validate_generated_code lane currently runs generate
and then checks Sources/StreamChat/Generated/PredefinedFilter+Generated.swift
with a raw git status, which can fail if Sourcery output isn't formatted exactly
like the committed file; update the lane (or the generate task) to format the
generated file before comparing — e.g., after calling generate (or within the
make generate target that runs sourcery), run the project's formatter/linter fix
command (the same invoked by fastlane run_swift_format / swiftformat --fix
and/or swiftlint --fix) on the generated file
(Sources/StreamChat/Generated/PredefinedFilter+Generated.swift) and only then
perform the git status check in validate_generated_code so the staleness test
ignores differences only due to formatting.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7b68af7b-5f82-4fa6-b330-7dbd504636ce
⛔ Files ignored due to path filters (2)
Sources/StreamChat/Generated/PredefinedFilter+Generated.swiftis excluded by!**/generated/**Sources/StreamChat/Generated/PredefinedFilter.stencilis excluded by!**/generated/**
📒 Files selected for processing (10)
.github/workflows/smoke-checks.yml.swiftlint.ymlGithubfileMakefilePackage.swiftScripts/bootstrap.shSources/StreamChat/.sourcery.ymlSources/StreamChat/Query/ChannelListQuery.swiftTests/StreamChatTests/Query/ChannelListQuery_PredefinedFilter_Tests.swiftfastlane/Fastfile
💤 Files with no reviewable changes (1)
- Sources/StreamChat/Query/ChannelListQuery.swift
✅ Files skipped from review due to trivial changes (4)
- Sources/StreamChat/.sourcery.yml
- Makefile
- .swiftlint.yml
- Githubfile
🚧 Files skipped from review as they are similar to previous changes (1)
- Tests/StreamChatTests/Query/ChannelListQuery_PredefinedFilter_Tests.swift
| private let channelListUpdater: ChannelListUpdater | ||
| private let client: ChatClient | ||
| @MainActor private var stateBuilder: StateBuilder<ChannelListState> | ||
| let query: ChannelListQuery |
There was a problem hiding this comment.
I was only for sync repo and now it needs to be mutable. Easiest was to just get rid of it.
martinmitrevski
left a comment
There was a problem hiding this comment.
lgtm ✅ another one for @testableapple to keep an eye on.
| // Generated using Sourcery 2.3.0 — https://github.com/krzysztofzablocki/Sourcery | ||
| // DO NOT EDIT | ||
|
|
||
| // Run `make generate` after changing channel-list FilterKey or ChannelListSortingKey members. |
There was a problem hiding this comment.
this is a good solution
# Conflicts: # CHANGELOG.md # Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelListPayload.swift # Sources/StreamChat/Database/DTOs/ChannelDTO.swift # Sources/StreamChat/Database/DTOs/ChannelListQueryDTO.swift # Sources/StreamChat/Database/DatabaseSession.swift # Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents # Sources/StreamChat/Repositories/SyncOperations.swift # Sources/StreamChat/StateLayer/ChannelList.swift # Sources/StreamChat/StateLayer/ChannelListState.swift # Sources/StreamChat/Workers/ChannelListUpdater.swift # TestTools/StreamChatTestTools/Mocks/StreamChat/Database/DatabaseSession_Mock.swift # TestTools/StreamChatTestTools/SpyPattern/Spy/ChannelListUpdater_Spy.swift # Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelListPayload_Tests.swift # Tests/StreamChatTests/Query/Sorting/ListDatabaseObserver+Sorting_Tests.swift # Tests/StreamChatTests/StateLayer/ChannelList_Tests.swift # Tests/StreamChatTests/Workers/ChannelListUpdater_Tests.swift
SDK Size
|
Public Interface public class ChatChannelListController: DataController, DelegateCallable, DataStoreProvider, @unchecked Sendable
- public let query: ChannelListQuery
+ public internal var query: ChannelListQuery
@MainActor public final class ChannelListState: ObservableObject
- public private var query: ChannelListQuery
+ public internal var query: ChannelListQuery
public struct ChannelListQuery: Encodable, Sendable, LocalConvertibleSortingQuery
-
+ case predefinedFilter = "predefined_filter"
-
+ case filterValues = "filter_values"
- public let filter: Filter<ChannelListFilterScope>
+ case sortValues = "sort_values"
- public let sort: [Sorting<ChannelListSortingKey>]
+
- public var pagination: Pagination
+
- public let messagesLimit: Int?
+ public internal var filter: Filter<ChannelListFilterScope>
- public let membersLimit: Int?
+ public internal var sort: [Sorting<ChannelListSortingKey>]
- public var options: QueryOptions
+ public var pagination: Pagination
-
+ public let messagesLimit: Int?
-
+ public let membersLimit: Int?
- public init(filter: Filter<ChannelListFilterScope>,sort: [Sorting<ChannelListSortingKey>] = [],pageSize: Int = .channelsPageSize,messagesLimit: Int? = nil,membersLimit: Int? = nil)
+ public var options: QueryOptions
-
+ public let predefinedFilter: String?
-
+ public let filterValues: [String: RawJSON]?
- public func encode(to encoder: Encoder)throws
+ public let sortValues: [String: RawJSON]?
+
+
+ public init(filter: Filter<ChannelListFilterScope>,sort: [Sorting<ChannelListSortingKey>] = [],pageSize: Int = .channelsPageSize,messagesLimit: Int? = nil,membersLimit: Int? = nil)
+ public init(predefinedFilter: String,filterValues: [String: RawJSON]? = nil,sortValues: [String: RawJSON]? = nil,pageSize: Int = .channelsPageSize,messagesLimit: Int? = nil,membersLimit: Int? = nil)
+
+
+ public func encode(to encoder: Encoder)throws |
StreamChat XCSize
Show 45 more objects
|
StreamChatUI XCSize
|
|
| - run: bundle exec fastlane rubocop | ||
| - run: bundle exec fastlane run_swift_format strict:true | ||
| - run: bundle exec fastlane validate_public_interface | ||
| - run: bundle exec fastlane validate_generated_code |



🔗 Issue Links
https://linear.app/stream/issue/IOS-1706
🎯 Goal
Let apps reference a server-side predefined channel-list filter by name and have the server resolve its filter/sort templates, instead of constructing the filter client-side.
📝 Summary
ChannelListQuery(predefinedFilter:filterValues:sortValues:)initializer.predefined_filterresponse are decoded and re-applied locally for Core Data caching.ChatChannelListController.queryandChannelListState.querybecomeinternal(set)so the SDK can swap to the resolved query after the first response.Filterdecoder now supports multi-key objects as implicit$andand the{ key: value }short form as implicit$eq, matching the Stream filter JSON shape.🛠 Implementation
ChannelListQuery+PredefinedFilter.swiftdecodes persisted filter/sort JSON and re-attaches Core Data wiring (keyPathString,valueMapper,predicateMapper) viaChannelListFilterScope.predefinedFilterKeyMapping. Unknown keys are logged and dropped.predefinedFilterKeyMapping/predefinedSortingKeyMappingregistries are generated by Sourcery from the channel-listFilterKey/ChannelListSortingKeydeclarations (Generated/PredefinedFilter+Generated.swift). Adding a key no longer needs a manual map edit —make generateregenerates them and CI fails if the committed output is stale.ChannelListPayloaddecodes the newpredefined_filterresponse key.ChannelListUpdaterpersists the resolved filter+sort ontoChannelListQueryDTOand returns an updatedChannelListQueryto the controller/state layer.loadPredefinedFilter(for:)rehydrates the locally cached resolved query so Core Data fetch predicates work before the first server response lands.🧪 Manual Testing Notes
New pre-defined filters can be added through the dashboard.
☑️ Contributor Checklist
docs-contentrepoSummary by CodeRabbit
New Features
ChannelListQuery(predefinedFilter:filterValues:sortValues:), enabling server-side filtering with substitutable parameter values.Documentation