@stream-io/feeds-client 0.2.15 → 0.2.17

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 (43) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/index.js +1 -1
  3. package/dist/cjs/react-bindings.js +1 -1
  4. package/dist/es/index.mjs +2 -2
  5. package/dist/es/react-bindings.mjs +1 -1
  6. package/dist/{index-BSzSBlMh.mjs → index-BZL77zNq.mjs} +184 -27
  7. package/dist/index-BZL77zNq.mjs.map +1 -0
  8. package/dist/{index-DRX66SIx.js → index-nq6SDtbt.js} +184 -27
  9. package/dist/index-nq6SDtbt.js.map +1 -0
  10. package/dist/tsconfig.tsbuildinfo +1 -1
  11. package/dist/types/feed/event-handlers/activity/handle-activity-reaction-updated.d.ts +14 -0
  12. package/dist/types/feed/event-handlers/activity/handle-activity-reaction-updated.d.ts.map +1 -0
  13. package/dist/types/feed/event-handlers/activity/index.d.ts +1 -0
  14. package/dist/types/feed/event-handlers/activity/index.d.ts.map +1 -1
  15. package/dist/types/feed/event-handlers/comment/handle-comment-reaction-added.d.ts.map +1 -1
  16. package/dist/types/feed/event-handlers/comment/handle-comment-reaction-updated.d.ts +6 -0
  17. package/dist/types/feed/event-handlers/comment/handle-comment-reaction-updated.d.ts.map +1 -0
  18. package/dist/types/feed/event-handlers/comment/index.d.ts +1 -0
  19. package/dist/types/feed/event-handlers/comment/index.d.ts.map +1 -1
  20. package/dist/types/feed/feed.d.ts.map +1 -1
  21. package/dist/types/feeds-client/feeds-client.d.ts.map +1 -1
  22. package/dist/types/gen/feeds/FeedsApi.d.ts +4 -1
  23. package/dist/types/gen/feeds/FeedsApi.d.ts.map +1 -1
  24. package/dist/types/gen/models/index.d.ts +7 -0
  25. package/dist/types/gen/models/index.d.ts.map +1 -1
  26. package/dist/types/utils/state-update-queue.d.ts +5 -1
  27. package/dist/types/utils/state-update-queue.d.ts.map +1 -1
  28. package/package.json +1 -1
  29. package/src/feed/event-handlers/activity/handle-activity-reaction-updated.test.ts +282 -0
  30. package/src/feed/event-handlers/activity/handle-activity-reaction-updated.ts +140 -0
  31. package/src/feed/event-handlers/activity/index.ts +1 -0
  32. package/src/feed/event-handlers/comment/handle-comment-reaction-added.ts +1 -2
  33. package/src/feed/event-handlers/comment/handle-comment-reaction-updated.test.ts +350 -0
  34. package/src/feed/event-handlers/comment/handle-comment-reaction-updated.ts +72 -0
  35. package/src/feed/event-handlers/comment/index.ts +1 -1
  36. package/src/feed/feed.ts +4 -2
  37. package/src/feeds-client/feeds-client.ts +15 -3
  38. package/src/gen/feeds/FeedsApi.ts +28 -0
  39. package/src/gen/models/index.ts +10 -0
  40. package/src/test-utils/response-generators.ts +52 -0
  41. package/src/utils/state-update-queue.ts +14 -2
  42. package/dist/index-BSzSBlMh.mjs.map +0 -1
  43. package/dist/index-DRX66SIx.js.map +0 -1
