stream-chat 9.50.2 → 9.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/cjs/index.browser.js +252 -94
  2. package/dist/cjs/index.browser.js.map +3 -3
  3. package/dist/cjs/index.node.js +252 -94
  4. package/dist/cjs/index.node.js.map +3 -3
  5. package/dist/esm/index.mjs +252 -94
  6. package/dist/esm/index.mjs.map +3 -3
  7. package/dist/types/channel.d.ts +3 -2
  8. package/dist/types/client.d.ts +13 -9
  9. package/dist/types/messageComposer/middleware/textComposer/mentions.d.ts +15 -9
  10. package/dist/types/offline-support/offline_support_api.d.ts +7 -1
  11. package/dist/types/offline-support/offline_sync_manager.d.ts +19 -1
  12. package/dist/types/search/BaseSearchSource.d.ts +42 -24
  13. package/dist/types/search/ChannelMemberSearchSource.d.ts +9 -4
  14. package/dist/types/search/ChannelSearchSource.d.ts +9 -3
  15. package/dist/types/search/MessageSearchSource.d.ts +14 -3
  16. package/dist/types/search/UserSearchSource.d.ts +9 -3
  17. package/dist/types/search/types.d.ts +14 -1
  18. package/dist/types/types.d.ts +20 -1
  19. package/dist/types/utils.d.ts +1 -1
  20. package/package.json +7 -7
  21. package/src/channel.ts +4 -0
  22. package/src/client.ts +54 -12
  23. package/src/messageComposer/middleware/textComposer/mentions.ts +75 -23
  24. package/src/offline-support/offline_support_api.ts +17 -2
  25. package/src/offline-support/offline_sync_manager.ts +73 -14
  26. package/src/search/BaseSearchSource.ts +123 -39
  27. package/src/search/ChannelMemberSearchSource.ts +22 -11
  28. package/src/search/ChannelSearchSource.ts +16 -5
  29. package/src/search/MessageSearchSource.ts +36 -4
  30. package/src/search/UserSearchSource.ts +16 -5
  31. package/src/search/types.ts +14 -1
  32. package/src/types.ts +21 -1
  33. package/src/utils.ts +6 -3
@@ -783,7 +783,8 @@ var debounce = (fn, timeout = 0, { leading = false, trailing = true } = {}) => {
783
783
  }
784
784
  runningTimeout = null;
785
785
  };
786
- runningTimeout = setTimeout(timeoutHandler, timeout);
786
+ const delay = typeof timeout === "function" ? timeout(...args) : timeout;
787
+ runningTimeout = setTimeout(timeoutHandler, delay);
787
788
  return lastResult;
788
789
  };
