stream-chat 9.50.3 → 10.0.0-rc.2

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 (242) hide show
  1. package/dist/cjs/index.browser.js +19798 -14472
  2. package/dist/cjs/index.browser.js.map +4 -4
  3. package/dist/cjs/index.node.js +19817 -14482
  4. package/dist/cjs/index.node.js.map +4 -4
  5. package/dist/esm/index.mjs +19795 -14469
  6. package/dist/esm/index.mjs.map +4 -4
  7. package/dist/types/ChannelManager.d.ts +221 -0
  8. package/dist/types/EventHandlerPipeline.d.ts +104 -0
  9. package/dist/types/api-client.d.ts +26 -0
  10. package/dist/types/campaign.d.ts +0 -44
  11. package/dist/types/channel.d.ts +454 -479
  12. package/dist/types/channel_batch_updater.d.ts +0 -93
  13. package/dist/types/channel_state.d.ts +53 -201
  14. package/dist/types/client.d.ts +451 -1920
  15. package/dist/types/client_state.d.ts +4 -4
  16. package/dist/types/configuration/InstanceConfigurationService.d.ts +26 -0
  17. package/dist/types/configuration/index.d.ts +1 -0
  18. package/dist/types/configuration/types.d.ts +53 -0
  19. package/dist/types/connection.d.ts +49 -41
  20. package/dist/types/connection_fallback.d.ts +6 -4
  21. package/dist/types/constants.d.ts +0 -4
  22. package/dist/types/custom_types.d.ts +2 -0
  23. package/dist/types/entityStore/EntityStore.d.ts +111 -0
  24. package/dist/types/entityStore/StoreBackedItemIndex.d.ts +60 -0
  25. package/dist/types/entityStore/applyReactionLocally.d.ts +26 -0
  26. package/dist/types/entityStore/index.d.ts +2 -0
  27. package/dist/types/errors.d.ts +10 -2
  28. package/dist/types/gen/chat/ChannelApi.d.ts +45 -0
  29. package/dist/types/gen/chat/ChatApi.d.ts +334 -0
  30. package/dist/types/gen/model-decoders/decoders.d.ts +3 -0
  31. package/dist/types/gen/model-decoders/event-decoder-mapping.d.ts +6 -0
  32. package/dist/types/gen/models/index.d.ts +8836 -0
  33. package/dist/types/gen/moderation/ModerationApi.d.ts +50 -0
  34. package/dist/types/gen-imports.d.ts +3 -0
  35. package/dist/types/index.d.ts +8 -7
  36. package/dist/types/insights.d.ts +9 -8
  37. package/dist/types/logger.d.ts +9 -0
  38. package/dist/types/messageComposer/LocationComposer.d.ts +7 -4
  39. package/dist/types/messageComposer/attachmentIdentity.d.ts +2 -2
  40. package/dist/types/messageComposer/attachmentManager.d.ts +8 -4
  41. package/dist/types/messageComposer/configuration/types.d.ts +11 -11
  42. package/dist/types/messageComposer/linkPreviewsManager.d.ts +25 -13
  43. package/dist/types/messageComposer/messageComposer.d.ts +10 -10
  44. package/dist/types/messageComposer/middleware/messageComposer/types.d.ts +3 -3
  45. package/dist/types/messageComposer/middleware/pollComposer/types.d.ts +4 -7
  46. package/dist/types/messageComposer/middleware/textComposer/commandUtils.d.ts +3 -3
  47. package/dist/types/messageComposer/middleware/textComposer/commands.d.ts +7 -7
  48. package/dist/types/messageComposer/middleware/textComposer/mentions.d.ts +218 -4
  49. package/dist/types/messageComposer/middleware/textComposer/types.d.ts +9 -4
  50. package/dist/types/messageComposer/pollComposer.d.ts +5 -4
  51. package/dist/types/messageComposer/textComposer.d.ts +8 -3
  52. package/dist/types/messageComposer/types.d.ts +6 -28
  53. package/dist/types/messageDelivery/MessageDeliveryReporter.d.ts +40 -25
  54. package/dist/types/messageDelivery/MessageReceiptsTracker.d.ts +53 -12
  55. package/dist/types/messageOperations/MessageOperationStatePolicy.d.ts +19 -0
  56. package/dist/types/messageOperations/MessageOperations.d.ts +17 -0
  57. package/dist/types/messageOperations/index.d.ts +3 -0
  58. package/dist/types/messageOperations/types.d.ts +49 -0
  59. package/dist/types/moderation.d.ts +21 -203
  60. package/dist/types/notifications/types.d.ts +8 -7
  61. package/dist/types/offline-support/offline_support_api.d.ts +111 -87
  62. package/dist/types/offline-support/offline_sync_manager.d.ts +0 -7
  63. package/dist/types/offline-support/types.d.ts +19 -23
  64. package/dist/types/offline-support/util.d.ts +4 -2
  65. package/dist/types/pagination/FilterBuilder.d.ts +1 -1
  66. package/dist/types/pagination/ItemIndex.d.ts +25 -0
  67. package/dist/types/pagination/cursorDerivation/createdAtAroundPaginationFlags.d.ts +9 -0
  68. package/dist/types/pagination/cursorDerivation/idAroundPaginationFlags.d.ts +6 -0
  69. package/dist/types/pagination/cursorDerivation/index.d.ts +1 -0
  70. package/dist/types/pagination/cursorDerivation/linearPaginationFlags.d.ts +6 -0
  71. package/dist/types/pagination/filterCompiler.d.ts +7 -0
  72. package/dist/types/pagination/index.d.ts +1 -3
  73. package/dist/types/pagination/paginators/BasePaginator.d.ts +848 -0
  74. package/dist/types/pagination/paginators/ChannelPaginator.d.ts +212 -0
  75. package/dist/types/pagination/paginators/MessageIntervalPaginator.d.ts +282 -0
  76. package/dist/types/pagination/paginators/MessagePaginator.d.ts +208 -0
  77. package/dist/types/pagination/paginators/PinnedMessagePaginator.d.ts +39 -0
  78. package/dist/types/pagination/paginators/ReminderPaginator.d.ts +19 -0
  79. package/dist/types/pagination/paginators/UserGroupPaginator.d.ts +24 -0
  80. package/dist/types/pagination/paginators/index.d.ts +7 -0
  81. package/dist/types/pagination/paginators/stateThrottling.d.ts +2 -0
  82. package/dist/types/pagination/sortCompiler.d.ts +49 -0
  83. package/dist/types/pagination/types.normalization.d.ts +11 -0
  84. package/dist/types/pagination/utility.normalization.d.ts +18 -0
  85. package/dist/types/pagination/utility.queryChannel.d.ts +26 -0
  86. package/dist/types/pagination/utility.search.d.ts +13 -0
  87. package/dist/types/permissions.d.ts +1 -1
  88. package/dist/types/poll.d.ts +29 -32
  89. package/dist/types/poll_manager.d.ts +2 -2
  90. package/dist/types/reminders/Reminder.d.ts +2 -5
  91. package/dist/types/reminders/ReminderManager.d.ts +2 -9
  92. package/dist/types/search/MessageSearchSource.d.ts +4 -4
  93. package/dist/types/search/UserSearchSource.d.ts +1 -1
  94. package/dist/types/search/types.d.ts +3 -3
  95. package/dist/types/segment.d.ts +0 -34
  96. package/dist/types/signing.d.ts +105 -65
  97. package/dist/types/store.d.ts +1 -0
  98. package/dist/types/thread.d.ts +70 -34
  99. package/dist/types/thread_manager.d.ts +3 -3
  100. package/dist/types/token_manager.d.ts +15 -10
  101. package/dist/types/types.d.ts +197 -3404
  102. package/dist/types/utils/FixedSizeQueueCache.d.ts +12 -6
  103. package/dist/types/utils/WithSubscriptions.d.ts +2 -1
  104. package/dist/types/utils/concurrency.d.ts +4 -4
  105. package/dist/types/utils/mergeWith/mergeWith.d.ts +3 -3
  106. package/dist/types/utils/mergeWith/mergeWithCore.d.ts +8 -3
  107. package/dist/types/utils/retryable.d.ts +34 -0
  108. package/dist/types/utils/throttling/throttle.d.ts +36 -0
  109. package/dist/types/utils.d.ts +100 -194
  110. package/package.json +7 -3
  111. package/src/ChannelManager.ts +724 -0
  112. package/src/CooldownTimer.ts +2 -2
  113. package/src/EventHandlerPipeline.ts +228 -0
  114. package/src/LiveLocationManager.ts +12 -11
  115. package/src/api-client.ts +275 -0
  116. package/src/campaign.ts +1 -77
  117. package/src/channel.ts +1344 -1282
  118. package/src/channel_batch_updater.ts +1 -211
  119. package/src/channel_state.ts +144 -1013
  120. package/src/client.ts +936 -3995
  121. package/src/client_state.ts +4 -4
  122. package/src/configuration/InstanceConfigurationService.ts +73 -0
  123. package/src/configuration/index.ts +1 -0
  124. package/src/configuration/types.ts +81 -0
  125. package/src/connection.ts +145 -94
  126. package/src/connection_fallback.ts +29 -27
  127. package/src/constants.ts +1 -5
  128. package/src/custom_types.ts +4 -1
  129. package/src/entityStore/EntityStore.ts +208 -0
  130. package/src/entityStore/StoreBackedItemIndex.ts +115 -0
  131. package/src/entityStore/applyReactionLocally.ts +113 -0
  132. package/src/entityStore/index.ts +2 -0
  133. package/src/errors.ts +14 -2
  134. package/src/gen/chat/ChannelApi.ts +275 -0
  135. package/src/gen/chat/ChatApi.ts +2554 -0
  136. package/src/gen/model-decoders/decoders.ts +2680 -0
  137. package/src/gen/model-decoders/event-decoder-mapping.ts +198 -0
  138. package/src/gen/models/index.ts +12673 -0
  139. package/src/gen/moderation/ModerationApi.ts +607 -0
  140. package/src/gen-imports.ts +3 -0
  141. package/src/index.ts +11 -12
  142. package/src/insights.ts +6 -5
  143. package/src/logger.ts +28 -0
  144. package/src/messageComposer/LocationComposer.ts +9 -5
  145. package/src/messageComposer/MessageComposerEffectHandlers.ts +1 -0
  146. package/src/messageComposer/attachmentIdentity.ts +16 -9
  147. package/src/messageComposer/attachmentManager.ts +13 -8
  148. package/src/messageComposer/configuration/types.ts +11 -11
  149. package/src/messageComposer/linkPreviewsManager.ts +9 -10
  150. package/src/messageComposer/messageComposer.ts +57 -53
  151. package/src/messageComposer/middleware/messageComposer/attachments.ts +1 -2
  152. package/src/messageComposer/middleware/messageComposer/cleanData.ts +1 -1
  153. package/src/messageComposer/middleware/messageComposer/compositionValidation.ts +2 -2
  154. package/src/messageComposer/middleware/messageComposer/messageComposerState.ts +2 -2
  155. package/src/messageComposer/middleware/messageComposer/sharedLocation.ts +4 -3
  156. package/src/messageComposer/middleware/messageComposer/textComposer.ts +2 -2
  157. package/src/messageComposer/middleware/messageComposer/types.ts +3 -4
  158. package/src/messageComposer/middleware/messageComposer/userDataInjection.ts +9 -5
  159. package/src/messageComposer/middleware/pollComposer/state.ts +0 -2
  160. package/src/messageComposer/middleware/pollComposer/types.ts +4 -7
  161. package/src/messageComposer/middleware/textComposer/TextComposerMiddlewareExecutor.ts +10 -1
  162. package/src/messageComposer/middleware/textComposer/commandEffects.ts +4 -4
  163. package/src/messageComposer/middleware/textComposer/commandUtils.ts +3 -6
  164. package/src/messageComposer/middleware/textComposer/commands.ts +3 -3
  165. package/src/messageComposer/middleware/textComposer/mentionUtils.ts +1 -1
  166. package/src/messageComposer/middleware/textComposer/mentions.ts +31 -15
  167. package/src/messageComposer/middleware/textComposer/types.ts +9 -4
  168. package/src/messageComposer/pollComposer.ts +6 -8
  169. package/src/messageComposer/textComposer.ts +27 -2
  170. package/src/messageComposer/types.ts +6 -28
  171. package/src/messageDelivery/MessageDeliveryReporter.ts +101 -45
  172. package/src/messageDelivery/MessageReceiptsTracker.ts +294 -21
  173. package/src/messageOperations/MessageOperationStatePolicy.ts +77 -0
  174. package/src/messageOperations/MessageOperations.ts +212 -0
  175. package/src/messageOperations/index.ts +10 -0
  176. package/src/messageOperations/types.ts +64 -0
  177. package/src/moderation.ts +38 -431
  178. package/src/notifications/types.ts +8 -7
  179. package/src/offline-support/offline_support_api.ts +200 -133
  180. package/src/offline-support/offline_sync_manager.ts +23 -44
  181. package/src/offline-support/types.ts +23 -26
  182. package/src/offline-support/util.ts +5 -4
  183. package/src/pagination/FilterBuilder.ts +4 -1
  184. package/src/pagination/ItemIndex.ts +25 -0
  185. package/src/pagination/cursorDerivation/createdAtAroundPaginationFlags.ts +73 -0
  186. package/src/pagination/cursorDerivation/idAroundPaginationFlags.ts +53 -0
  187. package/src/pagination/cursorDerivation/index.ts +1 -0
  188. package/src/pagination/cursorDerivation/linearPaginationFlags.ts +86 -0
  189. package/src/pagination/filterCompiler.ts +196 -0
  190. package/src/pagination/index.ts +1 -3
  191. package/src/pagination/paginators/BasePaginator.ts +2859 -0
  192. package/src/pagination/paginators/ChannelPaginator.ts +729 -0
  193. package/src/pagination/paginators/MessageIntervalPaginator.ts +1116 -0
  194. package/src/pagination/paginators/MessagePaginator.ts +508 -0
  195. package/src/pagination/paginators/PinnedMessagePaginator.ts +108 -0
  196. package/src/pagination/paginators/ReminderPaginator.ts +107 -0
  197. package/src/pagination/paginators/UserGroupPaginator.ts +127 -0
  198. package/src/pagination/paginators/index.ts +7 -0
  199. package/src/pagination/paginators/stateThrottling.ts +31 -0
  200. package/src/pagination/sortCompiler.ts +193 -0
  201. package/src/pagination/types.normalization.ts +14 -0
  202. package/src/pagination/utility.normalization.ts +106 -0
  203. package/src/pagination/utility.queryChannel.ts +84 -0
  204. package/src/pagination/utility.search.ts +80 -0
  205. package/src/permissions.ts +7 -8
  206. package/src/poll.ts +101 -110
  207. package/src/poll_manager.ts +14 -8
  208. package/src/reminders/Reminder.ts +2 -5
  209. package/src/reminders/ReminderManager.ts +19 -21
  210. package/src/search/BaseSearchSource.ts +0 -3
  211. package/src/search/ChannelMemberSearchSource.ts +7 -1
  212. package/src/search/ChannelSearchSource.ts +10 -3
  213. package/src/search/MessageSearchSource.ts +29 -30
  214. package/src/search/UserSearchSource.ts +12 -8
  215. package/src/search/types.ts +3 -4
  216. package/src/segment.ts +1 -95
  217. package/src/signing.ts +107 -67
  218. package/src/store.ts +2 -1
  219. package/src/thread.ts +562 -238
  220. package/src/thread_manager.ts +37 -16
  221. package/src/token_manager.ts +21 -10
  222. package/src/types.ts +364 -4533
  223. package/src/uploadManager.ts +8 -0
  224. package/src/utils/FixedSizeQueueCache.ts +12 -6
  225. package/src/utils/WithSubscriptions.ts +2 -1
  226. package/src/utils/concurrency.ts +4 -4
  227. package/src/utils/mergeWith/mergeWith.ts +3 -3
  228. package/src/utils/mergeWith/mergeWithCore.ts +140 -164
  229. package/src/utils/mergeWith/mergeWithDiff.ts +3 -3
  230. package/src/utils/retryable.ts +117 -0
  231. package/src/utils/throttling/throttle.ts +130 -0
  232. package/src/utils.ts +208 -754
  233. package/dist/types/channel_manager.d.ts +0 -122
  234. package/dist/types/events.d.ts +0 -72
  235. package/dist/types/pagination/BasePaginator.d.ts +0 -69
  236. package/dist/types/pagination/ReminderPaginator.d.ts +0 -16
  237. package/dist/types/pagination/UserGroupPaginator.d.ts +0 -21
  238. package/src/channel_manager.ts +0 -830
  239. package/src/events.ts +0 -75
  240. package/src/pagination/BasePaginator.ts +0 -184
  241. package/src/pagination/ReminderPaginator.ts +0 -56
  242. package/src/pagination/UserGroupPaginator.ts +0 -93