@@ -0,0 +1,350 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { Feed, handleCommentReactionUpdated } from '../../../feed';
3
+ import { FeedsClient } from '../../../feeds-client';
4
+ import {
5
+ generateCommentResponse,
6
+ generateFeedResponse,
7
+ generateOwnUser,
8
+ getHumanId,
9
+ generateFeedReactionResponse,
10
+ generateCommentReactionUpdatedEvent,
11
+ } from '../../../test-utils';
12
+ import type {
13
+ CommentResponse,
14
+ FeedsReactionResponse,
15
+ } from '../../../gen/models';
16
+ import { shouldUpdateState } from '../../../utils';
17
+ import type { EventPayload } from '../../../types-internal';
18
+
19
+ describe(handleCommentReactionUpdated.name, () => {
20
+ let feed: Feed;
21
+ let client: FeedsClient;
22
+ let currentUserId: string;
23
+ let activityId: string;
24
+
25
+ beforeEach(() => {
26
+ client = new FeedsClient('mock-api-key');
27
+ currentUserId = getHumanId();
28
+ client.state.partialNext({
29
+ connected_user: generateOwnUser({ id: currentUserId }),
30
+ });
31
+ const feedResponse = generateFeedResponse({
32
+ id: 'main',
33
+ group_id: 'user',
34
+ created_by: { id: currentUserId },
35
+ });
36
+ feed = new Feed(
37
+ client,
38
+ feedResponse.group_id,
39
+ feedResponse.id,
40
+ feedResponse,
41
+ );
42
+ activityId = `activity-${getHumanId()}`;
43
+ });
44
+
45
+ it('updates own_reactions for current user and updates comment fields from the event', () => {
46
+ const existingCommentId = `comment-${getHumanId()}`;
47
+ const existingReaction = generateFeedReactionResponse({
48
+ type: 'like',
49
+ user: { id: currentUserId },
50
+ activity_id: activityId,
51
+ comment_id: existingCommentId,
52
+ });
53
+ const existingComment = generateCommentResponse({
54
+ id: existingCommentId,
55
+ object_id: activityId,
56
+ latest_reactions: [],
57
+ reaction_groups: {},
58
+ own_reactions: [existingReaction],
59
+ });
60
+ feed.state.partialNext({
61
+ comments_by_entity_id: {
62
+ [activityId]: {
63
+ comments: [existingComment],
64
+ pagination: { sort: 'first' },
65
+ },
66
+ },
67
+ });
68
+
69
+ const event = generateCommentReactionUpdatedEvent({
70
+ comment: {
71
+ id: existingComment.id,
72
+ object_id: activityId,
73
+ latest_reactions: [],
74
+ reaction_groups: {},
75
+ },
76
+ reaction: {
77
+ type: 'downvote',
78
+ user: { id: currentUserId },
79
+ activity_id: activityId,
80
+ comment_id: existingCommentId,
81
+ },
82
+ });
83
+
84
+ const stateBefore = feed.currentState;
85
+ const [initialReaction] = (stateBefore.comments_by_entity_id[activityId]
86
+ ?.comments ?? [])[0].own_reactions;
87
+ expect(
88
+ stateBefore.comments_by_entity_id[activityId]?.comments?.[0]
89
+ ?.own_reactions,
90
+ ).toHaveLength(1);
91
+ expect(initialReaction).toBe(existingReaction);
92
+
93
+ handleCommentReactionUpdated.call(feed, event);
94
+
95
+ const stateAfter = feed.currentState;
96
+ const [updated] = stateAfter.comments_by_entity_id[activityId]!.comments!;
97
+ expect(updated.own_reactions).toHaveLength(1);
98
+ expect(updated.own_reactions[0]).toBe(event.reaction);
99
+ // ensure we used event's latest_reactions & reaction_groups (Object.is check)
100
+ expect(updated.latest_reactions).toBe(event.comment.latest_reactions);
101
+ expect(updated.reaction_groups).toBe(event.comment.reaction_groups);
102
+ });
103
+
104
+ it('does modify own_reactions if the target reaction belongs to another user', () => {
105
+ const existingComment = generateCommentResponse({
106
+ object_id: activityId,
107
+ latest_reactions: [],
108
+ reaction_groups: {},
109
+ own_reactions: [],
110
+ });
111
+ feed.state.partialNext({
112
+ comments_by_entity_id: {
113
+ [activityId]: {
114
+ comments: [existingComment],
115
+ pagination: { sort: 'first' },
116
+ },
117
+ },
118
+ });
119
+
120
+ const event = generateCommentReactionUpdatedEvent({
121
+ comment: {
122
+ id: existingComment.id,
123
+ object_id: activityId,
124
+ latest_reactions: [],
125
+ reaction_groups: {},
126
+ },
127
+ reaction: {
128
+ type: 'laugh',
129
+ user: { id: 'other-user' },
130
+ activity_id: activityId,
131
+ comment_id: existingComment.id,
132
+ },
133
+ });
134
+
135
+ handleCommentReactionUpdated.call(feed, event);
136
+ const stateAfter = feed.currentState;
137
+ const [updated] = stateAfter.comments_by_entity_id[activityId]!.comments!;
138
+ expect(updated.own_reactions).toHaveLength(0);
139
+ expect(updated.latest_reactions).toBe(event.comment.latest_reactions);
140
+ expect(updated.reaction_groups).toBe(event.comment.reaction_groups);
141
+ });
142
+
143
+ it('updates the proper entity state (prefers parent_id)', () => {
144
+ const parentId = `comment-${getHumanId()}`;
145
+ const existingCommentId = `comment-${getHumanId()}`;
146
+ const existingReaction = generateFeedReactionResponse({
147
+ type: 'like',
148
+ user: { id: currentUserId },
149
+ activity_id: activityId,
150
+ comment_id: existingCommentId,
151
+ });
152
+ const existingComment = generateCommentResponse({
153
+ id: existingCommentId,
154
+ object_id: activityId,
155
+ parent_id: parentId,
156
+ latest_reactions: [],
157
+ reaction_groups: {},
158
+ own_reactions: [existingReaction],
159
+ });
160
+ feed.state.partialNext({
161
+ comments_by_entity_id: {
162
+ [parentId]: {
163
+ comments: [existingComment],
164
+ pagination: { sort: 'first' },
165
+ },
166
+ },
167
+ });
168
+
169
+ const addedEvent = generateCommentReactionUpdatedEvent({
170
+ comment: {
171
+ id: existingComment.id,
172
+ object_id: activityId,
173
+ parent_id: parentId,
174
+ latest_reactions: [],
175
+ reaction_groups: {},
176
+ },
177
+ reaction: {
178
+ type: 'downvote',
179
+ user: { id: currentUserId },
180
+ activity_id: activityId,
181
+ comment_id: existingComment.id,
182
+ },
183
+ });
184
+
185
+ const stateBefore = feed.currentState;
186
+ expect(
187
+ stateBefore.comments_by_entity_id[parentId]?.comments?.[0]?.own_reactions,
188
+ ).toHaveLength(1);
189
+ const [initialReaction] = (stateBefore.comments_by_entity_id[parentId]
190
+ ?.comments ?? [])[0].own_reactions;
191
+ expect(initialReaction).toBe(existingReaction);
192
+
193
+ handleCommentReactionUpdated.call(feed, addedEvent);
194
+ const stateAfter1 = feed.currentState;
195
+ const [updated1] = stateAfter1.comments_by_entity_id[parentId]!.comments!;
196
+ expect(updated1.own_reactions).toHaveLength(1);
197
+ expect(updated1.own_reactions[0]).toBe(addedEvent.reaction);
198
+ expect(updated1.latest_reactions).toBe(addedEvent.comment.latest_reactions);
199
+ expect(updated1.reaction_groups).toBe(addedEvent.comment.reaction_groups);
200
+ });
201
+
202
+ it('does nothing if comment is not found in state', () => {
203
+ const addedEvent = generateCommentReactionUpdatedEvent({
204
+ comment: { object_id: activityId },
205
+ reaction: { user: { id: currentUserId } },
206
+ });
207
+ const stateBefore = feed.currentState;
208
+
209
+ handleCommentReactionUpdated.call(feed, addedEvent);
210
+ const stateAfter = feed.currentState;
211
+ expect(stateAfter).toBe(stateBefore);
212
+ });
213
+
214
+ describe(`Comment reaction updated ${shouldUpdateState.name} integration`, () => {
215
+ let currentUserPayload: EventPayload<'feeds.comment.reaction.updated'>;
216
+ let existingComment: CommentResponse;
217
+ let existingReaction: FeedsReactionResponse;
218
+ let newReaction: FeedsReactionResponse;
219
+ let commentId: string;
220
+
221
+ beforeEach(() => {
222
+ commentId = `comment-${getHumanId()}`;
223
+ existingReaction = generateFeedReactionResponse({
224
+ type: 'heart',
225
+ user: { id: currentUserId },
226
+ activity_id: activityId,
227
+ comment_id: commentId,
228
+ });
229
+ newReaction = generateFeedReactionResponse({
230
+ type: 'like',
231
+ user: { id: currentUserId },
232
+ activity_id: activityId,
233
+ comment_id: commentId,
234
+ });
235
+ existingComment = generateCommentResponse({
236
+ id: commentId,
237
+ object_id: activityId,
238
+ own_reactions: [existingReaction],
239
+ });
240
+
241
+ currentUserPayload = generateCommentReactionUpdatedEvent({
242
+ comment: existingComment,
243
+ reaction: newReaction,
244
+ });
245
+
246
+ feed.state.partialNext({
247
+ comments_by_entity_id: {
248
+ [activityId]: {
249
+ comments: [existingComment],
250
+ pagination: { sort: 'first' },
251
+ },
252
+ },
253
+ });
254
+ feed.state.partialNext({ watch: true });
255
+ });
256
+
257
+ it(`skips update if ${shouldUpdateState.name} returns false`, () => {
258
+ // 1. HTTP and then WS
259
+
260
+ handleCommentReactionUpdated.call(feed, currentUserPayload, false);
261
+
262
+ let stateBefore = feed.currentState;
263
+
264
+ handleCommentReactionUpdated.call(feed, currentUserPayload);
265
+
266
+ let stateAfter = feed.currentState;
267
+
268
+ expect(stateAfter).toBe(stateBefore);
269
+ // @ts-expect-error Using Feed internals for tests only
270
+ expect(feed.stateUpdateQueue.size).toEqual(0);
271
+
272
+ // 2. WS and the HTTP
273
+
274
+ handleCommentReactionUpdated.call(feed, currentUserPayload);
275
+
276
+ stateBefore = feed.currentState;
277
+
278
+ handleCommentReactionUpdated.call(feed, currentUserPayload, false);
279
+
280
+ stateAfter = feed.currentState;
281
+
282
+ expect(stateAfter).toBe(stateBefore);
283
+ // @ts-expect-error Using Feed internals for tests only
284
+ expect(feed.stateUpdateQueue.size).toEqual(0);
285
+ });
286
+
287
+ it('allows update again from WS after clearing the stateUpdateQueue', () => {
288
+ handleCommentReactionUpdated.call(feed, currentUserPayload);
289
+
290
+ // Clear the queue
291
+ (feed as any).stateUpdateQueue.clear();
292
+
293
+ // Now update should be allowed from another WS event
294
+ handleCommentReactionUpdated.call(feed, currentUserPayload);
295
+
296
+ const comments =
297
+ feed.currentState.comments_by_entity_id[activityId]?.comments;
298
+ const comment = comments?.find((a) => a.id === commentId);
299
+ const [latestReaction] = (comment?.own_reactions ?? []).toReversed();
300
+
301
+ expect(comment?.own_reactions.length).toEqual(1);
302
+ expect(latestReaction).toMatchObject(newReaction);
303
+ });
304
+
305
+ it('allows update again from HTTP response after clearing the stateUpdateQueue', () => {
306
+ handleCommentReactionUpdated.call(feed, currentUserPayload, false);
307
+
308
+ // Clear the queue
309
+ (feed as any).stateUpdateQueue.clear();
310
+
311
+ // Now update should be allowed from another HTTP response
312
+ handleCommentReactionUpdated.call(feed, currentUserPayload, false);
313
+
314
+ const comments =
315
+ feed.currentState.comments_by_entity_id[activityId]?.comments;
316
+ const comment = comments?.find((a) => a.id === commentId);
317
+ const [latestReaction] = (comment?.own_reactions ?? []).toReversed();
318
+
319
+ expect(comment?.own_reactions.length).toEqual(1);
320
+ expect(latestReaction).toMatchObject(newReaction);
321
+ });
322
+
323
+ it('should not insert anything into the stateUpdateQueue if the connected_user did not trigger the comment reaction deletion', () => {
324
+ const otherUserPayload = generateCommentReactionUpdatedEvent({
325
+ comment: existingComment,
326
+ reaction: {
327
+ ...existingReaction,
328
+ user: {
329
+ id: getHumanId(),
330
+ },
331
+ },
332
+ });
333
+
334
+ handleCommentReactionUpdated.call(feed, otherUserPayload);
335
+
336
+ expect((feed as any).stateUpdateQueue).toEqual(new Set());
337
+
338
+ handleCommentReactionUpdated.call(feed, otherUserPayload);
339
+
340
+ const comments =
341
+ feed.currentState.comments_by_entity_id[activityId]?.comments;
342
+ const comment = comments?.find((a) => a.id === commentId);
343
+ const [latestReaction] = comment?.own_reactions ?? [];
344
+
345
+ expect((feed as any).stateUpdateQueue).toEqual(new Set());
346
+ expect(comment?.own_reactions.length).toEqual(1);
347
+ expect(latestReaction).toMatchObject(existingReaction);
348
+ });
349
+ });
350
+ });
@@ -0,0 +1,72 @@
1
+ import type { Feed } from '../../feed';
2
+ import type { EventPayload } from '../../../types-internal';
3
+ import { type PartializeAllBut } from '../../../types-internal';
4
+ import { getStateUpdateQueueId, shouldUpdateState } from '../../../utils';
5
+
6
+ export type CommentReactionUpdatedPayload = PartializeAllBut<
7
+ EventPayload<'feeds.comment.reaction.updated'>,
8
+ 'comment' | 'reaction'
9
+ >;
10
+
11
+ export function handleCommentReactionUpdated(
12
+ this: Feed,
13
+ payload: CommentReactionUpdatedPayload,
14
+ fromWs?: boolean,
15
+ ) {
16
+ const { comment, reaction } = payload;
17
+ const connectedUser = this.client.state.getLatestValue().connected_user;
18
+
19
+ const isOwnReaction = reaction.user.id === connectedUser?.id;
20
+
21
+ if (
22
+ !shouldUpdateState({
23
+ stateUpdateQueueId: getStateUpdateQueueId(
24
+ payload,
25
+ 'comment-reaction-updated',
26
+ ),
27
+ stateUpdateQueue: this.stateUpdateQueue,
28
+ watch: this.currentState.watch,
29
+ fromWs,
30
+ isTriggeredByConnectedUser: isOwnReaction,
31
+ })
32
+ ) {
33
+ return;
34
+ }
35
+
36
+ this.state.next((currentState) => {
37
+ const commentIndex = this.getCommentIndex(comment, currentState);
38
+
39
+ if (commentIndex === -1) return currentState;
40
+
41
+ const forId = comment.parent_id ?? comment.object_id;
42
+
43
+ const entityState = currentState.comments_by_entity_id[forId];
44
+
45
+ const newComments = entityState?.comments?.concat([]) ?? [];
46
+
47
+ let ownReactions = newComments[commentIndex].own_reactions;
48
+
49
+ if (isOwnReaction) {
50
+ ownReactions = [reaction];
51
+ }
52
+
53
+ newComments[commentIndex] = {
54
+ ...newComments[commentIndex],
55
+ reaction_count: comment.reaction_count ?? 0,
56
+ latest_reactions: comment.latest_reactions ?? [],
57
+ reaction_groups: comment.reaction_groups ?? {},
58
+ own_reactions: ownReactions,
59
+ };
60
+
61
+ return {
62
+ ...currentState,
63
+ comments_by_entity_id: {
64
+ ...currentState.comments_by_entity_id,
65
+ [forId]: {
66
+ ...entityState,
67
+ comments: newComments,
68
+ },
69
+ },
70
+ };
71
+ });
72
+ }
@@ -3,4 +3,4 @@ export * from './handle-comment-deleted';
3
3
  export * from './handle-comment-updated';
