@stream-io/feeds-client 0.3.32 → 0.3.34

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 (58) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +3 -2
  3. package/dist/cjs/index.js +3 -2
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/react-bindings.js +40 -36
  6. package/dist/cjs/react-bindings.js.map +1 -1
  7. package/dist/es/index.mjs +4 -3
  8. package/dist/es/index.mjs.map +1 -1
  9. package/dist/es/react-bindings.mjs +40 -36
  10. package/dist/es/react-bindings.mjs.map +1 -1
  11. package/dist/{feeds-client-BNhvggk2.mjs → feeds-client-C-2_fdH1.mjs} +229 -209
  12. package/dist/feeds-client-C-2_fdH1.mjs.map +1 -0
  13. package/dist/{feeds-client-B9rwEWH3.js → feeds-client-Xj6kDjVH.js} +229 -209
  14. package/dist/feeds-client-Xj6kDjVH.js.map +1 -0
  15. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  16. package/dist/types/activity-with-state-updates/activity-with-state-updates.d.ts +4 -2
  17. package/dist/types/activity-with-state-updates/activity-with-state-updates.d.ts.map +1 -1
  18. package/dist/types/bindings/react/hooks/feed-state-hooks/index.d.ts +1 -0
  19. package/dist/types/bindings/react/hooks/feed-state-hooks/index.d.ts.map +1 -1
  20. package/dist/types/bindings/react/hooks/feed-state-hooks/useOwnCapabilities.d.ts.map +1 -1
  21. package/dist/types/bindings/react/hooks/feed-state-hooks/useOwnFollowings.d.ts +8 -0
  22. package/dist/types/bindings/react/hooks/feed-state-hooks/useOwnFollowings.d.ts.map +1 -0
  23. package/dist/types/bindings/react/hooks/useCreateFeedsClient.d.ts.map +1 -1
  24. package/dist/types/common/types.d.ts +1 -1
  25. package/dist/types/common/types.d.ts.map +1 -1
  26. package/dist/types/feed/event-handlers/activity/handle-activity-added.d.ts.map +1 -1
  27. package/dist/types/feed/event-handlers/activity/handle-activity-updated.d.ts.map +1 -1
  28. package/dist/types/feed/feed.d.ts +2 -1
  29. package/dist/types/feed/feed.d.ts.map +1 -1
  30. package/dist/types/feeds-client/active-activity.d.ts +2 -1
  31. package/dist/types/feeds-client/active-activity.d.ts.map +1 -1
  32. package/dist/types/feeds-client/feeds-client.d.ts +14 -12
  33. package/dist/types/feeds-client/feeds-client.d.ts.map +1 -1
  34. package/dist/types/utils/check-own-fields-equality.d.ts +2 -0
  35. package/dist/types/utils/check-own-fields-equality.d.ts.map +1 -1
  36. package/dist/types/utils/throttling/index.d.ts +1 -1
  37. package/dist/types/utils/throttling/index.d.ts.map +1 -1
  38. package/dist/types/utils/throttling/throttled-get-batched-own-fields.d.ts +14 -0
  39. package/dist/types/utils/throttling/throttled-get-batched-own-fields.d.ts.map +1 -0
  40. package/package.json +1 -1
  41. package/src/activity-with-state-updates/activity-with-state-updates.ts +17 -3
  42. package/src/bindings/react/hooks/feed-state-hooks/index.ts +1 -0
  43. package/src/bindings/react/hooks/feed-state-hooks/useOwnCapabilities.ts +14 -23
  44. package/src/bindings/react/hooks/feed-state-hooks/useOwnFollowings.ts +18 -0
  45. package/src/bindings/react/hooks/useCreateFeedsClient.ts +1 -3
  46. package/src/common/types.ts +1 -1
  47. package/src/feed/event-handlers/activity/handle-activity-added.ts +0 -6
  48. package/src/feed/event-handlers/activity/handle-activity-updated.ts +0 -4
  49. package/src/feed/feed.ts +53 -39
  50. package/src/feeds-client/active-activity.ts +8 -4
  51. package/src/feeds-client/feeds-client.ts +110 -114
  52. package/src/utils/check-own-fields-equality.ts +37 -10
  53. package/src/utils/throttling/index.ts +1 -1
  54. package/src/utils/throttling/{throttled-get-batched-own-capabilities.ts → throttled-get-batched-own-fields.ts} +10 -10
  55. package/dist/feeds-client-B9rwEWH3.js.map +0 -1
  56. package/dist/feeds-client-BNhvggk2.mjs.map +0 -1
  57. package/dist/types/utils/throttling/throttled-get-batched-own-capabilities.d.ts +0 -14
  58. package/dist/types/utils/throttling/throttled-get-batched-own-capabilities.d.ts.map +0 -1
