@baukit/sync-client 0.5.0 → 0.5.1
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/README.md +43 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/scheduler.d.ts +33 -2
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +89 -9
- package/dist/scheduler.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,11 +74,50 @@ The environment reports foreground state and connectivity. The scheduler runs on
|
|
|
74
74
|
app returns to the foreground, when connectivity returns, and on the interval. It installs no
|
|
75
75
|
interval while backgrounded and ignores connectivity events there.
|
|
76
76
|
|
|
77
|
-
A failing run reaches `onError` and does not stop scheduling.
|
|
78
|
-
|
|
77
|
+
A failing run reaches `onError` and does not stop scheduling. Without `retry`, the next attempt
|
|
78
|
+
waits for the next trigger.
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
### Retry and backoff
|
|
81
|
+
|
|
82
|
+
Pass `retry` to retry a failed run after a delay. The product decides which failures are worth a
|
|
83
|
+
retry and how long to wait, because only it knows its error types; the scheduler owns the attempt
|
|
84
|
+
count, the wait, and waking it early. `fullJitterBackoffMs` from `@baukit/api-runtime/backoff` is
|
|
85
|
+
the Baukit delay policy. The root entry of this package still imports nothing, so the product
|
|
86
|
+
passes the policy in:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { fullJitterBackoffMs } from '@baukit/api-runtime/backoff';
|
|
90
|
+
import { SyncRateLimitError, SyncScheduler, SyncTransportError } from '@baukit/sync-client';
|
|
91
|
+
|
|
92
|
+
const scheduler = new SyncScheduler(() => engine.syncOnce(), environment, {
|
|
93
|
+
retry: {
|
|
94
|
+
maxRetries: 4,
|
|
95
|
+
delayMs: (error, retryIndex) => {
|
|
96
|
+
if (error instanceof SyncRateLimitError) return Math.max(0, Date.parse(error.retryAt) - Date.now());
|
|
97
|
+
if (error instanceof SyncTransportError && !error.retryable) return null;
|
|
98
|
+
return fullJitterBackoffMs(retryIndex, { baseDelayMs: 500, maxDelayMs: 30_000 });
|
|
99
|
+
},
|
|
100
|
+
onRetryScheduled: ({ delayMs }) => status.showRetryAt(Date.now() + delayMs),
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
After a failed run the scheduler asks `delayMs(error, retryIndex)`. `null` means no retry. A number
|
|
106
|
+
is the wait in milliseconds, and `onRetryScheduled` hears about it before the wait starts. The
|
|
107
|
+
retry index counts from 0 and restarts after a success. After `maxRetries` retries the scheduler
|
|
108
|
+
waits for the next trigger. A delay that is negative or not finite, or a `delayMs` that throws,
|
|
109
|
+
reaches `onError` and ends the retries. A `maxRetries` that is not a non-negative integer throws a
|
|
110
|
+
`RangeError` from the constructor.
|
|
111
|
+
|
|
112
|
+
The wait is a one-shot timer built from the environment's `setInterval`, so no environment
|
|
113
|
+
changes. Any trigger during the wait ends it at once and restarts the retry index at 0: a manual
|
|
114
|
+
`trigger()`, the interval, returning to the foreground, or connectivity returning while in the
|
|
115
|
+
foreground. A follow-up request during the wait does not end it, because the retry will send the
|
|
116
|
+
same writes. `stop()` ends the wait without another run, and a wait that ends while the app is
|
|
117
|
+
backgrounded runs nothing until the app returns.
|
|
118
|
+
|
|
119
|
+
If the engine waits inside its own retry delay instead, pass `onRecoverySignal` to the scheduler
|
|
120
|
+
and wake that delay there. The scheduler calls it with `active` before a foreground-triggered run and with
|
|
82
121
|
`online` before an online-triggered run. Online recovery is reported even while the scheduler is
|
|
83
122
|
backgrounded, although the scheduler still waits for foreground before starting another run.
|
|
84
123
|
Retry wake-up belongs in this scheduler option, not in a decorated environment, because the
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type { HlcPhysicalClock, HlcStorage, HybridLogicalClockErrorCode, HybridL
|
|
|
5
5
|
export { dependencyRankByOrder, rankPushBatch, validatePushOutcomeCoverage } from './push-batch.js';
|
|
6
6
|
export type { PushCandidate, PushOutcomeCoverageOptions, RankedPushItem, RankPushBatchOptions, } from './push-batch.js';
|
|
7
7
|
export { SyncScheduler } from './scheduler.js';
|
|
8
|
-
export type { SyncSchedulerEnvironment, SyncSchedulerOptions, SyncSchedulerRecoverySignal, SyncSchedulerTimer, } from './scheduler.js';
|
|
8
|
+
export type { SyncSchedulerEnvironment, SyncSchedulerOptions, SyncSchedulerRecoverySignal, SyncSchedulerRetry, SyncSchedulerRetryOptions, SyncSchedulerTimer, } from './scheduler.js';
|
|
9
9
|
export { deriveInitialSyncState, deriveLocalStoreReadiness, SyncStatusStore } from './store.js';
|
|
10
10
|
export type { InitialPullStatus, InitialSyncState, LocalStoreReadiness, LocalStoreReadinessInput, SyncAttentionItem, SyncFailureUpdate, SyncStatus, SyncStatusHydration, SyncStatusSink, SyncStatusSnapshot, SyncStatusStoreOptions, } from './store.js';
|
|
11
11
|
export { commitCursorAfterLocalTransaction, DEFAULT_RETRY_AFTER_FALLBACK_MS, parseRetryAfter, SyncTransport, validatePullPage, } from './transport.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,0BAA0B,EAC1B,6BAA6B,EAC7B,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EACL,8BAA8B,EAC9B,4BAA4B,EAC5B,4BAA4B,EAC5B,kBAAkB,EAClB,uBAAuB,EACvB,4BAA4B,EAC5B,eAAe,EACf,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;AACpG,YAAY,EACV,aAAa,EACb,0BAA0B,EAC1B,cAAc,EACd,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,2BAA2B,EAC3B,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAChG,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iCAAiC,EACjC,+BAA+B,EAC/B,eAAe,EACf,aAAa,EACb,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,mBAAmB,EACnB,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EACpB,SAAS,EACT,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,mCAAmC,EACnC,kBAAkB,EAClB,eAAe,EACf,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,0BAA0B,EAC1B,6BAA6B,EAC7B,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EACL,8BAA8B,EAC9B,4BAA4B,EAC5B,4BAA4B,EAC5B,kBAAkB,EAClB,uBAAuB,EACvB,4BAA4B,EAC5B,eAAe,EACf,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;AACpG,YAAY,EACV,aAAa,EACb,0BAA0B,EAC1B,cAAc,EACd,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,2BAA2B,EAC3B,kBAAkB,EAClB,yBAAyB,EACzB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAChG,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iCAAiC,EACjC,+BAA+B,EAC/B,eAAe,EACf,aAAa,EACb,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,mBAAmB,EACnB,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EACpB,SAAS,EACT,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,mCAAmC,EACnC,kBAAkB,EAClB,eAAe,EACf,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,0BAA0B,EAC1B,6BAA6B,EAC7B,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,8BAA8B,EAC9B,4BAA4B,EAC5B,4BAA4B,EAC5B,kBAAkB,EAClB,uBAAuB,EACvB,4BAA4B,EAC5B,eAAe,EACf,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAOlB,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;AAOpG,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,0BAA0B,EAC1B,6BAA6B,EAC7B,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,8BAA8B,EAC9B,4BAA4B,EAC5B,4BAA4B,EAC5B,kBAAkB,EAClB,uBAAuB,EACvB,4BAA4B,EAC5B,eAAe,EACf,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAOlB,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;AAOpG,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAS/C,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAchG,OAAO,EACL,iCAAiC,EACjC,+BAA+B,EAC/B,eAAe,EACf,aAAa,EACb,gBAAgB,GACjB,MAAM,gBAAgB,CAAC"}
|
package/dist/scheduler.d.ts
CHANGED
|
@@ -16,10 +16,30 @@ export interface SyncSchedulerEnvironment {
|
|
|
16
16
|
export type SyncSchedulerTimer = {
|
|
17
17
|
readonly __syncSchedulerTimer?: never;
|
|
18
18
|
} & object;
|
|
19
|
+
/** A retry the scheduler is about to wait for. */
|
|
20
|
+
export interface SyncSchedulerRetry {
|
|
21
|
+
readonly error: unknown;
|
|
22
|
+
/** Counts retries since the last success or wake-up, from 0. */
|
|
23
|
+
readonly retryIndex: number;
|
|
24
|
+
readonly delayMs: number;
|
|
25
|
+
}
|
|
26
|
+
/** Retries a failed run after a delay the product chooses. */
|
|
27
|
+
export interface SyncSchedulerRetryOptions {
|
|
28
|
+
/** Retries after a failed run before the scheduler waits for the next trigger. */
|
|
29
|
+
readonly maxRetries: number;
|
|
30
|
+
/**
|
|
31
|
+
* The delay before retry `retryIndex`, or `null` when the failure must not be retried.
|
|
32
|
+
* `fullJitterBackoffMs` from `@baukit/api-runtime/backoff` gives bounded, jittered delays.
|
|
33
|
+
*/
|
|
34
|
+
readonly delayMs: (error: unknown, retryIndex: number) => number | null;
|
|
35
|
+
/** Runs before each wait, for example to show when the next attempt starts. */
|
|
36
|
+
readonly onRetryScheduled?: (retry: SyncSchedulerRetry) => void;
|
|
37
|
+
}
|
|
19
38
|
export interface SyncSchedulerOptions {
|
|
20
39
|
intervalMs?: number;
|
|
21
40
|
onError?: (error: unknown) => void;
|
|
22
41
|
onRecoverySignal?: (signal: SyncSchedulerRecoverySignal) => void;
|
|
42
|
+
retry?: SyncSchedulerRetryOptions;
|
|
23
43
|
}
|
|
24
44
|
export type SyncSchedulerRecoverySignal = 'active' | 'online';
|
|
25
45
|
/**
|
|
@@ -27,7 +47,9 @@ export type SyncSchedulerRecoverySignal = 'active' | 'online';
|
|
|
27
47
|
*
|
|
28
48
|
* A trigger that arrives while a run is active joins that run. A follow-up
|
|
29
49
|
* requested during a run replays the callback once after it settles, so writes
|
|
30
|
-
* made mid-run are never left unsent without starting a parallel run.
|
|
50
|
+
* made mid-run are never left unsent without starting a parallel run. With
|
|
51
|
+
* `retry`, a failed run is retried after the chosen delay; a trigger during
|
|
52
|
+
* that delay ends it and restarts the retry count.
|
|
31
53
|
*/
|
|
32
54
|
export declare class SyncScheduler {
|
|
33
55
|
private readonly run;
|
|
@@ -35,20 +57,29 @@ export declare class SyncScheduler {
|
|
|
35
57
|
private readonly intervalMs;
|
|
36
58
|
private readonly onError;
|
|
37
59
|
private readonly onRecoverySignal;
|
|
60
|
+
private readonly retry;
|
|
38
61
|
private active;
|
|
39
62
|
private started;
|
|
40
63
|
private inFlight;
|
|
41
64
|
private rerunRequested;
|
|
42
65
|
private interval;
|
|
43
66
|
private subscriptions;
|
|
67
|
+
private wakeRetry;
|
|
68
|
+
private stops;
|
|
44
69
|
constructor(run: () => Promise<unknown>, environment: SyncSchedulerEnvironment, options?: SyncSchedulerOptions);
|
|
45
70
|
start(): void;
|
|
46
71
|
stop(): void;
|
|
47
|
-
/** Starts a run, or joins the active one. */
|
|
72
|
+
/** Starts a run, or joins the active one and ends its retry delay. */
|
|
48
73
|
trigger(): Promise<void>;
|
|
49
74
|
/** Queues exactly one more run when a run is already active. */
|
|
50
75
|
requestFollowUp(): Promise<void>;
|
|
51
76
|
private runUntilSettled;
|
|
77
|
+
/** Read through a method: a follow-up can arrive while `run` is awaited. */
|
|
78
|
+
private followUpRequested;
|
|
79
|
+
private runOnce;
|
|
80
|
+
private retryDelay;
|
|
81
|
+
/** The environment has interval timers only, so the delay clears its interval on the first tick. */
|
|
82
|
+
private waitForRetry;
|
|
52
83
|
private refreshInterval;
|
|
53
84
|
private stopInterval;
|
|
54
85
|
}
|
package/dist/scheduler.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,IAAI,OAAO,CAAC;IACpB,eAAe,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACjE,eAAe,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAClD,WAAW,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,YAAY,EAAE,MAAM,GAAG,kBAAkB,CAAC;IAC5E,aAAa,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACjD;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAAE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG,MAAM,CAAC;AAEpF,MAAM,WAAW,oBAAoB;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,2BAA2B,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,IAAI,OAAO,CAAC;IACpB,eAAe,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACjE,eAAe,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAClD,WAAW,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,YAAY,EAAE,MAAM,GAAG,kBAAkB,CAAC;IAC5E,aAAa,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACjD;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAAE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG,MAAM,CAAC;AAEpF,kDAAkD;AAClD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,gEAAgE;IAChE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,8DAA8D;AAC9D,MAAM,WAAW,yBAAyB;IACxC,kFAAkF;IAClF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACxE,+EAA+E;IAC/E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACjE;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,2BAA2B,KAAK,IAAI,CAAC;IACjE,KAAK,CAAC,EAAE,yBAAyB,CAAC;CACnC;AAED,MAAM,MAAM,2BAA2B,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAmB9D;;;;;;;;GAQG;AACH,qBAAa,aAAa;IAetB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAf9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;IACnD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAgD;IACjF,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwC;IAC9D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,KAAK,CAAK;gBAGC,GAAG,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAC3B,WAAW,EAAE,wBAAwB,EACtD,OAAO,GAAE,oBAAyB;IASpC,KAAK,IAAI,IAAI;IA4Bb,IAAI,IAAI,IAAI;IAYZ,sEAAsE;IACtE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBxB,gEAAgE;IAChE,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;YAQlB,eAAe;IA0B7B,4EAA4E;IAC5E,OAAO,CAAC,iBAAiB;YAIX,OAAO;IAUrB,OAAO,CAAC,UAAU;IAkBlB,oGAAoG;IACpG,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,YAAY;CAOrB"}
|
package/dist/scheduler.js
CHANGED
|
@@ -2,12 +2,22 @@ const DEFAULT_INTERVAL_MS = 5 * 60 * 1000;
|
|
|
2
2
|
function noop() {
|
|
3
3
|
return;
|
|
4
4
|
}
|
|
5
|
+
function validateRetry(retry) {
|
|
6
|
+
if (retry === undefined) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (!Number.isInteger(retry.maxRetries) || retry.maxRetries < 0) {
|
|
10
|
+
throw new RangeError('retry.maxRetries must be a non-negative integer');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
5
13
|
/**
|
|
6
14
|
* Runs one opaque sync callback at most once at a time.
|
|
7
15
|
*
|
|
8
16
|
* A trigger that arrives while a run is active joins that run. A follow-up
|
|
9
17
|
* requested during a run replays the callback once after it settles, so writes
|
|
10
|
-
* made mid-run are never left unsent without starting a parallel run.
|
|
18
|
+
* made mid-run are never left unsent without starting a parallel run. With
|
|
19
|
+
* `retry`, a failed run is retried after the chosen delay; a trigger during
|
|
20
|
+
* that delay ends it and restarts the retry count.
|
|
11
21
|
*/
|
|
12
22
|
export class SyncScheduler {
|
|
13
23
|
run;
|
|
@@ -15,18 +25,23 @@ export class SyncScheduler {
|
|
|
15
25
|
intervalMs;
|
|
16
26
|
onError;
|
|
17
27
|
onRecoverySignal;
|
|
28
|
+
retry;
|
|
18
29
|
active = false;
|
|
19
30
|
started = false;
|
|
20
31
|
inFlight = null;
|
|
21
32
|
rerunRequested = false;
|
|
22
33
|
interval = null;
|
|
23
34
|
subscriptions = [];
|
|
35
|
+
wakeRetry = null;
|
|
36
|
+
stops = 0;
|
|
24
37
|
constructor(run, environment, options = {}) {
|
|
25
38
|
this.run = run;
|
|
26
39
|
this.environment = environment;
|
|
40
|
+
validateRetry(options.retry);
|
|
27
41
|
this.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
28
42
|
this.onError = options.onError ?? noop;
|
|
29
43
|
this.onRecoverySignal = options.onRecoverySignal ?? noop;
|
|
44
|
+
this.retry = options.retry;
|
|
30
45
|
}
|
|
31
46
|
start() {
|
|
32
47
|
if (this.started) {
|
|
@@ -57,16 +72,19 @@ export class SyncScheduler {
|
|
|
57
72
|
}
|
|
58
73
|
stop() {
|
|
59
74
|
this.started = false;
|
|
75
|
+
this.stops += 1;
|
|
60
76
|
this.rerunRequested = false;
|
|
77
|
+
this.wakeRetry?.('stopped');
|
|
61
78
|
this.stopInterval();
|
|
62
79
|
this.subscriptions.forEach((unsubscribe) => {
|
|
63
80
|
unsubscribe();
|
|
64
81
|
});
|
|
65
82
|
this.subscriptions = [];
|
|
66
83
|
}
|
|
67
|
-
/** Starts a run, or joins the active one. */
|
|
84
|
+
/** Starts a run, or joins the active one and ends its retry delay. */
|
|
68
85
|
trigger() {
|
|
69
86
|
if (this.inFlight) {
|
|
87
|
+
this.wakeRetry?.('triggered');
|
|
70
88
|
return this.inFlight;
|
|
71
89
|
}
|
|
72
90
|
const task = Promise.resolve()
|
|
@@ -88,18 +106,80 @@ export class SyncScheduler {
|
|
|
88
106
|
return this.trigger();
|
|
89
107
|
}
|
|
90
108
|
async runUntilSettled() {
|
|
91
|
-
|
|
92
|
-
|
|
109
|
+
const stopsAtStart = this.stops;
|
|
110
|
+
let retryIndex = 0;
|
|
111
|
+
for (;;) {
|
|
93
112
|
this.rerunRequested = false;
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
const failure = await this.runOnce();
|
|
114
|
+
if (failure === null) {
|
|
115
|
+
retryIndex = 0;
|
|
116
|
+
if (!this.followUpRequested())
|
|
117
|
+
return;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const delayMs = this.retryDelay(failure.error, retryIndex);
|
|
121
|
+
if (delayMs === null) {
|
|
122
|
+
if (!this.followUpRequested())
|
|
123
|
+
return;
|
|
124
|
+
continue;
|
|
96
125
|
}
|
|
97
|
-
|
|
98
|
-
|
|
126
|
+
this.retry?.onRetryScheduled?.({ error: failure.error, retryIndex, delayMs });
|
|
127
|
+
const wake = await this.waitForRetry(delayMs);
|
|
128
|
+
const stopped = this.stops !== stopsAtStart;
|
|
129
|
+
if (stopped || (wake === 'elapsed' && this.started && !this.active)) {
|
|
130
|
+
return;
|
|
99
131
|
}
|
|
100
|
-
|
|
132
|
+
retryIndex = wake === 'triggered' ? 0 : retryIndex + 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Read through a method: a follow-up can arrive while `run` is awaited. */
|
|
136
|
+
followUpRequested() {
|
|
137
|
+
return this.rerunRequested;
|
|
138
|
+
}
|
|
139
|
+
async runOnce() {
|
|
140
|
+
try {
|
|
141
|
+
await this.run();
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
this.onError(error);
|
|
146
|
+
return { error };
|
|
101
147
|
}
|
|
102
148
|
}
|
|
149
|
+
retryDelay(error, retryIndex) {
|
|
150
|
+
if (this.retry === undefined || retryIndex >= this.retry.maxRetries) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
let delayMs;
|
|
154
|
+
try {
|
|
155
|
+
delayMs = this.retry.delayMs(error, retryIndex);
|
|
156
|
+
}
|
|
157
|
+
catch (delayError) {
|
|
158
|
+
this.onError(delayError);
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (delayMs !== null && (!Number.isFinite(delayMs) || delayMs < 0)) {
|
|
162
|
+
this.onError(new RangeError('retry.delayMs must return a finite non-negative number'));
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
return delayMs;
|
|
166
|
+
}
|
|
167
|
+
/** The environment has interval timers only, so the delay clears its interval on the first tick. */
|
|
168
|
+
waitForRetry(delayMs) {
|
|
169
|
+
return new Promise((resolve) => {
|
|
170
|
+
const wake = (reason) => {
|
|
171
|
+
this.environment.clearInterval(timer);
|
|
172
|
+
if (this.wakeRetry === wake) {
|
|
173
|
+
this.wakeRetry = null;
|
|
174
|
+
}
|
|
175
|
+
resolve(reason);
|
|
176
|
+
};
|
|
177
|
+
const timer = this.environment.setInterval(() => {
|
|
178
|
+
wake('elapsed');
|
|
179
|
+
}, delayMs);
|
|
180
|
+
this.wakeRetry = wake;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
103
183
|
refreshInterval() {
|
|
104
184
|
this.stopInterval();
|
|
105
185
|
if (!this.started || !this.active) {
|
package/dist/scheduler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED