@stream-io/feeds-client 0.3.41 → 0.3.43
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.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react-bindings.js +1 -1
- package/dist/es/index.mjs +2 -2
- package/dist/es/index.mjs.map +1 -1
- package/dist/es/react-bindings.mjs +1 -1
- package/dist/{feeds-client-KQeNaHO_.js → feeds-client-B1mzWjrM.js} +46 -11
- package/dist/feeds-client-B1mzWjrM.js.map +1 -0
- package/dist/{feeds-client-CCo39BpN.mjs → feeds-client-O3lXU8Bl.mjs} +46 -11
- package/dist/feeds-client-O3lXU8Bl.mjs.map +1 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/types/common/real-time/event-models.d.ts +2 -2
- package/dist/types/common/real-time/event-models.d.ts.map +1 -1
- package/dist/types/feed/feed.d.ts.map +1 -1
- package/dist/types/feeds-client/feeds-client.d.ts.map +1 -1
- package/dist/types/gen/models/index.d.ts +8 -0
- package/dist/types/gen/models/index.d.ts.map +1 -1
- package/dist/types/utils/retry.d.ts +25 -0
- package/dist/types/utils/retry.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/common/real-time/event-models.ts +2 -2
- package/src/feed/feed.ts +2 -1
- package/src/feeds-client/feeds-client.ts +11 -8
- package/src/gen/models/index.ts +14 -0
- package/src/utils/retry.ts +76 -0
- package/dist/feeds-client-CCo39BpN.mjs.map +0 -1
- package/dist/feeds-client-KQeNaHO_.js.map +0 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type RetryOptions = {
|
|
2
|
+
maxRetries?: number;
|
|
3
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Checks if an error is a client error (4xx HTTP status code) that should not be retried.
|
|
7
|
+
* Client errors indicate issues with the request itself (bad input, unauthorized, not found, etc.)
|
|
8
|
+
* and retrying won't help.
|
|
9
|
+
*
|
|
10
|
+
* @param error - The error to check
|
|
11
|
+
* @returns true if the error should be retried, false if it's a client error
|
|
12
|
+
*/
|
|
13
|
+
export declare function isRetryableError(error: unknown): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Wraps an async function with automatic retry logic using exponential backoff.
|
|
16
|
+
*
|
|
17
|
+
* By default, retries are performed for server errors (5xx) and network errors,
|
|
18
|
+
* but not for client errors (4xx) which indicate issues with the request itself.
|
|
19
|
+
*
|
|
20
|
+
* @param fn - The async function to retry
|
|
21
|
+
* @param options - Retry options
|
|
22
|
+
* @returns The result of the function
|
|
23
|
+
*/
|
|
24
|
+
export declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
25
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../../src/utils/retry.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5D,CAAC;AAIF;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CASxD;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,CAAC,CAAC,CAiCZ"}
|
package/package.json
CHANGED
|
@@ -40,7 +40,7 @@ export interface ConnectedEvent {
|
|
|
40
40
|
|
|
41
41
|
export enum UnhandledErrorType {
|
|
42
42
|
ReconnectionReconciliation = 'reconnection-reconciliation',
|
|
43
|
-
|
|
43
|
+
FetchingOwnFieldsOnNewActivity = 'fetching-own-fields-on-new-activity',
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export type SyncFailure = {
|
|
@@ -58,7 +58,7 @@ export type UnhandledErrorEvent = {
|
|
|
58
58
|
failures: SyncFailure[];
|
|
59
59
|
}
|
|
60
60
|
| {
|
|
61
|
-
error_type: UnhandledErrorType.
|
|
61
|
+
error_type: UnhandledErrorType.FetchingOwnFieldsOnNewActivity;
|
|
62
62
|
error: StreamApiError;
|
|
63
63
|
}
|
|
64
64
|
);
|
package/src/feed/feed.ts
CHANGED
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
feedsLoggerSystem,
|
|
67
67
|
uniqueArrayMerge,
|
|
68
68
|
} from '../utils';
|
|
69
|
+
import { withRetry } from '../utils/retry';
|
|
69
70
|
import { handleActivityFeedback } from './event-handlers/activity/handle-activity-feedback';
|
|
70
71
|
import { deepEqual } from '../utils/deep-equal';
|
|
71
72
|
import { getOrCreateActiveFeed } from '../feeds-client/get-or-create-active-feed';
|
|
@@ -294,7 +295,7 @@ export class Feed extends FeedApi {
|
|
|
294
295
|
const { last_get_or_create_request_config } = this.state.getLatestValue();
|
|
295
296
|
if (last_get_or_create_request_config?.watch) {
|
|
296
297
|
this.inProgressGetOrCreate = undefined;
|
|
297
|
-
await this.getOrCreate(last_get_or_create_request_config);
|
|
298
|
+
await withRetry(() => this.getOrCreate(last_get_or_create_request_config));
|
|
298
299
|
}
|
|
299
300
|
}
|
|
300
301
|
|
|
@@ -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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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) {
|
package/src/gen/models/index.ts
CHANGED
|
@@ -4078,6 +4078,18 @@ export interface NotificationFeedUpdatedEvent {
|
|
|
4078
4078
|
user?: UserResponseCommonFields;
|
|
4079
4079
|
}
|
|
4080
4080
|
|
|
4081
|
+
export interface NotificationParentActivity {
|
|
4082
|
+
id: string;
|
|
4083
|
+
|
|
4084
|
+
text?: string;
|
|
4085
|
+
|
|
4086
|
+
type?: string;
|
|
4087
|
+
|
|
4088
|
+
user_id?: string;
|
|
4089
|
+
|
|
4090
|
+
attachments?: Attachment[];
|
|
4091
|
+
}
|
|
4092
|
+
|
|
4081
4093
|
export interface NotificationStatusResponse {
|
|
4082
4094
|
unread: number;
|
|
4083
4095
|
|
|
@@ -4106,6 +4118,8 @@ export interface NotificationTarget {
|
|
|
4106
4118
|
attachments?: Attachment[];
|
|
4107
4119
|
|
|
4108
4120
|
comment?: NotificationComment;
|
|
4121
|
+
|
|
4122
|
+
parent_activity?: NotificationParentActivity;
|
|
4109
4123
|
}
|
|
4110
4124
|
|
|
4111
4125
|
export interface NotificationTrigger {
|
|
@@ -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
|
+
}
|