@@ -0,0 +1,107 @@
1
+ import { BasePaginator, ZERO_PAGE_CURSOR } from './BasePaginator';
2
+ import type {
3
+ PaginationQueryParams,
4
+ PaginationQueryReturnValue,
5
+ PaginatorOptions,
6
+ } from './BasePaginator';
7
+ import type {
8
+ QueryRemindersOptions,
9
+ ReminderFilters,
10
+ ReminderResponseData,
11
+ ReminderSort,
12
+ } from '../../types';
13
+ import type { StreamChat } from '../../client';
14
+ import { StoreBackedItemIndex } from '../../entityStore/StoreBackedItemIndex';
15
+ import { makeComparator } from '../sortCompiler';
16
+ import { resolveDotPathValue } from '../utility.normalization';
17
+
18
+ // Reminders are keyed by the message they belong to; used for interval dedup and index addressing.
19
+ const getReminderId = (reminder: ReminderResponseData) => reminder.message_id;
20
+
21
+ // Fallback order for interval placement when no explicit sort is set. Order is not a pinned contract
22
+ // (ReminderManager stores reminders in a message_id-keyed Map), but interval storage needs a total
23
+ // order, so default to a deterministic one.
24
+ const DEFAULT_SORT: ReminderSort = [{ direction: 1, field: 'created_at' }];
25
+
26
+ export class ReminderPaginator extends BasePaginator<
27
+ ReminderResponseData,
28
+ QueryRemindersOptions
29
+ > {
30
+ private client: StreamChat;
31
+ protected _filters: ReminderFilters | undefined;
32
+ protected _sort: ReminderSort | undefined;
33
+
34
+ get filters(): ReminderFilters | undefined {
35
+ return this._filters;
36
+ }
37
+
38
+ get sort(): ReminderSort | undefined {
39
+ return this._sort;
40
+ }
41
+
42
+ set filters(filters: ReminderFilters | undefined) {
43
+ this._filters = filters;
44
+ this.resetState();
45
+ }
46
+
47
+ set sort(sort: ReminderSort | undefined) {
48
+ this._sort = sort;
49
+ this.sortComparator = this.buildSortComparator();
50
+ this.resetState();
51
+ }
52
+
53
+ constructor(
54
+ client: StreamChat,
55
+ options?: PaginatorOptions<ReminderResponseData, QueryRemindersOptions>,
56
+ ) {
57
+ super({
58
+ initialCursor: ZERO_PAGE_CURSOR,
59
+ itemIndex: new StoreBackedItemIndex<ReminderResponseData>({
60
+ getEntityId: getReminderId,
61
+ }),
62
+ ...options,
63
+ });
64
+ this.client = client;
65
+ this.sortComparator = this.buildSortComparator();
66
+ }
67
+
68
+ getItemId(item: ReminderResponseData): string {
69
+ return getReminderId(item);
70
+ }
71
+
72
+ // Interval storage needs a total order. Derive it from the requested sort (rebuilt when `sort`
73
+ // changes, which also resets the accumulated pages), with a message_id tiebreaker.
74
+ private buildSortComparator() {
75
+ return makeComparator<ReminderResponseData>({
76
+ sort: this._sort ?? DEFAULT_SORT,
77
+ resolvePathValue: resolveDotPathValue,
78
+ tiebreaker: (l, r) =>
79
+ l.message_id < r.message_id ? -1 : l.message_id > r.message_id ? 1 : 0,
80
+ });
81
+ }
82
+
83
+ protected getNextQueryShape({
84
+ direction,
85
+ }: Required<
86
+ Pick<PaginationQueryParams<QueryRemindersOptions>, 'direction'>
87
+ >): QueryRemindersOptions {
88
+ const cursor = this.cursor?.[direction];
89
+ return {
90
+ filter: this.filters,
91
+ sort: this.sort,
92
+ limit: this.pageSize,
93
+ [direction]: cursor,
94
+ };
95
+ }
96
+
97
+ query = async ({
98
+ queryShape,
99
+ }: PaginationQueryParams<QueryRemindersOptions>): Promise<
100
+ PaginationQueryReturnValue<ReminderResponseData>
101
+ > => {
102
+ const { reminders: items, next, prev } = await this.client.queryReminders(queryShape);
103
+ return { items, headward: prev, tailward: next };
104
+ };
105
+
106
+ filterQueryResults = (items: ReminderResponseData[]) => items;
107
+ }
@@ -0,0 +1,127 @@
1
+ import { BasePaginator, ZERO_PAGE_CURSOR } from './BasePaginator';
2
+ import type {
3
+ PaginationQueryParams,
4
+ PaginationQueryReturnValue,
5
+ PaginatorOptions,
6
+ PaginatorState,
7
+ } from './BasePaginator';
8
+ import type { ListUserGroupsOptions, UserGroupResponse } from '../../types';
9
+ import type { StreamChat } from '../../client';
10
+ import { StoreBackedItemIndex } from '../../entityStore/StoreBackedItemIndex';
11
+
12
+ type UserGroupListCursor = {
13
+ created_at_gt: string;
14
+ id_gt: string;
15
+ };
16
+
17
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
18
+ typeof value === 'object' && value !== null;
19
+
20
+ const decodeCursor = <TCursor extends object>(cursor: string | null | undefined) => {
21
+ if (!cursor) return undefined;
22
+
23
+ try {
24
+ const parsed = JSON.parse(cursor);
25
+ return isRecord(parsed) ? (parsed as TCursor) : undefined;
26
+ } catch {
27
+ return undefined;
28
+ }
29
+ };
30
+
31
+ /**
32
+ * Paginates user-group listing through `/usergroups`.
33
+ *
34
+ * This entity only supports forward (tailward) cursor pagination via
35
+ * `created_at_gt` and `id_gt`. Backward (headward) pagination is not available
36
+ * because the API does not expose a backward cursor, so the headward cursor is
37
+ * initialized as exhausted (`null`).
38
+ */
39
+ export class UserGroupPaginator extends BasePaginator<
40
+ UserGroupResponse,
41
+ ListUserGroupsOptions
42
+ > {
43
+ private client: StreamChat;
44
+ protected _teamId: string | undefined;
45
+
46
+ constructor(
47
+ client: StreamChat,
48
+ options?: PaginatorOptions<UserGroupResponse, ListUserGroupsOptions>,
49
+ ) {
50
+ super({
51
+ initialCursor: { ...ZERO_PAGE_CURSOR, headward: null },
52
+ itemIndex: new StoreBackedItemIndex<UserGroupResponse>({
53
+ getEntityId: (group) => group.id,
54
+ }),
55
+ ...options,
56
+ });
57
+ this.client = client;
58
+ // Interval storage needs a total order for its placement/merge math. The listing is ordered by
59
+ // the forward cursor (`created_at_gt`, `id_gt`), i.e. ascending `created_at` then `id` — mirror
60
+ // that here so the visible order matches the server's.
61
+ this.sortComparator = (a, b) => {
62
+ if (a.created_at !== b.created_at) return a.created_at < b.created_at ? -1 : 1;
63
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
64
+ };
65
+ }
66
+
67
+ get initialState(): PaginatorState<UserGroupResponse> {
68
+ return {
69
+ ...super.initialState,
70
+ hasMoreHead: false,
71
+ };
72
+ }
73
+
74
+ get teamId() {
75
+ return this._teamId;
76
+ }
77
+
78
+ set teamId(teamId: string | undefined) {
79
+ if (teamId === this._teamId) return;
80
+ this._teamId = teamId;
81
+ this.resetState();
82
+ }
83
+
84
+ private buildNextCursor = (items: UserGroupResponse[]) => {
85
+ if (items.length < this.pageSize) return undefined;
86
+ const lastItem = items[items.length - 1];
87
+ if (!lastItem) return undefined;
88
+
89
+ return JSON.stringify({
90
+ created_at_gt: lastItem.created_at.toISOString(),
91
+ id_gt: lastItem.id,
92
+ } satisfies UserGroupListCursor);
93
+ };
94
+
95
+ // The query shape must stay stable across pages: the paginator resets its
96
+ // accumulated list when the query shape changes ('auto' reset policy), so the
97
+ // forward cursor is NOT part of the shape — it is applied per request in `query`.
98
+ protected getNextQueryShape(): ListUserGroupsOptions {
99
+ return {
100
+ limit: this.pageSize,
101
+ ...(this.teamId ? { team_id: this.teamId } : {}),
102
+ };
103
+ }
104
+
105
+ query = async ({
106
+ direction,
107
+ queryShape,
108
+ }: PaginationQueryParams<ListUserGroupsOptions>): Promise<
109
+ PaginationQueryReturnValue<UserGroupResponse>
110
+ > => {
111
+ if (direction === 'headward') {
112
+ return { items: [] };
113
+ }
114
+
115
+ const cursor = decodeCursor<UserGroupListCursor>(this.cursor?.tailward);
116
+ const options: ListUserGroupsOptions = {
117
+ ...(queryShape ?? this.getNextQueryShape()),
118
+ ...(cursor?.id_gt ? { id_gt: cursor.id_gt } : {}),
119
+ ...(cursor?.created_at_gt ? { created_at_gt: cursor.created_at_gt } : {}),
120
+ };
121
+
122
+ const { user_groups: items } = await this.client.listUserGroups(options);
123
+ return { items, tailward: this.buildNextCursor(items) };
124
+ };
125
+
126
+ filterQueryResults = (items: UserGroupResponse[]) => items;
127
+ }
@@ -0,0 +1,7 @@
1
+ export * from './BasePaginator';
2
+ export * from './ChannelPaginator';
3
+ export { MessageIntervalPaginator } from './MessageIntervalPaginator';
4
+ export * from './MessagePaginator';
5
+ export * from './PinnedMessagePaginator';
6
+ export * from './ReminderPaginator';
7
+ export * from './UserGroupPaginator';
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Global switch for the message-list `state` publish throttle (see `BasePaginator.scheduleWindowPublish`).
3
+ *
4
+ * Auto-disabled under test runners (Vitest / Jest) so the existing unit suites keep their synchronous,
5
+ * un-throttled behavior with zero per-test changes: with throttling off, every live mutation publishes
6
+ * to `state` immediately, exactly as before this feature. A dedicated throttle test flips it on
7
+ * (around fake timers) via {@link setStateThrottlingEnabled} and restores it afterwards.
8
+ *
9
+ * Production (an app bundle) defaults ON.
10
+ */
11
+ const isTestRunner = (): boolean => {
12
+ try {
13
+ return (
14
+ typeof process !== 'undefined' &&
15
+ !!process.env &&
16
+ (!!process.env.VITEST ||
17
+ !!process.env.JEST_WORKER_ID ||
18
+ process.env.NODE_ENV === 'test')
19
+ );
20
+ } catch {
21
+ return false;
22
+ }
23
+ };
24
+
25
+ let enabled = !isTestRunner();
26
+
27
+ export const isStateThrottlingEnabled = (): boolean => enabled;
28
+
29
+ export const setStateThrottlingEnabled = (value: boolean): void => {
30
+ enabled = value;
31
+ };
@@ -0,0 +1,193 @@
1
+ import {
2
+ compare,
3
+ resolveDotPathValue as defaultResolvePathValue,
4
+ normalizeComparedValues,
5
+ } from './utility.normalization';
6
+ import type { SortParamRequest } from '../types';
7
+ import type { Comparator, PathResolver } from './types.normalization';
8
+
9
+ export type ItemLocation = {
10
+ currentIndex: number;
11
+ insertionIndex: number;
12
+ };
13
+
14
+ /**
15
+ * Generic binary-search + plateau lookup over an abstract sorted array.
16
+ *
17
+ * The array is represented by:
18
+ * - its length
19
+ * - a getter `getItemAt(index)` that returns the item (or undefined)
20
+ *
21
+ * It returns:
22
+ * - current: actual index of the item in the array
23
+ * - expected: lower-bound position where the item belongs according to compare function
24
+ */
25
+ export function binarySearch<T>({
26
+ needle,
27
+ length,
28
+ getItemAt,
29
+ itemIdentityEquals,
30
+ compare,
31
+ plateauScan,
32
+ }: {
33
+ /** Target item in the searched array */
34
+ needle: T;
35
+ length: number;
36
+ /** Retrieves the item from an array. The array could be just an array of reference by id to an index.
37
+ * Therefore, we do not access the array directly but allow to determine, how the item is constructed.
38
+ */
39
+ getItemAt: (index: number) => T | undefined;
40
+ /** Used to determine identity, not equality based on sort / comparator rules */
41
+ itemIdentityEquals: (item1: T, item2: T) => boolean;
42
+ /** Used to determine equality from the sort order point of view. */
43
+ compare: Comparator<T>;
44
+ plateauScan?: boolean;
45
+ }): ItemLocation {
46
+ // empty array
47
+ if (length === 0) return { currentIndex: -1, insertionIndex: 0 };
48
+
49
+ // --- 1) Binary search to find lower bound (insertionIndex) ---
50
+ let lo = 0;
51
+ let hi = length;
52
+
53
+ while (lo < hi) {
54
+ const mid = (lo + hi) >> 1; // fast floor((low+high)/2)
55
+ const midItem = getItemAt(mid);
56
+ if (!midItem) {
57
+ // Corruption: we have an ID but no backing item.
58
+ // Bail out with "not found".
59
+ return { currentIndex: -1, insertionIndex: -1 };
60
+ }
61
+
62
+ if (itemIdentityEquals(midItem, needle) && mid > 0) {
63
+ // The probe landed on the needle's own slot, where `compare` is 0 by definition and says
64
+ // nothing about where it belongs. That matters when relocating an item whose sort value just
65
+ // changed: the array is sorted EXCEPT at this slot, because items held by reference (a
66
+ // `Channel` mutated in place before the paginator is told) already carry the new value.
67
+ // Walking right on that 0 reports "insert just after where you are" — read downstream as
68
+ // "already in place" — so the item never moves. The left neighbour is the reliable signal;
69
+ // otherwise fall through, keeping the established end-of-plateau result.
70
+ const leftItem = getItemAt(mid - 1);
71
+ if (leftItem && compare(leftItem, needle) > 0) {
72
+ hi = mid;
73
+ continue;
74
+ }
75
+ }
76
+
77
+ if (compare(midItem, needle) <= 0) {
78
+ // midItem < needle ⇒ go right
79
+ lo = mid + 1;
80
+ } else {
81
+ // midItem ≥ needle ⇒ go left
82
+ hi = mid;
83
+ }
84
+ }
85
+
86
+ const insertionIndex = lo;
87
+
88
+ // item is located where it is expected to be according to the sort
89
+ const itemAtExpectedIndex = getItemAt(insertionIndex);
90
+ if (itemAtExpectedIndex && itemIdentityEquals(itemAtExpectedIndex, needle)) {
91
+ return { currentIndex: insertionIndex, insertionIndex };
92
+ }
93
+
94
+ if (!plateauScan) {
95
+ return { currentIndex: -1, insertionIndex };
96
+ }
97
+
98
+ // --- 2) Plateau scan around insertionIndex ---
99
+
100
+ const checkSide = (atIndex: number) => {
101
+ const result = { exhausted: false, found: false };
102
+ const item = getItemAt(atIndex);
103
+ if (!item) result.exhausted = true;
104
+ else if (itemIdentityEquals(item, needle)) result.found = true;
105
+ return result;
106
+ };
107
+
108
+ // Alternating left/right scan
109
+ let iLeft = insertionIndex - 1;
110
+ let iRight = insertionIndex + 1; // we've already checked insertionIndex
111
+ let leftDone = iLeft < 0;
112
+ let rightDone = iRight >= length;
113
+
114
+ while (!leftDone || !rightDone) {
115
+ if (!leftDone) {
116
+ const result = checkSide(iLeft);
117
+ if (result.found) return { currentIndex: iLeft, insertionIndex };
118
+ leftDone = result.exhausted || --iLeft < 0;
119
+ }
120
+
121
+ if (!rightDone) {
122
+ const result = checkSide(iRight);
123
+ if (result.found) return { currentIndex: iRight, insertionIndex };
124
+ rightDone = result.exhausted || ++iRight >= length;
125
+ }
126
+ }
127
+
128
+ // Not found in plateau; insertion index is still the correct lower bound.
129
+ return { currentIndex: -1, insertionIndex };
130
+ }
131
+
132
+ /**
133
+ * Negative number (< 0) → a comes before b
134
+ *
135
+ * Zero (0) → leave a and b unchanged relative to each other
136
+ * (but they can still move relative to others — sort in JS is not guaranteed stable in older engines, though modern V8/Node/Chrome/Firefox make it stable)
137
+ *
138
+ * Positive number (> 0) → a comes after b
139
+ *
140
+ * @param params - Comparator configuration.
141
+ * @param params.sort - The sort specification defining fields and directions.
142
+ * @param params.resolvePathValue - Resolver used to read a field value from an item.
143
+ * @param params.tiebreaker - Comparator applied when all sort terms are equal.
144
+ */
145
+ export function makeComparator<T>({
146
+ sort,
147
+ resolvePathValue = defaultResolvePathValue,
148
+ tiebreaker = (a, b) => compare((a as any).cid, (b as any).cid),
149
+ }: {
150
+ sort: SortParamRequest[];
151
+ resolvePathValue?: PathResolver<T>;
152
+ tiebreaker?: Comparator<T>;
153
+ }): Comparator<T> {
154
+ return (a: T, b: T) => {
155
+ for (const { field: path, direction } of sort) {
156
+ const leftValue = resolvePathValue(a, path as string);
157
+ const rightValue = resolvePathValue(b, path as string);
158
+ const normalized = normalizeComparedValues(leftValue, rightValue);
159
+ let comparison: number;
160
+ switch (normalized.kind) {
161
+ case 'date':
162
+ case 'number':
163
+ case 'string':
164
+ case 'boolean':
165
+ comparison = compare(normalized.a, normalized.b);
166
+ break;
167
+ default: {
168
+ // Null/undefined always sort to the tail, INDEPENDENT of `direction`. Return here so the
169
+ // result bypasses the direction flip below — otherwise a descending sort (e.g.
170
+ // `{ last_message_at: -1 }`) negates "null last" into "null first" and floats value-less
171
+ // items (e.g. channels with no last message) to the head of the list.
172
+ if (leftValue == null && rightValue == null) {
173
+ comparison = 0; // tie on this term; fall through to the next term / tiebreaker
174
+ break;
175
+ }
176
+ if (leftValue == null) return 1; // a (null) sorts after b
177
+ if (rightValue == null) return -1; // b (null) sorts after a
178
+ // Both non-null but not normalizable: deterministic string compare (respects direction).
179
+ const stringLeftValue = String(leftValue),
180
+ stringRightValue = String(rightValue);
181
+ comparison =
182
+ stringLeftValue === stringRightValue
183
+ ? 0
184
+ : stringLeftValue < stringRightValue
185
+ ? -1
186
+ : 1;
187
+ }
188
+ }
189
+ if (comparison !== 0) return direction === 1 ? comparison : -comparison;
190
+ }
191
+ return tiebreaker ? tiebreaker(a, b) : 0;
192
+ };
193
+ }
@@ -0,0 +1,14 @@
1
+ export type PathResolver<DataSource> = (item: DataSource, field: string) => unknown;
2
+
3
+ export enum ComparisonResult {
4
+ A_PRECEDES_B = -1,
5
+ A_IS_EQUAL_TO_B = 0,
6
+ A_COMES_AFTER_B = 1,
7
+ }
8
+
9
+ export type Comparator<T> = (left: T, right: T) => ComparisonResult;
10
+
11
+ export type FieldToDataResolver<DataSource> = {
12
+ matchesField: (field: string) => boolean;
13
+ resolve: PathResolver<DataSource>;
14
+ };
@@ -0,0 +1,106 @@
1
+ export function asArray(v: any): any[] {
2
+ return Array.isArray(v) ? v : [v];
3
+ }
4
+
5
+ export function isISODateString(x: any): x is string {
6
+ return typeof x === 'string' && x.includes('T') && !Number.isNaN(Date.parse(x));
7
+ }
8
+
9
+ export function toEpochMillis(x: any): number | null {
10
+ if (x instanceof Date) return x.getTime();
11
+ if (typeof x === 'number' && Number.isFinite(x)) return x; // treat as epoch ms
12
+ if (isISODateString(x)) return Date.parse(x);
13
+ return null;
14
+ }
15
+
16
+ export function toNumberLike(x: any): number | null {
17
+ if (typeof x === 'number' && Number.isFinite(x)) return x;
18
+ if (typeof x === 'string' && x.trim() !== '') {
19
+ const n = Number(x);
20
+ if (Number.isFinite(n)) return n;
21
+ }
22
+ return null;
23
+ }
24
+
25
+ export function normalizeComparedValues(a: any, b: any) {
26
+ const Ad = toEpochMillis(a),
27
+ Bd = toEpochMillis(b);
28
+ if (Ad !== null && Bd !== null) return { kind: 'date', a: Ad, b: Bd };
29
+
30
+ const An = toNumberLike(a),
31
+ Bn = toNumberLike(b);
32
+ if (An !== null && Bn !== null) return { kind: 'number', a: An, b: Bn };
33
+
34
+ if (typeof a === 'string' && typeof b === 'string') return { kind: 'string', a, b };
35
+ if (typeof a === 'boolean' && typeof b === 'boolean') return { kind: 'boolean', a, b };
36
+
37
+ return { kind: 'incomparable', a, b };
38
+ }
39
+
40
+ export function normKey(x: unknown): string {
41
+ // Use your normalizeComparedValues to coerce pairs; here we need a unary form.
42
+ // We can piggyback by normalizing x against itself:
43
+ const n = normalizeComparedValues(x, x);
44
+ switch (n.kind) {
45
+ case 'date':
46
+ case 'number':
47
+ case 'string':
48
+ case 'boolean':
49
+ return `${n.kind}:${String(n.a)}`;
50
+ default:
51
+ // fallback: use JSON-like string with type tag for determinism
52
+ return `other:${String(x)}`;
53
+ }
54
+ }
55
+
56
+ export function compare(a: any, b: any): number {
57
+ if (a === b) return 0;
58
+ return a < b ? -1 : 1;
59
+ }
60
+
61
+ export function arraysEqualAsSets(aList: unknown[], bList: unknown[]): boolean {
62
+ // de-duplicate by normalized key
63
+ const aKeys = new Set(aList.map(normKey));
64
+ const bKeys = new Set(bList.map(normKey));
65
+ if (aKeys.size !== bKeys.size) return false;
66
+ for (const k of aKeys) if (!bKeys.has(k)) return false;
67
+ return true;
68
+ }
69
+
70
+ export function normalizeString(s: string): string {
71
+ return s.normalize('NFKC').toLowerCase().trim();
72
+ }
73
+
74
+ export function normalizeStringAccentInsensitive(s: string): string {
75
+ return s
76
+ .normalize('NFKD')
77
+ .replace(/[\u0300-\u036f]/g, '')
78
+ .toLowerCase()
79
+ .trim();
80
+ }
81
+
82
+ export function tokenize(s: string): string[] {
83
+ // split on whitespace; keep simple & deterministic
84
+ return normalizeString(s).split(/\s+/).filter(Boolean);
85
+ }
86
+
87
+ // dot-path accessor
88
+ export function resolveDotPathValue(obj: any, path: string): unknown[] {
89
+ return path
90
+ .split('.')
91
+ .reduce((reduced, key) => (!reduced ? undefined : reduced[key]), obj);
92
+ }
93
+
94
+ export function isIterableButNotString(v: unknown): v is Iterable<unknown> {
95
+ return (
96
+ v != null &&
97
+ typeof v !== 'string' &&
98
+ typeof (v as any)[Symbol.iterator] === 'function'
99
+ );
100
+ }
101
+
102
+ export function toIterableArray(v: unknown): unknown[] {
103
+ if (Array.isArray(v)) return v;
104
+ if (isIterableButNotString(v)) return Array.from(v as Iterable<unknown>);
105
+ return [v]; // scalar as a single-element list
106
+ }
@@ -0,0 +1,84 @@
1
+ import type { ChannelGetOrCreateRequest, ChannelStateResponse } from '../types';
2
+ import type { StreamChat } from '../client';
3
+ import type { Channel } from '../channel';
4
+ import { generateChannelTempCid } from '../utils';
5
+
6
+ /**
7
+ * prevent from duplicate invocation of channel.watch()
8
+ * when events 'notification.message_new' and 'notification.added_to_channel' arrive at the same time
9
+ */
10
+ const WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL: Record<
11
+ string,
12
+ Promise<ChannelStateResponse> | undefined
13
+ > = {};
14
+
15
+ type GetChannelParams = {
16
+ client: StreamChat;
17
+ channel?: Channel;
18
+ id?: string;
19
+ members?: string[];
20
+ options?: ChannelGetOrCreateRequest;
21
+ type?: string;
22
+ };
23
+ /**
24
+ * Watches a channel, coalescing concurrent invocations for the same CID.
25
+ * If a watch is already in flight, this call waits for it to settle instead of
26
+ * issuing another network request.
27
+ *
28
+ * @param params - The channel query parameters.
29
+ * @param params.client - The chat client instance.
30
+ * @param params.members - Member user ids used to construct or identify the channel.
31
+ * @param params.options - Options forwarded to the underlying channel watch request.
32
+ * @param params.type - The channel type.
33
+ * @param params.id - The channel id.
34
+ * @param params.channel - An existing channel to watch (skips construction from type/id/members).
35
+ */
36
+ export const getChannel = async ({
37
+ channel,
38
+ client,
39
+ id,
40
+ members,
41
+ options,
42
+ type,
43
+ }: GetChannelParams) => {
44
+ if (!channel && !type) {
45
+ throw new Error('Channel or channel type have to be provided to query a channel.');
46
+ }
47
+
48
+ // unfortunately typescript is not able to infer that if (!channel && !type) === false, then channel or type has to be truthy
49
+
50
+ const theChannel =
51
+ channel ||
52
+ // `members` are member IDs; the OpenAPI `ChannelData.members` expects member objects.
53
+ client.channel(type as string, id, {
54
+ members: members?.map((user_id) => ({ user_id })),
55
+ });
56
+
57
+ // need to keep as with call to channel.watch the id can be changed from undefined to an actual ID generated server-side
58
+ const originalCid = theChannel?.id
59
+ ? theChannel.cid
60
+ : members && members.length
61
+ ? generateChannelTempCid(theChannel.type, members)
62
+ : undefined;
63
+
64
+ if (!originalCid) {
65
+ throw new Error(
66
+ 'Channel ID or channel members array have to be provided to query a channel.',
67
+ );
68
+ }
69
+
70
+ const queryPromise = WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid];
71
+
72
+ if (queryPromise) {
73
+ await queryPromise;
74
+ } else {
75
+ try {
76
+ WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid] = theChannel.watch(options);
77
+ await WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid];
78
+ } finally {
79
+ delete WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid];
80
+ }
81
+ }
82
+
83
+ return theChannel;
84
+ };