@stream-io/feeds-client 0.2.13 → 0.2.15

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 (51) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/index.js +2 -1
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/react-bindings.js +2 -2
  5. package/dist/cjs/react-bindings.js.map +1 -1
  6. package/dist/es/index.mjs +3 -2
  7. package/dist/es/react-bindings.mjs +2 -2
  8. package/dist/es/react-bindings.mjs.map +1 -1
  9. package/dist/{index-gvcJhGPH.mjs → index-BSzSBlMh.mjs} +96 -15
  10. package/dist/index-BSzSBlMh.mjs.map +1 -0
  11. package/dist/{index-RzB4c4g6.js → index-DRX66SIx.js} +96 -15
  12. package/dist/index-DRX66SIx.js.map +1 -0
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/dist/types/common/real-time/StableWSConnection.d.ts +3 -3
  15. package/dist/types/common/real-time/StableWSConnection.d.ts.map +1 -1
  16. package/dist/types/feed/event-handlers/{notification-feed/handle-notification-feed-updated.d.ts → aggregated-feed/handle-aggregated-feed-updated.d.ts} +10 -4
  17. package/dist/types/feed/event-handlers/aggregated-feed/handle-aggregated-feed-updated.d.ts.map +1 -0
  18. package/dist/types/feed/event-handlers/aggregated-feed/index.d.ts +2 -0
  19. package/dist/types/feed/event-handlers/aggregated-feed/index.d.ts.map +1 -0
  20. package/dist/types/feed/event-handlers/index.d.ts +1 -1
  21. package/dist/types/feed/event-handlers/index.d.ts.map +1 -1
  22. package/dist/types/feed/feed.d.ts +2 -2
  23. package/dist/types/feed/feed.d.ts.map +1 -1
  24. package/dist/types/feeds-client/feeds-client.d.ts +8 -2
  25. package/dist/types/feeds-client/feeds-client.d.ts.map +1 -1
  26. package/dist/types/gen/feeds/FeedsApi.d.ts +1 -1
  27. package/dist/types/gen/feeds/FeedsApi.d.ts.map +1 -1
  28. package/dist/types/gen/model-decoders/event-decoder-mapping.d.ts.map +1 -1
  29. package/dist/types/gen/models/index.d.ts +17 -1
  30. package/dist/types/gen/models/index.d.ts.map +1 -1
  31. package/dist/types/utils/unique-array-merge.d.ts +1 -0
  32. package/dist/types/utils/unique-array-merge.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/src/bindings/react/hooks/util/useReactionActions.ts +1 -1
  35. package/src/feed/event-handlers/{notification-feed/handle-notification-feed-updated.test.ts → aggregated-feed/handle-aggregated-feed-updated.test.ts} +227 -1
  36. package/src/feed/event-handlers/{notification-feed/handle-notification-feed-updated.ts → aggregated-feed/handle-aggregated-feed-updated.ts} +71 -15
  37. package/src/feed/event-handlers/aggregated-feed/index.ts +1 -0
  38. package/src/feed/event-handlers/index.ts +1 -1
  39. package/src/feed/feed.ts +2 -0
  40. package/src/feeds-client/feeds-client.ts +15 -7
  41. package/src/gen/feeds/FeedsApi.ts +2 -2
  42. package/src/gen/model-decoders/decoders.ts +14 -0
  43. package/src/gen/model-decoders/event-decoder-mapping.ts +3 -0
  44. package/src/gen/models/index.ts +26 -2
  45. package/src/utils/unique-array-merge.ts +30 -0
  46. package/dist/index-RzB4c4g6.js.map +0 -1
  47. package/dist/index-gvcJhGPH.mjs.map +0 -1
  48. package/dist/types/feed/event-handlers/notification-feed/handle-notification-feed-updated.d.ts.map +0 -1
  49. package/dist/types/feed/event-handlers/notification-feed/index.d.ts +0 -2
  50. package/dist/types/feed/event-handlers/notification-feed/index.d.ts.map +0 -1
  51. package/src/feed/event-handlers/notification-feed/index.ts +0 -1
