stream-chat 9.50.2 → 10.0.0-rc.1

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 (229) hide show
  1. package/dist/cjs/index.browser.js +18869 -13986
  2. package/dist/cjs/index.browser.js.map +4 -4
  3. package/dist/cjs/index.node.js +18888 -13992
  4. package/dist/cjs/index.node.js.map +4 -4
  5. package/dist/esm/index.mjs +18866 -13983
  6. package/dist/esm/index.mjs.map +4 -4
  7. package/dist/types/ChannelPaginatorsOrchestrator.d.ts +130 -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 +436 -480
  12. package/dist/types/channel_batch_updater.d.ts +0 -93
  13. package/dist/types/channel_manager.d.ts +8 -10
  14. package/dist/types/channel_state.d.ts +53 -201
  15. package/dist/types/client.d.ts +440 -1910
  16. package/dist/types/client_state.d.ts +4 -4
  17. package/dist/types/configuration/InstanceConfigurationService.d.ts +26 -0
  18. package/dist/types/configuration/index.d.ts +1 -0
  19. package/dist/types/configuration/types.d.ts +53 -0
  20. package/dist/types/connection.d.ts +49 -41
  21. package/dist/types/connection_fallback.d.ts +6 -4
  22. package/dist/types/constants.d.ts +0 -4
  23. package/dist/types/custom_types.d.ts +2 -0
  24. package/dist/types/errors.d.ts +2 -2
  25. package/dist/types/gen/chat/ChannelApi.d.ts +45 -0
  26. package/dist/types/gen/chat/ChatApi.d.ts +334 -0
  27. package/dist/types/gen/model-decoders/decoders.d.ts +3 -0
  28. package/dist/types/gen/model-decoders/event-decoder-mapping.d.ts +6 -0
  29. package/dist/types/gen/models/index.d.ts +8836 -0
  30. package/dist/types/gen/moderation/ModerationApi.d.ts +50 -0
  31. package/dist/types/gen-imports.d.ts +3 -0
  32. package/dist/types/index.d.ts +5 -5
  33. package/dist/types/insights.d.ts +7 -6
  34. package/dist/types/logger.d.ts +9 -0
  35. package/dist/types/messageComposer/LocationComposer.d.ts +7 -4
  36. package/dist/types/messageComposer/attachmentIdentity.d.ts +2 -2
  37. package/dist/types/messageComposer/attachmentManager.d.ts +8 -4
  38. package/dist/types/messageComposer/configuration/types.d.ts +11 -11
  39. package/dist/types/messageComposer/linkPreviewsManager.d.ts +25 -13
  40. package/dist/types/messageComposer/messageComposer.d.ts +10 -10
  41. package/dist/types/messageComposer/middleware/messageComposer/types.d.ts +3 -3
  42. package/dist/types/messageComposer/middleware/pollComposer/types.d.ts +4 -7
  43. package/dist/types/messageComposer/middleware/textComposer/commandUtils.d.ts +3 -3
  44. package/dist/types/messageComposer/middleware/textComposer/commands.d.ts +7 -7
  45. package/dist/types/messageComposer/middleware/textComposer/mentions.d.ts +218 -4
  46. package/dist/types/messageComposer/middleware/textComposer/types.d.ts +9 -4
  47. package/dist/types/messageComposer/pollComposer.d.ts +5 -4
  48. package/dist/types/messageComposer/textComposer.d.ts +8 -3
  49. package/dist/types/messageComposer/types.d.ts +6 -28
  50. package/dist/types/messageDelivery/MessageDeliveryReporter.d.ts +40 -25
  51. package/dist/types/messageDelivery/MessageReceiptsTracker.d.ts +53 -12
  52. package/dist/types/messageOperations/MessageOperationStatePolicy.d.ts +19 -0
  53. package/dist/types/messageOperations/MessageOperations.d.ts +17 -0
  54. package/dist/types/messageOperations/index.d.ts +3 -0
  55. package/dist/types/messageOperations/types.d.ts +49 -0
  56. package/dist/types/moderation.d.ts +21 -203
  57. package/dist/types/notifications/types.d.ts +8 -7
  58. package/dist/types/offline-support/offline_support_api.d.ts +110 -87
  59. package/dist/types/offline-support/types.d.ts +19 -23
  60. package/dist/types/offline-support/util.d.ts +4 -2
  61. package/dist/types/pagination/FilterBuilder.d.ts +1 -1
  62. package/dist/types/pagination/ItemIndex.d.ts +82 -0
  63. package/dist/types/pagination/cursorDerivation/createdAtAroundPaginationFlags.d.ts +9 -0
  64. package/dist/types/pagination/cursorDerivation/idAroundPaginationFlags.d.ts +6 -0
  65. package/dist/types/pagination/cursorDerivation/index.d.ts +1 -0
  66. package/dist/types/pagination/cursorDerivation/linearPaginationFlags.d.ts +6 -0
  67. package/dist/types/pagination/filterCompiler.d.ts +7 -0
  68. package/dist/types/pagination/index.d.ts +1 -3
  69. package/dist/types/pagination/paginators/BasePaginator.d.ts +709 -0
  70. package/dist/types/pagination/paginators/ChannelPaginator.d.ts +58 -0
  71. package/dist/types/pagination/paginators/MessageIntervalPaginator.d.ts +260 -0
  72. package/dist/types/pagination/paginators/MessagePaginator.d.ts +208 -0
  73. package/dist/types/pagination/paginators/PinnedMessagePaginator.d.ts +39 -0
  74. package/dist/types/pagination/paginators/ReminderPaginator.d.ts +19 -0
  75. package/dist/types/pagination/paginators/UserGroupPaginator.d.ts +24 -0
  76. package/dist/types/pagination/paginators/index.d.ts +7 -0
  77. package/dist/types/pagination/sortCompiler.d.ts +49 -0
  78. package/dist/types/pagination/types.normalization.d.ts +11 -0
  79. package/dist/types/pagination/utility.normalization.d.ts +18 -0
  80. package/dist/types/pagination/utility.queryChannel.d.ts +26 -0
  81. package/dist/types/pagination/utility.search.d.ts +13 -0
  82. package/dist/types/permissions.d.ts +1 -1
  83. package/dist/types/poll.d.ts +29 -32
  84. package/dist/types/poll_manager.d.ts +2 -2
  85. package/dist/types/reminders/Reminder.d.ts +2 -5
  86. package/dist/types/reminders/ReminderManager.d.ts +2 -9
  87. package/dist/types/search/MessageSearchSource.d.ts +4 -4
  88. package/dist/types/search/UserSearchSource.d.ts +1 -1
  89. package/dist/types/search/types.d.ts +3 -3
  90. package/dist/types/segment.d.ts +0 -34
  91. package/dist/types/signing.d.ts +105 -65
  92. package/dist/types/store.d.ts +1 -0
  93. package/dist/types/thread.d.ts +51 -34
  94. package/dist/types/thread_manager.d.ts +3 -3
  95. package/dist/types/token_manager.d.ts +15 -10
  96. package/dist/types/types.d.ts +196 -3384
  97. package/dist/types/utils/FixedSizeQueueCache.d.ts +12 -6
  98. package/dist/types/utils/WithSubscriptions.d.ts +2 -1
  99. package/dist/types/utils/concurrency.d.ts +4 -4
  100. package/dist/types/utils/mergeWith/mergeWith.d.ts +3 -3
  101. package/dist/types/utils/mergeWith/mergeWithCore.d.ts +8 -3
  102. package/dist/types/utils/retryable.d.ts +34 -0
  103. package/dist/types/utils.d.ts +104 -137
  104. package/package.json +7 -3
  105. package/src/ChannelPaginatorsOrchestrator.ts +545 -0
  106. package/src/CooldownTimer.ts +2 -2
  107. package/src/EventHandlerPipeline.ts +228 -0
  108. package/src/LiveLocationManager.ts +12 -11
  109. package/src/api-client.ts +275 -0
  110. package/src/campaign.ts +1 -77
  111. package/src/channel.ts +1262 -1277
  112. package/src/channel_batch_updater.ts +1 -211
  113. package/src/channel_manager.ts +92 -88
  114. package/src/channel_state.ts +144 -1013
  115. package/src/client.ts +888 -3944
  116. package/src/client_state.ts +4 -4
  117. package/src/configuration/InstanceConfigurationService.ts +73 -0
  118. package/src/configuration/index.ts +1 -0
  119. package/src/configuration/types.ts +81 -0
  120. package/src/connection.ts +145 -94
  121. package/src/connection_fallback.ts +29 -27
  122. package/src/constants.ts +1 -5
  123. package/src/custom_types.ts +4 -1
  124. package/src/errors.ts +2 -2
  125. package/src/gen/chat/ChannelApi.ts +275 -0
  126. package/src/gen/chat/ChatApi.ts +2554 -0
  127. package/src/gen/model-decoders/decoders.ts +2680 -0
  128. package/src/gen/model-decoders/event-decoder-mapping.ts +198 -0
  129. package/src/gen/models/index.ts +12673 -0
  130. package/src/gen/moderation/ModerationApi.ts +607 -0
  131. package/src/gen-imports.ts +3 -0
  132. package/src/index.ts +5 -10
  133. package/src/insights.ts +6 -5
  134. package/src/logger.ts +28 -0
  135. package/src/messageComposer/LocationComposer.ts +9 -5
  136. package/src/messageComposer/MessageComposerEffectHandlers.ts +1 -0
  137. package/src/messageComposer/attachmentIdentity.ts +16 -9
  138. package/src/messageComposer/attachmentManager.ts +13 -8
  139. package/src/messageComposer/configuration/types.ts +11 -11
  140. package/src/messageComposer/linkPreviewsManager.ts +9 -10
  141. package/src/messageComposer/messageComposer.ts +57 -53
  142. package/src/messageComposer/middleware/messageComposer/attachments.ts +1 -2
  143. package/src/messageComposer/middleware/messageComposer/cleanData.ts +1 -1
  144. package/src/messageComposer/middleware/messageComposer/compositionValidation.ts +2 -2
  145. package/src/messageComposer/middleware/messageComposer/messageComposerState.ts +2 -2
  146. package/src/messageComposer/middleware/messageComposer/sharedLocation.ts +4 -3
  147. package/src/messageComposer/middleware/messageComposer/textComposer.ts +2 -2
  148. package/src/messageComposer/middleware/messageComposer/types.ts +3 -4
  149. package/src/messageComposer/middleware/messageComposer/userDataInjection.ts +9 -5
  150. package/src/messageComposer/middleware/pollComposer/state.ts +0 -2
  151. package/src/messageComposer/middleware/pollComposer/types.ts +4 -7
  152. package/src/messageComposer/middleware/textComposer/TextComposerMiddlewareExecutor.ts +10 -1
  153. package/src/messageComposer/middleware/textComposer/commandEffects.ts +4 -4
  154. package/src/messageComposer/middleware/textComposer/commandUtils.ts +3 -6
  155. package/src/messageComposer/middleware/textComposer/commands.ts +3 -3
  156. package/src/messageComposer/middleware/textComposer/mentionUtils.ts +1 -1
  157. package/src/messageComposer/middleware/textComposer/mentions.ts +31 -15
  158. package/src/messageComposer/middleware/textComposer/types.ts +9 -4
  159. package/src/messageComposer/pollComposer.ts +6 -8
  160. package/src/messageComposer/textComposer.ts +27 -2
  161. package/src/messageComposer/types.ts +6 -28
  162. package/src/messageDelivery/MessageDeliveryReporter.ts +101 -45
  163. package/src/messageDelivery/MessageReceiptsTracker.ts +294 -21
  164. package/src/messageOperations/MessageOperationStatePolicy.ts +77 -0
  165. package/src/messageOperations/MessageOperations.ts +212 -0
  166. package/src/messageOperations/index.ts +10 -0
  167. package/src/messageOperations/types.ts +64 -0
  168. package/src/moderation.ts +38 -431
  169. package/src/notifications/types.ts +8 -7
  170. package/src/offline-support/offline_support_api.ts +198 -132
  171. package/src/offline-support/offline_sync_manager.ts +17 -7
  172. package/src/offline-support/types.ts +23 -26
  173. package/src/offline-support/util.ts +5 -4
  174. package/src/pagination/FilterBuilder.ts +4 -1
  175. package/src/pagination/ItemIndex.ts +112 -0
  176. package/src/pagination/cursorDerivation/createdAtAroundPaginationFlags.ts +73 -0
  177. package/src/pagination/cursorDerivation/idAroundPaginationFlags.ts +53 -0
  178. package/src/pagination/cursorDerivation/index.ts +1 -0
  179. package/src/pagination/cursorDerivation/linearPaginationFlags.ts +86 -0
  180. package/src/pagination/filterCompiler.ts +196 -0
  181. package/src/pagination/index.ts +1 -3
  182. package/src/pagination/paginators/BasePaginator.ts +2393 -0
  183. package/src/pagination/paginators/ChannelPaginator.ts +427 -0
  184. package/src/pagination/paginators/MessageIntervalPaginator.ts +1073 -0
  185. package/src/pagination/paginators/MessagePaginator.ts +496 -0
  186. package/src/pagination/paginators/PinnedMessagePaginator.ts +105 -0
  187. package/src/pagination/paginators/ReminderPaginator.ts +105 -0
  188. package/src/pagination/paginators/UserGroupPaginator.ts +125 -0
  189. package/src/pagination/paginators/index.ts +7 -0
  190. package/src/pagination/sortCompiler.ts +178 -0
  191. package/src/pagination/types.normalization.ts +14 -0
  192. package/src/pagination/utility.normalization.ts +106 -0
  193. package/src/pagination/utility.queryChannel.ts +84 -0
  194. package/src/pagination/utility.search.ts +80 -0
  195. package/src/permissions.ts +7 -8
  196. package/src/poll.ts +101 -110
  197. package/src/poll_manager.ts +14 -8
  198. package/src/reminders/Reminder.ts +2 -5
  199. package/src/reminders/ReminderManager.ts +19 -21
  200. package/src/search/BaseSearchSource.ts +0 -3
  201. package/src/search/ChannelMemberSearchSource.ts +7 -1
  202. package/src/search/ChannelSearchSource.ts +10 -3
  203. package/src/search/MessageSearchSource.ts +29 -30
  204. package/src/search/UserSearchSource.ts +12 -8
  205. package/src/search/types.ts +3 -4
  206. package/src/segment.ts +1 -95
  207. package/src/signing.ts +107 -67
  208. package/src/store.ts +2 -1
  209. package/src/thread.ts +439 -236
  210. package/src/thread_manager.ts +36 -15
  211. package/src/token_manager.ts +21 -10
  212. package/src/types.ts +375 -4524
  213. package/src/uploadManager.ts +8 -0
  214. package/src/utils/FixedSizeQueueCache.ts +12 -6
  215. package/src/utils/WithSubscriptions.ts +2 -1
  216. package/src/utils/concurrency.ts +4 -4
  217. package/src/utils/mergeWith/mergeWith.ts +3 -3
  218. package/src/utils/mergeWith/mergeWithCore.ts +140 -164
  219. package/src/utils/mergeWith/mergeWithDiff.ts +3 -3
  220. package/src/utils/retryable.ts +117 -0
  221. package/src/utils.ts +133 -524
  222. package/dist/types/events.d.ts +0 -72
  223. package/dist/types/pagination/BasePaginator.d.ts +0 -69
  224. package/dist/types/pagination/ReminderPaginator.d.ts +0 -16
  225. package/dist/types/pagination/UserGroupPaginator.d.ts +0 -21
  226. package/src/events.ts +0 -75
  227. package/src/pagination/BasePaginator.ts +0 -184
  228. package/src/pagination/ReminderPaginator.ts +0 -56
  229. package/src/pagination/UserGroupPaginator.ts +0 -93
