@spectrum-ts/core 5.2.0 → 6.1.0
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/dist/authoring.d.ts +12 -0
- package/dist/authoring.js +37 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/authoring.d.ts
CHANGED
|
@@ -69,6 +69,18 @@ interface ResumableOrderedStreamOptions<TLive, TMissed, TOutput> {
|
|
|
69
69
|
maxRetryDelayMs?: number;
|
|
70
70
|
processLive: (event: TLive) => Promise<ResumableStreamItem<TOutput>>;
|
|
71
71
|
processMissed: (event: TMissed) => Promise<ResumableStreamItem<TOutput>>;
|
|
72
|
+
/**
|
|
73
|
+
* Invoked when reconnects have failed persistently (the consecutive-failure
|
|
74
|
+
* count has reached a multiple of `PERSISTENT_FAILURE_ERROR_THRESHOLD`) to
|
|
75
|
+
* rebuild state a plain reconnect cannot refresh — e.g. re-mint an auth token
|
|
76
|
+
* the server rejects after a restart. Runs between the failure and the next
|
|
77
|
+
* retry, throttled to once per threshold window, and resets once the stream
|
|
78
|
+
* recovers. Errors thrown are logged and swallowed; the reconnect loop
|
|
79
|
+
* continues either way, so a recover that itself fails is harmless.
|
|
80
|
+
*/
|
|
81
|
+
recover?: (error: unknown, failureCount: number) => Promise<void> | void;
|
|
82
|
+
/** Cap on how long a `recover` hook may run before it is abandoned; injectable for tests. */
|
|
83
|
+
recoverTimeoutMs?: number;
|
|
72
84
|
subscribeLive: (cursor?: string) => CloseableAsyncIterable<TLive>;
|
|
73
85
|
}
|
|
74
86
|
/**
|
package/dist/authoring.js
CHANGED
|
@@ -147,6 +147,18 @@ const closeIterable = async (iterable) => {
|
|
|
147
147
|
await iterable.close?.();
|
|
148
148
|
};
|
|
149
149
|
const ignoreCleanupError = () => void 0;
|
|
150
|
+
const RECOVER_TIMEOUT_MS = 3e4;
|
|
151
|
+
const runWithTimeout = async (work, timeoutMs, onTimeout) => {
|
|
152
|
+
work.catch(ignoreCleanupError);
|
|
153
|
+
let timer;
|
|
154
|
+
try {
|
|
155
|
+
await Promise.race([work, new Promise((_resolve, reject) => {
|
|
156
|
+
timer = setTimeout(() => reject(onTimeout()), timeoutMs);
|
|
157
|
+
})]);
|
|
158
|
+
} finally {
|
|
159
|
+
if (timer) clearTimeout(timer);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
150
162
|
const jitterDelay = (delayMs) => delayMs * (.5 + Math.random() * .5);
|
|
151
163
|
async function* throwOnCursorRejection(source, isCursorRejected) {
|
|
152
164
|
try {
|
|
@@ -178,10 +190,12 @@ const resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
178
190
|
const initialRetryDelayMs = options.initialRetryDelayMs ?? 500;
|
|
179
191
|
const maxRetryDelayMs = options.maxRetryDelayMs ?? 3e4;
|
|
180
192
|
const jitter = options.jitter ?? jitterDelay;
|
|
193
|
+
const recoverTimeoutMs = options.recoverTimeoutMs ?? RECOVER_TIMEOUT_MS;
|
|
181
194
|
const label = options.label;
|
|
182
195
|
let activeLive;
|
|
183
196
|
let closed = false;
|
|
184
197
|
let failedAttempts = 0;
|
|
198
|
+
let lastRecoverAttempt = 0;
|
|
185
199
|
let lastCursor;
|
|
186
200
|
let retryDelayMs = initialRetryDelayMs;
|
|
187
201
|
let sleepTimer;
|
|
@@ -197,6 +211,7 @@ const resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
197
211
|
});
|
|
198
212
|
const noteRecovery = () => {
|
|
199
213
|
retryDelayMs = initialRetryDelayMs;
|
|
214
|
+
lastRecoverAttempt = 0;
|
|
200
215
|
if (failedAttempts === 0) return;
|
|
201
216
|
log.info("stream recovered", {
|
|
202
217
|
"spectrum.stream.id": streamId,
|
|
@@ -267,6 +282,25 @@ const resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
267
282
|
log.warn("stream interrupted; reconnecting", attrs, error);
|
|
268
283
|
return delayMs;
|
|
269
284
|
};
|
|
285
|
+
const maybeRecover = async (error) => {
|
|
286
|
+
if (!options.recover || failedAttempts < 5 || failedAttempts - lastRecoverAttempt < 5) return;
|
|
287
|
+
lastRecoverAttempt = failedAttempts;
|
|
288
|
+
try {
|
|
289
|
+
await runWithTimeout(Promise.resolve(options.recover(error, failedAttempts)), recoverTimeoutMs, () => /* @__PURE__ */ new Error(`recover hook did not settle within ${recoverTimeoutMs}ms`));
|
|
290
|
+
log.info("stream recover hook ran", {
|
|
291
|
+
"spectrum.stream.id": streamId,
|
|
292
|
+
"spectrum.stream.label": label,
|
|
293
|
+
"spectrum.stream.attempt": failedAttempts
|
|
294
|
+
});
|
|
295
|
+
} catch (recoverError) {
|
|
296
|
+
log.warn("stream recover hook failed", {
|
|
297
|
+
"spectrum.stream.id": streamId,
|
|
298
|
+
"spectrum.stream.label": label,
|
|
299
|
+
"spectrum.stream.attempt": failedAttempts,
|
|
300
|
+
...errorAttrs(recoverError)
|
|
301
|
+
}, recoverError);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
270
304
|
const consumeLive = async () => {
|
|
271
305
|
const live = options.subscribeLive(lastCursor);
|
|
272
306
|
activeLive = live;
|
|
@@ -366,7 +400,9 @@ const resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
366
400
|
await closeIterable(activeLive).catch(ignoreCleanupError);
|
|
367
401
|
activeLive = void 0;
|
|
368
402
|
if (closed) break;
|
|
369
|
-
|
|
403
|
+
const delayMs = handleFailure(error, phase);
|
|
404
|
+
await maybeRecover(error);
|
|
405
|
+
await sleep(delayMs);
|
|
370
406
|
}
|
|
371
407
|
}
|
|
372
408
|
end();
|
package/dist/index.js
CHANGED
|
@@ -2994,7 +2994,7 @@ function definePlatform(name, rawDef) {
|
|
|
2994
2994
|
}
|
|
2995
2995
|
//#endregion
|
|
2996
2996
|
//#region src/build-env.ts
|
|
2997
|
-
const SPECTRUM_SDK_VERSION = "
|
|
2997
|
+
const SPECTRUM_SDK_VERSION = "6.1.0";
|
|
2998
2998
|
//#endregion
|
|
2999
2999
|
//#region src/utils/provider-packages.ts
|
|
3000
3000
|
const OFFICIAL_PROVIDER_PACKAGES = {
|