@camera.ui/transport 0.0.23 → 0.0.25
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/effects/tokenLifecycle.d.ts +5 -2
- package/dist/index.js +20 -12
- package/dist/race.d.ts +3 -2
- package/package.json +2 -2
|
@@ -5,11 +5,13 @@ export type RefreshReason = 'proactive' | 'auth-error';
|
|
|
5
5
|
export interface TokenLifecycleOptions {
|
|
6
6
|
readonly kernel: Kernel;
|
|
7
7
|
readonly transports: readonly Transport[];
|
|
8
|
-
readonly refresh: (target: ConnectionTarget, reason: RefreshReason) => Promise<Tokens>;
|
|
9
8
|
readonly graceMs?: number;
|
|
10
|
-
readonly isTransientError?: (err: unknown) => boolean;
|
|
11
9
|
readonly maxTransientRetries?: number;
|
|
12
10
|
readonly transientRetryDelayMs?: number;
|
|
11
|
+
readonly maxServerErrorRetries?: number;
|
|
12
|
+
readonly refresh: (target: ConnectionTarget, reason: RefreshReason) => Promise<Tokens>;
|
|
13
|
+
readonly isTransientError?: (err: unknown) => boolean;
|
|
14
|
+
readonly isServerError?: (err: unknown) => boolean;
|
|
13
15
|
readonly now?: () => number;
|
|
14
16
|
readonly setTimer?: (cb: () => void, ms: number) => unknown;
|
|
15
17
|
readonly clearTimer?: (handle: unknown) => void;
|
|
@@ -20,6 +22,7 @@ export interface TokenLifecycleOptions {
|
|
|
20
22
|
readonly onRefreshSkipped?: (reason: RefreshReason, tokens: Tokens) => void;
|
|
21
23
|
readonly onRefreshError?: (reason: RefreshReason, error: unknown, info: {
|
|
22
24
|
transient: boolean;
|
|
25
|
+
serverError: boolean;
|
|
23
26
|
retriesLeft: number;
|
|
24
27
|
willRetry: boolean;
|
|
25
28
|
}) => void;
|
package/dist/index.js
CHANGED
|
@@ -192,7 +192,7 @@ var RaceFirstError = class extends Error {
|
|
|
192
192
|
}
|
|
193
193
|
};
|
|
194
194
|
function raceFirst(candidates, options = {}) {
|
|
195
|
-
const { timeoutByMode = (mode) => DEFAULT_RACE_TIMEOUT_BY_MODE[mode] ?? 5e3, shortCircuit, parentSignal, prefer } = options;
|
|
195
|
+
const { timeoutByMode = (mode) => DEFAULT_RACE_TIMEOUT_BY_MODE[mode] ?? 5e3, shortCircuit, informative, parentSignal, prefer } = options;
|
|
196
196
|
const preferGraceMs = options.preferGraceMs ?? DEFAULT_PREFER_GRACE_MS;
|
|
197
197
|
return new Promise((resolve, reject) => {
|
|
198
198
|
if (candidates.length === 0) {
|
|
@@ -215,7 +215,7 @@ function raceFirst(candidates, options = {}) {
|
|
|
215
215
|
const timers = [];
|
|
216
216
|
function cleanupAllExcept(except) {
|
|
217
217
|
for (const t of timers) clearTimeout(t);
|
|
218
|
-
for (const a of abortControllers) if (a !== except) a.abort();
|
|
218
|
+
for (const a of abortControllers) if (a !== except) a.abort("race-settled");
|
|
219
219
|
}
|
|
220
220
|
function finishSuccess(endpoint, value, except) {
|
|
221
221
|
if (settled) return;
|
|
@@ -259,7 +259,7 @@ function raceFirst(candidates, options = {}) {
|
|
|
259
259
|
const ctrl = new AbortController();
|
|
260
260
|
abortControllers.push(ctrl);
|
|
261
261
|
const delay = timeoutByMode(cand.endpoint.mode);
|
|
262
|
-
const timer = setTimeout(() => ctrl.abort(), delay);
|
|
262
|
+
const timer = setTimeout(() => ctrl.abort("race-timeout"), delay);
|
|
263
263
|
timers.push(timer);
|
|
264
264
|
cand.run(ctrl.signal).then((value) => handleSuccess(cand.endpoint, value, ctrl), (err) => {
|
|
265
265
|
if (settled) {
|
|
@@ -282,10 +282,11 @@ function raceFirst(candidates, options = {}) {
|
|
|
282
282
|
}
|
|
283
283
|
remaining--;
|
|
284
284
|
if (remaining <= 0) {
|
|
285
|
-
const
|
|
285
|
+
const usable = [...lastErrors.entries()].filter(([, e]) => {
|
|
286
286
|
if (e instanceof RaceFirstError) return e.kind !== "all-failed" || !(e.cause instanceof Error && e.cause.message === "aborted");
|
|
287
287
|
return true;
|
|
288
|
-
})
|
|
288
|
+
});
|
|
289
|
+
const [endpoint, cause] = (informative ? usable.find(([, e]) => informative(e instanceof RaceFirstError ? e.cause : e)) : void 0) ?? usable[0] ?? [cand.endpoint, finalErr];
|
|
289
290
|
finishFail(new RaceFirstError("raceFirst: all candidates failed", endpoint, cause, "all-failed"));
|
|
290
291
|
}
|
|
291
292
|
});
|
|
@@ -778,9 +779,9 @@ function attachProbeLoop(options) {
|
|
|
778
779
|
options.onProbeSuccess?.(endpoint, tokens);
|
|
779
780
|
return tokens;
|
|
780
781
|
} catch (err) {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
782
|
+
let finalErr = err;
|
|
783
|
+
if (signal.aborted && !ctrl.signal.aborted && !isProbeFailure(err)) if (signal.reason === "race-settled") finalErr = makeProbeFailure("transient", "superseded");
|
|
784
|
+
else finalErr = makeProbeFailure("transient", `timeout (${(options.timeoutByMode ?? ((m) => DEFAULT_RACE_TIMEOUT_BY_MODE[m] ?? 5e3))(endpoint.mode)}ms)`);
|
|
784
785
|
options.onProbeError?.(endpoint, finalErr);
|
|
785
786
|
throw finalErr;
|
|
786
787
|
}
|
|
@@ -792,7 +793,8 @@ function attachProbeLoop(options) {
|
|
|
792
793
|
parentSignal: ctrl.signal,
|
|
793
794
|
prefer: options.prefer,
|
|
794
795
|
preferGraceMs: options.preferGraceMs,
|
|
795
|
-
shortCircuit: (err) => isProbeFailure(err) && (err.kind === "needs-auth" || err.kind === "fatal"
|
|
796
|
+
shortCircuit: (err) => isProbeFailure(err) && (err.kind === "needs-auth" || err.kind === "fatal"),
|
|
797
|
+
informative: (cause) => !(isProbeFailure(cause) && cause.kind === "aborted")
|
|
796
798
|
});
|
|
797
799
|
if (ctrl.signal.aborted) return;
|
|
798
800
|
options.kernel.dispatch({
|
|
@@ -836,9 +838,11 @@ var DEFAULT_TRANSIENT_RETRY_DELAY_MS = 2e3;
|
|
|
836
838
|
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
837
839
|
function attachTokenLifecycle(options) {
|
|
838
840
|
const graceMs = options.graceMs ?? DEFAULT_GRACE_MS;
|
|
839
|
-
const isTransient = options.isTransientError ?? (() => false);
|
|
840
841
|
const maxTransientRetries = options.maxTransientRetries ?? DEFAULT_MAX_TRANSIENT_RETRIES;
|
|
842
|
+
const maxServerErrorRetries = options.maxServerErrorRetries ?? maxTransientRetries;
|
|
841
843
|
const transientRetryDelayMs = options.transientRetryDelayMs ?? DEFAULT_TRANSIENT_RETRY_DELAY_MS;
|
|
844
|
+
const isTransient = options.isTransientError ?? (() => false);
|
|
845
|
+
const isServerError = options.isServerError ?? (() => false);
|
|
842
846
|
const now = options.now ?? (() => Date.now());
|
|
843
847
|
const setTimer = options.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
844
848
|
const clearTimer = options.clearTimer ?? ((h) => clearTimeout(h));
|
|
@@ -926,11 +930,14 @@ function attachTokenLifecycle(options) {
|
|
|
926
930
|
} catch (err) {
|
|
927
931
|
if (detached) return;
|
|
928
932
|
const transient = isTransient(err);
|
|
929
|
-
|
|
933
|
+
const serverError = transient && isServerError(err);
|
|
934
|
+
const budget = serverError ? maxServerErrorRetries : maxTransientRetries;
|
|
935
|
+
if (transient && transientRetries < budget) {
|
|
930
936
|
transientRetries++;
|
|
931
|
-
const retriesLeft =
|
|
937
|
+
const retriesLeft = budget - transientRetries;
|
|
932
938
|
options.onRefreshError?.(reason, err, {
|
|
933
939
|
transient: true,
|
|
940
|
+
serverError,
|
|
934
941
|
retriesLeft,
|
|
935
942
|
willRetry: true
|
|
936
943
|
});
|
|
@@ -943,6 +950,7 @@ function attachTokenLifecycle(options) {
|
|
|
943
950
|
transientRetries = 0;
|
|
944
951
|
options.onRefreshError?.(reason, err, {
|
|
945
952
|
transient,
|
|
953
|
+
serverError,
|
|
946
954
|
retriesLeft: 0,
|
|
947
955
|
willRetry: false
|
|
948
956
|
});
|
package/dist/race.d.ts
CHANGED
|
@@ -6,11 +6,12 @@ export interface RaceCandidate<T> {
|
|
|
6
6
|
readonly run: (signal: AbortSignal) => Promise<T>;
|
|
7
7
|
}
|
|
8
8
|
export interface RaceFirstOptions {
|
|
9
|
+
readonly parentSignal?: AbortSignal;
|
|
10
|
+
readonly preferGraceMs?: number;
|
|
9
11
|
readonly timeoutByMode?: TimeoutByModeFn;
|
|
10
12
|
readonly shortCircuit?: (err: unknown) => boolean;
|
|
11
|
-
readonly
|
|
13
|
+
readonly informative?: (cause: unknown) => boolean;
|
|
12
14
|
readonly prefer?: (endpoint: Endpoint) => boolean;
|
|
13
|
-
readonly preferGraceMs?: number;
|
|
14
15
|
}
|
|
15
16
|
export interface RaceFirstResult<T> {
|
|
16
17
|
readonly endpoint: Endpoint;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camera.ui/transport",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"description": "camera.ui transport layer — framework-agnostic connection kernel, reducer state, pluggable transports (HTTP/WS/Socket.IO/NATS), lifecycle effects and worker bridge",
|
|
5
5
|
"author": "seydx (https://github.com/cameraui/clients)",
|
|
6
6
|
"type": "module",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"@typescript-eslint/parser": "^8.65.0",
|
|
67
67
|
"@vue/language-core": "^3.3.8",
|
|
68
68
|
"eslint": "9.39.2",
|
|
69
|
-
"globals": "^17.
|
|
69
|
+
"globals": "^17.8.0",
|
|
70
70
|
"jiti": "^2.7.0",
|
|
71
71
|
"prettier": "^3.9.6",
|
|
72
72
|
"rimraf": "^6.1.3",
|