@messenger-box/platform-client 10.0.3-alpha.165 → 10.0.3-alpha.171

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 (83) hide show
  1. package/lib/graphql/mutations/messages-mutation.gql +0 -26
  2. package/package.json +3 -3
  3. package/CHANGELOG.md +0 -373
  4. package/jest.config.js +0 -25
  5. package/lib/graphql/subscription/sandboxError.gql +0 -17
  6. package/rollup.config.mjs +0 -36
  7. package/src/graphql/fragments/configuration.gql +0 -12
  8. package/src/graphql/fragments/extended-user-account.gql +0 -10
  9. package/src/graphql/fragments/file-info.gql +0 -12
  10. package/src/graphql/fragments/messenger-user.gql +0 -15
  11. package/src/graphql/fragments/minimal-user.gql +0 -9
  12. package/src/graphql/fragments/post-message.gql +0 -152
  13. package/src/graphql/fragments/post-thread-message.gql +0 -77
  14. package/src/graphql/fragments/user.gql +0 -11
  15. package/src/graphql/id-generation.ts +0 -8
  16. package/src/graphql/index.ts +0 -24
  17. package/src/graphql/mutations/channel-mutation.gql +0 -77
  18. package/src/graphql/mutations/expo-notification-token.gql +0 -13
  19. package/src/graphql/mutations/message-threads-mutation.gql +0 -44
  20. package/src/graphql/mutations/messages-mutation.gql +0 -113
  21. package/src/graphql/mutations/messages-mutation.gql.bk +0 -66
  22. package/src/graphql/mutations/organization-mutation.gql.bk +0 -14
  23. package/src/graphql/mutations/teams-mutation.gql.bk +0 -8
  24. package/src/graphql/policies/channel-policies.ts +0 -153
  25. package/src/graphql/policies/index.ts +0 -7
  26. package/src/graphql/policies/messages-policies.ts +0 -321
  27. package/src/graphql/policies/post-thread-policies.ts +0 -179
  28. package/src/graphql/policies/posts-policies.ts +0 -110
  29. package/src/graphql/policies/teams-policies.ts +0 -19
  30. package/src/graphql/policies/user-account-policies.ts +0 -15
  31. package/src/graphql/policies/user-policies.ts +0 -32
  32. package/src/graphql/queries/channel.gql +0 -57
  33. package/src/graphql/queries/channels-by-user.gql +0 -86
  34. package/src/graphql/queries/check-for-new-messages.gql +0 -5
  35. package/src/graphql/queries/file-url.gql +0 -5
  36. package/src/graphql/queries/get-device-token.gql +0 -8
  37. package/src/graphql/queries/messages.gql +0 -28
  38. package/src/graphql/queries/organization-query.gql +0 -125
  39. package/src/graphql/queries/post-message.gql +0 -8
  40. package/src/graphql/queries/post-thread-message.gql +0 -24
  41. package/src/graphql/queries/support-service-channels.gql +0 -18
  42. package/src/graphql/queries/teams-query.gql +0 -32
  43. package/src/graphql/queries/user-account.gql +0 -5
  44. package/src/graphql/queries/users.gql +0 -48
  45. package/src/graphql/schema/index.ts +0 -6
  46. package/src/graphql/schema/messages.graphql +0 -5
  47. package/src/graphql/schema/post.graphql +0 -44
  48. package/src/graphql/schema/services.graphql +0 -6
  49. package/src/graphql/schema/user-account.graphql +0 -3
  50. package/src/graphql/subscription/chat-message-added.gql +0 -6
  51. package/src/graphql/subscription/fileUpdated.gql +0 -11
  52. package/src/graphql/subscription/public-post-added.gql +0 -5
  53. package/src/graphql/subscription/thread-chat-message-added.gql +0 -5
  54. package/src/graphql/subscription/thread-created-updated.gql +0 -12
  55. package/src/hooks/index.ts +0 -4
  56. package/src/hooks/use-base-file-upload.hook.ts +0 -148
  57. package/src/hooks/use-upload-file.hook.native.ts +0 -22
  58. package/src/hooks/use-upload-file.hook.ts +0 -22
  59. package/src/hooks/use-upload-files.hook.native.ts +0 -22
  60. package/src/hooks/use-upload-files.hook.ts +0 -22
  61. package/src/index.ts +0 -3
  62. package/src/inversify-containers/index.ts +0 -1
  63. package/src/inversify-containers/module.ts +0 -4
  64. package/src/packages/constants/constants.ts +0 -1831
  65. package/src/packages/types/channels.ts +0 -194
  66. package/src/packages/types/emojis.ts +0 -41
  67. package/src/packages/types/files.ts +0 -43
  68. package/src/packages/types/posts.ts +0 -155
  69. package/src/packages/types/reactions.ts +0 -8
  70. package/src/packages/types/teams.ts +0 -109
  71. package/src/packages/types/utilities.ts +0 -30
  72. package/src/services/index.ts +0 -0
  73. package/src/tests/mutation/__snapshots__/set-device-token.test.ts.snap +0 -18
  74. package/src/tests/mutation/set-device-token.test.ts +0 -85
  75. package/src/utils/constants.tsx +0 -1120
  76. package/src/utils/i18n.tsx +0 -15
  77. package/src/utils/index.ts +0 -34
  78. package/src/utils/post_list.ts +0 -54
  79. package/src/utils/post_utils.ts +0 -33
  80. package/src/utils/user_agent.tsx +0 -153
  81. package/src/utils/utils.tsx +0 -54
  82. package/tsconfig.json +0 -14
  83. package/webpack.config.js +0 -58