789
790
  debouncedFn.cancel = () => {
@@ -4221,13 +4222,41 @@ function isErrorResponse(res) {
4221
4222
 
4222
4223
  // src/search/BaseSearchSource.ts
4223
4224
  var DEFAULT_SEARCH_SOURCE_OPTIONS = {
4224
- debounceMs: 300,
4225
4225
  pageSize: 10,
4226
4226
  allowEmptySearchString: false,
4227
4227
  resetOnNewSearchQuery: true
4228
4228
  };
4229
+ var resolveDebounceOptions = ({
4230
+ debounceMs,
4231
+ shortQueryDebounceMs,
4232
+ longQueryDebounceMs,
4233
+ shortQueryMaxLength
4234
+ }) => ({
4235
+ shortQueryDebounceMs: shortQueryDebounceMs ?? debounceMs ?? 500,
4236
+ longQueryDebounceMs: longQueryDebounceMs ?? debounceMs ?? 300,
4237
+ shortQueryMaxLength: shortQueryMaxLength ?? 2
4238
+ });
4229
4239
  var BaseSearchSourceBase = class {
4230
4240
  constructor(options) {
4241
+ this.setDebounceOptions = (options = {}) => {
4242
+ const resolved = resolveDebounceOptions(options);
4243
+ this.shortQueryDebounceMs = resolved.shortQueryDebounceMs;
4244
+ this.longQueryDebounceMs = resolved.longQueryDebounceMs;
4245
+ this.shortQueryMaxLength = resolved.shortQueryMaxLength;
4246
+ this.searchDebounced = debounce(
4247
+ this.executeQuery.bind(this),
4248
+ (searchString) => this.getDebounceMs(searchString)
4249
+ );
4250
+ };
4251
+ /**
4252
+ * Short queries match too much and are slow server-side, so they get a longer debounce.
4253
+ * Resolved per call, which is what lets the interval switch as the user keeps typing.
4254
+ */
4255
+ this.getDebounceMs = (searchString) => {
4256
+ const { length } = searchString ?? this.searchQuery;
4257
+ return length <= this.shortQueryMaxLength ? this.shortQueryDebounceMs : this.longQueryDebounceMs;
4258
+ };
4259
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
4231
4260
  this.activate = () => {
4232
4261
  if (this.isActive) return;
4233
4262
  this.state.partialNext({ isActive: true });
@@ -4239,7 +4268,7 @@ var BaseSearchSourceBase = class {
4239
4268
  this.canExecuteQuery = (newSearchString) => {
4240
4269
  const hasNewSearchQuery = typeof newSearchString !== "undefined";
4241
4270
  const searchString = newSearchString ?? this.searchQuery;
4242
- return !!(this.isActive && !this.isLoading && (this.hasNext || hasNewSearchQuery) && (this.allowEmptySearchString || searchString));
4271
+ return !!(this.isActive && this.canDispatchQuery(hasNewSearchQuery) && (this.allowEmptySearchString || searchString));
4243
4272
  };
4244
4273
  const { pageSize, allowEmptySearchString, resetOnNewSearchQuery } = {
4245
4274
  ...DEFAULT_SEARCH_SOURCE_OPTIONS,
@@ -4249,6 +4278,14 @@ var BaseSearchSourceBase = class {
4249
4278
  this.allowEmptySearchString = allowEmptySearchString;
4250
4279
  this.resetOnNewSearchQuery = resetOnNewSearchQuery;
4251
4280
  this.state = new StateStore(this.initialState);
4281
+ this.setDebounceOptions(options ?? {});
4282
+ }
4283
+ /**
4284
+ * A new search query preempts an in-flight one; pagination waits for it to finish
4285
+ * and needs a next page to fetch.
4286
+ */
4287
+ canDispatchQuery(hasNewSearchQuery) {
4288
+ return hasNewSearchQuery || !this.isLoading && this.hasNext;
4252
4289
  }
4253
4290
  get lastQueryError() {
4254
4291
  return this.state.getLatestValue().lastQueryError;
@@ -4343,21 +4380,30 @@ var BaseSearchSourceBase = class {
4343
4380
  }
4344
4381
  };
4345
4382
  var BaseSearchSource = class extends BaseSearchSourceBase {
4346
- constructor(options) {
4347
- const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
4348
- super(options);
4349
- this.setDebounceOptions = ({ debounceMs }) => {
4350
- this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
4351
- };
4352
- this.search = (searchQuery) => this.searchDebounced(searchQuery);
4353
- this.setDebounceOptions({ debounceMs });
4383
+ constructor() {
4384
+ super(...arguments);
4385
+ /** Aborts the in-flight request, if any, once a newer query is dispatched. */
4386
+ this.queryAbortController = null;
4387
+ }
4388
+ /** Aborts the in-flight request, if any. Returns true when one was aborted. */
4389
+ abortInFlightQuery() {
4390
+ if (!this.queryAbortController) return false;
4391
+ this.queryAbortController.abort();
4392
+ this.queryAbortController = null;
4393
+ return true;
4394
+ }
4395
+ resetState() {
4396
+ this.abortInFlightQuery();
4397
+ super.resetState();
4354
4398
  }
4355
4399
  async executeQuery(newSearchString) {
4356
4400
  if (!this.canExecuteQuery(newSearchString)) return;
4401
+ this.abortInFlightQuery();
4402
+ const { signal } = this.queryAbortController = new AbortController();
4357
4403
  const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
4358
4404
  let stateUpdate = {};
4359
4405
  try {
4360
- const results = await this.query(searchString);
4406
+ const results = await this.query(searchString, { signal });
4361
4407
  if (!results) return;
4362
4408
  const { items } = results;
4363
4409
  stateUpdate = this.updatePaginationStateFromQuery(results);
@@ -4368,23 +4414,19 @@ var BaseSearchSource = class extends BaseSearchSourceBase {
4368
4414
  stateUpdate.hasNext = false;
4369
4415
  }
4370
4416
  } finally {
4371
- this.state.next(this.getStateAfterQuery(stateUpdate, hasNewSearchQuery));
4417
+ if (!signal.aborted) {
4418
+ this.state.next(this.getStateAfterQuery(stateUpdate, hasNewSearchQuery));
4419
+ }
4372
4420
  }
4373
4421
  }
4374
4422
  cancelScheduledQuery() {
4375
4423
  this.searchDebounced.cancel();
4424
+ if (this.abortInFlightQuery() && this.isLoading) {
4425
+ this.state.partialNext({ isLoading: false });
4426
+ }
4376
4427
  }
4377
4428
  };
4378
4429
  var BaseSearchSourceSync = class extends BaseSearchSourceBase {
4379
- constructor(options) {
4380
- const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
4381
- super(options);
4382
- this.setDebounceOptions = ({ debounceMs }) => {
4383
- this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
4384
- };
4385
- this.search = (searchQuery) => this.searchDebounced(searchQuery);
4386
- this.setDebounceOptions({ debounceMs });
4387
- }
4388
4430
  executeQuery(newSearchString) {
4389
4431
  if (!this.canExecuteQuery(newSearchString)) return;
4390
4432
  const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
@@ -4680,13 +4722,13 @@ var UserGroupPaginator = class extends BasePaginator {
4680
4722
  // src/search/ChannelMemberSearchSource.ts
4681
4723
  var ChannelMemberSearchSource = class extends BaseSearchSource {
4682
4724
  constructor(channel, options, filterBuilderOptions = {}) {
4683
- super(options);
4725
+ const { filters, sort, searchOptions, ...restOptions } = options || {};
4726
+ super({ ...restOptions, allowEmptySearchString: true });
4684
4727
  this.type = "members";
4685
- this.canExecuteQuery = (newSearchString) => {
4686
- const hasNewSearchQuery = typeof newSearchString !== "undefined";
4687
- return this.isActive && !this.isLoading && (this.hasNext || hasNewSearchQuery);
4688
- };
4689
4728
  this.channel = channel;
4729
+ this.filters = filters;
4730
+ this.sort = sort;
4731
+ this.searchOptions = searchOptions;
4690
4732
  this.filterBuilder = new FilterBuilder({
4691
4733
  initialFilterConfig: {
4692
4734
  default: {
@@ -4702,7 +4744,7 @@ var ChannelMemberSearchSource = class extends BaseSearchSource {
4702
4744
  ...filterBuilderOptions
4703
4745
  });
4704
4746
  }
4705
- async query(searchQuery) {
4747
+ async query(searchQuery, queryOptions = {}) {
4706
4748
  const filters = this.filterBuilder.buildFilters({
4707
4749
  baseFilters: this.filters,
4708
4750
  context: {
@@ -4711,7 +4753,12 @@ var ChannelMemberSearchSource = class extends BaseSearchSource {
4711
4753
  });
4712
4754
  const sort = this.sort ?? [];
4713
4755
  const options = { ...this.searchOptions, limit: this.pageSize, offset: this.offset };
4714
- const { members } = await this.channel.queryMembers(filters ?? {}, sort, options);
4756
+ const { members } = await this.channel.queryMembers(
4757
+ filters ?? {},
4758
+ sort,
4759
+ options,
4760
+ queryOptions
4761
+ );
4715
4762
  return { items: members };
4716
4763
  }
4717
4764
  filterQueryResults(items) {
@@ -4826,9 +4873,13 @@ var SearchController = class {
4826
4873
  // src/search/UserSearchSource.ts
4827
4874
  var UserSearchSource = class extends BaseSearchSource {
4828
4875
  constructor(client, options, filterBuilderOptions = {}) {
4829
- super(options);
4876
+ const { filters, sort, searchOptions, ...restOptions } = options || {};
4877
+ super(restOptions);
4830
4878
  this.type = "users";
4831
4879
  this.client = client;
4880
+ this.filters = filters;
4881
+ this.sort = sort;
4882
+ this.searchOptions = searchOptions;
4832
4883
  this.filterBuilder = new FilterBuilder({
4833
4884
  initialFilterConfig: {
4834
4885
  $or: {
@@ -4844,7 +4895,7 @@ var UserSearchSource = class extends BaseSearchSource {
4844
4895
  ...filterBuilderOptions
4845
4896
  });
4846
4897
  }
4847
- async query(searchQuery) {
4898
+ async query(searchQuery, queryOptions = {}) {
4848
4899
  const filters = this.filterBuilder.buildFilters({
4849
4900
  baseFilters: this.filters,
4850
4901
  context: { searchQuery }
@@ -4857,7 +4908,7 @@ var UserSearchSource = class extends BaseSearchSource {
4857
4908
  sort = { id: 1, ...this.sort };
4858
4909
  }
4859
4910
  const options = { ...this.searchOptions, limit: this.pageSize, offset: this.offset };
4860
- const { users } = await this.client.queryUsers(filters, sort, options);
4911
+ const { users } = await this.client.queryUsers(filters, sort, options, queryOptions);
4861
4912
  return { items: users };
4862
4913
  }
4863
4914
  filterQueryResults(items) {
@@ -4868,9 +4919,13 @@ var UserSearchSource = class extends BaseSearchSource {
4868
4919
  // src/search/ChannelSearchSource.ts
4869
4920
  var ChannelSearchSource = class extends BaseSearchSource {
4870
4921
  constructor(client, options, filterBuilderOptions = {}) {
4871
- super(options);
4922
+ const { filters, sort, searchOptions, ...restOptions } = options || {};
4923
+ super(restOptions);
4872
4924
  this.type = "channels";
4873
4925
  this.client = client;
4926
+ this.filters = filters;
4927
+ this.sort = sort;
4928
+ this.searchOptions = searchOptions;
4874
4929
  this.filterBuilder = new FilterBuilder({
4875
4930
  ...filterBuilderOptions,
4876
4931
  initialFilterConfig: {
@@ -4882,7 +4937,7 @@ var ChannelSearchSource = class extends BaseSearchSource {
4882
4937
  }
4883
4938
  });
4884
4939
  }
4885
- async query(searchQuery) {
4940
+ async query(searchQuery, queryOptions = {}) {
4886
4941
  const filters = this.filterBuilder.buildFilters({
4887
4942
  baseFilters: {
4888
4943
  ...this.client.userID ? { members: { $in: [this.client.userID] } } : {},
@@ -4892,7 +4947,7 @@ var ChannelSearchSource = class extends BaseSearchSource {
4892
4947
  });
4893
4948
  const sort = this.sort ?? {};
4894
4949
  const options = { ...this.searchOptions, limit: this.pageSize, offset: this.offset };
4895
- const items = await this.client.queryChannels(filters, sort, options);
4950
+ const items = await this.client.queryChannels(filters, sort, options, queryOptions);
4896
4951
  return { items };
4897
4952
  }
4898
4953
  filterQueryResults(items) {
@@ -4903,9 +4958,24 @@ var ChannelSearchSource = class extends BaseSearchSource {
4903
4958
  // src/search/MessageSearchSource.ts
4904
4959
  var MessageSearchSource = class extends BaseSearchSource {
4905
4960
  constructor(client, options, filterBuilderOptions) {
4906
- super(options);
4961
+ const {
4962
+ messageSearchChannelFilters,
4963
+ messageSearchFilters,
4964
+ messageSearchSort,
4965
+ channelQueryFilters,
4966
+ channelQuerySort,
4967
+ channelQueryOptions,
4968
+ ...restOptions
4969
+ } = options || {};
4970
+ super(restOptions);
4907
4971
  this.type = "messages";
4908
4972
  this.client = client;
4973
+ this.messageSearchChannelFilters = messageSearchChannelFilters;
4974
+ this.messageSearchFilters = messageSearchFilters;
4975
+ this.messageSearchSort = messageSearchSort;
4976
+ this.channelQueryFilters = channelQueryFilters;
4977
+ this.channelQuerySort = channelQuerySort;
4978
+ this.channelQueryOptions = channelQueryOptions;
4909
4979
  this.messageSearchChannelFilterBuilder = new FilterBuilder(filterBuilderOptions?.messageSearchChannel);
4910
4980
  this.messageSearchFilterBuilder = new FilterBuilder({
4911
4981
  ...filterBuilderOptions?.messageSearch,
@@ -4928,7 +4998,7 @@ var MessageSearchSource = class extends BaseSearchSource {
4928
4998
  }
4929
4999
  });
4930
5000
  }
4931
- async query(searchQuery) {
5001
+ async query(searchQuery, queryOptions = {}) {
4932
5002
  if (!this.client.userID || this.next === null) return { items: [] };
4933
5003
  const channelFilters = this.messageSearchChannelFilterBuilder.buildFilters({
4934
5004
  baseFilters: {
@@ -4956,9 +5026,11 @@ var MessageSearchSource = class extends BaseSearchSource {
4956
5026
  const { next, results } = await this.client.search(
4957
5027
  channelFilters,
4958
5028
  messageFilters,
4959
- options
5029
+ options,
5030
+ queryOptions
4960
5031
  );
4961
5032
  const items = results.map(({ message }) => message);
5033
+ if (queryOptions.signal?.aborted) return { items, next };
4962
5034
  const cids = Array.from(
4963
5035
  items.reduce((acc, message) => {
4964
5036
  if (message.cid && !this.client.activeChannels[message.cid]) acc.add(message.cid);
@@ -4976,7 +5048,8 @@ var MessageSearchSource = class extends BaseSearchSource {
4976
5048
  last_message_at: -1,
4977
5049
  ...this.channelQuerySort
4978
5050
  },
4979
- this.channelQueryOptions
5051
+ this.channelQueryOptions,
5052
+ queryOptions
4980
5053
  );
4981
5054
  }
4982
5055
  return { items, next };
@@ -6270,13 +6343,18 @@ var MentionsSearchSource = class extends BaseSearchSource {
6270
6343
  constructor(channel, options) {
6271
6344
  const {
6272
6345
  mentionAllAppUsers,
6346
+ memberFilters,
6347
+ memberSort,
6348
+ searchOptions,
6273
6349
  suggestionFactoryMappers,
6274
6350
  textComposerText,
6275
6351
  transliterate,
6276
6352
  trigger,
6353
+ userFilters,
6354
+ userSort,
6277
6355
  ...restOptions
6278
6356
  } = options || {};
6279
- super(restOptions);
6357
+ super({ ...restOptions, allowEmptySearchString: true });
6280
6358
  this.type = "mentions";
6281
6359
  this.normalizeSearchValue = (value) => this.transliterate(removeDiacritics(value)).toLowerCase();
6282
6360
  this.matchesSearchQuery = (value, searchQuery) => {
@@ -6314,10 +6392,6 @@ var MentionsSearchSource = class extends BaseSearchSource {
6314
6392
  this.toHereMentionSuggestion = (searchToken = this.searchQuery) => this.mapMentionSuggestion("here", "here", searchToken);
6315
6393
  this.toRoleMentionSuggestion = (role, searchToken = this.searchQuery) => this.mapMentionSuggestion("role", role, searchToken);
6316
6394
  this.toUserGroupMentionSuggestion = (userGroup, searchToken = this.searchQuery) => this.mapMentionSuggestion("user_group", userGroup, searchToken);
6317
- this.canExecuteQuery = (newSearchString) => {
6318
- const hasNewSearchQuery = typeof newSearchString !== "undefined";
6319
- return this.isActive && !this.isLoading && (hasNewSearchQuery || this.hasNext);
6320
- };
6321
6395
  this.transliterate = (text) => text;
6322
6396
  this.getMembersAndWatchers = () => {
6323
6397
  const memberUsers = Object.values(this.channel.state.members ?? {}).map(
@@ -6337,10 +6411,10 @@ var MentionsSearchSource = class extends BaseSearchSource {
6337
6411
  ...this.isMentionTypeAllowed("channel") ? [this.toChannelMentionSuggestion(searchQuery)] : [],
6338
6412
  ...this.isMentionTypeAllowed("here") ? [this.toHereMentionSuggestion(searchQuery)] : []
6339
6413
  ].filter(({ name }) => this.matchesPrefixSearchQuery(name, searchQuery));
6340
- this.getRoleMentionSuggestions = async (query) => {
6414
+ this.getRoleMentionSuggestions = async (query, queryOptions = {}) => {
6341
6415
  if (!this.isMentionTypeAllowed("role")) return [];
6342
6416
  if (!query) return [];
6343
- const { roles } = await this.client.searchRoles({ query });
6417
+ const { roles } = await this.client.searchRoles({ query }, queryOptions);
6344
6418
  return [...roles?.map((role) => role.name) ?? []].sort((left, right) => left.localeCompare(right)).map((role) => this.toRoleMentionSuggestion(role, query));
6345
6419
  };
6346
6420
  this.searchMembersLocally = (searchQuery) => {
@@ -6402,20 +6476,25 @@ var MentionsSearchSource = class extends BaseSearchSource {
6402
6476
  options: { ...this.searchOptions, limit: this.pageSize, offset }
6403
6477
  };
6404
6478
  };
6405
- this.queryUsers = async (searchQuery, offset = 0) => {
6479
+ this.queryUsers = async (searchQuery, offset = 0, queryOptions = {}) => {
6406
6480
  const { filters, sort, options } = this.prepareQueryUsersParams(searchQuery, offset);
6407
- const { users } = await this.client.queryUsers(filters, sort, options);
6481
+ const { users } = await this.client.queryUsers(filters, sort, options, queryOptions);
6408
6482
  return users;
6409
6483
  };
6410
- this.queryMembers = async (searchQuery, offset = 0) => {
6484
+ this.queryMembers = async (searchQuery, offset = 0, queryOptions = {}) => {
6411
6485
  const { filters, sort, options } = this.prepareQueryMembersParams(
6412
6486
  searchQuery,
6413
6487
  offset
6414
6488
  );
6415
- const response = await this.channel.queryMembers(filters, sort, options);
6489
+ const response = await this.channel.queryMembers(
6490
+ filters,
6491
+ sort,
6492
+ options,
6493
+ queryOptions
6494
+ );
6416
6495
  return response.members.map((member) => member.user);
6417
6496
  };
6418
- this.getUserSuggestionsPage = async (searchQuery, userOffset = 0) => {
6497
+ this.getUserSuggestionsPage = async (searchQuery, userOffset = 0, queryOptions = {}) => {
6419
6498
  if (!this.isMentionTypeAllowed("user")) {
6420
6499
  return {
6421
6500
  items: [],
@@ -6425,7 +6504,7 @@ var MentionsSearchSource = class extends BaseSearchSource {
6425
6504
  let users;
6426
6505
  const shouldSearchLocally = this.allMembersLoadedWithInitialChannelQuery || !searchQuery;
6427
6506
  if (this.config.mentionAllAppUsers) {
6428
- users = await this.queryUsers(searchQuery, userOffset);
6507
+ users = await this.queryUsers(searchQuery, userOffset, queryOptions);
6429
6508
  } else if (shouldSearchLocally) {
6430
6509
  const localUsers = this.searchMembersLocally(searchQuery);
6431
6510
  const items2 = localUsers.slice(userOffset, userOffset + this.pageSize).map((user) => this.toUserSuggestion(user, searchQuery));
@@ -6434,7 +6513,7 @@ var MentionsSearchSource = class extends BaseSearchSource {
6434
6513
  nextOffset: localUsers.length > userOffset + this.pageSize ? userOffset + items2.length : void 0
6435
6514
  };
6436
6515
  } else {
6437
- users = await this.queryMembers(searchQuery, userOffset);
6516
+ users = await this.queryMembers(searchQuery, userOffset, queryOptions);
6438
6517
  }
6439
6518
  const items = users.map((user) => this.toUserSuggestion(user, searchQuery));
6440
6519
  return {
@@ -6451,7 +6530,7 @@ var MentionsSearchSource = class extends BaseSearchSource {
6451
6530
  name_gt: lastItem.name
6452
6531
  });
6453
6532
  };
6454
- this.getUserGroupSuggestionsPage = async (searchQuery, cursor) => {
6533
+ this.getUserGroupSuggestionsPage = async (searchQuery, cursor, queryOptions = {}) => {
6455
6534
  if (!this.isMentionTypeAllowed("user_group")) {
6456
6535
  return {
6457
6536
  items: [],
@@ -6473,7 +6552,7 @@ var MentionsSearchSource = class extends BaseSearchSource {
6473
6552
  ...userGroupCursor?.id_gt ? { id_gt: userGroupCursor.id_gt } : {},
6474
6553
  ...userGroupCursor?.name_gt ? { name_gt: userGroupCursor.name_gt } : {}
6475
6554
  };
6476
- const { user_groups } = await this.client.searchUserGroups(options);
6555
+ const { user_groups } = await this.client.searchUserGroups(options, queryOptions);
6477
6556
  return {
6478
6557
  items: user_groups.map(
6479
6558
  (userGroup) => this.toUserGroupMentionSuggestion(userGroup, searchQuery)
@@ -6483,6 +6562,11 @@ var MentionsSearchSource = class extends BaseSearchSource {
6483
6562
  };
6484
6563
  this.client = channel.getClient();
6485
6564
  this.channel = channel;
6565
+ this.userFilters = userFilters;
6566
+ this.userSort = userSort;
6567
+ this.memberFilters = memberFilters;
6568
+ this.memberSort = memberSort;
6569
+ this.searchOptions = searchOptions;
6486
6570
  this.config = {
6487
6571
  mentionAllAppUsers,
6488
6572
  suggestionFactoryMappers,
@@ -6515,16 +6599,21 @@ var MentionsSearchSource = class extends BaseSearchSource {
6515
6599
  offset: (this.offset ?? 0) + userPaginationState.itemCount
6516
6600
  };
6517
6601
  }
6518
- async query(searchQuery) {
6602
+ async query(searchQuery, queryOptions = {}) {
6519
6603
  const userOffset = this.offset ?? 0;
6520
6604
  const isFirstPage = userOffset === 0 && typeof this.userGroupCursor === "undefined";
6521
6605
  const previousUserPaginationState = this.latestUserPaginationState;
6522
6606
  const previousUserGroupCursor = this.userGroupCursor;
6523
6607
  const [userResultsState, userGroupResultsState, roleSuggestionsState] = await Promise.allSettled([
6524
- this.getUserSuggestionsPage(searchQuery, userOffset),
6525
- this.getUserGroupSuggestionsPage(searchQuery, previousUserGroupCursor),
6526
- isFirstPage ? this.getRoleMentionSuggestions(searchQuery) : Promise.resolve([])
6608
+ this.getUserSuggestionsPage(searchQuery, userOffset, queryOptions),
6609
+ this.getUserGroupSuggestionsPage(
6610
+ searchQuery,
6611
+ previousUserGroupCursor,
6612
+ queryOptions
6613
+ ),
6614
+ isFirstPage ? this.getRoleMentionSuggestions(searchQuery, queryOptions) : Promise.resolve([])
6527
6615
  ]);
6616
+ if (queryOptions.signal?.aborted) return { items: [] };
6528
6617
  const userResults = userResultsState.status === "fulfilled" ? userResultsState.value : {
6529
6618
  items: [],
6530
6619
  nextOffset: isFirstPage ? void 0 : previousUserPaginationState?.nextOffset
@@ -9200,10 +9289,11 @@ var Channel = class {
9200
9289
  * @param {MemberSort} [sort] Sort options, for instance [{created_at: -1}].
9201
9290
  * When using multiple fields, make sure you use array of objects to guarantee field order, for instance [{name: -1}, {created_at: 1}]
9202
9291
  * @param {{ limit?: number; offset?: number }} [options] Option object, {limit: 10, offset:10}
9292
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
9203
9293
  *
9204
9294
  * @return {Promise<ChannelMemberAPIResponse>} Query Members response
9205
9295
  */
9206
- async queryMembers(filterConditions, sort = [], options = {}) {
9296
+ async queryMembers(filterConditions, sort = [], options = {}, requestOptions = {}) {
9207
9297
  let id;
9208
9298
  const type = this.type;
9209
9299
  let members;
@@ -9223,7 +9313,8 @@ var Channel = class {
9223
9313
  filter_conditions: filterConditions,
9224
9314
  ...options
9225
9315
  }
9226
- }
9316
+ },
9317
+ requestOptions
9227
9318
  );
9228
9319
  }
9229
9320
  /**
@@ -14355,6 +14446,7 @@ var StreamChat = class _StreamChat {
14355
14446
  return this.handleResponse(response);
14356
14447
  } catch (e) {
14357
14448
  e.client_request_id = requestConfig.headers?.["x-client-request-id"];
14449
+ if (axios3.isCancel(e)) throw e;
14358
14450
  this._logApiError(type, url, e);
14359
14451
  this.consecutiveFailures += 1;
14360
14452
  if (e.response) {
@@ -14666,7 +14758,7 @@ var StreamChat = class _StreamChat {
14666
14758
  return this.userAgent;
14667
14759
  }
14668
14760
  if (!this.cachedUserAgent) {
14669
- const version = "9.50.2";
14761
+ const version = "9.51.0";
14670
14762
  const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
14671
14763
  const { name: appName, version: appVersion } = this.appIdentifier ?? {};
14672
14764
  const { os, model: deviceModel } = this.deviceIdentifier ?? {};
@@ -15042,14 +15134,14 @@ var StreamChat = class _StreamChat {
15042
15134
  error
15043
15135
  });
15044
15136
  }
15045
- get(url, params) {
15046
- return this.doAxiosRequest("get", url, null, { params });
15137
+ get(url, params, config) {
15138
+ return this.doAxiosRequest("get", url, null, { params, config });
15047
15139
  }
15048
15140
  put(url, data) {
15049
15141
  return this.doAxiosRequest("put", url, data);
15050
15142
  }
15051
- post(url, data) {
15052
- return this.doAxiosRequest("post", url, data);
15143
+ post(url, data, config) {
15144
+ return this.doAxiosRequest("post", url, data, { config });
15053
15145
  }
15054
15146
  patch(url, data) {
15055
15147
  return this.doAxiosRequest("patch", url, data);
@@ -15244,10 +15336,11 @@ var StreamChat = class _StreamChat {
15244
15336
  * @param {UserSort} sort Sort options, for instance [{last_active: -1}].
15245
15337
  * When using multiple fields, make sure you use array of objects to guarantee field order, for instance [{last_active: -1}, {created_at: 1}]
15246
15338
  * @param {UserOptions} options Option object, {presence: true}
15339
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
15247
15340
  *
15248
15341
  * @return {Promise<{ users: Array<UserResponse> }>} User Query Response
15249
15342
  */
15250
- async queryUsers(filterConditions, sort = [], options = {}) {
15343
+ async queryUsers(filterConditions, sort = [], options = {}, requestOptions = {}) {
15251
15344
  const defaultOptions = {
15252
15345
  presence: false
15253
15346
  };
@@ -15264,7 +15357,8 @@ var StreamChat = class _StreamChat {
15264
15357
  ...defaultOptions,
15265
15358
  ...options
15266
15359
  }
15267
- }
15360
+ },
15361
+ requestOptions
15268
15362
  );
15269
15363
  this.state.updateUsers(data.users);
15270
15364
  return data;
@@ -15310,13 +15404,14 @@ var StreamChat = class _StreamChat {
15310
15404
  * searchUserGroups - Search user groups by prefix for autocomplete
15311
15405
  *
15312
15406
  * @param {SearchUserGroupsOptions} options The search options
15313
- *
15407
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
15314
15408
  * @return {Promise<SearchUserGroupsResponse>} User Group Search Response
15315
15409
  */
15316
- async searchUserGroups(options) {
15410
+ async searchUserGroups(options, requestOptions = {}) {
15317
15411
  return await this.get(
15318
15412
  this.baseURL + "/usergroups/search",
15319
- options
15413
+ options,
15414
+ requestOptions
15320
15415
  );
15321
15416
  }
15322
15417
  /**
@@ -15436,10 +15531,11 @@ var StreamChat = class _StreamChat {
15436
15531
  * @param {ChannelSort} [sort] Sort options, for instance {created_at: -1}.
15437
15532
  * When using multiple fields, make sure you use array of objects to guarantee field order, for instance [{last_updated: -1}, {created_at: 1}]
15438
15533
  * @param {ChannelOptions} [options] Options object. Can include predefined_filter, filter_values, and sort_values for using predefined filters.
15534
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
15439
15535
  *
15440
15536
  * @return {Promise<QueryChannelsAPIResponse>} full search channels response
15441
15537
  */
15442
- async queryChannelsRequestWithResponse(filterConditions, sort = [], options = {}) {
15538
+ async queryChannelsRequestWithResponse(filterConditions, sort = [], options = {}, requestOptions = {}) {
15443
15539
  const defaultOptions = {
15444
15540
  state: true,
15445
15541
  watch: true,
@@ -15464,7 +15560,11 @@ var StreamChat = class _StreamChat {
15464
15560
  ...defaultOptions,
15465
15561
  ...restOptions
15466
15562
  };
15467
- return await this.post(this.baseURL + "/channels", payload);
15563
+ return await this.post(
15564
+ this.baseURL + "/channels",
15565
+ payload,
15566
+ requestOptions
15567
+ );
15468
15568
  }
15469
15569
  /**
15470
15570
  * queryChannelsRequest - Queries channels and returns the raw channel response list.
@@ -15493,7 +15593,9 @@ var StreamChat = class _StreamChat {
15493
15593
  const queryChannelsResponse = await this.queryChannelsRequestWithResponse(
15494
15594
  filterConditions,
15495
15595
  sort,
15496
- options
15596
+ options,
15597
+ // stateOptions is never serialized, so it carries the abort signal
15598
+ { signal: stateOptions.signal }
15497
15599
  );
15498
15600
  const channels = queryChannelsResponse.channels;
15499
15601
  this.dispatchEvent({
@@ -15610,10 +15712,11 @@ var StreamChat = class _StreamChat {
15610
15712
  * @param {ChannelFilters} filterConditions MongoDB style filter conditions
15611
15713
  * @param {MessageFilters | string} query search query or object MongoDB style filters
15612
15714
  * @param {SearchOptions} [options] Option object, {user_id: 'tommaso'}
15715
+ * @param {RequestOptions} [requestOptions] Carries an abort signal. Not sent in the request.
15613
15716
  *
15614
15717
  * @return {Promise<SearchAPIResponse>} search messages response
15615
15718
  */
15616
- async search(filterConditions, query, options = {}) {
15719
+ async search(filterConditions, query, options = {}, requestOptions = {}) {
15617
15720
  if (options.offset && options.next) {
15618
15721
  throw Error(`Cannot specify offset with next`);
15619
15722
  }
@@ -15630,7 +15733,11 @@ var StreamChat = class _StreamChat {
15630
15733
  throw Error(`Invalid type ${typeof query} for query parameter`);
15631
15734
  }
15632
15735
  await this.wsPromise;
15633
- return await this.get(this.baseURL + "/search", { payload });
15736
+ return await this.get(
15737
+ this.baseURL + "/search",
15738
+ { payload },
15739
+ requestOptions
15740
+ );
15634
15741
  }
15635
15742
  /**
15636
15743
  * setLocalDevice - Set the device info for the current client(device) that will be sent via WS connection automatically
@@ -16667,6 +16774,7 @@ var StreamChat = class _StreamChat {
16667
16774
  headers: axiosRequestConfigHeaders,
16668
16775
  ...axiosRequestConfigRest
16669
16776
  } = this.options.axiosRequestConfig || {};
16777
+ const resolvedSignal = options.config?.signal ?? axiosRequestConfigRest.signal ?? signal;
16670
16778
  return {
16671
16779
  params: {
16672
16780
  user_id: this.userID,
@@ -16682,9 +16790,9 @@ var StreamChat = class _StreamChat {
16682
16790
  ...options.headers,
16683
16791
  ...axiosRequestConfigHeaders || {}
16684
16792
  },
16685
- ...signal ? { signal } : {},
16686
16793
  ...options.config,
16687
- ...axiosRequestConfigRest || {}
16794
+ ...axiosRequestConfigRest,
16795
+ ...resolvedSignal ? { signal: resolvedSignal } : {}
16688
16796
  };
16689
16797
  }
16690
16798
  _getToken() {
@@ -16824,8 +16932,12 @@ var StreamChat = class _StreamChat {
16824
16932
  *
16825
16933
  * @returns {Promise<APIResponse>}
16826
16934
  */
16827
- searchRoles(options) {
16828
- return this.get(`${this.baseURL}/roles/search`, options);
16935
+ searchRoles(options, requestOptions = {}) {
16936
+ return this.get(
16937
+ `${this.baseURL}/roles/search`,
16938
+ options,
16939
+ requestOptions
16940
+ );
16829
16941
  }
16830
16942
  /** deleteRole - deletes a custom role
16831
16943
  *
@@ -18182,7 +18294,8 @@ import { isAxiosError } from "axios";
18182
18294
  var OfflineDBSyncManager = class {
18183
18295
  constructor({
18184
18296
  client,
18185
- offlineDb
18297
+ offlineDb,
18298
+ syncMaxEventCount
18186
18299
  }) {
18187
18300
  this.syncStatus = false;
18188
18301
  this.connectionChangedListener = null;
@@ -18250,10 +18363,22 @@ var OfflineDBSyncManager = class {
18250
18363
  */
18251
18364
  this.invokeSyncStatusListeners = async (status) => {
18252
18365
  this.syncStatus = status;
18253
- this.syncStatusListeners.forEach((l) => l(status));
18366
+ this.syncStatusListeners.forEach((l) => {
18367
+ try {
18368
+ l(status);
18369
+ } catch (error) {
18370
+ console.log("Error in a sync status listener.", error);
18371
+ }
18372
+ });
18254
18373
  if (status) {
18255
18374
  const promises = Array.from(this.scheduledSyncStatusCallbacks.values()).map(
18256
- (cb) => cb()
18375
+ async (cb) => {
18376
+ try {
18377
+ await cb();
18378
+ } catch (error) {
18379
+ console.log("Error executing a scheduled sync status callback.", error);
18380
+ }
18381
+ }
18257
18382
  );
18258
18383
  await Promise.all(promises);
18259
18384
  this.scheduledSyncStatusCallbacks.clear();
@@ -18287,13 +18412,23 @@ var OfflineDBSyncManager = class {
18287
18412
  await this.offlineDb.resetDB();
18288
18413
  } else {
18289
18414
  const result = await this.client.sync(cids, lastSyncedAtDate.toISOString());
18290
- const queryPromises = result.events.map(
18291
- (event) => this.offlineDb.handleEvent({ event, execute: false })
18292
- );
18293
- const queriesArray = await Promise.all(queryPromises);
18294
- const queries = queriesArray.flat();
18295
- if (queries.length) {
18296
- await this.offlineDb.executeSqlBatch(queries);
18415
+ const { syncMaxEventCount } = this;
18416
+ const exceedsLimit = typeof syncMaxEventCount === "number" && syncMaxEventCount > 0 && result.events.length > syncMaxEventCount;
18417
+ if (exceedsLimit) {
18418
+ this.client.logger(
18419
+ "warn",
18420
+ `Skipping sync event replay: received ${result.events.length} events, which exceeds the configured limit of ${syncMaxEventCount}. Visible channels are refreshed on reconnect instead.`,
18421
+ { tags: ["sync", "offline"] }
18422
+ );
18423
+ } else {
18424
+ const queryPromises = result.events.map(
18425
+ (event) => this.offlineDb.handleEvent({ event, execute: false })
18426
+ );
18427
+ const queriesArray = await Promise.all(queryPromises);
18428
+ const queries = queriesArray.flat();
18429
+ if (queries.length) {
18430
+ await this.offlineDb.executeSqlBatch(queries);
18431
+ }
18297
18432
  }
18298
18433
  }
18299
18434
  }
@@ -18315,19 +18450,38 @@ var OfflineDBSyncManager = class {
18315
18450
  };
18316
18451
  /**
18317
18452
  * Executes any tasks that were queued while offline and then performs a sync.
18453
+ *
18454
+ * Each step is isolated so a failure in one does not prevent the other, and
18455
+ * neither can escape to the callers (init + the connection.changed handler).
18456
+ * This guarantees the subsequent invokeSyncStatusListeners(true) always runs,
18457
+ * so syncStatus recovers to true and gated channel queries are unblocked.
18458
+ * Failed syncs degrade to "possibly stale data until the next query" rather
18459
+ * than freezing all future queries. See issue #1816.
18318
18460
  */
18319
18461
  this.syncAndExecutePendingTasks = async () => {
18320
- await this.offlineDb.executePendingTasks();
18321
- await this.sync();
18462
+ try {
18463
+ await this.offlineDb.executePendingTasks();
18464
+ } catch (error) {
18465
+ console.log("Error executing pending tasks during sync.", error);
18466
+ }
18467
+ try {
18468
+ await this.sync();
18469
+ } catch (error) {
18470
+ console.log("Error while syncing the DB.", error);
18471
+ }
18322
18472
  };
18323
18473
  this.client = client;
18324
18474
  this.offlineDb = offlineDb;
18475
+ this.syncMaxEventCount = syncMaxEventCount;
18325
18476
  }
18326
18477
  };
18327
18478
 
18328
18479
  // src/offline-support/offline_support_api.ts
18329
18480
  var AbstractOfflineDB = class {
18330
- constructor({ client }) {
18481
+ constructor({
18482
+ client,
18483
+ syncMaxEventCount
18484
+ }) {
18331
18485
  /**
18332
18486
  * Initializes the DB as well as its syncManager for a given userId.
18333
18487
  * It will update the DBs reactive state with initialization values.
@@ -19017,7 +19171,11 @@ var AbstractOfflineDB = class {
19017
19171
  }
19018
19172
  };
19019
19173
  this.client = client;
19020
- this.syncManager = new OfflineDBSyncManager({ client, offlineDb: this });
19174
+ this.syncManager = new OfflineDBSyncManager({
19175
+ client,
19176
+ offlineDb: this,
19177
+ syncMaxEventCount
19178
+ });
19021
19179
  this.state = new StateStore({
19022
19180
  initialized: false,
19023
19181
  userId: this.client.userID