@syncular/client 0.27.0 → 0.29.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/client.js +6 -0
- package/dist/errors.d.ts +7 -3
- package/dist/errors.js +9 -4
- package/dist/progress.d.ts +2 -0
- package/dist/reactive-store.js +1 -1
- package/package.json +3 -3
- package/src/client.ts +6 -0
- package/src/errors.ts +13 -4
- package/src/progress.ts +2 -0
- package/src/reactive-store.ts +1 -0
package/dist/client.js
CHANGED
|
@@ -176,6 +176,7 @@ export class SyncClient {
|
|
|
176
176
|
#localResetEpoch = 0;
|
|
177
177
|
/** Retry policy belongs to the operation that classified the failure. */
|
|
178
178
|
#retryDelayMs = 250;
|
|
179
|
+
#roundRetryDelayMs;
|
|
179
180
|
#hasBlobs;
|
|
180
181
|
/** §8.6 presence: scopeKey → (peerKey `actorId clientId` → peer). */
|
|
181
182
|
#presence = new Map();
|
|
@@ -2334,6 +2335,9 @@ export class SyncClient {
|
|
|
2334
2335
|
errorCode: typeof code === 'string'
|
|
2335
2336
|
? this.#diagnosticCode(code)
|
|
2336
2337
|
: 'client.unknown_failure',
|
|
2338
|
+
...(this.#roundRetryDelayMs !== undefined
|
|
2339
|
+
? { retryDelayMs: this.#roundRetryDelayMs }
|
|
2340
|
+
: {}),
|
|
2337
2341
|
});
|
|
2338
2342
|
this.#lastRound = {
|
|
2339
2343
|
status: 'failed',
|
|
@@ -2381,6 +2385,7 @@ export class SyncClient {
|
|
|
2381
2385
|
: 'client.unknown_failure';
|
|
2382
2386
|
}
|
|
2383
2387
|
async #runSync() {
|
|
2388
|
+
this.#roundRetryDelayMs = undefined;
|
|
2384
2389
|
this.#progress.emit({
|
|
2385
2390
|
attempt: ++this.#progressAttempt,
|
|
2386
2391
|
state: 'running',
|
|
@@ -2508,6 +2513,7 @@ export class SyncClient {
|
|
|
2508
2513
|
delayMs: delay,
|
|
2509
2514
|
};
|
|
2510
2515
|
this.#retryDelayMs = Math.min(this.#retryDelayMs * 2, 30_000);
|
|
2516
|
+
this.#roundRetryDelayMs = delay;
|
|
2511
2517
|
this.#emitSyncIntent(intent);
|
|
2512
2518
|
}
|
|
2513
2519
|
throw error;
|
package/dist/errors.d.ts
CHANGED
|
@@ -25,13 +25,17 @@ export declare class ClientSyncError extends Error {
|
|
|
25
25
|
/**
|
|
26
26
|
* SPEC §7.5: the latest sync attempt failed while a live query still waited
|
|
27
27
|
* for its required coverage. `code` is the attempt's static progress error
|
|
28
|
-
* code; `attempt` is the progress attempt that failed.
|
|
29
|
-
*
|
|
28
|
+
* code; `attempt` is the progress attempt that failed. `retryable` is true when
|
|
29
|
+
* the client scheduled a background retry for the attempt, and `retryDelayMs`
|
|
30
|
+
* is that retry's delay from the failure; a non-retryable failure has no
|
|
31
|
+
* delay and no automatic next attempt. The query returns to waiting when the
|
|
32
|
+
* next attempt starts.
|
|
30
33
|
*/
|
|
31
34
|
export declare class SyncRoundFailedError extends ClientSyncError {
|
|
32
35
|
readonly name = "SyncRoundFailedError";
|
|
33
36
|
readonly attempt: number;
|
|
34
|
-
|
|
37
|
+
readonly retryDelayMs: number | undefined;
|
|
38
|
+
constructor(code: string, attempt: number, retryDelayMs: number | undefined);
|
|
35
39
|
}
|
|
36
40
|
/**
|
|
37
41
|
* SPEC §8.8: the `required` realtime policy refused a round because the
|
package/dist/errors.js
CHANGED
|
@@ -28,15 +28,20 @@ export class ClientSyncError extends Error {
|
|
|
28
28
|
/**
|
|
29
29
|
* SPEC §7.5: the latest sync attempt failed while a live query still waited
|
|
30
30
|
* for its required coverage. `code` is the attempt's static progress error
|
|
31
|
-
* code; `attempt` is the progress attempt that failed.
|
|
32
|
-
*
|
|
31
|
+
* code; `attempt` is the progress attempt that failed. `retryable` is true when
|
|
32
|
+
* the client scheduled a background retry for the attempt, and `retryDelayMs`
|
|
33
|
+
* is that retry's delay from the failure; a non-retryable failure has no
|
|
34
|
+
* delay and no automatic next attempt. The query returns to waiting when the
|
|
35
|
+
* next attempt starts.
|
|
33
36
|
*/
|
|
34
37
|
export class SyncRoundFailedError extends ClientSyncError {
|
|
35
38
|
name = 'SyncRoundFailedError';
|
|
36
39
|
attempt;
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
retryDelayMs;
|
|
41
|
+
constructor(code, attempt, retryDelayMs) {
|
|
42
|
+
super(code, 'the sync round failed before the query coverage completed', retryDelayMs !== undefined);
|
|
39
43
|
this.attempt = attempt;
|
|
44
|
+
this.retryDelayMs = retryDelayMs;
|
|
40
45
|
}
|
|
41
46
|
}
|
|
42
47
|
/**
|
package/dist/progress.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface SyncProgress {
|
|
|
11
11
|
readonly rowsProcessed: number;
|
|
12
12
|
readonly rowsTotal?: number;
|
|
13
13
|
readonly errorCode?: string;
|
|
14
|
+
/** Present on `failed` when the core scheduled a background retry. */
|
|
15
|
+
readonly retryDelayMs?: number;
|
|
14
16
|
}
|
|
15
17
|
export type SyncProgressListener = (progress: SyncProgress) => void;
|
|
16
18
|
/** Shared by the direct client and event-forwarding handles. */
|
package/dist/reactive-store.js
CHANGED
|
@@ -976,7 +976,7 @@ export class ReactiveClientStore {
|
|
|
976
976
|
this.#offProgress = this.client.onProgress((progress) => {
|
|
977
977
|
const current = roundFailure.getSnapshot();
|
|
978
978
|
if (progress.state === 'failed' && current?.attempt !== progress.attempt)
|
|
979
|
-
roundFailure.set(new SyncRoundFailedError(progress.errorCode ?? 'client.unknown_failure', progress.attempt));
|
|
979
|
+
roundFailure.set(new SyncRoundFailedError(progress.errorCode ?? 'client.unknown_failure', progress.attempt, progress.retryDelayMs));
|
|
980
980
|
else if (progress.state !== 'failed' &&
|
|
981
981
|
current !== undefined &&
|
|
982
982
|
progress.attempt !== current.attempt)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Syncular TypeScript client core — offline-first sync over SQLite (WASM/OPFS, Bun, Node)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -89,10 +89,10 @@
|
|
|
89
89
|
},
|
|
90
90
|
"dependencies": {
|
|
91
91
|
"@sqlite.org/sqlite-wasm": "^3.53.0-build1",
|
|
92
|
-
"@syncular/core": "0.
|
|
92
|
+
"@syncular/core": "0.29.0"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@syncular/server": "0.
|
|
95
|
+
"@syncular/server": "0.29.0",
|
|
96
96
|
"playwright": "1.63.0"
|
|
97
97
|
}
|
|
98
98
|
}
|
package/src/client.ts
CHANGED
|
@@ -779,6 +779,7 @@ export class SyncClient {
|
|
|
779
779
|
#localResetEpoch = 0;
|
|
780
780
|
/** Retry policy belongs to the operation that classified the failure. */
|
|
781
781
|
#retryDelayMs = 250;
|
|
782
|
+
#roundRetryDelayMs: number | undefined;
|
|
782
783
|
readonly #hasBlobs: boolean;
|
|
783
784
|
/** §8.6 presence: scopeKey → (peerKey `actorId clientId` → peer). */
|
|
784
785
|
readonly #presence = new Map<string, Map<string, PresencePeer>>();
|
|
@@ -3399,6 +3400,9 @@ export class SyncClient {
|
|
|
3399
3400
|
typeof code === 'string'
|
|
3400
3401
|
? this.#diagnosticCode(code)
|
|
3401
3402
|
: 'client.unknown_failure',
|
|
3403
|
+
...(this.#roundRetryDelayMs !== undefined
|
|
3404
|
+
? { retryDelayMs: this.#roundRetryDelayMs }
|
|
3405
|
+
: {}),
|
|
3402
3406
|
});
|
|
3403
3407
|
this.#lastRound = {
|
|
3404
3408
|
status: 'failed',
|
|
@@ -3454,6 +3458,7 @@ export class SyncClient {
|
|
|
3454
3458
|
}
|
|
3455
3459
|
|
|
3456
3460
|
async #runSync(): Promise<SyncSummary> {
|
|
3461
|
+
this.#roundRetryDelayMs = undefined;
|
|
3457
3462
|
this.#progress.emit({
|
|
3458
3463
|
attempt: ++this.#progressAttempt,
|
|
3459
3464
|
state: 'running',
|
|
@@ -3601,6 +3606,7 @@ export class SyncClient {
|
|
|
3601
3606
|
delayMs: delay,
|
|
3602
3607
|
};
|
|
3603
3608
|
this.#retryDelayMs = Math.min(this.#retryDelayMs * 2, 30_000);
|
|
3609
|
+
this.#roundRetryDelayMs = delay;
|
|
3604
3610
|
this.#emitSyncIntent(intent);
|
|
3605
3611
|
}
|
|
3606
3612
|
throw error;
|
package/src/errors.ts
CHANGED
|
@@ -38,16 +38,25 @@ export class ClientSyncError extends Error {
|
|
|
38
38
|
/**
|
|
39
39
|
* SPEC §7.5: the latest sync attempt failed while a live query still waited
|
|
40
40
|
* for its required coverage. `code` is the attempt's static progress error
|
|
41
|
-
* code; `attempt` is the progress attempt that failed.
|
|
42
|
-
*
|
|
41
|
+
* code; `attempt` is the progress attempt that failed. `retryable` is true when
|
|
42
|
+
* the client scheduled a background retry for the attempt, and `retryDelayMs`
|
|
43
|
+
* is that retry's delay from the failure; a non-retryable failure has no
|
|
44
|
+
* delay and no automatic next attempt. The query returns to waiting when the
|
|
45
|
+
* next attempt starts.
|
|
43
46
|
*/
|
|
44
47
|
export class SyncRoundFailedError extends ClientSyncError {
|
|
45
48
|
override readonly name = 'SyncRoundFailedError';
|
|
46
49
|
readonly attempt: number;
|
|
50
|
+
readonly retryDelayMs: number | undefined;
|
|
47
51
|
|
|
48
|
-
constructor(code: string, attempt: number) {
|
|
49
|
-
super(
|
|
52
|
+
constructor(code: string, attempt: number, retryDelayMs: number | undefined) {
|
|
53
|
+
super(
|
|
54
|
+
code,
|
|
55
|
+
'the sync round failed before the query coverage completed',
|
|
56
|
+
retryDelayMs !== undefined,
|
|
57
|
+
);
|
|
50
58
|
this.attempt = attempt;
|
|
59
|
+
this.retryDelayMs = retryDelayMs;
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
|
package/src/progress.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface SyncProgress {
|
|
|
11
11
|
readonly rowsProcessed: number;
|
|
12
12
|
readonly rowsTotal?: number;
|
|
13
13
|
readonly errorCode?: string;
|
|
14
|
+
/** Present on `failed` when the core scheduled a background retry. */
|
|
15
|
+
readonly retryDelayMs?: number;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export type SyncProgressListener = (progress: SyncProgress) => void;
|