stream-chat 9.44.1 → 9.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/cjs/index.browser.js +3546 -2681
  2. package/dist/cjs/index.browser.js.map +4 -4
  3. package/dist/cjs/index.node.js +3555 -2681
  4. package/dist/cjs/index.node.js.map +4 -4
  5. package/dist/esm/index.mjs +3546 -2681
  6. package/dist/esm/index.mjs.map +4 -4
  7. package/dist/types/channel_manager.d.ts +5 -2
  8. package/dist/types/channel_state.d.ts +1 -1
  9. package/dist/types/client.d.ts +112 -5
  10. package/dist/types/constants.d.ts +1 -0
  11. package/dist/types/messageComposer/LocationComposer.d.ts +1 -1
  12. package/dist/types/messageComposer/configuration/commands.configuration.d.ts +6 -0
  13. package/dist/types/messageComposer/configuration/configuration.d.ts +1 -2
  14. package/dist/types/messageComposer/configuration/index.d.ts +4 -0
  15. package/dist/types/messageComposer/configuration/types.d.ts +21 -0
  16. package/dist/types/messageComposer/fileUtils.d.ts +1 -1
  17. package/dist/types/messageComposer/messageComposer.d.ts +6 -4
  18. package/dist/types/messageComposer/middleware/messageComposer/compositionValidation.d.ts +2 -1
  19. package/dist/types/messageComposer/middleware/messageComposer/textComposer.d.ts +1 -1
  20. package/dist/types/messageComposer/middleware/textComposer/commandUtils.d.ts +10 -1
  21. package/dist/types/messageComposer/middleware/textComposer/mentionUtils.d.ts +8 -0
  22. package/dist/types/messageComposer/middleware/textComposer/mentions.d.ts +77 -15
  23. package/dist/types/messageComposer/middleware/textComposer/types.d.ts +51 -2
  24. package/dist/types/messageComposer/pollComposer.d.ts +2 -2
  25. package/dist/types/messageComposer/textComposer.d.ts +17 -3
  26. package/dist/types/pagination/UserGroupPaginator.d.ts +21 -0
  27. package/dist/types/pagination/index.d.ts +1 -0
  28. package/dist/types/types.d.ts +132 -2
  29. package/dist/types/utils.d.ts +2 -0
  30. package/package.json +38 -31
  31. package/src/channel_manager.ts +88 -13
  32. package/src/client.ts +217 -12
  33. package/src/constants.ts +1 -0
  34. package/src/messageComposer/MessageComposerEffectHandlers.ts +1 -0
  35. package/src/messageComposer/configuration/commands.configuration.ts +55 -0
  36. package/src/messageComposer/configuration/configuration.ts +3 -1
  37. package/src/messageComposer/configuration/index.ts +4 -0
  38. package/src/messageComposer/configuration/types.ts +27 -0
  39. package/src/messageComposer/messageComposer.ts +73 -22
  40. package/src/messageComposer/middleware/messageComposer/compositionValidation.ts +23 -15
  41. package/src/messageComposer/middleware/messageComposer/textComposer.ts +151 -31
  42. package/src/messageComposer/middleware/textComposer/commandUtils.ts +68 -1
  43. package/src/messageComposer/middleware/textComposer/commands.ts +6 -2
  44. package/src/messageComposer/middleware/textComposer/mentionUtils.ts +33 -0
  45. package/src/messageComposer/middleware/textComposer/mentions.ts +596 -66
  46. package/src/messageComposer/middleware/textComposer/types.ts +70 -2
  47. package/src/messageComposer/textComposer.ts +154 -10
  48. package/src/pagination/UserGroupPaginator.ts +93 -0
  49. package/src/pagination/index.ts +1 -0
  50. package/src/permissions.ts +1 -0
  51. package/src/types.ts +161 -2
  52. package/src/utils.ts +1 -0
@@ -2,7 +2,7 @@ import { TextComposerMiddlewareExecutor } from './middleware';
2
2
  import { StateStore } from '../store';
3
3
  import type { TextComposerSuggestion } from './middleware/textComposer/types';
4
4
  import type { TextSelection } from './middleware/textComposer/types';
5
- import type { TextComposerState, TextComposerStateSnapshot } from './middleware/textComposer/types';
5
+ import type { MentionEntity, TextComposerState, TextComposerStateSnapshot } from './middleware/textComposer/types';
6
6
  import type { Suggestions } from './middleware/textComposer/types';