@@ -8,7 +8,7 @@ import {
8
8
  updateNotificationFeedFromEvent,
9
9
  addAggregatedActivitiesToState,
10
10
  updateNotificationStatus,
11
- } from './handle-notification-feed-updated';
11
+ } from './handle-aggregated-feed-updated';
12
12
 
13
13
  const createMockNotificationFeedUpdatedEvent = (
14
14
  overrides: Partial<NotificationFeedUpdatedEvent> = {},
@@ -389,6 +389,232 @@ describe('notification-feed-utils', () => {
389
389
  ...newActivities,
390
390
  ]);
391
391
  });
392
+
393
+ describe('replace position', () => {
394
+ it('should replace existing activities with same group', () => {
395
+ const baseDate = new Date('2023-01-01');
396
+ const existingActivities = [
397
+ createMockAggregatedActivity({
398
+ group: 'group1',
399
+ activity_count: 1,
400
+ score: 10,
401
+ updated_at: baseDate,
402
+ }),
403
+ createMockAggregatedActivity({
404
+ group: 'group2',
405
+ activity_count: 2,
406
+ score: 20,
407
+ updated_at: baseDate,
408
+ }),
409
+ ];
410
+ const newActivities = [
411
+ createMockAggregatedActivity({
412
+ group: 'group1',
413
+ activity_count: 3,
414
+ score: 30,
415
+ updated_at: new Date('2023-01-02'),
416
+ }),
417
+ createMockAggregatedActivity({
418
+ group: 'group3',
419
+ activity_count: 4,
420
+ score: 40,
421
+ updated_at: new Date('2023-01-02'),
422
+ }),
423
+ ];
424
+
425
+ const result = addAggregatedActivitiesToState(
426
+ newActivities,
427
+ existingActivities,
428
+ 'replace',
429
+ );
430
+
431
+ expect(result.changed).toBe(true);
432
+ expect(result.aggregated_activities).toHaveLength(3);
433
+
434
+ // Check that group1 was replaced
435
+ const replacedGroup1 = result.aggregated_activities.find(
436
+ (a) => a.group === 'group1',
437
+ );
438
+ expect(replacedGroup1?.activity_count).toBe(3);
439
+ expect(replacedGroup1?.score).toBe(30);
440
+ expect(replacedGroup1?.updated_at).toEqual(new Date('2023-01-02'));
441
+
442
+ // Check that group2 remains unchanged
443
+ const unchangedGroup2 = result.aggregated_activities.find(
444
+ (a) => a.group === 'group2',
445
+ );
446
+ expect(unchangedGroup2?.activity_count).toBe(2);
447
+ expect(unchangedGroup2?.score).toBe(20);
448
+ expect(unchangedGroup2?.updated_at).toEqual(baseDate);
449
+
450
+ // Check that group3 was added
451
+ const newGroup3 = result.aggregated_activities.find(
452
+ (a) => a.group === 'group3',
453
+ );
454
+ expect(newGroup3?.activity_count).toBe(4);
455
+ expect(newGroup3?.score).toBe(40);
456
+ expect(newGroup3?.updated_at).toEqual(new Date('2023-01-02'));
457
+ });
458
+
459
+ it('should preserve order of existing activities when replacing', () => {
460
+ const existingActivities = [
461
+ createMockAggregatedActivity({ group: 'group1', activity_count: 1 }),
462
+ createMockAggregatedActivity({ group: 'group2', activity_count: 2 }),
463
+ createMockAggregatedActivity({ group: 'group3', activity_count: 3 }),
464
+ ];
465
+ const newActivities = [
466
+ createMockAggregatedActivity({ group: 'group2', activity_count: 20 }),
467
+ createMockAggregatedActivity({ group: 'group1', activity_count: 10 }),
468
+ ];
469
+
470
+ const result = addAggregatedActivitiesToState(
471
+ newActivities,
472
+ existingActivities,
473
+ 'replace',
474
+ );
475
+
476
+ expect(result.changed).toBe(true);
477
+ expect(result.aggregated_activities).toHaveLength(3);
478
+
479
+ // Check that order is preserved
480
+ expect(result.aggregated_activities[0].group).toBe('group1');
481
+ expect(result.aggregated_activities[0].activity_count).toBe(10);
482
+ expect(result.aggregated_activities[1].group).toBe('group2');
483
+ expect(result.aggregated_activities[1].activity_count).toBe(20);
484
+ expect(result.aggregated_activities[2].group).toBe('group3');
485
+ expect(result.aggregated_activities[2].activity_count).toBe(3);
486
+ });
487
+
488
+ it('should add new activities at the end when replacing', () => {
489
+ const existingActivities = [
490
+ createMockAggregatedActivity({ group: 'group1', activity_count: 1 }),
491
+ createMockAggregatedActivity({ group: 'group2', activity_count: 2 }),
492
+ ];
493
+ const newActivities = [
494
+ createMockAggregatedActivity({ group: 'group1', activity_count: 10 }),
495
+ createMockAggregatedActivity({ group: 'group3', activity_count: 3 }),
496
+ createMockAggregatedActivity({ group: 'group4', activity_count: 4 }),
497
+ ];
498
+
499
+ const result = addAggregatedActivitiesToState(
500
+ newActivities,
501
+ existingActivities,
502
+ 'replace',
503
+ );
504
+
505
+ expect(result.changed).toBe(true);
506
+ expect(result.aggregated_activities).toHaveLength(4);
507
+
508
+ // Check that existing activities are in their original positions
509
+ expect(result.aggregated_activities[0].group).toBe('group1');
510
+ expect(result.aggregated_activities[0].activity_count).toBe(10);
511
+ expect(result.aggregated_activities[1].group).toBe('group2');
512
+ expect(result.aggregated_activities[1].activity_count).toBe(2);
513
+
514
+ // Check that new activities are added at the end
515
+ expect(result.aggregated_activities[2].group).toBe('group3');
516
+ expect(result.aggregated_activities[2].activity_count).toBe(3);
517
+ expect(result.aggregated_activities[3].group).toBe('group4');
518
+ expect(result.aggregated_activities[3].activity_count).toBe(4);
519
+ });
520
+
521
+ it('should handle empty new activities', () => {
522
+ const existingActivities = [
523
+ createMockAggregatedActivity({ group: 'group1', activity_count: 1 }),
524
+ createMockAggregatedActivity({ group: 'group2', activity_count: 2 }),
525
+ ];
526
+
527
+ const result = addAggregatedActivitiesToState(
528
+ [],
529
+ existingActivities,
530
+ 'replace',
531
+ );
532
+
533
+ expect(result.changed).toBe(false);
534
+ expect(result.aggregated_activities).toStrictEqual(existingActivities);
535
+ });
536
+
537
+ it('should handle both arrays being empty', () => {
538
+ const result = addAggregatedActivitiesToState([], [], 'replace');
539
+
540
+ expect(result.changed).toBe(false);
541
+ expect(result.aggregated_activities).toStrictEqual([]);
542
+ });
543
+
544
+ it('should replace multiple activities with same groups', () => {
545
+ const existingActivities = [
546
+ createMockAggregatedActivity({
547
+ group: 'group1',
548
+ activity_count: 1,
549
+ score: 10,
550
+ }),
551
+ createMockAggregatedActivity({
552
+ group: 'group2',
553
+ activity_count: 2,
554
+ score: 20,
555
+ }),
556
+ createMockAggregatedActivity({
557
+ group: 'group3',
558
+ activity_count: 3,
559
+ score: 30,
560
+ }),
561
+ ];
562
+ const newActivities = [
563
+ createMockAggregatedActivity({
564
+ group: 'group1',
565
+ activity_count: 10,
566
+ score: 100,
567
+ }),
568
+ createMockAggregatedActivity({
569
+ group: 'group3',
570
+ activity_count: 30,
571
+ score: 300,
572
+ }),
573
+ createMockAggregatedActivity({
574
+ group: 'group4',
575
+ activity_count: 4,
576
+ score: 40,
577
+ }),
578
+ ];
579
+
580
+ const result = addAggregatedActivitiesToState(
581
+ newActivities,
582
+ existingActivities,
583
+ 'replace',
584
+ );
585
+
586
+ expect(result.changed).toBe(true);
587
+ expect(result.aggregated_activities).toHaveLength(4);
588
+
589
+ // Check that group1 was replaced
590
+ const replacedGroup1 = result.aggregated_activities.find(
591
+ (a) => a.group === 'group1',
592
+ );
593
+ expect(replacedGroup1?.activity_count).toBe(10);
594
+ expect(replacedGroup1?.score).toBe(100);
595
+
596
+ // Check that group2 remains unchanged
597
+ const unchangedGroup2 = result.aggregated_activities.find(
598
+ (a) => a.group === 'group2',
599
+ );
600
+ expect(unchangedGroup2?.activity_count).toBe(2);
601
+ expect(unchangedGroup2?.score).toBe(20);
602
+
603
+ // Check that group3 was replaced
604
+ const replacedGroup3 = result.aggregated_activities.find(
605
+ (a) => a.group === 'group3',
606
+ );
607
+ expect(replacedGroup3?.activity_count).toBe(30);
608
+ expect(replacedGroup3?.score).toBe(300);
609
+
610
+ // Check that group4 was added
611
+ const newGroup4 = result.aggregated_activities.find(
612
+ (a) => a.group === 'group4',
613
+ );
614
+ expect(newGroup4?.activity_count).toBe(4);
615
+ expect(newGroup4?.score).toBe(40);
616
+ });
617
+ });
392
618
  });
