@stream-io/feeds-client 0.3.42 → 0.3.44

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.
@@ -92,6 +92,7 @@ import {
92
92
  type GetBatchedOwnFieldsThrottledCallback,
93
93
  DEFAULT_BATCH_OWN_FIELDS_THROTTLING_INTERVAL,
94
94
  } from '../utils/throttling';
95
+ import { withRetry } from '../utils/retry';
95
96
  import { ActivityWithStateUpdates } from '../activity-with-state-updates/activity-with-state-updates';
96
97
  import { getFeed } from '../activity-with-state-updates/get-feed';
97
98
  import {
@@ -291,8 +292,7 @@ export class FeedsClient extends FeedsApi {
291
292
  }).catch((error) => {
292
293
  this.eventDispatcher.dispatch({
293
294
  type: 'errors.unhandled',
294
- error_type:
295
- UnhandledErrorType.FetchingOwnCapabilitiesOnNewActivity,
295
+ error_type: UnhandledErrorType.FetchingOwnFieldsOnNewActivity,
296
296
  error,
297
297
  });
298
298
  });
@@ -328,11 +328,13 @@ export class FeedsClient extends FeedsApi {
328
328
  return [{ feed, reason: result.reason, activity_id: activity?.id }];
329
329
  });
330
330
 
331
- this.eventDispatcher.dispatch({
332
- type: 'errors.unhandled',
333
- error_type: UnhandledErrorType.ReconnectionReconciliation,
334
- failures,
335
- });
331
+ if (failures.length > 0) {
332
+ this.eventDispatcher.dispatch({
333
+ type: 'errors.unhandled',
334
+ error_type: UnhandledErrorType.ReconnectionReconciliation,
335
+ failures,
336
+ });
337
+ }
336
338
  }
337
339
  };
338
340
 