7
7
  import type { MessageComposer } from './messageComposer';
8
8
  import type { CommandResponse, DraftMessage, LocalMessage, UserResponse } from '../types';
@@ -18,7 +18,7 @@ export declare class TextComposer {
18
18
  middlewareExecutor: TextComposerMiddlewareExecutor;
19
19
  constructor({ composer, message }: TextComposerOptions);
20
20
  get channel(): import("..").Channel;
21
- get config(): import("./configuration").TextComposerConfig;
21
+ get config(): import("..").TextComposerConfig;
22
22
  get enabled(): boolean;
23
23
  set enabled(enabled: boolean);
24
24
  get defaultValue(): string | undefined;
@@ -31,8 +31,9 @@ export declare class TextComposer {
31
31
  set publishTypingEvents(publishTypingEvents: boolean);
32
32
  get command(): CommandResponse | null | undefined;
33
33
  get mentionedUsers(): UserResponse[];
34
+ get mentions(): MentionEntity[];
34
35
  get selection(): TextSelection;
35
- get suggestions(): Suggestions<import("./middleware").Suggestion> | undefined;
36
+ get suggestions(): Suggestions<import("..").Suggestion> | undefined;
36
37
  get text(): string;
37
38
  get textIsEmpty(): boolean;
38
39
  initState: ({ message }?: {
@@ -41,10 +42,23 @@ export declare class TextComposer {
41
42
  getSnapshot: (state?: TextComposerState) => TextComposerSnapshot;
42
43
  restoreSnapshot: (snapshot: TextComposerSnapshot) => void;
43
44
  setMentionedUsers(users: UserResponse[]): void;
45
+ setMentions(entities: MentionEntity[]): void;
44
46
  clearCommand(): void;
47
+ /**
48
+ * @deprecated Use `upsertMentionEntity({ ...user, mentionType: 'user' })` instead.
49
+ */
45
50
  upsertMentionedUser: (user: UserResponse) => void;
51
+ /**
52
+ * @deprecated Use `getMentionEntity('user', userId)` instead.
53
+ */
46
54
  getMentionedUser: (userId: string) => UserResponse | undefined;
55
+ /**
56
+ * @deprecated Use `removeMentionEntity('user', userId)` instead.
57
+ */
47
58
  removeMentionedUser: (userId: string) => void;
59
+ upsertMentionEntity: (entity: MentionEntity) => void;
60
+ getMentionEntity: (mentionType: MentionEntity["mentionType"], id: MentionEntity["id"]) => MentionEntity | undefined;
61
+ removeMentionEntity: (mentionType: MentionEntity["mentionType"], id: MentionEntity["id"]) => void;
48
62
  setCommand: (command: CommandResponse | null) => void;
49
63
  setText: (text: string) => void;
50
64
  setSelection: (selection: TextSelection) => void;
@@ -0,0 +1,21 @@
1
+ import { BasePaginator } from './BasePaginator';
2
+ import type { PaginationQueryParams, PaginationQueryReturnValue, PaginatorOptions, PaginatorState } from './BasePaginator';
3
+ import type { UserGroupResponse } from '../types';
4
+ import type { StreamChat } from '../client';
5
+ /**
6
+ * Paginates user-group listing through `/usergroups`.
7
+ *
8
+ * This entity only supports forward cursor pagination via `created_at_gt` and `id_gt`.
9
+ * Previous-page pagination is not available because the API does not expose a backward cursor.
10
+ */
11
+ export declare class UserGroupPaginator extends BasePaginator<UserGroupResponse> {
12
+ private client;
13
+ protected _teamId: string | undefined;
14
+ constructor(client: StreamChat, options?: PaginatorOptions);
15
+ get initialState(): PaginatorState<UserGroupResponse>;
16
+ get teamId(): string | undefined;
17
+ set teamId(teamId: string | undefined);
18
+ private buildNextCursor;
19
+ query: ({ direction, }: PaginationQueryParams) => Promise<PaginationQueryReturnValue<UserGroupResponse>>;
20
+ filterQueryResults: (items: UserGroupResponse[]) => UserGroupResponse[];
21
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './BasePaginator';
2
2
  export * from './FilterBuilder';
3
3
  export * from './ReminderPaginator';
4
+ export * from './UserGroupPaginator';
@@ -88,6 +88,7 @@ export type AppSettingsAPIResponse = APIResponse & {
88
88
  async_url_enrich_enabled?: boolean;
89
89
  auto_translation_enabled?: boolean;
90
90
  before_message_send_hook_url?: string;
91
+ before_message_send_hook_attempt_timeout_ms?: number;
91
92
  campaign_enabled?: boolean;
92
93
  cdn_expiration_seconds?: number;
93
94
  custom_action_handler_url?: string;
@@ -631,6 +632,8 @@ export type MessageResponseBase = MessageBase & {
631
632
  mentioned_users?: UserResponse[];
632
633
  mentioned_channel?: boolean;
633
634
  mentioned_here?: boolean;
635
+ mentioned_group_ids?: string[];
636
+ mentioned_groups?: UserGroupResponse[];
634
637
  mentioned_roles?: string[];
635
638
  message_text_updated_at?: string;
636
639
  moderation?: ModerationResponse;
@@ -766,6 +769,29 @@ export type SearchAPIResponse = APIResponse & {
766
769
  previous?: string;
767
770
  results_warning?: SearchWarning | null;
768
771
  };
772
+ export type RoleResponse = {
773
+ name: Role;
774
+ custom: boolean;
775
+ scopes: string[];
776
+ created_at: string;
777
+ updated_at: string;
778
+ };
779
+ export type CreateRoleAPIResponse = APIResponse & {
780
+ role: RoleResponse;
781
+ };
782
+ export type ListRolesAPIResponse = APIResponse & {
783
+ roles: RoleResponse[];
784
+ };
785
+ export type SearchRolesAPIResponse = APIResponse & {
786
+ roles: RoleResponse[];
787
+ };
788
+ export type SearchRolesOptions = {
789
+ query: string;
790
+ include_global_roles?: boolean;
791
+ limit?: number;
792
+ name_gt?: string;
793
+ role_type?: 'user' | 'channel';
794
+ };
769
795
  export type SearchWarning = {
770
796
  channel_search_cids: string[];
771
797
  channel_search_count: number;
@@ -949,6 +975,15 @@ export type ChannelStateOptions = {
949
975
  offlineMode?: boolean;
950
976
  skipInitialization?: string[];
951
977
  skipHydration?: boolean;
978
+ /**
979
+ * Returns the full query response with hydrated channels from `queryChannels()`.
980
+ *
981
+ * This is a compatibility bridge for internal callers that need response level
982
+ * metadata such as `predefined_filter`. The default `queryChannels()` return value
983
+ * remains `Channel[]` to avoid a breaking change. This should be folded into a
984
+ * single full response API in the next major release.
985
+ */
986
+ withResponse?: boolean;
952
987
  };
953
988
  export type CreateChannelOptions = {
954
989
  automod?: ChannelConfigAutomod;
@@ -1861,6 +1896,7 @@ export type AppSettings = {
1861
1896
  async_url_enrich_enabled?: boolean;
1862
1897
  auto_translation_enabled?: boolean;
1863
1898
  before_message_send_hook_url?: string;
1899
+ before_message_send_hook_attempt_timeout_ms?: number;
1864
1900
  cdn_expiration_seconds?: number;
1865
1901
  custom_action_handler_url?: string;
1866
1902
  disable_auth_checks?: boolean;
@@ -2165,6 +2201,9 @@ export type Message = Partial<MessageBase & {
2165
2201
  mentioned_users: string[];
2166
2202
  shared_location?: StaticLocationPayload | LiveLocationPayload;
2167
2203
  mentioned_channel?: boolean;
2204
+ mentioned_here?: boolean;
2205
+ mentioned_group_ids?: string[];
2206
+ mentioned_roles?: string[];
2168
2207
  }>;
2169
2208
  export type MessageBase = CustomMessageData & {
2170
2209
  id: string;
@@ -2318,8 +2357,12 @@ export type TokenProvider = () => Promise<string>;
2318
2357
  export type TranslationLanguages = 'af' | 'am' | 'ar' | 'az' | 'bg' | 'bn' | 'bs' | 'cs' | 'da' | 'de' | 'el' | 'en' | 'es' | 'es-MX' | 'et' | 'fa' | 'fa-AF' | 'fi' | 'fr' | 'fr-CA' | 'ha' | 'he' | 'hi' | 'hr' | 'hu' | 'id' | 'it' | 'ja' | 'ka' | 'ko' | 'lt' | 'lv' | 'ms' | 'nl' | 'no' | 'pl' | 'ps' | 'pt' | 'ro' | 'ru' | 'sk' | 'sl' | 'so' | 'sq' | 'sr' | 'sv' | 'sw' | 'ta' | 'th' | 'tl' | 'tr' | 'uk' | 'ur' | 'vi' | 'zh' | 'zh-TW' | (string & {});
2319
2358
  export type TypingStartEvent = Event;
2320
2359
  export type ReservedUpdatedMessageFields = keyof typeof RESERVED_UPDATED_MESSAGE_FIELDS;
2321
- export type UpdatedMessage = Omit<MessageResponse, ReservedUpdatedMessageFields> & {
2360
+ export type UpdatedMessage = Omit<MessageResponse, ReservedUpdatedMessageFields | 'mentioned_groups'> & {
2322
2361
  mentioned_users?: string[];
2362
+ mentioned_channel?: boolean;
2363
+ mentioned_here?: boolean;
2364
+ mentioned_group_ids?: string[];
2365
+ mentioned_roles?: string[];
2323
2366
  type?: MessageLabel;
2324
2367
  };
2325
2368
  /**
@@ -3329,7 +3372,7 @@ export type GetDraftResponse = APIResponse & {
3329
3372
  export type QueryDraftsResponse = APIResponse & {
3330
3373
  drafts: DraftResponse[];
3331
3374
  } & Omit<Pager, 'limit'>;
3332
- export type DraftMessagePayload = PartializeKeys<DraftMessage, 'id'> & {
3375
+ export type DraftMessagePayload = PartializeKeys<Omit<DraftMessage, 'mentioned_groups'>, 'id'> & {
3333
3376
  user_id?: string;
3334
3377
  };
3335
3378
  export type DraftMessage = {
@@ -3339,6 +3382,11 @@ export type DraftMessage = {
3339
3382
  custom?: {};
3340
3383
  html?: string;
3341
3384
  mentioned_users?: string[];
3385
+ mentioned_channel?: boolean;
3386
+ mentioned_here?: boolean;
3387
+ mentioned_group_ids?: string[];
3388
+ mentioned_groups?: UserGroupResponse[];
3389
+ mentioned_roles?: string[];
3342
3390
  mml?: string;
3343
3391
  parent_id?: string;
3344
3392
  poll_id?: string;
@@ -3469,6 +3517,88 @@ export type QueryRemindersResponse = {
3469
3517
  prev?: string;
3470
3518
  next?: string;
3471
3519
  };
3520
+ export type UserGroupMemberResponse = {
3521
+ group_id: string;
3522
+ user_id: string;
3523
+ is_admin: boolean;
3524
+ created_at: string;
3525
+ };
3526
+ export type UserGroupResponse = {
3527
+ id: string;
3528
+ name: string;
3529
+ created_at: string;
3530
+ updated_at: string;
3531
+ description?: string;
3532
+ team_id?: string;
3533
+ members?: UserGroupMemberResponse[];
3534
+ created_by?: string;
3535
+ };
3536
+ export type CreateUserGroupOptions = {
3537
+ /** Human-readable user group name */
3538
+ name: string;
3539
+ /** Optional user group description shown to members */
3540
+ description?: string;
3541
+ /** Optional custom user group ID. If omitted, the backend generates one */
3542
+ id?: string;
3543
+ /** Optional list of user IDs to add as members when the group is created */
3544
+ member_ids?: string[];
3545
+ /** Optional team ID that scopes the user group to a specific team */
3546
+ team_id?: string;
3547
+ };
3548
+ export type CreateUserGroupResponse = APIResponse & {
3549
+ user_group: UserGroupResponse;
3550
+ };
3551
+ export type GetUserGroupOptions = {
3552
+ team_id?: string;
3553
+ };
3554
+ export type GetUserGroupResponse = APIResponse & {
3555
+ user_group: UserGroupResponse;
3556
+ };
3557
+ export type QueryUserGroupsOptions = {
3558
+ limit?: number;
3559
+ id_gt?: string;
3560
+ created_at_gt?: string;
3561
+ team_id?: string;
3562
+ };
3563
+ export type QueryUserGroupsResponse = APIResponse & {
3564
+ user_groups: UserGroupResponse[];
3565
+ };
3566
+ export type SearchUserGroupsOptions = {
3567
+ query: string;
3568
+ limit?: number;
3569
+ id_gt?: string;
3570
+ name_gt?: string;
3571
+ team_id?: string;
3572
+ };
3573
+ export type SearchUserGroupsResponse = APIResponse & {
3574
+ user_groups: UserGroupResponse[];
3575
+ };
3576
+ export type UpdateUserGroupOptions = {
3577
+ description?: string;
3578
+ name?: string;
3579
+ team_id?: string;
3580
+ };
3581
+ export type UpdateUserGroupResponse = APIResponse & {
3582
+ user_group: UserGroupResponse;
3583
+ };
3584
+ export type DeleteUserGroupOptions = {
3585
+ team_id?: string;
3586
+ };
3587
+ export type AddUserGroupMembersOptions = {
3588
+ member_ids: string[];
3589
+ as_admin?: boolean;
3590
+ team_id?: string;
3591
+ };
3592
+ export type AddUserGroupMembersResponse = APIResponse & {
3593
+ user_group: UserGroupResponse;
3594
+ };
3595
+ export type RemoveUserGroupMembersOptions = {
3596
+ member_ids: string[];
3597
+ team_id?: string;
3598
+ };
3599
+ export type RemoveUserGroupMembersResponse = APIResponse & {
3600
+ user_group: UserGroupResponse;
3601
+ };
3472
3602
  export type HookType = 'webhook' | 'sqs' | 'sns' | 'pending_message';
3473
3603
  export type EventHook = {
3474
3604
  id?: string;
@@ -90,6 +90,7 @@ export declare const toDeletedMessage: ({ message, deletedAt, hardDelete, }: {
90
90
  text?: string | undefined;
91
91
  user_id?: string | undefined;
92
92
  command?: string | undefined;
93
+ mentioned_groups?: import("./types").UserGroupResponse[] | undefined;
93
94
  mentioned_users?: UserResponse[] | undefined;
94
95
  latest_reactions?: import("./types").ReactionResponse[] | undefined;
95
96
  own_reactions?: import("./types").ReactionResponse[] | null | undefined;
@@ -123,6 +124,7 @@ export declare const toDeletedMessage: ({ message, deletedAt, hardDelete, }: {
123
124
  member?: import("./types").ChannelMemberResponse | undefined;
124
125
  mentioned_channel?: boolean | undefined;
125
126
  mentioned_here?: boolean | undefined;
127
+ mentioned_group_ids?: string[] | undefined;
126
128
  mentioned_roles?: string[] | undefined;
127
129
  message_text_updated_at?: string | undefined;
128
130
  moderation?: import("./types").ModerationResponse | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stream-chat",
3
- "version": "9.44.1",
3
+ "version": "9.45.0",
4
4
  "description": "JS SDK for the Stream Chat API",
5
5
  "homepage": "https://getstream.io/chat/",
6
6
  "author": {
@@ -29,11 +29,11 @@
29
29
  }
30
30
  },
31
31
  "browser": {
32
- "https": false,
33
32
  "crypto": false,
34
- "zlib": false,
33
+ "https": false,
35
34
  "jsonwebtoken": false,
36
- "ws": false
35
+ "ws": false,
36
+ "zlib": false
37
37
  },
38
38
  "license": "SEE LICENSE IN LICENSE",
39
39
  "keywords": [
@@ -51,40 +51,39 @@
51
51
  ],
52
52
  "dependencies": {
53
53
  "@types/jsonwebtoken": "^9.0.8",
54
- "@types/ws": "^8.5.14",
55
- "axios": "^1.15.1",
54
+ "@types/ws": "^8.18.1",
55
+ "axios": "^1.16.1",
56
56
  "base64-js": "^1.5.1",
57
- "form-data": "^4.0.4",
57
+ "form-data": "^4.0.5",
58
58
  "isomorphic-ws": "^5.0.0",
59
59
  "jsonwebtoken": "^9.0.3",
60
- "linkifyjs": "^4.3.2",
61
- "ws": "^8.18.1"
60
+ "linkifyjs": "^4.3.3",
61
+ "ws": "^8.20.1"
62
62
  },
63
63
  "devDependencies": {
64
- "@commitlint/cli": "^19.7.1",
65
- "@commitlint/config-conventional": "^19.7.1",
66
- "@eslint/js": "^9.21.0",
64
+ "@commitlint/cli": "^21.0.1",
65
+ "@commitlint/config-conventional": "^21.0.1",
66
+ "@eslint/js": "^9.39.4",
67
67
  "@semantic-release/changelog": "^6.0.3",
68
68
  "@semantic-release/git": "^10.0.1",
69
- "@types/base64-js": "^1.3.0",
70
- "@types/node": "^22.15.21",
69
+ "@types/node": "^22.19.19",
71
70
  "@types/sinon": "^10.0.6",
72
- "@vitest/coverage-v8": "3.1.4",
73
- "concurrently": "^9.1.2",
74
- "conventional-changelog-conventionalcommits": "^8.0.0",
75
- "dotenv": "^8.2.0",
76
- "esbuild": "^0.25.4",
77
- "eslint": "^9.27.0",
78
- "eslint-plugin-import": "^2.31.0",
79
- "globals": "^16.0.0",
71
+ "@vitest/coverage-v8": "^4.1.7",
72
+ "concurrently": "^9.2.1",
73
+ "conventional-changelog-conventionalcommits": "^9.3.1",
74
+ "dotenv": "^17.4.2",
75
+ "esbuild": "^0.28.0",
76
+ "eslint": "^9.39.4",
77
+ "eslint-plugin-import": "^2.32.0",
78
+ "globals": "^17.6.0",
80
79
  "husky": "^9.1.7",
81
- "lint-staged": "^15.2.2",
82
- "prettier": "^3.5.3",
83
- "semantic-release": "^25.0.2",
80
+ "lint-staged": "^17.0.5",
81
+ "prettier": "^3.8.3",
82
+ "semantic-release": "^25.0.3",
84
83
  "sinon": "^12.0.1",
85
- "typescript": "^5.8.3",
86
- "typescript-eslint": "^8.32.1",
87
- "vitest": "^3.1.4"
84
+ "typescript": "^6.0.3",
85
+ "typescript-eslint": "^8.59.4",
86
+ "vitest": "^4.1.7"
88
87
  },
89
88
  "scripts": {
90
89
  "build": "rm -rf dist && concurrently 'tsc' './scripts/bundle.mjs'",
@@ -97,7 +96,6 @@
97
96
  "eslint": "eslint --max-warnings 0",
98
97
  "eslint-fix": "yarn run eslint --fix",
99
98
  "test": "yarn test-unit",
100
- "testwatch": "NODE_ENV=test nodemon ./node_modules/.bin/mocha --timeout 20000 --require test-entry.js test/test.js",
101
99
  "test-types": "yarn run-test-types && yarn run-types-gen",
102
100
  "run-test-types": "node test/typescript/index.js",
103
101
  "run-types-gen": "tsc --esModuleInterop true --noEmit true --strictNullChecks true --noImplicitAny true --strict true test/typescript/*.ts",
@@ -105,10 +103,19 @@
105
103
  "test-coverage": "vitest run --coverage",
106
104
  "fix-staged": "lint-staged --config .lintstagedrc.fix.json --concurrent 1",
107
105
  "semantic-release": "semantic-release",
108
- "prepare": "husky; yarn run build"
106
+ "postinstall": "husky",
107
+ "prepare": "yarn run build"
109
108
  },
110
109
  "engines": {
111
110
  "node": ">=18"
112
111
  },
113
- "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
112
+ "packageManager": "yarn@4.15.0",
113
+ "dependenciesMeta": {
114
+ "esbuild": {
115
+ "built": true
116
+ },
117
+ "husky": {
118
+ "built": true
119
+ }
120
+ }
114
121
  }
@@ -1,10 +1,11 @@
1
- import type { StreamChat } from './client';
1
+ import type { QueryChannelsResponseWithChannels, StreamChat } from './client';
2
2
  import type {
3
3
  ChannelFilters,
4
4
  ChannelOptions,
5
5
  ChannelSort,
6
6
  ChannelStateOptions,
7
7
  Event,
8
+ QueryChannelsAPIResponse,
8
9
  } from './types';
9
10
  import type { ValueOrPatch } from './store';
10
11
  import { isPatch, StateStore } from './store';
@@ -34,6 +35,8 @@ export type ChannelManagerPagination = {
34
35
  isLoading: boolean;
35
36
  isLoadingNext: boolean;
36
37
  options: ChannelOptions;
38
+ responseFilters?: ChannelFilters;
39
+ responseSort?: ChannelSort;
37
40
  sort: ChannelSort;
38
41
  };
39
42
 
@@ -133,9 +136,14 @@ export type ChannelManagerOptions = {
133
136
  lockChannelOrder?: boolean;
134
137
  };
135
138
 
139
+ export type QueryChannelsRequestOutput = Channel[] | QueryChannelsResponseWithChannels;
140
+
136
141
  export type QueryChannelsRequestType = (
137
- ...params: Parameters<StreamChat['queryChannels']>
138
- ) => Promise<Channel[]>;
142
+ filters: ChannelFilters,
143
+ sort?: ChannelSort,
144
+ options?: ChannelOptions,
145
+ stateOptions?: ChannelStateOptions,
146
+ ) => Promise<QueryChannelsRequestOutput>;
139
147
 
140
148
  export const DEFAULT_CHANNEL_MANAGER_OPTIONS = {
141
149
  abortInFlightQuery: false,
@@ -152,6 +160,54 @@ export const DEFAULT_CHANNEL_MANAGER_PAGINATION_OPTIONS = {
152
160
  offset: 0,
153
161
  };
154
162
 
163
+ const mapPredefinedFilterSortToChannelSort = (
164
+ sort: NonNullable<QueryChannelsAPIResponse['predefined_filter']>['sort'],
165
+ ): ChannelSort =>
166
+ (sort ?? []).map(({ direction = 1, field }) => ({
167
+ [field]: direction,
168
+ })) as ChannelSort;
169
+
170
+ const getResponsePaginationParams = ({
171
+ queryChannelsResponse,
172
+ sort,
173
+ }: {
174
+ queryChannelsResponse?: Pick<QueryChannelsAPIResponse, 'predefined_filter'>;
175
+ sort: ChannelSort;
176
+ }): Pick<ChannelManagerPagination, 'responseFilters' | 'responseSort'> => {
177
+ const predefinedFilter = queryChannelsResponse?.predefined_filter;
178
+
179
+ if (!predefinedFilter) {
180
+ return {};
181
+ }
182
+
183
+ return {
184
+ responseFilters: predefinedFilter.filter as ChannelFilters,
185
+ responseSort:
186
+ predefinedFilter.sort !== undefined
187
+ ? mapPredefinedFilterSortToChannelSort(predefinedFilter.sort)
188
+ : sort,
189
+ };
190
+ };
191
+
192
+ const getResponseFiltersAndSort = (
193
+ pagination: ChannelManagerPagination,
194
+ ): Pick<ChannelManagerPagination, 'filters' | 'sort'> => ({
195
+ filters: pagination.responseFilters ?? pagination.filters,
196
+ sort: pagination.responseSort ?? pagination.sort,
197
+ });
198
+
199
+ const omitResponsePaginationParams = (pagination: ChannelManagerPagination) => {
200
+ const paginationWithoutResponseParams = { ...pagination };
201
+ delete paginationWithoutResponseParams.responseFilters;
202
+ delete paginationWithoutResponseParams.responseSort;
203
+
204
+ return paginationWithoutResponseParams;
205
+ };
206
+
207
+ const isQueryChannelsResponseWithChannels = (
208
+ response: QueryChannelsRequestOutput,
209
+ ): response is QueryChannelsResponseWithChannels => !Array.isArray(response);
210
+
155
211
  /**
156
212
  * A class that manages a list of channels and changes it based on configuration and WS events. The
157
213
  * list of channels is reactive as well as the pagination and it can be subscribed to for state updates.
@@ -279,23 +335,39 @@ export class ChannelManager extends WithSubscriptions {
279
335
  ...options,
280
336
  };
281
337
  try {
282
- const channels = await this.queryChannelsRequest(
338
+ const queryChannelsResponse = await this.queryChannelsRequest(
283
339
  filters,
284
340
  sort,
285
341
  options,
286
- stateOptions,
342
+ { ...stateOptions, withResponse: true },
287
343
  );
344
+ const channels = isQueryChannelsResponseWithChannels(queryChannelsResponse)
345
+ ? queryChannelsResponse.channels
346
+ : queryChannelsResponse;
288
347
  const newOffset = offset + (channels?.length ?? 0);
289
348
  const newOptions = { ...options, offset: newOffset };
290
349
  const { pagination } = this.state.getLatestValue();
350
+ const responsePaginationParams = getResponsePaginationParams({
351
+ queryChannelsResponse: isQueryChannelsResponseWithChannels(queryChannelsResponse)
352
+ ? queryChannelsResponse
353
+ : undefined,
354
+ sort,
355
+ });
356
+ const paginationWithoutResponseParams = omitResponsePaginationParams(pagination);
291
357
 
292
358
  this.state.partialNext({
293
359
  channels,
294
360
  pagination: {
295
- ...pagination,
361
+ // Drop response derived filter/sort from the previous query before applying
362
+ // the current response. Non predefined queries do not return this metadata,
363
+ // so keeping the old values would make later WS mutations use stale
364
+ // predefined filter semantics. Also the predefined_filter might change, producing
365
+ // a different combination as well so we always need to first clean up.
366
+ ...paginationWithoutResponseParams,
296
367
  hasNext: (channels?.length ?? 0) >= (limit ?? 1),
297
368
  isLoading: false,
298
369
  options: newOptions,
370
+ ...responsePaginationParams,
299
371
  },
300
372
  initialized: true,
301
373
  error: undefined,
@@ -368,7 +440,7 @@ export class ChannelManager extends WithSubscriptions {
368
440
  this.state.next((currentState) => ({
369
441
  ...currentState,
370
442
  pagination: {
371
- ...currentState.pagination,
443
+ ...omitResponsePaginationParams(currentState.pagination),
372
444
  isLoading: true,
373
445
  isLoadingNext: false,
374
446
  filters,
@@ -434,12 +506,15 @@ export class ChannelManager extends WithSubscriptions {
434
506
  this.state.partialNext({
435
507
  pagination: { ...pagination, isLoading: false, isLoadingNext: true },
436
508
  });
437
- const nextChannels = await this.queryChannelsRequest(
509
+ const queryChannelsResponse = await this.queryChannelsRequest(
438
510
  filters,
439
511
  sort,
440
512
  options,
441
513
  this.stateOptions,
442
514
  );
515
+ const nextChannels = isQueryChannelsResponseWithChannels(queryChannelsResponse)
516
+ ? queryChannelsResponse.channels
517
+ : queryChannelsResponse;
443
518
  const { channels } = this.state.getLatestValue();
444
519
  const newOffset = offset + (nextChannels?.length ?? 0);
445
520
  const newOptions = { ...options, offset: newOffset };
@@ -498,7 +573,7 @@ export class ChannelManager extends WithSubscriptions {
498
573
  return;
499
574
  }
500
575
 
501
- const { sort } = pagination ?? {};
576
+ const { sort } = getResponseFiltersAndSort(pagination);
502
577
 
503
578
  this.setChannels(
504
579
  promoteChannel({
@@ -535,7 +610,7 @@ export class ChannelManager extends WithSubscriptions {
535
610
  if (!channels) {
536
611
  return;
537
612
  }
538
- const { filters, sort } = pagination ?? {};
613
+ const { filters, sort } = getResponseFiltersAndSort(pagination);
539
614
 
540
615
  const channelType = event.channel_type;
541
616
  const channelId = event.channel_id;
@@ -594,7 +669,7 @@ export class ChannelManager extends WithSubscriptions {
594
669
  });
595
670
 
596
671
  const { channels, pagination } = this.state.getLatestValue();
597
- const { filters, sort } = pagination ?? {};
672
+ const { filters, sort } = getResponseFiltersAndSort(pagination);
598
673
 
599
674
  const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
600
675
  const isTargetChannelArchived = isChannelArchived(channel);
@@ -631,7 +706,7 @@ export class ChannelManager extends WithSubscriptions {
631
706
  });
632
707
 
633
708
  const { channels, pagination } = this.state.getLatestValue();
634
- const { sort, filters } = pagination ?? {};
709
+ const { filters, sort } = getResponseFiltersAndSort(pagination);
635
710
 
636
711
  const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
637
712
  const isTargetChannelArchived = isChannelArchived(channel);
@@ -658,7 +733,7 @@ export class ChannelManager extends WithSubscriptions {
658
733
 
659
734
  private memberUpdatedHandler = (event: Event) => {
660
735
  const { pagination, channels } = this.state.getLatestValue();
661
- const { filters, sort } = pagination;
736
+ const { filters, sort } = getResponseFiltersAndSort(pagination);
662
737
  if (
663
738
  !event.member?.user ||
664
739
  event.member.user.id !== this.client.userID ||