393
619
 
394
620
  describe('updateNotificationStatus', () => {
@@ -1,16 +1,17 @@
1
- import type { Feed } from '../../../feed';
1
+ import type { Feed } from '../..';
2
2
  import type {
3
3
  AggregatedActivityResponse,
4
4
  NotificationFeedUpdatedEvent,
5
5
  NotificationStatusResponse,
6
+ StoriesFeedUpdatedEvent,
6
7
  } from '../../../gen/models';
7
8
  import type { EventPayload, UpdateStateResult } from '../../../types-internal';
8
- import { uniqueArrayMerge } from '../../../utils';
9
+ import { replaceUniqueArrayMerge, uniqueArrayMerge } from '../../../utils';
9
10
 
10
11
  export const addAggregatedActivitiesToState = (
11
12
  newAggregatedActivities: AggregatedActivityResponse[],
12
13
  aggregatedActivities: AggregatedActivityResponse[] | undefined,
13
- position: 'start' | 'end',
14
+ position: 'start' | 'end' | 'replace',
14
15
  ) => {
15
16
  let result: UpdateStateResult<{
16
17
  aggregated_activities: AggregatedActivityResponse[];
@@ -27,18 +28,29 @@ export const addAggregatedActivitiesToState = (
27
28
  };
28
29
  }
29
30
 
30
- result.aggregated_activities =
31
- position === 'start'
32
- ? uniqueArrayMerge(
33
- newAggregatedActivities,
34
- aggregatedActivities ?? [],
35
- (a) => a.group,
36
- )
37
- : uniqueArrayMerge(
38
- aggregatedActivities ?? [],
39
- newAggregatedActivities,
40
- (a) => a.group,
41
- );
31
+ switch (position) {
32
+ case 'start':
33
+ result.aggregated_activities = uniqueArrayMerge(
34
+ newAggregatedActivities,
35
+ aggregatedActivities ?? [],
36
+ (a) => a.group,
37
+ );
38
+ break;
39
+ case 'end':
40
+ result.aggregated_activities = uniqueArrayMerge(
41
+ aggregatedActivities ?? [],
42
+ newAggregatedActivities,
43
+ (a) => a.group,
44
+ );
45
+ break;
46
+ case 'replace':
47
+ result.aggregated_activities = replaceUniqueArrayMerge(
48
+ aggregatedActivities ?? [],
49
+ newAggregatedActivities,
50
+ (a) => a.group,
51
+ );
52
+ break;
53
+ }
42
54
 
43
55
  return result;
44
56
  };
@@ -135,3 +147,47 @@ export function handleNotificationFeedUpdated(
135
147
  });
136
148
  }