package/src/feed/feed.ts CHANGED
@@ -14,6 +14,7 @@ import type {
14
14
  FollowRequest,
15
15
  QueryCommentsRequest,
16
16
  ActivityAddedEvent,
17
+ EnrichmentOptions,
17
18
  } from '../gen/models';
18
19
  import type { StreamResponse } from '../gen-imports';
19
20
  import { StateStore } from '@stream-io/state-store';
@@ -63,16 +64,16 @@ import {
63
64
  checkHasAnotherPage,
64
65
  Constants,
65
66
  feedsLoggerSystem,
66
- ownFeedFields,
67
67
  uniqueArrayMerge,
68
68
  } from '../utils';
69
69
  import { handleActivityFeedback } from './event-handlers/activity/handle-activity-feedback';
70
70
  import { deepEqual } from '../utils/deep-equal';
71
71
  import { getOrCreateActiveFeed } from '../feeds-client/get-or-create-active-feed';
72
+ import { queueBatchedOwnFields } from '../utils/throttling';
72
73
 
73
74
  export type FeedState = Omit<
74
75
  Partial<GetOrCreateFeedResponse & FeedResponse>,
75
- 'feed' | 'own_capabilities' | 'duration'
76
+ 'feed' | 'duration'
76
77
  > & {
77
78
  /**
78
79
  * True when loading state using `getOrCreate`
@@ -333,11 +334,6 @@ export class Feed extends FeedApi {
333
334
  }
334
335
  }
335
336
 
336
- this.client.hydrateCapabilitiesCache([
337
- response.feed,
338
- ...currentActivityFeeds,
339
- ]);
340
-
341
337
  if (request?.next) {
342
338
  const { activities: currentActivities = [] } = this.currentState;
343
339
 
@@ -919,10 +915,7 @@ export class Feed extends FeedApi {
919
915
  ...request,
920
916
  feeds: [this.feed],
921
917
  });
922
- const currentFeed = response.activity.current_feed;
923
- if (currentFeed) {
924
- this.client.hydrateCapabilitiesCache([currentFeed]);
925
- }
918
+
926
919
  return response;
927
920
  }
928
921
 
@@ -934,34 +927,19 @@ export class Feed extends FeedApi {
934
927
 
935
928
  // no need to run noop function
936
929
  if (eventHandler !== Feed.noop) {
937
- if ('activity' in event && this.hasActivity(event.activity.id)) {
930
+ // Backfill current_feed if activity is posted to multiple feeds
931
+ if (
932
+ 'activity' in event &&
933
+ event.activity.feeds.length > 1 &&
934
+ this.hasActivity(event.activity.id)
935
+ ) {
938
936
  const currentActivity = this.currentState.activities?.find(
939
937
  (a) => a.id === event.activity.id,
940
938
  );
941
939
 
942
- // Backfill current_feed if activity is posted to multiple feeds
943
- if (
944
- event.activity.feeds.length > 1 &&
945
- !event.activity.current_feed &&
946
- currentActivity?.current_feed
947
- ) {
940
+ if (!event.activity.current_feed && currentActivity?.current_feed) {
948
941
  event.activity.current_feed = currentActivity.current_feed;
949
942
  }
950
-
951
- // Backfill own_ fields if activity is posted to a single feed
952
- if (
953
- event.activity.feeds.length === 1 &&
954
- event.activity.current_feed &&
955
- currentActivity?.current_feed
956
- ) {
957
- ownFeedFields.forEach((field) => {
958
- if (field in currentActivity.current_feed!) {
959
- // @ts-expect-error TODO: fix this
960
- event.activity.current_feed![field] =
961
- currentActivity.current_feed![field];
962
- }
963
- });
964
- }
965
943
  }
966
944
  // @ts-expect-error intersection of handler arguments results to never
967
945
  eventHandler?.(event);
@@ -994,10 +972,7 @@ export class Feed extends FeedApi {
994
972
  ) {
995
973
  const enrichmentOptions =
996
974
  this.currentState.last_get_or_create_request_config?.enrichment_options;
997
- if (
998
- !enrichmentOptions?.skip_activity_current_feed &&
999
- !enrichmentOptions?.skip_all
1000
- ) {
975
+ if (this.shouldAddToActiveFeeds(enrichmentOptions)) {
1001
976
  const feedsToGetOrCreate = new Map<string, FeedResponse>();
1002
977
  activities.forEach((activity) => {
1003
978
  if (
@@ -1010,14 +985,53 @@ export class Feed extends FeedApi {
1010
985
  );
1011
986
  }
1012
987
  });
1013
- Array.from(feedsToGetOrCreate.values()).forEach((feed) => {
988
+ const newFeeds = Array.from(feedsToGetOrCreate.values());
989
+ const fieldsToUpdate: Array<
990
+ 'own_capabilities' | 'own_follows' | 'own_followings' | 'own_membership'
991
+ > = [];
992
+ if (!options.fromWebSocket) {
993
+ fieldsToUpdate.push('own_membership');
994
+ if (!enrichmentOptions?.skip_own_capabilities) {
995
+ fieldsToUpdate.push('own_capabilities');
996
+ }
997
+ if (!enrichmentOptions?.skip_own_follows) {
998
+ fieldsToUpdate.push('own_follows');
999
+ }
1000
+ if (enrichmentOptions?.enrich_own_followings) {
1001
+ fieldsToUpdate.push('own_followings');
1002
+ }
1003
+ }
1004
+ newFeeds.forEach((feed) => {
1014
1005
  getOrCreateActiveFeed.bind(this.client)({
1015
1006
  group: feed.group_id,
1016
1007
  id: feed.id,
1017
1008
  data: feed,
1018
- fromWebSocket: options.fromWebSocket,
1009
+ fieldsToUpdate,
1019
1010
  });
1020
1011
  });
1012
+ if (options.fromWebSocket) {
1013
+ const uninitializedFeeds = newFeeds.filter((feedResponse) => {
1014
+ const feed = this.client.feed(feedResponse.group_id, feedResponse.id);
1015
+ // own_capabilities can only be undefined if we haven't fetched it yet
1016
+ return feed.currentState.own_capabilities === undefined;
1017
+ });
1018
+ if (uninitializedFeeds.length > 0) {
1019
+ queueBatchedOwnFields.bind(this.client)({
1020
+ feeds: uninitializedFeeds.map((feed) => feed.feed),
1021
+ });
1022
+ }
1023
+ }
1024
+ }
1025
+ }
1026
+
1027
+ private shouldAddToActiveFeeds(enrichmentOptions?: EnrichmentOptions) {
1028
+ if (!enrichmentOptions) {
1029
+ return true;
1021
1030
  }
1031
+ return (
1032
+ !enrichmentOptions?.skip_activity &&
1033
+ !enrichmentOptions?.skip_activity_current_feed &&
1034
+ !enrichmentOptions?.skip_all
1035
+ );
1022
1036
  }
1023
1037
  }
@@ -1,3 +1,4 @@
1
+ import type { ActivityWithStateUpdates } from '../activity-with-state-updates/activity-with-state-updates';
1
2
  import { Feed } from '../feed';
2
3
  import type { FeedsClient } from './feeds-client';
3
4
 
@@ -34,9 +35,12 @@ export function isAnyFeedWatched(this: FeedsClient, fids: string[]) {
34
35
  return false;
35
36
  }
36
37
 
37
- export function disconnectActivityFromFeed(this: FeedsClient, id: string) {
38
- const activeFeed = this.activeActivities[id];
39
- if (activeFeed) {
40
- delete this.activeActivities[id];
38
+ export function disconnectActivityFromFeed(
39
+ this: FeedsClient,
40
+ activity: ActivityWithStateUpdates,
41
+ ) {
42
+ const index = this.activeActivities.indexOf(activity);
43
+ if (index !== -1) {
44
+ this.activeActivities.splice(index, 1);
41
45
  }
42
46
  }
@@ -60,6 +60,7 @@ import { ModerationClient } from '../moderation-client';
60
60
  import { StreamPoll } from '../common/Poll';
61
61
  import {
62
62
  Feed,
63
+ type FeedState,
63
64
  handleActivityReactionAdded,
64
65
  handleActivityReactionDeleted,
65
66
  handleActivityReactionUpdated,
@@ -86,15 +87,16 @@ import { feedsLoggerSystem } from '../utils';
86
87
  import { handleCommentReactionUpdated } from '../feed/event-handlers/comment/handle-comment-reaction-updated';
87
88
  import {
88
89
  throttle,
89
- DEFAULT_BATCH_OWN_CAPABILITIES_THROTTLING_INTERVAL,
90
- type GetBatchedOwnCapabilitiesThrottledCallback,
91
- queueBatchedOwnCapabilities,
92
- type ThrottledGetBatchedOwnCapabilities,
93
90
  clearQueuedFeeds,
91
+ type ThrottledGetBatchedOwnFields,
92
+ type GetBatchedOwnFieldsThrottledCallback,
93
+ DEFAULT_BATCH_OWN_FIELDS_THROTTLING_INTERVAL,
94
94
  } from '../utils/throttling';
95
95
  import { ActivityWithStateUpdates } from '../activity-with-state-updates/activity-with-state-updates';
96
96
  import { getFeed } from '../activity-with-state-updates/get-feed';
97
97
  import {
98
+ isOwnCapabilitiesEqual,
99
+ isOwnFollowingsEqual,
98
100
  isOwnFollowsEqual,
99
101
  isOwnMembershipEqual,
100
102
  } from '../utils/check-own-fields-equality';
@@ -102,7 +104,6 @@ import {
102
104
  export type FeedsClientState = {
103
105
  connected_user: ConnectedUser | undefined;
104
106
  is_ws_connection_healthy: boolean;
105
- own_capabilities_by_fid: Record<string, FeedResponse['own_capabilities']>;
106
107
  };
107
108
 
108
109
  type FID = string;
@@ -123,14 +124,14 @@ export class FeedsClient extends FeedsApi {
123
124
 
124
125
  private readonly polls_by_id: Map<string, StreamPoll>;
125
126
 
126
- protected activeActivities: Record<ActivityId, ActivityWithStateUpdates> = {};
127
+ protected activeActivities: ActivityWithStateUpdates[] = [];
127
128
  protected activeFeeds: Record<FID, Feed> = {};
128
129
 
129
130
  private healthyConnectionChangedEventCount = 0;
130
131
 
131
- protected throttledGetBatchOwnCapabilities!: ThrottledGetBatchedOwnCapabilities;
132
- private cancelGetBatchOwnCapabilitiesTimer!: () => void;
133
- private query_batch_own_capabilties_throttling_interval!: number;
132
+ protected throttledGetBatchOwnFields!: ThrottledGetBatchedOwnFields;
133
+ private cancelGetBatchOwnFieldsTimer!: () => void;
134
+ private query_batch_own_fields_throttling_interval!: number;
134
135
 
135
136
  constructor(apiKey: string, options?: FeedsClientOptions) {
136
137
  const tokenManager = new TokenManager();
@@ -145,16 +146,15 @@ export class FeedsClient extends FeedsApi {
145
146
  this.state = new StateStore<FeedsClientState>({
146
147
  connected_user: undefined,
147
148
  is_ws_connection_healthy: false,
148
- own_capabilities_by_fid: {},
149
149
  });
150
150
  this.moderation = new ModerationClient(apiClient);
151
151
  this.tokenManager = tokenManager;
152
152
  this.connectionIdManager = connectionIdManager;
153
153
  this.polls_by_id = new Map();
154
154
 
155
- this.query_batch_own_capabilties_throttling_interval =
156
- options?.query_batch_own_capabilties_throttling_interval ??
157
- DEFAULT_BATCH_OWN_CAPABILITIES_THROTTLING_INTERVAL;
155
+ this.query_batch_own_fields_throttling_interval =
156
+ options?.query_batch_own_fields_throttling_interval ??
157
+ DEFAULT_BATCH_OWN_FIELDS_THROTTLING_INTERVAL;
158
158
 
159
159
  feedsLoggerSystem.configureLoggers(options?.configure_loggers_options);
160
160
 
@@ -184,6 +184,7 @@ export class FeedsClient extends FeedsApi {
184
184
  group: event.feed.group_id,
185
185
  id: event.feed.id,
186
186
  data: event.feed,
187
+ fieldsToUpdate: [],
187
188
  });
188
189
 
189
190
  break;
@@ -192,12 +193,9 @@ export class FeedsClient extends FeedsApi {
192
193
  feeds.forEach((f) => f.handleWSEvent(event as unknown as WSEvent));
193
194
  if (typeof fid === 'string') {
194
195
  delete this.activeFeeds[fid];
195
- Object.keys(this.activeActivities).forEach((activityId) => {
196
- const activity = this.activeActivities[activityId];
197
- if (getFeed.call(activity)?.feed === fid) {
198
- delete this.activeActivities[activityId];
199
- }
200
- });
196
+ this.activeActivities = this.activeActivities.filter(
197
+ (activity) => getFeed.call(activity)?.feed !== fid,
198
+ );
201
199
  }
202
200
  break;
203
201
  }
@@ -277,37 +275,36 @@ export class FeedsClient extends FeedsApi {
277
275
  default: {
278
276
  feeds.forEach((f) => f.handleWSEvent(event as unknown as WSEvent));
279
277
  if (event.type === 'feeds.activity.deleted') {
280
- delete this.activeActivities[event.activity.id];
278
+ this.activeActivities = this.activeActivities.filter(
279
+ (activity) => activity.id !== event.activity.id,
280
+ );
281
281
  }
282
282
  }
283
283
  }
284
284
  });
285
285
  }
286
286
 
287
- private setGetBatchOwnCapabilitiesThrottlingInterval = (
288
- throttlingMs: number,
289
- ) => {
290
- const {
291
- throttledFn: throttledGetBatchOwnCapabilities,
292
- cancelTimer: cancel,
293
- } = throttle<GetBatchedOwnCapabilitiesThrottledCallback>(
294
- (feeds, callback) => {
295
- this.ownBatch({
296
- feeds,
297
- }).catch((error) => {
298
- this.eventDispatcher.dispatch({
299
- type: 'errors.unhandled',
300
- error_type: UnhandledErrorType.FetchingOwnCapabilitiesOnNewActivity,
301
- error,
287
+ private setGetBatchOwnFieldsThrottlingInterval = (throttlingMs: number) => {
288
+ const { throttledFn: throttledGetBatchOwnFields, cancelTimer: cancel } =
289
+ throttle<GetBatchedOwnFieldsThrottledCallback>(
290
+ (feeds, callback) => {
291
+ this.ownBatch({
292
+ feeds,
293
+ }).catch((error) => {
294
+ this.eventDispatcher.dispatch({
295
+ type: 'errors.unhandled',
296
+ error_type:
297
+ UnhandledErrorType.FetchingOwnCapabilitiesOnNewActivity,
298
+ error,
299
+ });
302
300
  });
303
- });
304
- callback(feeds);
305
- },
306
- throttlingMs,
307
- { trailing: true },
308
- );
309
- this.throttledGetBatchOwnCapabilities = throttledGetBatchOwnCapabilities;
310
- this.cancelGetBatchOwnCapabilitiesTimer = cancel;
301
+ callback(feeds);
302
+ },
303
+ throttlingMs,
304
+ { trailing: true },
305
+ );
306
+ this.throttledGetBatchOwnFields = throttledGetBatchOwnFields;
307
+ this.cancelGetBatchOwnFieldsTimer = cancel;
311
308
  };
312
309
 
313
310
  private recoverOnReconnect = async () => {
@@ -316,18 +313,17 @@ export class FeedsClient extends FeedsApi {
316
313
  // we skip the first event as we could potentially be querying twice
317
314
  if (this.healthyConnectionChangedEventCount > 1) {
318
315
  const feedEntries = Object.entries(this.activeFeeds);
319
- const activityEntries = Object.entries(this.activeActivities);
320
316
 
321
317
  const results = await Promise.allSettled([
322
318
  ...feedEntries.map(([, feed]) => feed.synchronize()),
323
- ...activityEntries.map(([, activity]) => activity.synchronize()),
319
+ ...this.activeActivities.map((activity) => activity.synchronize()),
324
320
  ]);
325
321
 
326
322
  const failures: SyncFailure[] = results.flatMap((result, index) => {
327
323
  if (result.status === 'fulfilled') {
328
324
  return [];
329
325
  }
330
- const activity = activityEntries[index - feedEntries.length]?.[1];
326
+ const activity = this.activeActivities[index - feedEntries.length];
331
327
  const feed =
332
328
  feedEntries[index]?.[0] ?? (activity && getFeed.call(activity)?.feed);
333
329
 
@@ -369,34 +365,6 @@ export class FeedsClient extends FeedsApi {
369
365
  }
370
366
  }
371
367
 
372
- public hydrateCapabilitiesCache(
373
- feedResponses: Array<Pick<FeedResponse, 'feed' | 'own_capabilities'>>,
374
- ) {
375
- let ownCapabilitiesCache =
376
- this.state.getLatestValue().own_capabilities_by_fid;
377
-
378
- const capabilitiesToFetchQueue: string[] = [];
379
-
380
- for (const feedResponse of feedResponses) {
381
- const { feed, own_capabilities } = feedResponse;
382
-
383
- if (!Object.prototype.hasOwnProperty.call(ownCapabilitiesCache, feed)) {
384
- if (own_capabilities) {
385
- ownCapabilitiesCache = {
386
- ...ownCapabilitiesCache,
387
- [feed]: own_capabilities,
388
- };
389
- } else {
390
- capabilitiesToFetchQueue.push(feed);
391
- }
392
- }
393
- }
394
-
395
- queueBatchedOwnCapabilities.bind(this)({ feeds: capabilitiesToFetchQueue });
396
-
397
- this.state.partialNext({ own_capabilities_by_fid: ownCapabilitiesCache });
398
- }
399
-
400
368
  connectUser = async (user: UserRequest, tokenProvider?: TokenOrProvider) => {
401
369
  if (
402
370
  this.state.getLatestValue().connected_user !== undefined ||
@@ -407,8 +375,8 @@ export class FeedsClient extends FeedsApi {
407
375
 
408
376
  this.tokenManager.setTokenOrProvider(tokenProvider);
409
377
 
410
- this.setGetBatchOwnCapabilitiesThrottlingInterval(
411
- this.query_batch_own_capabilties_throttling_interval,
378
+ this.setGetBatchOwnFieldsThrottlingInterval(
379
+ this.query_batch_own_fields_throttling_interval,
412
380
  );
413
381
 
414
382
  try {
@@ -643,16 +611,15 @@ export class FeedsClient extends FeedsApi {
643
611
  // clear all caches
644
612
  this.polls_by_id.clear();
645
613
 
646
- this.activeActivities = {};
614
+ this.activeActivities = [];
647
615
  this.activeFeeds = {};
648
616
 
649
617
  this.state.partialNext({
650
618
  connected_user: undefined,
651
619
  is_ws_connection_healthy: false,
652
- own_capabilities_by_fid: {},
653
620
  });
654
621
 
655
- this.cancelGetBatchOwnCapabilitiesTimer();
622
+ this.cancelGetBatchOwnFieldsTimer();
656
623
  clearQueuedFeeds();
657
624
  };
658
625
 
@@ -680,6 +647,7 @@ export class FeedsClient extends FeedsApi {
680
647
  group: groupId,
681
648
  id,
682
649
  options,
650
+ fieldsToUpdate: [],
683
651
  });
684
652
  };
685
653
 
@@ -691,12 +659,14 @@ export class FeedsClient extends FeedsApi {
691
659
  * @param id - The id of the activity
692
660
  * @returns The activity with state updates
693
661
  */
694
- activityWithStateUpdates = (id: ActivityId) => {
695
- let activity = this.activeActivities[id];
696
- if (!activity) {
697
- activity = new ActivityWithStateUpdates(id, this);
698
- this.activeActivities[id] = activity;
699
- }
662
+ activityWithStateUpdates = (
663
+ id: ActivityId,
664
+ { fromResponse }: { fromResponse?: ActivityResponse } = {
665
+ fromResponse: undefined,
666
+ },
667
+ ) => {
668
+ const activity = new ActivityWithStateUpdates(id, this, { fromResponse });
669
+ this.activeActivities.push(activity);
700
670
  return activity;
701
671
  };
702
672
 
@@ -711,11 +681,15 @@ export class FeedsClient extends FeedsApi {
711
681
  id: feedResponse.id,
712
682
  data: feedResponse,
713
683
  watch: request?.watch,
684
+ fieldsToUpdate: [
685
+ 'own_capabilities',
686
+ 'own_follows',
687
+ 'own_membership',
688
+ 'own_followings',
689
+ ],
714
690
  }),
715
691
  );
