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.
- package/dist/cjs/index.browser.js +19798 -14472
- package/dist/cjs/index.browser.js.map +4 -4
- package/dist/cjs/index.node.js +19817 -14482
- package/dist/cjs/index.node.js.map +4 -4
- package/dist/esm/index.mjs +19795 -14469
- package/dist/esm/index.mjs.map +4 -4
- package/dist/types/ChannelManager.d.ts +221 -0
- package/dist/types/EventHandlerPipeline.d.ts +104 -0
- package/dist/types/api-client.d.ts +26 -0
- package/dist/types/campaign.d.ts +0 -44
- package/dist/types/channel.d.ts +454 -479
- package/dist/types/channel_batch_updater.d.ts +0 -93
- package/dist/types/channel_state.d.ts +53 -201
- package/dist/types/client.d.ts +451 -1920
- package/dist/types/client_state.d.ts +4 -4
- package/dist/types/configuration/InstanceConfigurationService.d.ts +26 -0
- package/dist/types/configuration/index.d.ts +1 -0
- package/dist/types/configuration/types.d.ts +53 -0
- package/dist/types/connection.d.ts +49 -41
- package/dist/types/connection_fallback.d.ts +6 -4
- package/dist/types/constants.d.ts +0 -4
- package/dist/types/custom_types.d.ts +2 -0
- package/dist/types/entityStore/EntityStore.d.ts +111 -0
- package/dist/types/entityStore/StoreBackedItemIndex.d.ts +60 -0
- package/dist/types/entityStore/applyReactionLocally.d.ts +26 -0
- package/dist/types/entityStore/index.d.ts +2 -0
- package/dist/types/errors.d.ts +10 -2
- package/dist/types/gen/chat/ChannelApi.d.ts +45 -0
- package/dist/types/gen/chat/ChatApi.d.ts +334 -0
- package/dist/types/gen/model-decoders/decoders.d.ts +3 -0
- package/dist/types/gen/model-decoders/event-decoder-mapping.d.ts +6 -0
- package/dist/types/gen/models/index.d.ts +8836 -0
- package/dist/types/gen/moderation/ModerationApi.d.ts +50 -0
- package/dist/types/gen-imports.d.ts +3 -0
- package/dist/types/index.d.ts +8 -7
- package/dist/types/insights.d.ts +9 -8
- package/dist/types/logger.d.ts +9 -0
- package/dist/types/messageComposer/LocationComposer.d.ts +7 -4
- package/dist/types/messageComposer/attachmentIdentity.d.ts +2 -2
- package/dist/types/messageComposer/attachmentManager.d.ts +8 -4
- package/dist/types/messageComposer/configuration/types.d.ts +11 -11
- package/dist/types/messageComposer/linkPreviewsManager.d.ts +25 -13
- package/dist/types/messageComposer/messageComposer.d.ts +10 -10
- package/dist/types/messageComposer/middleware/messageComposer/types.d.ts +3 -3
- package/dist/types/messageComposer/middleware/pollComposer/types.d.ts +4 -7
- package/dist/types/messageComposer/middleware/textComposer/commandUtils.d.ts +3 -3
- package/dist/types/messageComposer/middleware/textComposer/commands.d.ts +7 -7
- package/dist/types/messageComposer/middleware/textComposer/mentions.d.ts +218 -4
- package/dist/types/messageComposer/middleware/textComposer/types.d.ts +9 -4
- package/dist/types/messageComposer/pollComposer.d.ts +5 -4
- package/dist/types/messageComposer/textComposer.d.ts +8 -3
- package/dist/types/messageComposer/types.d.ts +6 -28
- package/dist/types/messageDelivery/MessageDeliveryReporter.d.ts +40 -25
- package/dist/types/messageDelivery/MessageReceiptsTracker.d.ts +53 -12
- package/dist/types/messageOperations/MessageOperationStatePolicy.d.ts +19 -0
- package/dist/types/messageOperations/MessageOperations.d.ts +17 -0
- package/dist/types/messageOperations/index.d.ts +3 -0
- package/dist/types/messageOperations/types.d.ts +49 -0
- package/dist/types/moderation.d.ts +21 -203
- package/dist/types/notifications/types.d.ts +8 -7
- package/dist/types/offline-support/offline_support_api.d.ts +111 -87
- package/dist/types/offline-support/offline_sync_manager.d.ts +0 -7
- package/dist/types/offline-support/types.d.ts +19 -23
- package/dist/types/offline-support/util.d.ts +4 -2
- package/dist/types/pagination/FilterBuilder.d.ts +1 -1
- package/dist/types/pagination/ItemIndex.d.ts +25 -0
- package/dist/types/pagination/cursorDerivation/createdAtAroundPaginationFlags.d.ts +9 -0
- package/dist/types/pagination/cursorDerivation/idAroundPaginationFlags.d.ts +6 -0
- package/dist/types/pagination/cursorDerivation/index.d.ts +1 -0
- package/dist/types/pagination/cursorDerivation/linearPaginationFlags.d.ts +6 -0
- package/dist/types/pagination/filterCompiler.d.ts +7 -0
- package/dist/types/pagination/index.d.ts +1 -3
- package/dist/types/pagination/paginators/BasePaginator.d.ts +848 -0
- package/dist/types/pagination/paginators/ChannelPaginator.d.ts +212 -0
- package/dist/types/pagination/paginators/MessageIntervalPaginator.d.ts +282 -0
- package/dist/types/pagination/paginators/MessagePaginator.d.ts +208 -0
- package/dist/types/pagination/paginators/PinnedMessagePaginator.d.ts +39 -0
- package/dist/types/pagination/paginators/ReminderPaginator.d.ts +19 -0
- package/dist/types/pagination/paginators/UserGroupPaginator.d.ts +24 -0
- package/dist/types/pagination/paginators/index.d.ts +7 -0
- package/dist/types/pagination/paginators/stateThrottling.d.ts +2 -0
- package/dist/types/pagination/sortCompiler.d.ts +49 -0
- package/dist/types/pagination/types.normalization.d.ts +11 -0
- package/dist/types/pagination/utility.normalization.d.ts +18 -0
- package/dist/types/pagination/utility.queryChannel.d.ts +26 -0
- package/dist/types/pagination/utility.search.d.ts +13 -0
- package/dist/types/permissions.d.ts +1 -1
- package/dist/types/poll.d.ts +29 -32
- package/dist/types/poll_manager.d.ts +2 -2
- package/dist/types/reminders/Reminder.d.ts +2 -5
- package/dist/types/reminders/ReminderManager.d.ts +2 -9
- package/dist/types/search/MessageSearchSource.d.ts +4 -4
- package/dist/types/search/UserSearchSource.d.ts +1 -1
- package/dist/types/search/types.d.ts +3 -3
- package/dist/types/segment.d.ts +0 -34
- package/dist/types/signing.d.ts +105 -65
- package/dist/types/store.d.ts +1 -0
- package/dist/types/thread.d.ts +70 -34
- package/dist/types/thread_manager.d.ts +3 -3
- package/dist/types/token_manager.d.ts +15 -10
- package/dist/types/types.d.ts +197 -3404
- package/dist/types/utils/FixedSizeQueueCache.d.ts +12 -6
- package/dist/types/utils/WithSubscriptions.d.ts +2 -1
- package/dist/types/utils/concurrency.d.ts +4 -4
- package/dist/types/utils/mergeWith/mergeWith.d.ts +3 -3
- package/dist/types/utils/mergeWith/mergeWithCore.d.ts +8 -3
- package/dist/types/utils/retryable.d.ts +34 -0
- package/dist/types/utils/throttling/throttle.d.ts +36 -0
- package/dist/types/utils.d.ts +100 -194
- package/package.json +7 -3
- package/src/ChannelManager.ts +724 -0
- package/src/CooldownTimer.ts +2 -2
- package/src/EventHandlerPipeline.ts +228 -0
- package/src/LiveLocationManager.ts +12 -11
- package/src/api-client.ts +275 -0
- package/src/campaign.ts +1 -77
- package/src/channel.ts +1344 -1282
- package/src/channel_batch_updater.ts +1 -211
- package/src/channel_state.ts +144 -1013
- package/src/client.ts +936 -3995
- package/src/client_state.ts +4 -4
- package/src/configuration/InstanceConfigurationService.ts +73 -0
- package/src/configuration/index.ts +1 -0
- package/src/configuration/types.ts +81 -0
- package/src/connection.ts +145 -94
- package/src/connection_fallback.ts +29 -27
- package/src/constants.ts +1 -5
- package/src/custom_types.ts +4 -1
- package/src/entityStore/EntityStore.ts +208 -0
- package/src/entityStore/StoreBackedItemIndex.ts +115 -0
- package/src/entityStore/applyReactionLocally.ts +113 -0
- package/src/entityStore/index.ts +2 -0
- package/src/errors.ts +14 -2
- package/src/gen/chat/ChannelApi.ts +275 -0
- package/src/gen/chat/ChatApi.ts +2554 -0
- package/src/gen/model-decoders/decoders.ts +2680 -0
- package/src/gen/model-decoders/event-decoder-mapping.ts +198 -0
- package/src/gen/models/index.ts +12673 -0
- package/src/gen/moderation/ModerationApi.ts +607 -0
- package/src/gen-imports.ts +3 -0
- package/src/index.ts +11 -12
- package/src/insights.ts +6 -5
- package/src/logger.ts +28 -0
- package/src/messageComposer/LocationComposer.ts +9 -5
- package/src/messageComposer/MessageComposerEffectHandlers.ts +1 -0
- package/src/messageComposer/attachmentIdentity.ts +16 -9
- package/src/messageComposer/attachmentManager.ts +13 -8
- package/src/messageComposer/configuration/types.ts +11 -11
- package/src/messageComposer/linkPreviewsManager.ts +9 -10
- package/src/messageComposer/messageComposer.ts +57 -53
- package/src/messageComposer/middleware/messageComposer/attachments.ts +1 -2
- package/src/messageComposer/middleware/messageComposer/cleanData.ts +1 -1
- package/src/messageComposer/middleware/messageComposer/compositionValidation.ts +2 -2
- package/src/messageComposer/middleware/messageComposer/messageComposerState.ts +2 -2
- package/src/messageComposer/middleware/messageComposer/sharedLocation.ts +4 -3
- package/src/messageComposer/middleware/messageComposer/textComposer.ts +2 -2
- package/src/messageComposer/middleware/messageComposer/types.ts +3 -4
- package/src/messageComposer/middleware/messageComposer/userDataInjection.ts +9 -5
- package/src/messageComposer/middleware/pollComposer/state.ts +0 -2
- package/src/messageComposer/middleware/pollComposer/types.ts +4 -7
- package/src/messageComposer/middleware/textComposer/TextComposerMiddlewareExecutor.ts +10 -1
- package/src/messageComposer/middleware/textComposer/commandEffects.ts +4 -4
- package/src/messageComposer/middleware/textComposer/commandUtils.ts +3 -6
- package/src/messageComposer/middleware/textComposer/commands.ts +3 -3
- package/src/messageComposer/middleware/textComposer/mentionUtils.ts +1 -1
- package/src/messageComposer/middleware/textComposer/mentions.ts +31 -15
- package/src/messageComposer/middleware/textComposer/types.ts +9 -4
- package/src/messageComposer/pollComposer.ts +6 -8
- package/src/messageComposer/textComposer.ts +27 -2
- package/src/messageComposer/types.ts +6 -28
- package/src/messageDelivery/MessageDeliveryReporter.ts +101 -45
- package/src/messageDelivery/MessageReceiptsTracker.ts +294 -21
- package/src/messageOperations/MessageOperationStatePolicy.ts +77 -0
- package/src/messageOperations/MessageOperations.ts +212 -0
- package/src/messageOperations/index.ts +10 -0
- package/src/messageOperations/types.ts +64 -0
- package/src/moderation.ts +38 -431
- package/src/notifications/types.ts +8 -7
- package/src/offline-support/offline_support_api.ts +200 -133
- package/src/offline-support/offline_sync_manager.ts +23 -44
- package/src/offline-support/types.ts +23 -26
- package/src/offline-support/util.ts +5 -4
- package/src/pagination/FilterBuilder.ts +4 -1
- package/src/pagination/ItemIndex.ts +25 -0
- package/src/pagination/cursorDerivation/createdAtAroundPaginationFlags.ts +73 -0
- package/src/pagination/cursorDerivation/idAroundPaginationFlags.ts +53 -0
- package/src/pagination/cursorDerivation/index.ts +1 -0
- package/src/pagination/cursorDerivation/linearPaginationFlags.ts +86 -0
- package/src/pagination/filterCompiler.ts +196 -0
- package/src/pagination/index.ts +1 -3
- package/src/pagination/paginators/BasePaginator.ts +2859 -0
- package/src/pagination/paginators/ChannelPaginator.ts +729 -0
- package/src/pagination/paginators/MessageIntervalPaginator.ts +1116 -0
- package/src/pagination/paginators/MessagePaginator.ts +508 -0
- package/src/pagination/paginators/PinnedMessagePaginator.ts +108 -0
- package/src/pagination/paginators/ReminderPaginator.ts +107 -0
- package/src/pagination/paginators/UserGroupPaginator.ts +127 -0
- package/src/pagination/paginators/index.ts +7 -0
- package/src/pagination/paginators/stateThrottling.ts +31 -0
- package/src/pagination/sortCompiler.ts +193 -0
- package/src/pagination/types.normalization.ts +14 -0
- package/src/pagination/utility.normalization.ts +106 -0
- package/src/pagination/utility.queryChannel.ts +84 -0
- package/src/pagination/utility.search.ts +80 -0
- package/src/permissions.ts +7 -8
- package/src/poll.ts +101 -110
- package/src/poll_manager.ts +14 -8
- package/src/reminders/Reminder.ts +2 -5
- package/src/reminders/ReminderManager.ts +19 -21
- package/src/search/BaseSearchSource.ts +0 -3
- package/src/search/ChannelMemberSearchSource.ts +7 -1
- package/src/search/ChannelSearchSource.ts +10 -3
- package/src/search/MessageSearchSource.ts +29 -30
- package/src/search/UserSearchSource.ts +12 -8
- package/src/search/types.ts +3 -4
- package/src/segment.ts +1 -95
- package/src/signing.ts +107 -67
- package/src/store.ts +2 -1
- package/src/thread.ts +562 -238
- package/src/thread_manager.ts +37 -16
- package/src/token_manager.ts +21 -10
- package/src/types.ts +364 -4533
- package/src/uploadManager.ts +8 -0
- package/src/utils/FixedSizeQueueCache.ts +12 -6
- package/src/utils/WithSubscriptions.ts +2 -1
- package/src/utils/concurrency.ts +4 -4
- package/src/utils/mergeWith/mergeWith.ts +3 -3
- package/src/utils/mergeWith/mergeWithCore.ts +140 -164
- package/src/utils/mergeWith/mergeWithDiff.ts +3 -3
- package/src/utils/retryable.ts +117 -0
- package/src/utils/throttling/throttle.ts +130 -0
- package/src/utils.ts +208 -754
- package/dist/types/channel_manager.d.ts +0 -122
- package/dist/types/events.d.ts +0 -72
- package/dist/types/pagination/BasePaginator.d.ts +0 -69
- package/dist/types/pagination/ReminderPaginator.d.ts +0 -16
- package/dist/types/pagination/UserGroupPaginator.d.ts +0 -21
- package/src/channel_manager.ts +0 -830
- package/src/events.ts +0 -75
- package/src/pagination/BasePaginator.ts +0 -184
- package/src/pagination/ReminderPaginator.ts +0 -56
- 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
|
+
};
|