137
149
  }
150
+
151
+ export function updateStoriesFeedFromEvent(
152
+ aggregatedActivities: AggregatedActivityResponse[] | undefined,
153
+ event: StoriesFeedUpdatedEvent,
154
+ ): UpdateStateResult<{
155
+ data?: {
156
+ aggregated_activities?: AggregatedActivityResponse[];
157
+ };
158
+ }> {
159
+ if (!aggregatedActivities) {
160
+ return {
161
+ changed: false,
162
+ };
163
+ }
164
+
165
+ if (event.aggregated_activities) {
166
+ const result = addAggregatedActivitiesToState(
167
+ event.aggregated_activities,
168
+ aggregatedActivities,
169
+ 'replace',
170
+ );
171
+
172
+ return result;
173
+ }
174
+
175
+ return {
176
+ changed: false,
177
+ };
178
+ }
179
+
180
+ export function handleStoriesFeedUpdated(
181
+ this: Feed,
182
+ event: EventPayload<'feeds.stories_feed.updated'>,
183
+ ) {
184
+ const result = updateStoriesFeedFromEvent(
185
+ this.currentState.aggregated_activities,
186
+ event,
187
+ );
188
+ if (result.changed) {
189
+ this.state.partialNext({
190
+ aggregated_activities: result.data?.aggregated_activities,
191
+ });
192
+ }
193
+ }
@@ -0,0 +1 @@
1
+ export * from './handle-aggregated-feed-updated';
@@ -4,5 +4,5 @@ export * from './feed-member';
4
4
  export * from './bookmark';
