stream-chat 9.15.0 → 9.17.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.
@@ -175,7 +175,7 @@ export declare class ChannelState {
175
175
  * @param {UserResponse} user
176
176
  * @param {boolean} hardDelete
177
177
  */
178
- deleteUserMessages: (user: UserResponse, hardDelete?: boolean) => void;
178
+ deleteUserMessages: (user: UserResponse, hardDelete?: boolean, deletedAt?: LocalMessage["deleted_at"]) => void;
179
179
  /**
180
180
  * filterErrorMessages - Removes error messages from the channel state.
181
181
  *
@@ -432,7 +432,7 @@ export declare class StreamChat {
432
432
  * @param {UserResponse} user
433
433
  * @param {boolean} hardDelete
434
434
  */
435
- _deleteUserMessageReference: (user: UserResponse, hardDelete?: boolean) => void;
435
+ _deleteUserMessageReference: (user: UserResponse, hardDelete?: boolean, deletedAt?: LocalMessage["deleted_at"]) => void;
436
436
  /**
437
437
  * @private
438
438
  *
@@ -45,6 +45,7 @@ export declare const EVENT_MAP: {
45
45
  'typing.stop': boolean;
46
46
  'user.banned': boolean;
47
47
  'user.deleted': boolean;
48
+ 'user.messages.deleted': boolean;
48
49
  'user.presence.changed': boolean;
49
50
  'user.unbanned': boolean;
50
51
  'user.unread_message_reminder': boolean;
@@ -0,0 +1,41 @@
1
+ import { StateStore } from '../store';
2
+ import type { ArrayOneOrMore, ArrayTwoOrMore, QueryFilter } from '../types';
3
+ type ElementType<T> = T extends (infer U)[] ? U : T;
4
+ export type ExtendedQueryFilter<T = string> = QueryFilter<T> & {
5
+ $autocomplete?: T extends string ? string : never;
6
+ $contains?: ElementType<T>;
7
+ $in?: ElementType<T>[];
8
+ $q?: T extends string ? string : never;
9
+ };
10
+ export type ExtendedQueryLogicalOperators<T> = {
11
+ $and?: ArrayOneOrMore<ExtendedQueryFilters<T>>;
12
+ $nor?: ArrayOneOrMore<ExtendedQueryFilters<T>>;
13
+ $or?: ArrayTwoOrMore<ExtendedQueryFilters<T>>;
14
+ };
15
+ export type ExtendedQueryFilters<T> = {
16
+ [K in keyof T]?: ExtendedQueryFilter<T[K]>;
17
+ } & ExtendedQueryLogicalOperators<T>;
18
+ export type FilterBuilderGenerators<TFilters, TContext extends Record<string, unknown> = {}> = {
19
+ [K in string]: {
20
+ enabled: boolean;
21
+ generate: (context: TContext) => Partial<TFilters> | null;
22
+ };
23
+ };
24
+ export type FilterBuilderOptions<TFilters, TContext extends Record<string, unknown>> = {
25
+ initialFilterConfig?: FilterBuilderGenerators<TFilters, TContext>;
26
+ initialContext?: TContext;
27
+ };
28
+ export declare class FilterBuilder<TFilters, TContext extends Record<string, unknown> = {}> {
29
+ filterConfig: StateStore<FilterBuilderGenerators<TFilters, TContext>>;
30
+ context: StateStore<TContext>;
31
+ constructor(params?: FilterBuilderOptions<TFilters, TContext>);
32
+ updateFilterConfig(config: Partial<FilterBuilderGenerators<TFilters, TContext>>): void;
33
+ enableFilter(filterKey: keyof FilterBuilderGenerators<TFilters, TContext>): void;
34
+ disableFilter(filterKey: keyof FilterBuilderGenerators<TFilters, TContext>): void;
35
+ updateContext(newContext: Partial<TContext>): void;
36
+ buildFilters(params?: {
37
+ context?: Partial<TContext>;
38
+ baseFilters?: Partial<TFilters>;
39
+ }): Partial<TFilters>;
40
+ }
41
+ export {};
@@ -1,2 +1,3 @@
1
1
  export * from './BasePaginator';
2
+ export * from './FilterBuilder';
2
3
  export * from './ReminderPaginator';
@@ -35,7 +35,7 @@ export interface SearchSourceSync<T = any> extends ISearchSource<T> {
35
35
  }
36
36
  declare abstract class BaseSearchSourceBase<T> implements ISearchSource<T> {
37
37
  state: StateStore<SearchSourceState<T>>;
38
- protected pageSize: number;
38
+ pageSize: number;
39
39
  abstract readonly type: SearchSourceType;
40
40
  protected constructor(options?: SearchSourceOptions);
41
41
  get lastQueryError(): Error | undefined;
@@ -1,17 +1,25 @@
1
1
  import { BaseSearchSource } from './BaseSearchSource';
2
+ import type { FilterBuilderOptions } from '../pagination';
3
+ import { FilterBuilder } from '../pagination';
2
4
  import type { Channel } from '../channel';
3
5
  import type { StreamChat } from '../client';
4
6
  import type { ChannelFilters, ChannelOptions, ChannelSort } from '../types';
5
7
  import type { SearchSourceOptions } from './types';
6
- export declare class ChannelSearchSource extends BaseSearchSource<Channel> {
8
+ type CustomContext = Record<string, unknown>;
9
+ export type ChannelSearchSourceFilterBuilderContext<C extends CustomContext = CustomContext> = {
10
+ searchQuery?: string;
11
+ } & C;
12
+ export declare class ChannelSearchSource<TFilterContext extends CustomContext = CustomContext> extends BaseSearchSource<Channel> {
7
13
  readonly type = "channels";
8
- private client;
14
+ client: StreamChat;
9
15
  filters: ChannelFilters | undefined;
10
16
  sort: ChannelSort | undefined;
11
17
  searchOptions: Omit<ChannelOptions, 'limit' | 'offset'> | undefined;
12
- constructor(client: StreamChat, options?: SearchSourceOptions);
18
+ filterBuilder: FilterBuilder<ChannelFilters, ChannelSearchSourceFilterBuilderContext<TFilterContext>>;
19
+ constructor(client: StreamChat, options?: SearchSourceOptions, filterBuilderOptions?: FilterBuilderOptions<ChannelFilters, ChannelSearchSourceFilterBuilderContext<TFilterContext>>);
13
20
  protected query(searchQuery: string): Promise<{
14
21
  items: Channel[];
15
22
  }>;
16
23
  protected filterQueryResults(items: Channel[]): Channel[];
17
24
  }
25
+ export {};
@@ -2,7 +2,31 @@ import { BaseSearchSource } from './BaseSearchSource';
2
2
  import type { ChannelFilters, ChannelOptions, ChannelSort, MessageFilters, MessageResponse, SearchMessageSort } from '../types';
3
3
  import type { StreamChat } from '../client';
4
4
  import type { SearchSourceOptions } from './types';
5
- export declare class MessageSearchSource extends BaseSearchSource<MessageResponse> {
5
+ import { FilterBuilder, type FilterBuilderOptions } from '../pagination';
6
+ type CustomContext = Record<string, unknown>;
7
+ type BuiltInContexts = {
8
+ messageSearchChannel: {
9
+ searchQuery?: string;
10
+ };
11
+ messageSearch: {
12
+ searchQuery?: string;
13
+ };
14
+ channelQuery: {
15
+ cids?: string[];
16
+ };
17
+ };
18
+ type MergeContext<B extends Record<string, unknown>, C extends CustomContext | undefined> = B & (C extends object ? C : {});
19
+ type MessageSearchSourceContexts = Partial<{
20
+ messageSearchChannelContext: Record<string, unknown>;
21
+ messageSearchContext: Record<string, unknown>;
22
+ channelQueryContext: Record<string, unknown>;
23
+ }>;
24
+ export type MessageSearchSourceFilterBuilderOptions<TContexts extends MessageSearchSourceContexts = {}> = Partial<{
25
+ messageSearchChannel: FilterBuilderOptions<ChannelFilters, MergeContext<BuiltInContexts['messageSearchChannel'], TContexts['messageSearchChannelContext']>>;
26
+ messageSearch: FilterBuilderOptions<MessageFilters, MergeContext<BuiltInContexts['messageSearch'], TContexts['messageSearchContext']>>;
27
+ channelQuery: FilterBuilderOptions<ChannelFilters, MergeContext<BuiltInContexts['channelQuery'], TContexts['channelQueryContext']>>;
28
+ }>;
29
+ export declare class MessageSearchSource<TContexts extends MessageSearchSourceContexts = {}> extends BaseSearchSource<MessageResponse> {
6
30
  readonly type = "messages";
7
31
  private client;
8
32
  messageSearchChannelFilters: ChannelFilters | undefined;
@@ -11,7 +35,10 @@ export declare class MessageSearchSource extends BaseSearchSource<MessageRespons
11
35
  channelQueryFilters: ChannelFilters | undefined;
12
36
  channelQuerySort: ChannelSort | undefined;
13
37
  channelQueryOptions: Omit<ChannelOptions, 'limit' | 'offset'> | undefined;
14
- constructor(client: StreamChat, options?: SearchSourceOptions);
38
+ messageSearchChannelFilterBuilder: FilterBuilder<ChannelFilters, MergeContext<BuiltInContexts['messageSearchChannel'], TContexts['messageSearchChannelContext']>>;
39
+ messageSearchFilterBuilder: FilterBuilder<MessageFilters, MergeContext<BuiltInContexts['messageSearch'], TContexts['messageSearchContext']>>;
40
+ channelQueryFilterBuilder: FilterBuilder<ChannelFilters, MergeContext<BuiltInContexts['channelQuery'], TContexts['channelQueryContext']>>;
41
+ constructor(client: StreamChat, options?: SearchSourceOptions, filterBuilderOptions?: MessageSearchSourceFilterBuilderOptions<TContexts>);
15
42
  protected query(searchQuery: string): Promise<{
16
43
  items: never[];
17
44
  next?: undefined;
@@ -21,3 +48,4 @@ export declare class MessageSearchSource extends BaseSearchSource<MessageRespons
21
48
  }>;
22
49
  protected filterQueryResults(items: MessageResponse[]): MessageResponse[];
23
50
  }
51
+ export {};
@@ -1,16 +1,23 @@
1
1
  import { BaseSearchSource } from './BaseSearchSource';
2
+ import { FilterBuilder, type FilterBuilderOptions } from '../pagination';
2
3
  import type { StreamChat } from '../client';
3
4
  import type { UserFilters, UserOptions, UserResponse, UserSort } from '../types';
4
5
  import type { SearchSourceOptions } from './types';
5
- export declare class UserSearchSource extends BaseSearchSource<UserResponse> {
6
+ type CustomContext = Record<string, unknown>;
7
+ export type UserSearchSourceFilterBuilderContext<C extends CustomContext = CustomContext> = {
8
+ searchQuery?: string;
9
+ } & C;
10
+ export declare class UserSearchSource<TFilterContext extends CustomContext = CustomContext> extends BaseSearchSource<UserResponse> {
6
11
  readonly type = "users";
7
- private client;
12
+ client: StreamChat;
8
13
  filters: UserFilters | undefined;
9
14
  sort: UserSort | undefined;
10
15
  searchOptions: Omit<UserOptions, 'limit' | 'offset'> | undefined;
11
- constructor(client: StreamChat, options?: SearchSourceOptions);
16
+ filterBuilder: FilterBuilder<UserFilters, UserSearchSourceFilterBuilderContext<TFilterContext>>;
17
+ constructor(client: StreamChat, options?: SearchSourceOptions, filterBuilderOptions?: FilterBuilderOptions<UserFilters, UserSearchSourceFilterBuilderContext<TFilterContext>>);
12
18
  protected query(searchQuery: string): Promise<{
13
19
  items: UserResponse[];
14
20
  }>;
15
21
  protected filterQueryResults(items: UserResponse[]): UserResponse[];
16
22
  }
23
+ export {};
@@ -1,6 +1,6 @@
1
1
  export * from './BaseSearchSource';
2
2
  export * from './SearchController';
3
- export { UserSearchSource } from './UserSearchSource';
4
- export { ChannelSearchSource } from './ChannelSearchSource';
5
- export { MessageSearchSource } from './MessageSearchSource';
3
+ export * from './UserSearchSource';
4
+ export * from './ChannelSearchSource';
5
+ export * from './MessageSearchSource';
6
6
  export * from './types';
@@ -236,6 +236,7 @@ export type ChannelResponse = CustomChannelData & {
236
236
  last_message_at?: string;
237
237
  member_count?: number;
238
238
  members?: ChannelMemberResponse[];
239
+ message_count?: number;
239
240
  muted?: boolean;
240
241
  mute_expires_at?: string;
241
242
  own_capabilities?: string[];
@@ -1250,6 +1251,8 @@ export type Event = CustomEventData & {
1250
1251
  ai_message?: string;
1251
1252
  ai_state?: AIState;
1252
1253
  channel?: ChannelResponse;
1254
+ channel_custom?: CustomChannelData;
1255
+ channel_member_count?: number;
1253
1256
  channel_id?: string;
1254
1257
  channel_type?: string;
1255
1258
  cid?: string;
@@ -1837,6 +1840,7 @@ export type ChannelConfigFields = {
1837
1840
  replies?: boolean;
1838
1841
  search?: boolean;
1839
1842
  shared_locations?: boolean;
1843
+ count_messages?: boolean;
1840
1844
  typing_events?: boolean;
1841
1845
  uploads?: boolean;
1842
1846
  url_enrichment?: boolean;
@@ -1,5 +1,5 @@
1
1
  import FormData from 'form-data';
2
- import type { AscDesc, ChannelFilters, ChannelQueryOptions, ChannelSort, ChannelSortBase, LocalMessage, Logger, Message, MessagePaginationOptions, MessageResponse, MessageResponseBase, MessageSet, OwnUserResponse, PromoteChannelParams, UpdatedMessage, UserResponse } from './types';
2
+ import type { AscDesc, ChannelFilters, ChannelQueryOptions, ChannelSort, ChannelSortBase, LocalMessage, LocalMessageBase, Logger, Message, MessagePaginationOptions, MessageResponse, MessageResponseBase, MessageSet, OwnUserResponse, PromoteChannelParams, ReactionGroupResponse, UpdatedMessage, UserResponse } from './types';
3
3
  import type { StreamChat } from './client';
4
4
  import type { Channel } from './channel';
5
5
  import type { AxiosRequestConfig } from 'axios';
@@ -63,6 +63,91 @@ export declare function formatMessage(message: MessageResponse | MessageResponse
63
63
  export declare function unformatMessage(message: LocalMessage): MessageResponse;
64
64
  export declare const localMessageToNewMessagePayload: (localMessage: LocalMessage) => Message;
65
65
  export declare const toUpdatedMessagePayload: (message: LocalMessage | Partial<MessageResponse>) => UpdatedMessage;
66
+ export declare const toDeletedMessage: ({ message, deletedAt, hardDelete, }: {
67
+ message: LocalMessage | LocalMessageBase;
68
+ deletedAt: LocalMessage["deleted_at"];
69
+ hardDelete: boolean;
70
+ }) => {
71
+ attachments: never[];
72
+ cid: string | undefined;
73
+ created_at: Date;
74
+ deleted_at: Date | null;
75
+ id: string;
76
+ latest_reactions: never[];
77
+ mentioned_users: never[];
78
+ own_reactions: never[];
79
+ parent_id: string | undefined;
80
+ reply_count: number | undefined;
81
+ status: string;
82
+ thread_participants: UserResponse[] | undefined;
83
+ type: "deleted";
84
+ updated_at: Date;
85
+ user: UserResponse | null | undefined;
86
+ } | {
87
+ attachments: never[];
88
+ type: string;
89
+ deleted_at: Date | null;
90
+ id: string;
91
+ html?: string | undefined;
92
+ mml?: string | undefined;
93
+ parent_id?: string | undefined;
94
+ pin_expires?: string | null | undefined;
95
+ pinned?: boolean | undefined;
96
+ poll_id?: string | undefined;
97
+ quoted_message_id?: string | undefined;
98
+ restricted_visibility?: string[] | undefined;
99
+ show_in_channel?: boolean | undefined;
100
+ silent?: boolean | undefined;
101
+ text?: string | undefined;
102
+ user?: (UserResponse | null) | undefined;
103
+ user_id?: string | undefined;
104
+ args?: string | undefined;
105
+ before_message_send_failed?: boolean | undefined;
106
+ channel?: import("./types").ChannelResponse | undefined;
107
+ cid?: string | undefined;
108
+ command?: string | undefined;
109
+ command_info?: {
110
+ name?: string;
111
+ } | undefined;
112
+ deleted_reply_count?: number | undefined;
113
+ i18n?: (import("./types").RequireAtLeastOne<Record<`${import("./types").TranslationLanguages}_text`, string>> & {
114
+ language: import("./types").TranslationLanguages;
115
+ }) | undefined;
116
+ latest_reactions?: import("./types").ReactionResponse[] | undefined;
117
+ mentioned_users?: UserResponse[] | undefined;
118
+ message_text_updated_at?: string | undefined;
119
+ moderation?: import("./types").ModerationResponse | undefined;
120
+ moderation_details?: import("./types").ModerationDetailsResponse | undefined;
121
+ own_reactions?: import("./types").ReactionResponse[] | null | undefined;
122
+ pinned_by?: (UserResponse | null) | undefined;
123
+ poll?: import("./types").PollResponse | undefined;
124
+ reaction_counts?: {
125
+ [key: string]: number;
126
+ } | null | undefined;
127
+ reaction_groups?: {
128
+ [key: string]: ReactionGroupResponse;
129
+ } | null | undefined;
130
+ reaction_scores?: {
131
+ [key: string]: number;
132
+ } | null | undefined;
133
+ reminder?: import("./types").ReminderResponseBase | undefined;
134
+ reply_count?: number | undefined;
135
+ shadowed?: boolean | undefined;
136
+ shared_location?: import("./types").SharedLocationResponse | undefined;
137
+ thread_participants?: UserResponse[] | undefined;
138
+ created_at: Date;
139
+ pinned_at: Date | null;
140
+ status: string;
141
+ updated_at: Date;
142
+ error?: import("./types").ErrorFromResponse<import("./types").APIErrorResponse>;
143
+ quoted_message?: LocalMessageBase;
144
+ };
145
+ export declare const deleteUserMessages: ({ messages, user, hardDelete, deletedAt, }: {
146
+ messages: Array<LocalMessage>;
147
+ user: UserResponse;
148
+ hardDelete: boolean;
149
+ deletedAt: LocalMessage["deleted_at"];
150
+ }) => void;
66
151
  export declare const findIndexInSortedArray: <T, L>({ needle, sortedArray, selectKey, selectValueToCompare, sortDirection, }: {
67
152
  needle: T;
68
153
  sortedArray: readonly T[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stream-chat",
3
- "version": "9.15.0",
3
+ "version": "9.17.0",
4
4
  "description": "JS SDK for the Stream Chat API",
5
5
  "homepage": "https://getstream.io/chat/",
6
6
  "author": {
@@ -56,7 +56,7 @@
56
56
  "form-data": "^4.0.4",
57
57
  "isomorphic-ws": "^5.0.0",
58
58
  "jsonwebtoken": "^9.0.2",
59
- "linkifyjs": "^4.2.0",
59
+ "linkifyjs": "^4.3.2",
60
60
  "ws": "^8.18.1"
61
61
  },
62
62
  "devDependencies": {
package/src/channel.ts CHANGED
@@ -1903,6 +1903,15 @@ export class Channel {
1903
1903
  }
1904
1904
  }
1905
1905
  break;
1906
+ case 'user.messages.deleted':
1907
+ if (event.user) {
1908
+ this.state.deleteUserMessages(
1909
+ event.user,
1910
+ !!event.hard_delete,
1911
+ new Date(event.created_at ?? Date.now()),
1912
+ );
1913
+ }
1914
+ break;
1906
1915
  case 'message.new':
1907
1916
  if (event.message) {
1908
1917
  /* if message belongs to current user, always assume timestamp is changed to filter it out and add again to avoid duplication */
@@ -11,7 +11,11 @@ import type {
11
11
  ReactionResponse,
12
12
  UserResponse,
13
13
  } from './types';
14
- import { addToMessageList, formatMessage } from './utils';
14
+ import {
15
+ deleteUserMessages as _deleteUserMessages,
16
+ addToMessageList,
17
+ formatMessage,
18
+ } from './utils';
15
19
  import { DEFAULT_MESSAGE_SET_PAGINATION } from './constants';
16
20
 
17
21
  type ChannelReadStatus = Record<
@@ -712,58 +716,30 @@ export class ChannelState {
712
716
  * @param {UserResponse} user
713
717
  * @param {boolean} hardDelete
714
718
  */
715
- deleteUserMessages = (user: UserResponse, hardDelete = false) => {
716
- const _deleteUserMessages = (
717
- messages: Array<ReturnType<ChannelState['formatMessage']>>,
718
- user: UserResponse,
719
- hardDelete = false,
720
- ) => {
721
- for (let i = 0; i < messages.length; i++) {
722
- const m = messages[i];
723
- if (m.user?.id !== user.id) {
724
- continue;
725
- }
726
-
727
- if (hardDelete) {
728
- /**
729
- * In case of hard delete, we need to strip down all text, html,
730
- * attachments and all the custom properties on message
731
- */
732
- messages[i] = {
733
- cid: m.cid,
734
- created_at: m.created_at,
735
- deleted_at: user.deleted_at,
736
- id: m.id,
737
- latest_reactions: [],
738
- mentioned_users: [],
739
- own_reactions: [],
740
- parent_id: m.parent_id,
741
- reply_count: m.reply_count,
742
- status: m.status,
743
- thread_participants: m.thread_participants,
744
- type: 'deleted',
745
- updated_at: m.updated_at,
746
- user: m.user,
747
- } as unknown as ReturnType<ChannelState['formatMessage']>;
748
- } else {
749
- messages[i] = {
750
- ...m,
751
- type: 'deleted',
752
- deleted_at: user.deleted_at ? new Date(user.deleted_at) : null,
753
- };
754
- }
755
- }
756
- };
757
-
758
- this.messageSets.forEach((set) =>
759
- _deleteUserMessages(set.messages, user, hardDelete),
719
+ deleteUserMessages = (
720
+ user: UserResponse,
721
+ hardDelete = false,
722
+ deletedAt?: LocalMessage['deleted_at'],
723
+ ) => {
724
+ this.messageSets.forEach(({ messages }) =>
725
+ _deleteUserMessages({ messages, user, hardDelete, deletedAt: deletedAt ?? null }),
760
726
  );
761
727
 
762
728
  for (const parentId in this.threads) {
763
- _deleteUserMessages(this.threads[parentId], user, hardDelete);
729
+ _deleteUserMessages({
730
+ messages: this.threads[parentId],
731
+ user,
732
+ hardDelete,
733
+ deletedAt: deletedAt ?? null,
734
+ });
764
735
  }
765
736
 
766
- _deleteUserMessages(this.pinnedMessages, user, hardDelete);
737
+ _deleteUserMessages({
738
+ messages: this.pinnedMessages,
739
+ user,
740
+ hardDelete,
741
+ deletedAt: deletedAt ?? null,
742
+ });
767
743
  };
768
744
 
769
745
  /**
package/src/client.ts CHANGED
@@ -1404,7 +1404,11 @@ export class StreamChat {
1404
1404
  * @param {UserResponse} user
1405
1405
  * @param {boolean} hardDelete
1406
1406
  */
1407
- _deleteUserMessageReference = (user: UserResponse, hardDelete = false) => {
1407
+ _deleteUserMessageReference = (
1408
+ user: UserResponse,
1409
+ hardDelete = false,
1410
+ deletedAt?: LocalMessage['deleted_at'],
1411
+ ) => {
1408
1412
  const refMap = this.state.userChannelReferences[user.id] || {};
1409
1413
 
1410
1414
  for (const channelID in refMap) {
@@ -1413,7 +1417,7 @@ export class StreamChat {
1413
1417
  const state = channel.state;
1414
1418
 
1415
1419
  /** deleted the messages from this user. */
1416
- state?.deleteUserMessages(user, hardDelete);
1420
+ state?.deleteUserMessages(user, hardDelete, deletedAt);
1417
1421
  }
1418
1422
  }
1419
1423
  };
@@ -1478,7 +1482,11 @@ export class StreamChat {
1478
1482
  event.user.deleted_at &&
1479
1483
  (event.mark_messages_deleted || event.hard_delete)
1480
1484
  ) {
1481
- this._deleteUserMessageReference(event.user, event.hard_delete);
1485
+ this._deleteUserMessageReference(
1486
+ event.user,
1487
+ event.hard_delete,
1488
+ event.user.deleted_at ? new Date(event.user.deleted_at) : null,
1489
+ );
1482
1490
  }
1483
1491
  };
1484
1492
 
@@ -1503,6 +1511,14 @@ export class StreamChat {
1503
1511
  this._handleUserEvent(event);
1504
1512
  }
1505
1513
 
1514
+ if (event.type === 'user.messages.deleted' && !event.cid && event.user) {
1515
+ this._deleteUserMessageReference(
1516
+ event.user,
1517
+ event.hard_delete,
1518
+ event.created_at ? new Date(event.created_at) : null,
1519
+ );
1520
+ }
1521
+
1506
1522
  if (event.type === 'health.check' && event.me) {
1507
1523
  client.user = event.me;
1508
1524
  client.state.updateUser(event.me);
package/src/events.ts CHANGED
@@ -45,6 +45,7 @@ export const EVENT_MAP = {
45
45
  'typing.stop': true,
46
46
  'user.banned': true,
47
47
  'user.deleted': true,
48
+ 'user.messages.deleted': true,
48
49
  'user.presence.changed': true,
49
50
  'user.unbanned': true,
50
51
  'user.unread_message_reminder': true,
@@ -0,0 +1,104 @@
1
+ import { StateStore } from '../store';
2
+ import type { ArrayOneOrMore, ArrayTwoOrMore, QueryFilter } from '../types';
3
+
4
+ type ElementType<T> = T extends (infer U)[] ? U : T;
5
+
6
+ // redeclared because QueryFilter does not account for the additional operators
7
+ export type ExtendedQueryFilter<T = string> = QueryFilter<T> & {
8
+ $autocomplete?: T extends string ? string : never;
9
+ $contains?: ElementType<T>;
10
+ $in?: ElementType<T>[];
11
+ $q?: T extends string ? string : never;
12
+ };
13
+
14
+ export type ExtendedQueryLogicalOperators<T> = {
15
+ $and?: ArrayOneOrMore<ExtendedQueryFilters<T>>;
16
+ $nor?: ArrayOneOrMore<ExtendedQueryFilters<T>>;
17
+ $or?: ArrayTwoOrMore<ExtendedQueryFilters<T>>;
18
+ };
19
+
20
+ export type ExtendedQueryFilters<T> = {
21
+ [K in keyof T]?: ExtendedQueryFilter<T[K]>;
22
+ } & ExtendedQueryLogicalOperators<T>;
23
+
24
+ export type FilterBuilderGenerators<
25
+ TFilters,
26
+ TContext extends Record<string, unknown> = {},
27
+ > = {
28
+ [K in string]: {
29
+ enabled: boolean;
30
+ generate: (context: TContext) => Partial<TFilters> | null;
31
+ };
32
+ };
33
+
34
+ export type FilterBuilderOptions<TFilters, TContext extends Record<string, unknown>> = {
35
+ initialFilterConfig?: FilterBuilderGenerators<TFilters, TContext>;
36
+ initialContext?: TContext;
37
+ };
38
+
39
+ export class FilterBuilder<TFilters, TContext extends Record<string, unknown> = {}> {
40
+ filterConfig: StateStore<FilterBuilderGenerators<TFilters, TContext>>;
41
+ context: StateStore<TContext>;
42
+
43
+ constructor(params?: FilterBuilderOptions<TFilters, TContext>) {
44
+ this.context = new StateStore(params?.initialContext ?? ({} as TContext));
45
+ this.filterConfig = new StateStore(
46
+ params?.initialFilterConfig ?? ({} as FilterBuilderGenerators<TFilters, TContext>),
47
+ );
48
+ }
49
+
50
+ updateFilterConfig(config: Partial<FilterBuilderGenerators<TFilters, TContext>>) {
51
+ this.filterConfig.partialNext(config);
52
+ }
53
+
54
+ enableFilter(filterKey: keyof FilterBuilderGenerators<TFilters, TContext>) {
55
+ const config = this.filterConfig.getLatestValue();
56
+ if (config[filterKey]) {
57
+ this.filterConfig.partialNext({
58
+ [filterKey]: {
59
+ ...config[filterKey],
60
+ enabled: true,
61
+ },
62
+ });
63
+ }
64
+ }
65
+
66
+ disableFilter(filterKey: keyof FilterBuilderGenerators<TFilters, TContext>) {
67
+ const config = this.filterConfig.getLatestValue();
68
+ if (config[filterKey]) {
69
+ this.filterConfig.partialNext({
70
+ [filterKey]: {
71
+ ...config[filterKey],
72
+ enabled: false,
73
+ },
74
+ });
75
+ }
76
+ }
77
+
78
+ updateContext(newContext: Partial<TContext>) {
79
+ this.context.partialNext(newContext);
80
+ }
81
+
82
+ buildFilters(params?: {
83
+ context?: Partial<TContext>;
84
+ baseFilters?: Partial<TFilters>;
85
+ }): Partial<TFilters> {
86
+ const filters: Partial<TFilters> = {
87
+ ...(params?.baseFilters ?? {}),
88
+ } as Partial<TFilters>;
89
+
90
+ const filterConfig = this.filterConfig.getLatestValue();
91
+ for (const key in filterConfig) {
92
+ const configItem = filterConfig[key];
93
+ if (!configItem?.enabled) continue;
94
+
95
+ const generated = configItem.generate({
96
+ ...this.context.getLatestValue(),
97
+ ...(params?.context ?? {}),
98
+ });
99
+ if (generated) Object.assign(filters, generated);
100
+ }
101
+
102
+ return filters;
103
+ }
104
+ }
@@ -1,2 +1,3 @@
1
1
  export * from './BasePaginator';
2
+ export * from './FilterBuilder';
2
3
  export * from './ReminderPaginator';
@@ -59,7 +59,7 @@ const DEFAULT_SEARCH_SOURCE_OPTIONS: Required<SearchSourceOptions> = {
59
59
 
60
60
  abstract class BaseSearchSourceBase<T> implements ISearchSource<T> {
61
61
  state: StateStore<SearchSourceState<T>>;
62
- protected pageSize: number;
62
+ pageSize: number;
63
63
  abstract readonly type: SearchSourceType;
64
64
 
65
65
  protected constructor(options?: SearchSourceOptions) {