4
4
  export * from './handle-comment-reaction-added';
5
5
  export * from './handle-comment-reaction-deleted';
6
- // export * from './handle-comment-reaction';
6
+ export * from './handle-comment-reaction-updated';
package/src/feed/feed.ts CHANGED
@@ -42,8 +42,10 @@ import {
42
42
  handleActivityMarked,
43
43
  handleActivityReactionAdded,
44
44
  handleActivityReactionDeleted,
45
+ handleActivityReactionUpdated,
45
46
  handleCommentReactionAdded,
46
47
  handleCommentReactionDeleted,
48
+ handleCommentReactionUpdated,
47
49
  addAggregatedActivitiesToState,
48
50
  updateNotificationStatus,
49
51
  handleStoriesFeedUpdated,
@@ -151,7 +153,7 @@ export class Feed extends FeedApi {
151
153
  'feeds.activity.deleted': handleActivityDeleted.bind(this),
152
154
  'feeds.activity.reaction.added': handleActivityReactionAdded.bind(this),
153
155
  'feeds.activity.reaction.deleted': handleActivityReactionDeleted.bind(this),
154
- 'feeds.activity.reaction.updated': Feed.noop,
156
+ 'feeds.activity.reaction.updated': handleActivityReactionUpdated.bind(this),
155
157
  'feeds.activity.removed_from_feed':
156
158
  handleActivityRemovedFromFeed.bind(this),
157
159
  'feeds.activity.updated': handleActivityUpdated.bind(this),
@@ -173,7 +175,7 @@ export class Feed extends FeedApi {
173
175
  'feeds.follow.updated': handleFollowUpdated.bind(this),
174
176
  'feeds.comment.reaction.added': handleCommentReactionAdded.bind(this),
175
177
  'feeds.comment.reaction.deleted': handleCommentReactionDeleted.bind(this),
176
- 'feeds.comment.reaction.updated': Feed.noop,
178
+ 'feeds.comment.reaction.updated': handleCommentReactionUpdated.bind(this),
177
179
  'feeds.feed_member.added': handleFeedMemberAdded.bind(this),
178
180
  'feeds.feed_member.removed': handleFeedMemberRemoved.bind(this),
179
181
  'feeds.feed_member.updated': handleFeedMemberUpdated.bind(this),
@@ -51,6 +51,7 @@ import {
51
51
  Feed,
52
52
  handleActivityReactionAdded,
53
53
  handleActivityReactionDeleted,
54
+ handleActivityReactionUpdated,
54
55
  handleActivityUpdated,
55
56
  handleCommentAdded,
56
57
  handleCommentDeleted,
@@ -68,7 +69,8 @@ import { handleUserUpdated } from './event-handlers';
68
69
  import type { SyncFailure } from '../common/real-time/event-models';
69
70
  import { UnhandledErrorType } from '../common/real-time/event-models';
70
71
  import { updateCommentCount } from '../feed/event-handlers/comment/utils';
71
- import { configureLoggers } from '../utils/logger';
72
+ import { configureLoggers } from '../utils';
73
+ import { handleCommentReactionUpdated } from '../feed/event-handlers/comment/handle-comment-reaction-updated';
72
74
 
73
75
  export type FeedsClientState = {
74
76
  connected_user: OwnUser | undefined;
@@ -417,9 +419,14 @@ export class FeedsClient extends FeedsApi {
417
419
  activity_id: string;
418
420
  },
419
421
  ) => {
422
+ const shouldEnforceUnique = request.enforce_unique;
420
423
  const response = await super.addActivityReaction(request);
421
424
  for (const feed of Object.values(this.activeFeeds)) {
422
- handleActivityReactionAdded.bind(feed)(response, false);
425
+ if (shouldEnforceUnique) {
426
+ handleActivityReactionUpdated.bind(feed)(response, false);
427
+ } else {
428
+ handleActivityReactionAdded.bind(feed)(response, false);
429
+ }
423
430
  }
424
431
  return response;
425
432
  };
@@ -449,9 +456,14 @@ export class FeedsClient extends FeedsApi {
449
456
  addCommentReaction = async (
450
457
  request: AddCommentReactionRequest & { id: string },
451
458
  ): Promise<StreamResponse<AddCommentReactionResponse>> => {
459
+ const shouldEnforceUnique = request.enforce_unique;
452
460
  const response = await super.addCommentReaction(request);
453
461
  for (const feed of Object.values(this.activeFeeds)) {
454
- handleCommentReactionAdded.bind(feed)(response, false);
462
+ if (shouldEnforceUnique) {
463
+ handleCommentReactionUpdated.bind(feed)(response, false);
464
+ } else {
465
+ handleCommentReactionAdded.bind(feed)(response, false);
466
+ }
455
467
  }
456
468
  return response;
457
469
  };
@@ -59,6 +59,8 @@ import type {
59
59
  ListBlockListResponse,
60
60
  ListDevicesResponse,
61
61
  MarkActivityRequest,
62
+ OwnCapabilitiesBatchRequest,
63
+ OwnCapabilitiesBatchResponse,
62
64
  PinActivityRequest,
63
65
  PinActivityResponse,
64
66
  PollOptionResponse,
@@ -1523,6 +1525,32 @@ export class FeedsApi {
1523
1525
  return { ...response.body, metadata: response.metadata };
1524
1526
  }
1525
1527
 
1528
+ async ownCapabilitiesBatch(
1529
+ request: OwnCapabilitiesBatchRequest & { connection_id?: string },
1530
+ ): Promise<StreamResponse<OwnCapabilitiesBatchResponse>> {
1531
+ const queryParams = {
1532
+ connection_id: request?.connection_id,
1533
+ };
1534
+ const body = {
1535
+ feeds: request?.feeds,
1536
+ };
1537
+
1538
+ const response = await this.apiClient.sendRequest<
1539
+ StreamResponse<OwnCapabilitiesBatchResponse>
1540
+ >(
1541
+ 'POST',
1542
+ '/api/v2/feeds/feeds/own_capabilities/batch',
1543
+ undefined,
1544
+ queryParams,
1545
+ body,
1546
+ 'application/json',
1547
+ );
1548
+
1549
+ decoders.OwnCapabilitiesBatchResponse?.(response.body);
1550
+
1551
+ return { ...response.body, metadata: response.metadata };
1552
+ }
1553
+
1526
1554
  protected async _queryFeeds(
1527
1555
  request?: QueryFeedsRequest & { connection_id?: string },
1528
1556
  ): Promise<StreamResponse<QueryFeedsResponse>> {
@@ -4177,6 +4177,16 @@ export interface OnlyUserID {
4177
4177
  id: string;
4178
4178
  }
4179
4179
 
4180
+ export interface OwnCapabilitiesBatchRequest {
4181
+ feeds: string[];
4182
+ }
4183
+
4184
+ export interface OwnCapabilitiesBatchResponse {
4185
+ duration: string;
4186
+
4187
+ capabilities: Record<string, FeedOwnCapability[]>;
4188
+ }
4189
+
4180
4190
  export interface OwnUser {
4181
4191
  banned: boolean;
4182
4192
 
@@ -283,6 +283,32 @@ export function generateActivityReactionAddedEvent(
283
283
  };
284
284
  }
285
285
 
286
+ export function generateActivityReactionUpdatedEvent(
287
+ overrides: Omit<
288
+ Partial<EventPayload<'feeds.activity.reaction.updated'>>,
289
+ 'activity' | 'type' | 'reaction' | 'user'
290
+ > & {
291
+ activity?: Parameters<typeof generateActivityResponse>[0];
292
+ reaction?: Parameters<typeof generateFeedReactionResponse>[0];
293
+ user?: Parameters<typeof generateUserResponse>[0];
294
+ } = {},
295
+ ): EventPayload<'feeds.activity.reaction.updated'> {
296
+ const activity = generateActivityResponse(overrides.activity);
297
+ const reaction = generateFeedReactionResponse(overrides.reaction);
298
+ const user = generateUserResponse(overrides.user);
299
+
300
+ return {
301
+ type: 'feeds.activity.reaction.updated',
302
+ created_at: new Date(),
303
+ fid: '',
304
+ custom: {},
305
+ ...overrides,
306
+ user,
307
+ reaction,
308
+ activity,
309
+ };
310
+ }
311
+
286
312
  export function generateActivityReactionDeletedEvent(
287
313
  overrides: Omit<
288
314
  Partial<EventPayload<'feeds.activity.reaction.deleted'>>,
@@ -532,6 +558,32 @@ export function generateCommentReactionDeletedEvent(
532
558
  };
533
559
  }
534
560
 
561
+ export function generateCommentReactionUpdatedEvent(
562
+ overrides: Omit<
563
+ Partial<EventPayload<'feeds.comment.reaction.updated'>>,
564
+ 'comment' | 'reaction' | 'activity' | 'type'
565
+ > & {
566
+ comment?: Parameters<typeof generateCommentResponse>[0];
567
+ reaction?: Parameters<typeof generateFeedReactionResponse>[0];
568
+ activity?: Parameters<typeof generateActivityResponse>[0];
569
+ } = {},
570
+ ): EventPayload<'feeds.comment.reaction.updated'> {
571
+ const comment = generateCommentResponse(overrides.comment);
572
+ const reaction = generateFeedReactionResponse(overrides.reaction);
573
+ const activity = generateActivityResponse(overrides.activity);
574
+
575
+ return {
576
+ type: 'feeds.comment.reaction.updated',
577
+ created_at: new Date(),
578
+ fid: '',
579
+ custom: {},
580
+ ...overrides,
581
+ comment,
582
+ reaction,
583
+ activity,
584
+ };
585
+ }
586
+
535
587
  export function generateFeedMemberAddedEvent(
536
588
  overrides: Omit<
537
589
  Partial<EventPayload<'feeds.feed_member.added'>>,
@@ -12,13 +12,21 @@ import type {
12
12
  CommentUpdatedPayload,
13
13
  } from '../feed';
14
14
  import { ensureExhausted } from './ensure-exhausted';
15
+ import type {
16
+ CommentReactionUpdatedPayload
17
+ } from '../feed/event-handlers/comment/handle-comment-reaction-updated';
18
+ import type {
19
+ ActivityReactionUpdatedPayload
20
+ } from '../feed/event-handlers/activity/handle-activity-reaction-updated';
15
21
 
16
22
  export type StateUpdateQueuePrefix =
17
23
  | 'activity-updated'
18
24
  | 'activity-reaction-created'
19
25
  | 'activity-reaction-deleted'
26
+ | 'activity-reaction-updated'
20
27
  | 'comment-reaction-created'
21
28
  | 'comment-reaction-deleted'
29
+ | 'comment-reaction-updated'
22
30
  | 'follow-created'
23
31
  | 'follow-deleted'
24
32
  | 'follow-updated'
@@ -30,8 +38,10 @@ type StateUpdateQueuePayloadByPrefix = {
30
38
  'activity-updated': ActivityUpdatedPayload;
31
39
  'activity-reaction-created': ActivityReactionAddedPayload;
32
40
  'activity-reaction-deleted': ActivityReactionDeletedPayload;
41
+ 'activity-reaction-updated': ActivityReactionUpdatedPayload;
33
42
  'comment-reaction-created': CommentReactionAddedPayload;
34
43
  'comment-reaction-deleted': CommentReactionDeletedPayload;
44
+ 'comment-reaction-updated': CommentReactionUpdatedPayload;
35
45
  'follow-created': FollowResponse;
36
46
  'follow-deleted': FollowResponse;
37
47
  'follow-updated': FollowResponse;
@@ -163,7 +173,8 @@ export function getStateUpdateQueueId(
163
173
  return toJoin.concat([data.activity.id]).join('-')
164
174
  }
165
175
  case 'activity-reaction-created':
166
- case 'activity-reaction-deleted': {
176
+ case 'activity-reaction-deleted':
177
+ case 'activity-reaction-updated': {
167
178
  return toJoin
168
179
  .concat([
169
180
  data.activity.id,
@@ -172,7 +183,8 @@ export function getStateUpdateQueueId(
172
183
  .join('-');
173
184
  }
174
185
  case 'comment-reaction-created':
175
- case 'comment-reaction-deleted': {
186
+ case 'comment-reaction-deleted':
187
+ case 'comment-reaction-updated': {
176
188
  return toJoin
177
189
  .concat([
178
190
  data.comment.id,