5
5
  export * from './activity';
6
6
  export * from './feed';
7
- export * from './notification-feed';
7
+ export * from './aggregated-feed';
8
8
  export * from './watch';
package/src/feed/feed.ts CHANGED
@@ -46,6 +46,7 @@ import {
46
46
  handleCommentReactionDeleted,
47
47
  addAggregatedActivitiesToState,
48
48
  updateNotificationStatus,
49
+ handleStoriesFeedUpdated,
49
50
  } from './event-handlers';
50
51
  import { capitalize } from '../common/utils';
51
52
  import type {
@@ -177,6 +178,7 @@ export class Feed extends FeedApi {
177
178
  'feeds.feed_member.removed': handleFeedMemberRemoved.bind(this),
178
179
  'feeds.feed_member.updated': handleFeedMemberUpdated.bind(this),
179
180
  'feeds.notification_feed.updated': handleNotificationFeedUpdated.bind(this),
181
+ 'feeds.stories_feed.updated': handleStoriesFeedUpdated.bind(this),
180
182
  // the poll events should be removed from here
181
183
  'feeds.poll.closed': Feed.noop,
182
184
  'feeds.poll.deleted': Feed.noop,
@@ -65,11 +65,8 @@ import {
65
65
  handleWatchStopped,
66
66
  } from '../feed';
67
67
  import { handleUserUpdated } from './event-handlers';
68
- import type {
69
- SyncFailure} from '../common/real-time/event-models';
70
- import {
71
- UnhandledErrorType,
72
- } from '../common/real-time/event-models';
68
+ import type { SyncFailure } from '../common/real-time/event-models';
69
+ import { UnhandledErrorType } from '../common/real-time/event-models';
73
70
  import { updateCommentCount } from '../feed/event-handlers/comment/utils';
74
71
  import { configureLoggers } from '../utils/logger';
75
72
 
@@ -415,18 +412,29 @@ export class FeedsClient extends FeedsApi {
415
412
  return response;
416
413
  };
417
414
 
418
- addReaction = async (
415
+ addActivityReaction = async (
419
416
  request: AddReactionRequest & {
420
417
  activity_id: string;
421
418
  },
422
419
  ) => {
423
- const response = await super.addReaction(request);
420
+ const response = await super.addActivityReaction(request);
424
421
  for (const feed of Object.values(this.activeFeeds)) {
425
422
  handleActivityReactionAdded.bind(feed)(response, false);
426
423
  }
427
424
  return response;
428
425
  };
429
426
 
427
+ /**
428
+ * @deprecated Use addActivityReaction instead
429
+ */
430
+ addReaction = (
431
+ request: AddReactionRequest & {
432
+ activity_id: string;
433
+ },
434
+ ) => {
435
+ return this.addActivityReaction(request);
436
+ };
437
+
430
438
  deleteActivityReaction = async (request: {
431
439
  activity_id: string;
432
440
  type: string;
@@ -571,7 +571,7 @@ export class FeedsApi {
571
571
  return { ...response.body, metadata: response.metadata };
572
572
  }
573
573
 
574
- async addReaction(
574
+ async addActivityReaction(
575
575
  request: AddReactionRequest & { activity_id: string },
576
576
  ): Promise<StreamResponse<AddReactionResponse>> {
577
577
  const pathParams = {
@@ -888,9 +888,9 @@ export class FeedsApi {
888
888
  request: AddCommentRequest,
889
889
  ): Promise<StreamResponse<AddCommentResponse>> {
890
890
  const body = {
891
- comment: request?.comment,
892
891
  object_id: request?.object_id,
893
892
  object_type: request?.object_type,
893
+ comment: request?.comment,
894
894
  create_notification_activity: request?.create_notification_activity,
895
895
  parent_id: request?.parent_id,
896
896
  skip_push: request?.skip_push,
@@ -1739,6 +1739,20 @@ decoders.SingleFollowResponse = (input?: Record<string, any>) => {
1739
1739
  return decode(typeMappings, input);
1740
1740
  };
1741
1741
 
1742
+ decoders.StoriesFeedUpdatedEvent = (input?: Record<string, any>) => {
1743
+ const typeMappings: TypeMapping = {
1744
+ created_at: { type: 'DatetimeType', isSingle: true },
1745
+
1746
+ received_at: { type: 'DatetimeType', isSingle: true },
1747
+
1748
+ aggregated_activities: {
1749
+ type: 'AggregatedActivityResponse',
1750
+ isSingle: false,
1751
+ },
1752
+ };
1753
+ return decode(typeMappings, input);
1754
+ };
1755
+
1742
1756
  decoders.SubmitActionResponse = (input?: Record<string, any>) => {
1743
1757
  const typeMappings: TypeMapping = {
1744
1758
  item: { type: 'ReviewQueueItemResponse', isSingle: true },
@@ -124,6 +124,9 @@ const eventDecoderMapping: Record<
124
124
  'feeds.poll.vote_removed': (data: Record<string, any>) =>
125
125
  decoders.PollVoteRemovedFeedEvent(data),
126
126
 
127
+ 'feeds.stories_feed.updated': (data: Record<string, any>) =>
128
+ decoders.StoriesFeedUpdatedEvent(data),
129
+
127
130
  'health.check': (data: Record<string, any>) =>
128
131
  decoders.HealthCheckEvent(data),
129
132
 
@@ -463,6 +463,8 @@ export interface ActivityResponse {
463
463
 
464
464
  hidden?: boolean;
465
465
 
466
+ is_watched?: boolean;
467
+
466
468
  text?: string;
467
469
 
468
470
  visibility_tag?: string;
@@ -680,6 +682,8 @@ export interface AggregatedActivityResponse {
680
682
  user_count_truncated: boolean;
681
683
 
682
684
  activities: ActivityResponse[];
685
+
686
+ is_watched?: boolean;
683
687
  }
684
688
 
685
689
  export interface AggregationConfig {
@@ -5548,9 +5552,27 @@ export interface SpeechSegmentConfig {
5548
5552
  }
5549
5553
 
5550
5554
  export interface StoriesConfig {
5551
- expiration_behaviour?: 'hide_for_everyone' | 'visible_for_author';
5552
-
5553
5555
  skip_watched?: boolean;
5556
+
5557
+ track_watched?: boolean;
5558
+ }
5559
+
5560
+ export interface StoriesFeedUpdatedEvent {
5561
+ created_at: Date;
5562
+
5563
+ fid: string;
5564
+
5565
+ custom: Record<string, any>;
5566
+
5567
+ type: string;
5568
+
5569
+ feed_visibility?: string;
5570
+
5571
+ received_at?: Date;
5572
+
5573
+ aggregated_activities?: AggregatedActivityResponse[];
5574
+
5575
+ user?: UserResponseCommonFields;
5554
5576
  }
5555
5577
 
5556
5578
  export interface SubmitActionRequest {
@@ -6571,6 +6593,7 @@ export type WSClientEvent =
6571
6593
  | ({ type: 'feeds.poll.vote_casted' } & PollVoteCastedFeedEvent)
6572
6594
  | ({ type: 'feeds.poll.vote_changed' } & PollVoteChangedFeedEvent)
6573
6595
  | ({ type: 'feeds.poll.vote_removed' } & PollVoteRemovedFeedEvent)
6596
+ | ({ type: 'feeds.stories_feed.updated' } & StoriesFeedUpdatedEvent)
6574
6597
  | ({ type: 'health.check' } & HealthCheckEvent)
6575
6598
  | ({ type: 'user.updated' } & UserUpdatedEvent);
6576
6599
 
@@ -6617,6 +6640,7 @@ export type WSEvent =
6617
6640
  | ({ type: 'feeds.poll.vote_casted' } & PollVoteCastedFeedEvent)
6618
6641
  | ({ type: 'feeds.poll.vote_changed' } & PollVoteChangedFeedEvent)
6619
6642
  | ({ type: 'feeds.poll.vote_removed' } & PollVoteRemovedFeedEvent)
6643
+ | ({ type: 'feeds.stories_feed.updated' } & StoriesFeedUpdatedEvent)
6620
6644
  | ({ type: 'health.check' } & HealthCheckEvent)
6621
6645
  | ({ type: 'moderation.custom_action' } & ModerationCustomActionEvent)
6622
6646
  | ({ type: 'moderation.flagged' } & ModerationFlaggedEvent)
@@ -17,3 +17,33 @@ export const uniqueArrayMerge = <T>(
17
17
 
18
18
  return existingArray.concat(filteredArrayToMerge);
19
19
  };
20
+
21
+ export const replaceUniqueArrayMerge = <T>(
22
+ existingArray: T[],
23
+ arrayToMerge: T[],
24
+ getKey: (v: T) => string,
25
+ ) => {
26
+ const existingMap = new Map<string, T>();
27
+ (existingArray ?? []).forEach((item) => {
28
+ existingMap.set(getKey(item), item);
29
+ });
30
+
31
+ const result: T[] = [];
32
+ arrayToMerge.forEach((item) => {
33
+ existingMap.set(getKey(item), item);
34
+ });
35
+
36
+ existingArray.forEach((originalItem) => {
37
+ const updatedItem = existingMap.get(getKey(originalItem));
38
+ if (updatedItem) {
39
+ result.push(updatedItem);
40
+ existingMap.delete(getKey(originalItem));
41
+ }
42
+ });
43
+
44
+ existingMap.forEach((item) => {
45
+ result.push(item);
46
+ });
47
+
48
+ return result;
49
+ };