@@ -1,153 +0,0 @@
1
- import { TypePolicies, gql } from '@apollo/client';
2
-
3
- export const channelPolicies: TypePolicies = {
4
- Query: {
5
- fields: {
6
- channelsByUser: {
7
- keyArgs: ['role', 'criteria', 'limit', 'skip', 'sort'],
8
- merge(existing = [], incoming = [], { readField }) {
9
- // If no existing data, just return incoming
10
- if (!existing || existing.length === 0) {
11
- return incoming;
12
- }
13
-
14
- // If no incoming data, keep existing
15
- if (!incoming || incoming.length === 0) {
16
- return existing;
17
- }
18
-
19
- // Create a map for efficient lookup
20
- const channelMap = new Map();
21
-
22
- // Add existing channels to map
23
- for (let i = 0; i < existing.length; i++) {
24
- const channel = existing[i];
25
- if (channel) {
26
- const id = readField('id', channel);
27
- if (id) {
28
- channelMap.set(id, channel);
29
- }
30
- }
31
- }
32
-
33
- // Merge in incoming channels, overwriting existing ones
34
- for (let i = 0; i < incoming.length; i++) {
35
- const channel = incoming[i];
36
- if (channel) {
37
- const id = readField('id', channel);
38
- if (id) {
39
- channelMap.set(id, channel);
40
- }
41
- }
42
- }
43
-
44
- // Convert map values back to array
45
- return Array.from(channelMap.values());
46
- },
47
- // Add a read function for more control over cache reads
48
- read(existing, { args }) {
49
- // Return undefined to force a network request if data doesn't exist
50
- if (!existing) return undefined;
51
- return existing;
52
- },
53
- },
54
- },
55
- },
56
- Channel: {
57
- keyFields: ['id'],
58
- fields: {
59
- members: {
60
- merge(existing = [], incoming) {
61
- return incoming;
62
- },
63
- },
64
- // Add field policies for other Channel fields if needed
65
- title: {
66
- read(title) {
67
- return title || '';
68
- },
69
- },
70
- displayName: {
71
- read(displayName) {
72
- return displayName || '';
73
- },
74
- },
75
- // Add computed fields if needed
76
- memberCount: {
77
- read(_, { readField }) {
78
- const members = readField('members') as any[];
79
- return members && Array.isArray(members) ? members.length : 0;
80
- },
81
- },
82
- lastMessage: {
83
- merge(existing, incoming, { readField }) {
84
- // If no incoming message, keep existing
85
- if (!incoming) return existing;
86
-
87
- // If no existing message, use incoming
88
- if (!existing) return incoming;
89
-
90
- // Compare timestamps to determine which is newer
91
- const existingCreatedAt = readField('createdAt', existing) as string | number | Date;
92
- const incomingCreatedAt = readField('createdAt', incoming) as string | number | Date;
93
-
94
- if (existingCreatedAt && incomingCreatedAt) {
95
- // Use the more recent message
96
- return new Date(incomingCreatedAt) > new Date(existingCreatedAt) ? incoming : existing;
97
- }
98
-
99
- // If timestamps are not available, prefer incoming
100
- return incoming;
101
- },
102
- read(lastMessage, { readField, cache, args }) {
103
- // If lastMessage is already available, return it
104
- if (lastMessage) return lastMessage;
105
-
106
- // Try to get the channel ID to reference messages cache
107
- const channelId = readField('id');
108
- if (!channelId) return null;
109
-
110
- try {
111
- // Read messages from cache for this channel
112
- const messagesQuery = cache.readQuery({
113
- query: gql`
114
- query GetChannelMessages($channelId: String!) {
115
- messages(channelId: $channelId, skip: 0, limit: 1) {
116
- data {
117
- id
118
- content
119
- createdAt
120
- updatedAt
121
- user {
122
- id
123
- username
124
- displayName
125
- }
126
- }
127
- }
128
- }
129
- `,
130
- variables: { channelId },
131
- }) as any;
132
-
133
- // Return the first (latest) message if available
134
- return messagesQuery?.messages?.data?.[0] || null;
135
- } catch (error) {
136
- // If messages query fails, return null
137
- return null;
138
- }
139
- },
140
- },
141
- },
142
- },
143
- // Add policies for ChannelMember type
144
- ChannelMember: {
145
- keyFields: ['id'],
146
- fields: {
147
- user: {
148
- // Ensure user references are properly merged
149
- merge: true,
150
- },
151
- },
152
- },
153
- };
@@ -1,7 +0,0 @@
1
- export * from './posts-policies';
2
- export * from './user-policies';
3
- export * from './channel-policies';
4
- export * from './user-account-policies';
5
- export * from './messages-policies';
6
- export * from './post-thread-policies';
7
- export * from './teams-policies';
@@ -1,321 +0,0 @@
1
- import { TypePolicies } from '@apollo/client';
2
-
3
- export const messagesPolicies: TypePolicies = {
4
- Messages: {
5
- // keyFields: ['messagesRefId'],
6
- //keyFields: [],
7
- // keyFields: ['data', ['channel', ['id']]],
8
- fields: {
9
- id: {
10
- read(existing, { variables }) {
11
- const { channelId, parentId }: any = variables;
12
- return parentId ? `${channelId}-${parentId}` : `${channelId}`;
13
- },
14
- },
15
- messagesRefId: {
16
- read(existing, { variables }) {
17
- const { channelId, parentId }: any = variables;
18
- return parentId ? `${channelId}-${parentId}` : `${channelId}`;
19
- },
20
- },
21
- data: {
22
- keyArgs: ['channelId', 'parentId'],
23
- merge: (existing = [], incoming = [], { readField }) => {
24
- // Use a Map for O(1) lookups instead of array iterations
25
- const existingMap = new Map();
26
-
27
- // Populate map with existing messages
28
- if (existing && existing.length > 0) {
29
- for (let i = 0; i < existing.length; i++) {
30
- const id = readField('id', existing[i]);
31
- if (id) {
32
- existingMap.set(id, existing[i]);
33
- }
34
- }
35
- }
36
-
37
- // Create result array with same capacity as total items
38
- const result = [];
39
-
40
- // Add incoming items, overwriting existing ones
41
- if (incoming && incoming.length > 0) {
42
- for (let i = 0; i < incoming.length; i++) {
43
- const item = incoming[i];
44
- const id = readField('id', item);
45
- if (id) {
46
- existingMap.set(id, item);
47
- }
48
- result.push(item);
49
- }
50
- }
51
-
52
- // Add remaining existing items not in incoming
53
- if (existing && existing.length > 0) {
54
- for (let i = 0; i < existing.length; i++) {
55
- const id = readField('id', existing[i]);
56
- if (id && !result.some((item) => readField('id', item) === id)) {
57
- result.push(existing[i]);
58
- }
59
- }
60
- }
61
-
62
- return result;
63
- },
64
- },
65
- totalCount: {
66
- keyArgs: ['channelId', 'parentId'],
67
- merge(existing, incoming) {
68
- return incoming !== undefined ? incoming : existing;
69
- },
70
- },
71
- // data: {
72
- // merge(existing: any[], incoming: any[], { args }) {
73
- // console.log('existing', JSON.stringify(existing), 'incoming', JSON.stringify(incoming));
74
- // const merged = existing ? existing.slice(0) : [];
75
- // // Insert the incoming elements in the right places, according to args.
76
- // const end = args?.skip + Math.min(args?.limit, incoming.length);
77
- // for (let i = args?.skip; i < end; ++i) {
78
- // merged[i] = incoming[i - args?.skip];
79
- // }
80
- // return merged;
81
- // },
82
- // },
83
- },
84
- },
85
- PostThreadMessages: {
86
- // keyFields: ['threadmessagesRefId'],
87
- fields: {
88
- threadmessagesRefId: {
89
- read(existing, { variables }) {
90
- const { channelId, parentId }: any = variables;
91
- return parentId ? `${channelId}-${parentId}` : `${channelId}`;
92
- },
93
- },
94
- id: {
95
- read(existing, { variables }) {
96
- const { channelId, parentId }: any = variables;
97
- return parentId ? `${channelId}-${parentId}` : `${channelId}`;
98
- },
99
- },
100
- },
101
- },
102
- Query: {
103
- fields: {
104
- messages: {
105
- keyArgs: ['channelId', 'parentId'],
106
- merge(existing, incoming, { args, readField }) {
107
- if (!incoming) return existing;
108
- if (!existing) return incoming;
109
-
110
- // Fast return if incoming has no data
111
- if (!incoming.data || incoming.data.length === 0) {
112
- return {
113
- ...existing,
114
- totalCount: incoming.totalCount ?? existing.totalCount,
115
- };
116
- }
117
-
118
- // Fast return if existing has no data
119
- if (!existing.data || existing.data.length === 0) {
120
- return incoming;
121
- }
122
-
123
- // Determine if this is likely a new message or pagination
124
- const isNewMessage = args && args.skip === 0;
125
-
126
- // Create a map for existing messages for fast lookups
127
- const idSet = new Set();
128
- const mergedData = [...existing.data];
129
-
130
- // Mark all existing IDs
131
- for (let i = 0; i < mergedData.length; i++) {
132
- const id = readField('id', mergedData[i]);
133
- if (id) idSet.add(id);
134
- }
135
-
136
- // Process incoming messages
137
- for (let i = 0; i < incoming.data.length; i++) {
138
- const incomingMsg = incoming.data[i];
139
- const id = readField('id', incomingMsg);
140
-
141
- if (!id) continue;
142
-
143
- if (idSet.has(id)) {
144
- // Replace existing message with same ID
145
- const existingIndex = mergedData.findIndex((msg) => readField('id', msg) === id);
146
- if (existingIndex >= 0) {
147
- mergedData[existingIndex] = incomingMsg;
148
- }
149
- } else {
150
- // Add new message
151
- if (isNewMessage) {
152
- // Add to beginning for new messages
153
- mergedData.unshift(incomingMsg);
154
- } else {
155
- // Add to end for pagination
156
- mergedData.push(incomingMsg);
157
- }
158
- idSet.add(id);
159
- }
160
- }
161
-
162
- return {
163
- ...existing,
164
- totalCount: incoming.totalCount ?? existing.totalCount,
165
- data: mergedData,
166
- };
167
- },
168
- },
169
- },
170
- },
171
- FilesInfo: {
172
- merge: true, // Use default merging behavior for FilesInfo
173
- fields: {
174
- data: {
175
- merge(existing = [], incoming = [], { readField }) {
176
- // If no existing data, just return incoming
177
- if (!existing || existing.length === 0) {
178
- return incoming;
179
- }
180
-
181
- // If no incoming data, keep existing
182
- if (!incoming || incoming.length === 0) {
183
- return existing;
184
- }
185
-
186
- // Create a map for efficient lookup
187
- const fileMap = new Map();
188
-
189
- // Add existing files to map
190
- for (let i = 0; i < existing.length; i++) {
191
- const file = existing[i];
192
- if (file) {
193
- const id = readField('id', file);
194
- if (id) {
195
- fileMap.set(id, file);
196
- }
197
- }
198
- }
199
-
200
- // Merge in incoming files, overwriting existing ones
201
- for (let i = 0; i < incoming.length; i++) {
202
- const file = incoming[i];
203
- if (file) {
204
- const id = readField('id', file);
205
- if (id) {
206
- fileMap.set(id, file);
207
- }
208
- }
209
- }
210
-
211
- // Convert map values back to array
212
- return Array.from(fileMap.values());
213
- },
214
- },
215
- },
216
- },
217
- FileInfo: {
218
- // Use ID as key field
219
- keyFields: ['id'],
220
- fields: {
221
- url: {
222
- // Ensure URL is always preserved and not normalized
223
- merge(existing, incoming) {
224
- return incoming ?? existing;
225
- },
226
- },
227
- },
228
- },
229
- Post: {
230
- fields: {
231
- replies: {
232
- merge(existing, incoming) {
233
- if (!incoming) return existing;
234
- if (!existing) return incoming;
235
-
236
- // Use a Set for fast duplicate checking
237
- const uniqueIds = new Set();
238
- const mergedData = [];
239
-
240
- // Add all existing items to the result and track IDs
241
- if (existing.data && existing.data.length > 0) {
242
- for (let i = 0; i < existing.data.length; i++) {
243
- const item = existing.data[i];
244
- if (item && item.id && !uniqueIds.has(item.id)) {
245
- uniqueIds.add(item.id);
246
- mergedData.push(item);
247
- }
248
- }
249
- }
250
-
251
- // Add incoming items that don't exist yet
252
- if (incoming.data && incoming.data.length > 0) {
253
- for (let i = 0; i < incoming.data.length; i++) {
254
- const item = incoming.data[i];
255
- if (item && item.id && !uniqueIds.has(item.id)) {
256
- uniqueIds.add(item.id);
257
- mergedData.push(item);
258
- }
259
- }
260
- }
261
-
262
- return {
263
- ...incoming,
264
- data: mergedData,
265
- };
266
- },
267
- },
268
- files: {
269
- merge(existing, incoming, { readField }) {
270
- if (!incoming) return existing;
271
- if (!existing) return incoming;
272
-
273
- // If either has no data or totalCount is 0, prefer the one with data
274
- if (!existing.data || existing.data.length === 0) {
275
- return incoming;
276
- }
277
-
278
- if (!incoming.data || incoming.data.length === 0) {
279
- return existing;
280
- }
281
-
282
- // Create a map for efficient lookup
283
- const fileMap = new Map();
284
-
285
- // Add existing files to map
286
- if (existing.data) {
287
- for (let i = 0; i < existing.data.length; i++) {
288
- const file = existing.data[i];
289
- if (file) {
290
- const id = readField('id', file);
291
- if (id) {
292
- fileMap.set(id, file);
293
- }
294
- }
295
- }
296
- }
297
-
298
- // Merge in incoming files, overwriting existing ones
299
- if (incoming.data) {
300
- for (let i = 0; i < incoming.data.length; i++) {
301
- const file = incoming.data[i];
302
- if (file) {
303
- const id = readField('id', file);
304
- if (id) {
305
- fileMap.set(id, file);
306
- }
307
- }
308
- }
309
- }
310
-
311
- // Create merged result
312
- return {
313
- __typename: 'FilesInfo',
314
- totalCount: Math.max(existing.totalCount || 0, incoming.totalCount || 0, fileMap.size),
315
- data: Array.from(fileMap.values()),
316
- };
317
- },
318
- },
319
- },
320
- },
321
- };
@@ -1,179 +0,0 @@
1
- import { TypePolicies } from '@apollo/client';
2
- import { gql } from '@apollo/client';
3
-
4
- // Define the fragment we'll use for cache operations
5
- const POST_THREAD_FRAGMENT = gql`
6
- fragment PostThreadInfo on PostThread {
7
- replies
8
- replyCount
9
- lastReplyAt
10
- updatedAt
11
- }
12
- `;
13
-
14
- export const postThreadPolicies: TypePolicies = {
15
- ThreadMessages: {
16
- keyFields: ['data', ['channel', ['id']]],
17
- fields: {
18
- data: {
19
- merge: (existing = [], incoming = [], { readField }) => {
20
- // Create a map for efficient lookups
21
- const threadMap = new Map();
22
-
23
- // Store existing threads
24
- if (existing && existing.length > 0) {
25
- for (const item of existing) {
26
- const id = readField('id', item);
27
- if (id) threadMap.set(id, item);
28
- }
29
- }
30
-
31
- // Add or update with incoming threads
32
- if (incoming && incoming.length > 0) {
33
- for (const item of incoming) {
34
- const id = readField('id', item);
35
- if (id) threadMap.set(id, item);
36
- }
37
- }
38
-
39
- // Convert back to array
40
- return Array.from(threadMap.values());
41
- },
42
- },
43
- totalCount: {
44
- merge(existing, incoming) {
45
- // Take the higher of the two counts
46
- return incoming !== undefined ? Math.max(existing || 0, incoming) : existing;
47
- },
48
- },
49
- },
50
- },
51
- Query: {
52
- fields: {
53
- threadMessages: {
54
- keyArgs: ['channelId'],
55
- merge(existing, incoming, { readField }) {
56
- if (!existing) return incoming;
57
- if (!incoming) return existing;
58
-
59
- return {
60
- ...incoming,
61
- data: [...(existing?.data || []), ...(incoming.data || [])].filter(
62
- (item, index, self) =>
63
- // Filter out duplicates
64
- index === self.findIndex((t) => readField('id', t) === readField('id', item)),
65
- ),
66
- };
67
- },
68
- },
69
- getPostThread: {
70
- keyArgs: ['channelId', 'postParentId', 'role'],
71
- merge(existing, incoming, { mergeObjects }) {
72
- if (!existing) return incoming;
73
- if (!incoming) return existing;
74
-
75
- // Carefully merge the two objects
76
- const result = mergeObjects(existing, incoming);
77
-
78
- // Special handling for replies to avoid duplicates
79
- if (existing.replies && incoming.replies) {
80
- const uniqueReplies = new Map();
81
-
82
- // Add existing replies
83
- for (const reply of existing.replies) {
84
- uniqueReplies.set(reply.id, reply);
85
- }
86
-
87
- // Add incoming replies, overwriting existing ones
88
- for (const reply of incoming.replies) {
89
- uniqueReplies.set(reply.id, reply);
90
- }
91
-
92
- // Replace replies with deduplicated list
93
- result.replies = Array.from(uniqueReplies.values());
94
- }
95
-
96
- return result;
97
- },
98
- },
99
- },
100
- },
101
- // Mutation: {
102
- // fields: {
103
- // createPostThread: {
104
- // merge(existing, incoming, { cache, args, readField }) {
105
- // // Early return if not enough data
106
- // if (!incoming?.lastMessage || !incoming?.data || !args?.channelId || !args?.postParentId) {
107
- // return incoming;
108
- // }
109
-
110
- // try {
111
- // // Use type policies to handle the cache update instead of direct manipulation
112
- // const queryRef = cache.identify({
113
- // __typename: 'Query',
114
- // getPostThread: {
115
- // channelId: args.channelId,
116
- // postParentId: args.postParentId,
117
- // role: args.threadMessageInput?.role
118
- // }
119
- // });
120
-
121
- // // Use cache.modify which doesn't require fragments
122
- // if (queryRef) {
123
- // cache.modify({
124
- // id: queryRef,
125
- // fields: {
126
- // getPostThread(existingThread = {}) {
127
- // if (!existingThread) return existingThread;
128
-
129
- // // Create a new object with the updated properties
130
- // return {
131
- // ...existingThread,
132
- // replies: [incoming.lastMessage, ...(existingThread.replies || [])],
133
- // replyCount: (existingThread.replyCount || 0) + 1,
134
- // lastReplyAt: incoming.lastMessage.createdAt,
135
- // updatedAt: incoming.lastMessage.createdAt
136
- // };
137
- // }
138
- // }
139
- // });
140
- // }
141
- // } catch (error) {
142
- // console.error('Error updating cache in createPostThread policy:', error);
143
- // }
144
-
145
- // return incoming;
146
- // },
147
- // },
148
- // },
149
- // },
150
- PostThread: {
151
- fields: {
152
- replies: {
153
- merge(existing = [], incoming = [], { readField }) {
154
- // Use a map for fast deduplication
155
- const replyMap = new Map();
156
-
157
- // Add existing replies
158
- if (existing && existing.length > 0) {
159
- for (const reply of existing) {
160
- const id = readField('id', reply);
161
- if (id) replyMap.set(id, reply);
162
- }
163
- }
164
-
165
- // Add or update with incoming replies
166
- if (incoming && incoming.length > 0) {
167
- for (const reply of incoming) {
168
- const id = readField('id', reply);
169
- if (id) replyMap.set(id, reply);
170
- }
171
- }
172
-
173
- // Convert back to array
174
- return Array.from(replyMap.values());
175
- },
176
- },
177
- },
178
- },
179
- };