@@ -620,6 +622,7 @@ export class FeedsClient extends FeedsApi {
620
622
 
621
623
  this.activeActivities = [];
622
624
  this.activeFeeds = {};
625
+ this.healthyConnectionChangedEventCount = 0;
623
626
 
624
627
  this.state.partialNext(this.initialState);
625
628
 
@@ -704,7 +707,7 @@ export class FeedsClient extends FeedsApi {
704
707
  }
705
708
 
706
709
  async ownBatch(request: OwnBatchRequest) {
707
- const response = await super.ownBatch(request);
710
+ const response = await withRetry(() => super.ownBatch(request));
708
711
  Object.entries(response.data).forEach(([fid, ownFields]) => {
709
712
  const feed = this.activeFeeds[fid];
710
713
  if (feed) {
@@ -313,6 +313,7 @@ export class FeedsApi {
313
313
  const body = {
314
314
  type: request?.type,
315
315
  feeds: request?.feeds,
316
+ copy_custom_to_notification: request?.copy_custom_to_notification,
316
317
  create_notification_activity: request?.create_notification_activity,
317
318
  expires_at: request?.expires_at,
318
319
  id: request?.id,
@@ -599,6 +600,7 @@ export class FeedsApi {
599
600
  };
600
601
  const body = {
601
602
  type: request?.type,
603
+ copy_custom_to_notification: request?.copy_custom_to_notification,
602
604
  create_notification_activity: request?.create_notification_activity,
603
605
  enforce_unique: request?.enforce_unique,
604
606
  skip_push: request?.skip_push,
@@ -723,6 +725,7 @@ export class FeedsApi {
723
725
  id: request?.id,
724
726
  };
725
727
  const body = {
728
+ copy_custom_to_notification: request?.copy_custom_to_notification,
726
729
  handle_mention_notifications: request?.handle_mention_notifications,
727
730
  run_activity_processors: request?.run_activity_processors,
728
731
  unset: request?.unset,
@@ -752,6 +755,7 @@ export class FeedsApi {
752
755
  id: request?.id,
753
756
  };
754
757
  const body = {
758
+ copy_custom_to_notification: request?.copy_custom_to_notification,
755
759
  expires_at: request?.expires_at,
756
760
  handle_mention_notifications: request?.handle_mention_notifications,
757
761
  poll_id: request?.poll_id,
@@ -1026,6 +1030,7 @@ export class FeedsApi {
1026
1030
  ): Promise<StreamResponse<AddCommentResponse>> {
1027
1031
  const body = {
1028
1032
  comment: request?.comment,
1033
+ copy_custom_to_notification: request?.copy_custom_to_notification,
1029
1034
  create_notification_activity: request?.create_notification_activity,
1030
1035
  id: request?.id,
1031
1036
  object_id: request?.object_id,
@@ -1150,6 +1155,7 @@ export class FeedsApi {
1150
1155
  };
1151
1156
  const body = {
1152
1157
  comment: request?.comment,
1158
+ copy_custom_to_notification: request?.copy_custom_to_notification,
1153
1159
  handle_mention_notifications: request?.handle_mention_notifications,
1154
1160
  skip_enrich_url: request?.skip_enrich_url,
1155
1161
  skip_push: request?.skip_push,
@@ -1182,6 +1188,7 @@ export class FeedsApi {
1182
1188
  };
1183
1189
  const body = {
1184
1190
  type: request?.type,
1191
+ copy_custom_to_notification: request?.copy_custom_to_notification,
1185
1192
  create_notification_activity: request?.create_notification_activity,
1186
1193
  enforce_unique: request?.enforce_unique,
1187
1194
  skip_push: request?.skip_push,
@@ -1740,6 +1747,7 @@ export class FeedsApi {
1740
1747
  const body = {
1741
1748
  source: request?.source,
1742
1749
  target: request?.target,
1750
+ copy_custom_to_notification: request?.copy_custom_to_notification,
1743
1751
  create_notification_activity: request?.create_notification_activity,
1744
1752
  follower_role: request?.follower_role,
1745
1753
  push_preference: request?.push_preference,
@@ -1769,6 +1777,7 @@ export class FeedsApi {
1769
1777
  const body = {
1770
1778
  source: request?.source,
1771
1779
  target: request?.target,
1780
+ copy_custom_to_notification: request?.copy_custom_to_notification,
1772
1781
  create_notification_activity: request?.create_notification_activity,
1773
1782
  push_preference: request?.push_preference,
1774
1783
  skip_push: request?.skip_push,
@@ -365,6 +365,8 @@ export interface ActivityRequest {
365
365
 
366
366
  feeds: string[];
367
367
 
368
+ copy_custom_to_notification?: boolean;
369
+
368
370
  create_notification_activity?: boolean;
369
371
 
370
372
  expires_at?: string;
@@ -565,6 +567,8 @@ export interface AddActivityRequest {
565
567
 
566
568
  feeds: string[];
567
569
 
570
+ copy_custom_to_notification?: boolean;
571
+
568
572
  create_notification_activity?: boolean;
569
573
 
570
574
  expires_at?: string;
@@ -629,6 +633,8 @@ export interface AddBookmarkResponse {
629
633
  export interface AddCommentReactionRequest {
630
634
  type: string;
631
635
 
636
+ copy_custom_to_notification?: boolean;
637
+
632
638
  create_notification_activity?: boolean;
633
639
 
634
640
  enforce_unique?: boolean;
@@ -651,6 +657,8 @@ export interface AddCommentReactionResponse {
651
657
  export interface AddCommentRequest {
652
658
  comment?: string;
653
659
 
660
+ copy_custom_to_notification?: boolean;
661
+
654
662
  create_notification_activity?: boolean;
655
663
 
656
664
  id?: string;
@@ -701,6 +709,8 @@ export interface AddFolderRequest {
701
709
  export interface AddReactionRequest {
702
710
  type: string;
703
711
 
712
+ copy_custom_to_notification?: boolean;
713
+
704
714
  create_notification_activity?: boolean;
705
715
 
706
716
  enforce_unique?: boolean;
@@ -3144,6 +3154,8 @@ export interface FollowRequest {
3144
3154
 
3145
3155
  target: string;
3146
3156
 
3157
+ copy_custom_to_notification?: boolean;
3158
+
3147
3159
  create_notification_activity?: boolean;
3148
3160
 
3149
3161
  push_preference?: 'all' | 'none';
@@ -5771,6 +5783,8 @@ export interface UnpinActivityResponse {
5771
5783
  }
5772
5784
 
5773
5785
  export interface UpdateActivityPartialRequest {
5786
+ copy_custom_to_notification?: boolean;
5787
+
5774
5788
  handle_mention_notifications?: boolean;
5775
5789
 
5776
5790
  run_activity_processors?: boolean;
@@ -5787,6 +5801,8 @@ export interface UpdateActivityPartialResponse {
5787
5801
  }
5788
5802
 
5789
5803
  export interface UpdateActivityRequest {
5804
+ copy_custom_to_notification?: boolean;
5805
+
5790
5806
  expires_at?: Date;
5791
5807
 
5792
5808
  handle_mention_notifications?: boolean;
@@ -5893,6 +5909,8 @@ export interface UpdateCollectionsResponse {
5893
5909
  export interface UpdateCommentRequest {
5894
5910
  comment?: string;
5895
5911
 
5912
+ copy_custom_to_notification?: boolean;
5913
+
5896
5914
  handle_mention_notifications?: boolean;
5897
5915
 
5898
5916
  skip_enrich_url?: boolean;
@@ -5955,6 +5973,8 @@ export interface UpdateFollowRequest {
5955
5973
 
5956
5974
  target: string;
5957
5975
 
5976
+ copy_custom_to_notification?: boolean;
5977
+
5958
5978
  create_notification_activity?: boolean;
5959
5979
 
5960
5980
  follower_role?: string;
@@ -0,0 +1,76 @@
1
+ import { retryInterval, sleep } from '../common/utils';
2
+ import { StreamApiError } from '../common/types';
3
+
4
+ export type RetryOptions = {
5
+ maxRetries?: number;
6
+ shouldRetry?: (error: unknown, attempt: number) => boolean;
7
+ };
8
+
9
+ const DEFAULT_MAX_RETRIES = 3;
10
+
11
+ /**
12
+ * Checks if an error is a client error (4xx HTTP status code) that should not be retried.
13
+ * Client errors indicate issues with the request itself (bad input, unauthorized, not found, etc.)
14
+ * and retrying won't help.
15
+ *
16
+ * @param error - The error to check
17
+ * @returns true if the error should be retried, false if it's a client error
18
+ */
19
+ export function isRetryableError(error: unknown): boolean {
20
+ if (error instanceof StreamApiError) {
21
+ const statusCode = error.metadata?.response_code;
22
+ // Don't retry on 4xx client errors
23
+ if (statusCode && statusCode >= 400 && statusCode < 500) {
24
+ return false;
25
+ }
26
+ }
27
+ return true;
28
+ }
29
+
30
+ /**
31
+ * Wraps an async function with automatic retry logic using exponential backoff.
32
+ *
33
+ * By default, retries are performed for server errors (5xx) and network errors,
34
+ * but not for client errors (4xx) which indicate issues with the request itself.
35
+ *
36
+ * @param fn - The async function to retry
37
+ * @param options - Retry options
38
+ * @returns The result of the function
39
+ */
40
+ export async function withRetry<T>(
41
+ fn: () => Promise<T>,
42
+ options: RetryOptions = {},
43
+ ): Promise<T> {
44
+ const { maxRetries = DEFAULT_MAX_RETRIES, shouldRetry } = options;
45
+
46
+ let lastError: unknown;
47
+
48
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
49
+ try {
50
+ return await fn();
51
+ } catch (error) {
52
+ lastError = error;
53
+
54
+ const isLastAttempt = attempt === maxRetries;
55
+
56
+ if (isLastAttempt) {
57
+ throw error;
58
+ }
59
+
60
+ // Use custom shouldRetry if provided, otherwise use default behavior
61
+ const shouldRetryResult = shouldRetry
62
+ ? shouldRetry(error, attempt)
63
+ : isRetryableError(error);
64
+
65
+ if (!shouldRetryResult) {
66
+ throw error;
67
+ }
68
+
69
+ const delay = retryInterval(attempt + 1);
70
+ await sleep(delay);
71
+ }
72
+ }
73
+
74
+ // This should never be reached, but TypeScript needs it
75
+ throw lastError;
76
+ }