@@ -0,0 +1,105 @@
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 { ItemIndex } from '../ItemIndex';
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 ItemIndex<ReminderResponseData>({ getId: getReminderId }),
60
+ ...options,
61
+ });
62
+ this.client = client;
63
+ this.sortComparator = this.buildSortComparator();
64
+ }
65
+
66
+ getItemId(item: ReminderResponseData): string {
67
+ return getReminderId(item);
68
+ }
69
+
70
+ // Interval storage needs a total order. Derive it from the requested sort (rebuilt when `sort`
71
+ // changes, which also resets the accumulated pages), with a message_id tiebreaker.
72
+ private buildSortComparator() {
73
+ return makeComparator<ReminderResponseData>({
74
+ sort: this._sort ?? DEFAULT_SORT,
75
+ resolvePathValue: resolveDotPathValue,
76
+ tiebreaker: (l, r) =>
77
+ l.message_id < r.message_id ? -1 : l.message_id > r.message_id ? 1 : 0,
78
+ });
79
+ }
80
+
81
+ protected getNextQueryShape({
82
+ direction,
83
+ }: Required<
84
+ Pick<PaginationQueryParams<QueryRemindersOptions>, 'direction'>
85
+ >): QueryRemindersOptions {
86
+ const cursor = this.cursor?.[direction];
87
+ return {
88
+ filter: this.filters,
89
+ sort: this.sort,
90
+ limit: this.pageSize,
91
+ [direction]: cursor,
92
+ };
93
+ }
94
+
95
+ query = async ({
96
+ queryShape,
97
+ }: PaginationQueryParams<QueryRemindersOptions>): Promise<
98
+ PaginationQueryReturnValue<ReminderResponseData>
99
+ > => {
100
+ const { reminders: items, next, prev } = await this.client.queryReminders(queryShape);
101
+ return { items, headward: prev, tailward: next };
102
+ };
103
+
104
+ filterQueryResults = (items: ReminderResponseData[]) => items;
105
+ }
@@ -0,0 +1,125 @@
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 { ItemIndex } from '../ItemIndex';
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 ItemIndex<UserGroupResponse>({ getId: (group) => group.id }),
53
+ ...options,
54
+ });
55
+ this.client = client;
56
+ // Interval storage needs a total order for its placement/merge math. The listing is ordered by
57
+ // the forward cursor (`created_at_gt`, `id_gt`), i.e. ascending `created_at` then `id` — mirror
58
+ // that here so the visible order matches the server's.
59
+ this.sortComparator = (a, b) => {
60
+ if (a.created_at !== b.created_at) return a.created_at < b.created_at ? -1 : 1;
61
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
62
+ };
63
+ }
64
+
65
+ get initialState(): PaginatorState<UserGroupResponse> {
66
+ return {
67
+ ...super.initialState,
68
+ hasMoreHead: false,
69
+ };
70
+ }
71
+
72
+ get teamId() {
73
+ return this._teamId;
74
+ }
75
+
76
+ set teamId(teamId: string | undefined) {
77
+ if (teamId === this._teamId) return;
78
+ this._teamId = teamId;
79
+ this.resetState();
80
+ }
81
+
82
+ private buildNextCursor = (items: UserGroupResponse[]) => {
83
+ if (items.length < this.pageSize) return undefined;
84
+ const lastItem = items[items.length - 1];
85
+ if (!lastItem) return undefined;
86
+
87
+ return JSON.stringify({
88
+ created_at_gt: lastItem.created_at.toISOString(),
89
+ id_gt: lastItem.id,
90
+ } satisfies UserGroupListCursor);
91
+ };
92
+
93
+ // The query shape must stay stable across pages: the paginator resets its
94
+ // accumulated list when the query shape changes ('auto' reset policy), so the
95
+ // forward cursor is NOT part of the shape — it is applied per request in `query`.
96
+ protected getNextQueryShape(): ListUserGroupsOptions {
97
+ return {
98
+ limit: this.pageSize,
99
+ ...(this.teamId ? { team_id: this.teamId } : {}),
100
+ };
101
+ }
102
+
103
+ query = async ({
104
+ direction,
105
+ queryShape,
106
+ }: PaginationQueryParams<ListUserGroupsOptions>): Promise<
107
+ PaginationQueryReturnValue<UserGroupResponse>
108
+ > => {
109
+ if (direction === 'headward') {
110
+ return { items: [] };
111
+ }
112
+
113
+ const cursor = decodeCursor<UserGroupListCursor>(this.cursor?.tailward);
114
+ const options: ListUserGroupsOptions = {
115
+ ...(queryShape ?? this.getNextQueryShape()),
116
+ ...(cursor?.id_gt ? { id_gt: cursor.id_gt } : {}),
117
+ ...(cursor?.created_at_gt ? { created_at_gt: cursor.created_at_gt } : {}),
118
+ };
119
+
120
+ const { user_groups: items } = await this.client.listUserGroups(options);
121
+ return { items, tailward: this.buildNextCursor(items) };
122
+ };
123
+
124
+ filterQueryResults = (items: UserGroupResponse[]) => items;
125
+ }
@@ -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,178 @@
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 (compare(midItem, needle) <= 0) {
63
+ // midItem < needle ⇒ go right
64
+ lo = mid + 1;
65
+ } else {
66
+ // midItem ≥ needle ⇒ go left
67
+ hi = mid;
68
+ }
69
+ }
70
+
71
+ const insertionIndex = lo;
72
+
73
+ // item is located where it is expected to be according to the sort
74
+ const itemAtExpectedIndex = getItemAt(insertionIndex);
75
+ if (itemAtExpectedIndex && itemIdentityEquals(itemAtExpectedIndex, needle)) {
76
+ return { currentIndex: insertionIndex, insertionIndex };
77
+ }
78
+
79
+ if (!plateauScan) {
80
+ return { currentIndex: -1, insertionIndex };
81
+ }
82
+
83
+ // --- 2) Plateau scan around insertionIndex ---
84
+
85
+ const checkSide = (atIndex: number) => {
86
+ const result = { exhausted: false, found: false };
87
+ const item = getItemAt(atIndex);
88
+ if (!item) result.exhausted = true;
89
+ else if (itemIdentityEquals(item, needle)) result.found = true;
90
+ return result;
91
+ };
92
+
93
+ // Alternating left/right scan
94
+ let iLeft = insertionIndex - 1;
95
+ let iRight = insertionIndex + 1; // we've already checked insertionIndex
96
+ let leftDone = iLeft < 0;
97
+ let rightDone = iRight >= length;
98
+
99
+ while (!leftDone || !rightDone) {
100
+ if (!leftDone) {
101
+ const result = checkSide(iLeft);
102
+ if (result.found) return { currentIndex: iLeft, insertionIndex };
103
+ leftDone = result.exhausted || --iLeft < 0;
104
+ }
105
+
106
+ if (!rightDone) {
107
+ const result = checkSide(iRight);
108
+ if (result.found) return { currentIndex: iRight, insertionIndex };
109
+ rightDone = result.exhausted || ++iRight >= length;
110
+ }
111
+ }
112
+
113
+ // Not found in plateau; insertion index is still the correct lower bound.
114
+ return { currentIndex: -1, insertionIndex };
115
+ }
116
+
117
+ /**
118
+ * Negative number (< 0) → a comes before b
119
+ *
120
+ * Zero (0) → leave a and b unchanged relative to each other
121
+ * (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)
122
+ *
123
+ * Positive number (> 0) → a comes after b
124
+ *
125
+ * @param params - Comparator configuration.
126
+ * @param params.sort - The sort specification defining fields and directions.
127
+ * @param params.resolvePathValue - Resolver used to read a field value from an item.
128
+ * @param params.tiebreaker - Comparator applied when all sort terms are equal.
129
+ */
130
+ export function makeComparator<T>({
131
+ sort,
132
+ resolvePathValue = defaultResolvePathValue,
133
+ tiebreaker = (a, b) => compare((a as any).cid, (b as any).cid),
134
+ }: {
135
+ sort: SortParamRequest[];
136
+ resolvePathValue?: PathResolver<T>;
137
+ tiebreaker?: Comparator<T>;
138
+ }): Comparator<T> {
139
+ return (a: T, b: T) => {
140
+ for (const { field: path, direction } of sort) {
141
+ const leftValue = resolvePathValue(a, path as string);
142
+ const rightValue = resolvePathValue(b, path as string);
143
+ const normalized = normalizeComparedValues(leftValue, rightValue);
144
+ let comparison: number;
145
+ switch (normalized.kind) {
146
+ case 'date':
147
+ case 'number':
148
+ case 'string':
149
+ case 'boolean':
150
+ comparison = compare(normalized.a, normalized.b);
151
+ break;
152
+ default: {
153
+ // Null/undefined always sort to the tail, INDEPENDENT of `direction`. Return here so the
154
+ // result bypasses the direction flip below — otherwise a descending sort (e.g.
155
+ // `{ last_message_at: -1 }`) negates "null last" into "null first" and floats value-less
156
+ // items (e.g. channels with no last message) to the head of the list.
157
+ if (leftValue == null && rightValue == null) {
158
+ comparison = 0; // tie on this term; fall through to the next term / tiebreaker
159
+ break;
160
+ }
161
+ if (leftValue == null) return 1; // a (null) sorts after b
162
+ if (rightValue == null) return -1; // b (null) sorts after a
163
+ // Both non-null but not normalizable: deterministic string compare (respects direction).
164
+ const stringLeftValue = String(leftValue),
165
+ stringRightValue = String(rightValue);
166
+ comparison =
167
+ stringLeftValue === stringRightValue
168
+ ? 0
169
+ : stringLeftValue < stringRightValue
170
+ ? -1
171
+ : 1;
172
+ }
173
+ }
174
+ if (comparison !== 0) return direction === 1 ? comparison : -comparison;
175
+ }
176
+ return tiebreaker ? tiebreaker(a, b) : 0;
177
+ };
178
+ }
@@ -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
+ };