716
692
 
717
- this.hydrateCapabilitiesCache(feedResponses);
718
-
719
693
  return {
720
694
  feeds,
721
695
  next: response.next,
@@ -727,13 +701,12 @@ export class FeedsClient extends FeedsApi {
727
701
 
728
702
  async ownBatch(request: OwnBatchRequest) {
729
703
  const response = await super.ownBatch(request);
730
- const feedResponses = Object.entries(response.data).map(
731
- ([feed, ownFields]) => ({
732
- feed,
733
- own_capabilities: ownFields.own_capabilities,
734
- }),
735
- );
736
- this.hydrateCapabilitiesCache(feedResponses);
704
+ Object.entries(response.data).forEach(([fid, ownFields]) => {
705
+ const feed = this.activeFeeds[fid];
706
+ if (feed) {
707
+ feed.state.partialNext(ownFields);
708
+ }
709
+ });
737
710
  return response;
738
711
  }
739
712
 
@@ -826,7 +799,6 @@ export class FeedsClient extends FeedsApi {
826
799
  },
827
800
  ) {
828
801
  const response = await super.getOrCreateFeed(request);
829
- this.hydrateCapabilitiesCache([response.feed]);
830
802
 
831
803
  if (request.watch) {
832
804
  const feeds = this.findAllActiveFeedsByFid(
@@ -840,19 +812,24 @@ export class FeedsClient extends FeedsApi {
840
812
 
841
813
  async getFollowSuggestions(
842
814
  ...params: Parameters<FeedsApi['getFollowSuggestions']>
843
- ): Promise<StreamResponse<GetFollowSuggestionsResponse>> {
815
+ ): Promise<StreamResponse<GetFollowSuggestionsResponse & { feeds: Feed[] }>> {
844
816
  const response = await super.getFollowSuggestions(...params);
845
817
 
846
- response.suggestions.forEach((suggestion) => {
847
- this.getOrCreateActiveFeed({
818
+ const feeds = response.suggestions.map((suggestion) => {
819
+ return this.getOrCreateActiveFeed({
848
820
  group: suggestion.group_id,
849
821
  id: suggestion.id,
850
822
  data: suggestion,
823
+ fieldsToUpdate: [
824
+ 'own_capabilities',
825
+ 'own_follows',
826
+ 'own_membership',
827
+ 'own_followings',
828
+ ],
851
829
  });
852
830
  });
853
831
 
854
- // TODO: return feed instance here https://linear.app/stream/issue/REACT-669/return-feed-instance-from-followsuggestions-breaking
855
- return response;
832
+ return { ...response, feeds };
856
833
  }
857
834
 
858
835
  protected readonly getOrCreateActiveFeed = ({
@@ -861,7 +838,7 @@ export class FeedsClient extends FeedsApi {
861
838
  data,
862
839
  watch,
863
840
  options,
864
- fromWebSocket = false,
841
+ fieldsToUpdate,
865
842
  }: {
866
843
  group: string;
867
844
  id: string;
@@ -871,7 +848,9 @@ export class FeedsClient extends FeedsApi {
871
848
  addNewActivitiesTo?: 'start' | 'end';
872
849
  activityAddedEventFilter?: (event: ActivityAddedEvent) => boolean;
873
850
  };
874
- fromWebSocket?: boolean;
851
+ fieldsToUpdate: Array<
852
+ 'own_capabilities' | 'own_follows' | 'own_followings' | 'own_membership'
853
+ >;
875
854
  }) => {
876
855
  const fid = `${group}:${id}`;
877
856
  let isCreated = false;
@@ -909,18 +888,35 @@ export class FeedsClient extends FeedsApi {
909
888
  handleFeedUpdated.call(feed, { feed: data });
910
889
  } else if (
911
890
  (feed.currentState.updated_at?.getTime() ?? 0) ===
912
- data.updated_at.getTime() &&
913
- !fromWebSocket
891
+ data.updated_at.getTime()
914
892
  ) {
915
- const fieldsToUpdate: Array<keyof FeedResponse> = [];
916
- if (!isOwnFollowsEqual(feed.currentState, data)) {
917
- fieldsToUpdate.push('own_follows');
918
- }
919
- if (!isOwnMembershipEqual(feed.currentState, data)) {
920
- fieldsToUpdate.push('own_membership');
921
- }
922
- if (fieldsToUpdate.length > 0) {
923
- const fieldsToUpdateData = fieldsToUpdate.reduce(
893
+ const fieldsToUpdateData: Array<keyof FeedResponse> = [];
894
+ const fieldChecks: Array<
895
+ [
896
+ (
897
+ | 'own_capabilities'
898
+ | 'own_follows'
899
+ | 'own_membership'
900
+ | 'own_followings'
901
+ ),
902
+ (currentState: FeedState, newState: FeedResponse) => boolean,
903
+ ]
904
+ > = [
905
+ ['own_capabilities', isOwnCapabilitiesEqual],
906
+ ['own_follows', isOwnFollowsEqual],
907
+ ['own_membership', isOwnMembershipEqual],
908
+ ['own_followings', isOwnFollowingsEqual],
909
+ ];
910
+ fieldChecks.forEach(([field, isEqual]) => {
911
+ if (
912
+ fieldsToUpdate.includes(field) &&
913
+ !isEqual(feed.currentState, data)
914
+ ) {
915
+ fieldsToUpdateData.push(field);
916
+ }
917
+ });
918
+ if (fieldsToUpdateData.length > 0) {
919
+ const fieldsToUpdatePayload = fieldsToUpdateData.reduce(
924
920
  (acc: Partial<FeedResponse>, field) => {
925
921
  // @ts-expect-error TODO: fix this
926
922
  acc[field] = data[field];
@@ -928,7 +924,7 @@ export class FeedsClient extends FeedsApi {
928
924
  },
929
925
  {},
930
926
  );
931
- feed.state.partialNext(fieldsToUpdateData);
927
+ feed.state.partialNext(fieldsToUpdatePayload);
932
928
  }
933
929
  }
934
930
  }
@@ -1,25 +1,25 @@
1
1
  import type { FeedState } from '../feed';
2
- import type { FeedResponse } from '../gen/models';
2
+ import type { FeedResponse, FollowResponse } from '../gen/models';
3
3
 
4
- export const isOwnFollowsEqual = (
5
- currentState: FeedState,
6
- newState: FeedResponse,
7
- ) => {
4
+ const areFollowArraysEqual = (
5
+ currentFollows: FollowResponse[] | undefined,
6
+ newFollows: FollowResponse[] | undefined,
7
+ ): boolean => {
8
8
  const existingFollows = new Set(
9
- currentState.own_follows?.map(
9
+ currentFollows?.map(
10
10
  (f) =>
11
11
  `${f.source_feed.feed}:${f.target_feed.feed}:${f.updated_at.getTime()}`,
12
12
  ),
13
13
  );
14
- const newFollows = new Set(
15
- newState.own_follows?.map(
14
+ const newFollowsSet = new Set(
15
+ newFollows?.map(
16
16
  (f) =>
17
17
  `${f.source_feed.feed}:${f.target_feed.feed}:${f.updated_at.getTime()}`,
18
18
  ),
19
19
  );
20
- if (existingFollows.size === newFollows.size) {
20
+ if (existingFollows.size === newFollowsSet.size) {
21
21
  const areEqual = Array.from(existingFollows).every((f) =>
22
- newFollows.has(f),
22
+ newFollowsSet.has(f),
23
23
  );
24
24
  if (areEqual) {
25
25
  return true;
@@ -29,6 +29,13 @@ export const isOwnFollowsEqual = (
29
29
  return false;
30
30
  };
31
31
 
32
+ export const isOwnFollowsEqual = (
33
+ currentState: FeedState,
34
+ newState: FeedResponse,
35
+ ) => {
36
+ return areFollowArraysEqual(currentState.own_follows, newState.own_follows);
37
+ };
38
+
32
39
  export const isOwnMembershipEqual = (
33
40
  currentState: FeedState,
34
41
  newState: FeedResponse,
@@ -38,3 +45,23 @@ export const isOwnMembershipEqual = (
38
45
  (newState.own_membership?.updated_at.getTime() ?? 0)
39
46
  );
40
47
  };
48
+
49
+ export const isOwnCapabilitiesEqual = (
50
+ currentState: FeedState,
51
+ newState: FeedResponse,
52
+ ) => {
53
+ return (
54
+ [...(currentState.own_capabilities ?? [])].sort().join(',') ===
55
+ [...(newState.own_capabilities ?? [])].sort().join(',')
56
+ );
57
+ };
58
+
59
+ export const isOwnFollowingsEqual = (
60
+ currentState: FeedState,
61
+ newState: FeedResponse,
62
+ ) => {
63
+ return areFollowArraysEqual(
64
+ currentState.own_followings,
65
+ newState.own_followings,
66
+ );
67
+ };
@@ -1,2 +1,2 @@
1
1
  export * from './throttle';
2
- export * from './throttled-get-batched-own-capabilities'
2
+ export * from './throttled-get-batched-own-fields';