@syncular/client 0.0.6-136 → 0.0.6-139
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.d.ts +8 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +6 -20
- package/dist/client.js.map +1 -1
- package/dist/create-client.d.ts +5 -4
- package/dist/create-client.d.ts.map +1 -1
- package/dist/create-client.js.map +1 -1
- package/dist/engine/SyncEngine.d.ts +8 -0
- package/dist/engine/SyncEngine.d.ts.map +1 -1
- package/dist/engine/SyncEngine.js +154 -19
- package/dist/engine/SyncEngine.js.map +1 -1
- package/dist/engine/types.d.ts +23 -5
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/handlers/types.d.ts +9 -0
- package/dist/handlers/types.d.ts.map +1 -1
- package/dist/pull-engine.d.ts.map +1 -1
- package/dist/pull-engine.js +9 -1
- package/dist/pull-engine.js.map +1 -1
- package/dist/push-engine.d.ts +2 -0
- package/dist/push-engine.d.ts.map +1 -1
- package/dist/push-engine.js +52 -3
- package/dist/push-engine.js.map +1 -1
- package/dist/sync-loop.d.ts +2 -0
- package/dist/sync-loop.d.ts.map +1 -1
- package/dist/sync-loop.js +48 -2
- package/dist/sync-loop.js.map +1 -1
- package/package.json +3 -3
- package/src/client.test.ts +43 -6
- package/src/client.ts +15 -27
- package/src/create-client.ts +5 -4
- package/src/engine/SyncEngine.test.ts +103 -4
- package/src/engine/SyncEngine.ts +207 -21
- package/src/engine/types.ts +26 -4
- package/src/handlers/types.ts +9 -0
- package/src/pull-engine.test.ts +94 -0
- package/src/pull-engine.ts +12 -1
- package/src/push-engine.ts +67 -3
- package/src/sync-loop.ts +70 -2
package/src/push-engine.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
import { countSyncMetric } from '@syncular/core';
|
|
11
11
|
import type { Kysely } from 'kysely';
|
|
12
12
|
import { upsertConflictsForRejectedCommit } from './conflicts';
|
|
13
|
+
import type { PushResultInfo } from './engine/types';
|
|
13
14
|
import {
|
|
14
15
|
getNextSendableOutboxCommit,
|
|
15
16
|
markOutboxCommitAcked,
|
|
@@ -31,6 +32,7 @@ export interface SyncPushOnceOptions {
|
|
|
31
32
|
export interface SyncPushOnceResult {
|
|
32
33
|
pushed: boolean;
|
|
33
34
|
response?: SyncPushResponse;
|
|
35
|
+
pushResult?: PushResultInfo;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
interface TransportWithWsPush extends SyncTransport {
|
|
@@ -48,6 +50,41 @@ function clonePushRequest(request: SyncPushRequest): SyncPushRequest {
|
|
|
48
50
|
return JSON.parse(JSON.stringify(request)) as SyncPushRequest;
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
function firstPushErrorCode(response: SyncPushResponse): string | null {
|
|
54
|
+
const firstError = response.results.find(
|
|
55
|
+
(result) => result.status === 'error'
|
|
56
|
+
);
|
|
57
|
+
if (
|
|
58
|
+
firstError &&
|
|
59
|
+
'code' in firstError &&
|
|
60
|
+
typeof firstError.code === 'string' &&
|
|
61
|
+
firstError.code
|
|
62
|
+
) {
|
|
63
|
+
return firstError.code;
|
|
64
|
+
}
|
|
65
|
+
const hasConflict = response.results.some(
|
|
66
|
+
(result) => result.status === 'conflict'
|
|
67
|
+
);
|
|
68
|
+
return hasConflict ? 'CONFLICT' : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildPushResult(args: {
|
|
72
|
+
outboxCommitId: string;
|
|
73
|
+
clientCommitId: string;
|
|
74
|
+
status: PushResultInfo['status'];
|
|
75
|
+
response: SyncPushResponse;
|
|
76
|
+
}): PushResultInfo {
|
|
77
|
+
return {
|
|
78
|
+
outboxCommitId: args.outboxCommitId,
|
|
79
|
+
clientCommitId: args.clientCommitId,
|
|
80
|
+
status: args.status,
|
|
81
|
+
commitSeq: args.response.commitSeq ?? null,
|
|
82
|
+
results: args.response.results,
|
|
83
|
+
errorCode: firstPushErrorCode(args.response),
|
|
84
|
+
timestamp: Date.now(),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
51
88
|
export async function syncPushOnce<DB extends SyncClientDb>(
|
|
52
89
|
db: Kysely<DB>,
|
|
53
90
|
transport: SyncTransport,
|
|
@@ -178,7 +215,16 @@ export async function syncPushOnce<DB extends SyncClientDb>(
|
|
|
178
215
|
commitSeq: responseToUse.commitSeq ?? null,
|
|
179
216
|
responseJson,
|
|
180
217
|
});
|
|
181
|
-
return {
|
|
218
|
+
return {
|
|
219
|
+
pushed: true,
|
|
220
|
+
response: responseToUse,
|
|
221
|
+
pushResult: buildPushResult({
|
|
222
|
+
outboxCommitId: next.id,
|
|
223
|
+
clientCommitId: next.client_commit_id,
|
|
224
|
+
status: responseToUse.status,
|
|
225
|
+
response: responseToUse,
|
|
226
|
+
}),
|
|
227
|
+
};
|
|
182
228
|
}
|
|
183
229
|
|
|
184
230
|
// Check if all errors are retriable - if so, keep pending for retry
|
|
@@ -198,7 +244,16 @@ export async function syncPushOnce<DB extends SyncClientDb>(
|
|
|
198
244
|
error: `Retriable: ${errorMessages}`,
|
|
199
245
|
responseJson,
|
|
200
246
|
});
|
|
201
|
-
return {
|
|
247
|
+
return {
|
|
248
|
+
pushed: true,
|
|
249
|
+
response: responseToUse,
|
|
250
|
+
pushResult: buildPushResult({
|
|
251
|
+
outboxCommitId: next.id,
|
|
252
|
+
clientCommitId: next.client_commit_id,
|
|
253
|
+
status: 'retriable',
|
|
254
|
+
response: responseToUse,
|
|
255
|
+
}),
|
|
256
|
+
};
|
|
202
257
|
}
|
|
203
258
|
|
|
204
259
|
// Terminal rejection - mark as failed and record conflicts
|
|
@@ -212,5 +267,14 @@ export async function syncPushOnce<DB extends SyncClientDb>(
|
|
|
212
267
|
error: 'REJECTED',
|
|
213
268
|
responseJson,
|
|
214
269
|
});
|
|
215
|
-
return {
|
|
270
|
+
return {
|
|
271
|
+
pushed: true,
|
|
272
|
+
response: responseToUse,
|
|
273
|
+
pushResult: buildPushResult({
|
|
274
|
+
outboxCommitId: next.id,
|
|
275
|
+
clientCommitId: next.client_commit_id,
|
|
276
|
+
status: 'rejected',
|
|
277
|
+
response: responseToUse,
|
|
278
|
+
}),
|
|
279
|
+
};
|
|
216
280
|
}
|
package/src/sync-loop.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
} from '@syncular/core';
|
|
16
16
|
import type { Kysely } from 'kysely';
|
|
17
17
|
import { upsertConflictsForRejectedCommit } from './conflicts';
|
|
18
|
+
import type { PushResultInfo } from './engine/types';
|
|
18
19
|
import type { ClientHandlerCollection } from './handlers/collection';
|
|
19
20
|
import {
|
|
20
21
|
getNextSendableOutboxCommit,
|
|
@@ -41,6 +42,42 @@ interface SyncPushUntilSettledOptions extends SyncPushOnceOptions {
|
|
|
41
42
|
|
|
42
43
|
interface SyncPushUntilSettledResult {
|
|
43
44
|
pushedCount: number;
|
|
45
|
+
pushResults: PushResultInfo[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function firstPushErrorCode(response: SyncPushResponse): string | null {
|
|
49
|
+
const firstError = response.results.find(
|
|
50
|
+
(result) => result.status === 'error'
|
|
51
|
+
);
|
|
52
|
+
if (
|
|
53
|
+
firstError &&
|
|
54
|
+
'code' in firstError &&
|
|
55
|
+
typeof firstError.code === 'string' &&
|
|
56
|
+
firstError.code
|
|
57
|
+
) {
|
|
58
|
+
return firstError.code;
|
|
59
|
+
}
|
|
60
|
+
const hasConflict = response.results.some(
|
|
61
|
+
(result) => result.status === 'conflict'
|
|
62
|
+
);
|
|
63
|
+
return hasConflict ? 'CONFLICT' : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function buildPushResult(args: {
|
|
67
|
+
outboxCommitId: string;
|
|
68
|
+
clientCommitId: string;
|
|
69
|
+
status: PushResultInfo['status'];
|
|
70
|
+
response: SyncPushResponse;
|
|
71
|
+
}): PushResultInfo {
|
|
72
|
+
return {
|
|
73
|
+
outboxCommitId: args.outboxCommitId,
|
|
74
|
+
clientCommitId: args.clientCommitId,
|
|
75
|
+
status: args.status,
|
|
76
|
+
commitSeq: args.response.commitSeq ?? null,
|
|
77
|
+
results: args.response.results,
|
|
78
|
+
errorCode: firstPushErrorCode(args.response),
|
|
79
|
+
timestamp: Date.now(),
|
|
80
|
+
};
|
|
44
81
|
}
|
|
45
82
|
|
|
46
83
|
async function syncPushUntilSettled<DB extends SyncClientDb>(
|
|
@@ -51,6 +88,7 @@ async function syncPushUntilSettled<DB extends SyncClientDb>(
|
|
|
51
88
|
const maxCommits = Math.max(1, Math.min(1000, options.maxCommits ?? 20));
|
|
52
89
|
|
|
53
90
|
let pushedCount = 0;
|
|
91
|
+
const pushResults: PushResultInfo[] = [];
|
|
54
92
|
for (let i = 0; i < maxCommits; i++) {
|
|
55
93
|
const res = await syncPushOnce(db, transport, {
|
|
56
94
|
clientId: options.clientId,
|
|
@@ -59,9 +97,12 @@ async function syncPushUntilSettled<DB extends SyncClientDb>(
|
|
|
59
97
|
});
|
|
60
98
|
if (!res.pushed) break;
|
|
61
99
|
pushedCount += 1;
|
|
100
|
+
if (res.pushResult) {
|
|
101
|
+
pushResults.push(res.pushResult);
|
|
102
|
+
}
|
|
62
103
|
}
|
|
63
104
|
|
|
64
|
-
return { pushedCount };
|
|
105
|
+
return { pushedCount, pushResults };
|
|
65
106
|
}
|
|
66
107
|
|
|
67
108
|
interface SyncPullUntilSettledOptions extends SyncPullOnceOptions {
|
|
@@ -176,6 +217,7 @@ export interface SyncOnceResult {
|
|
|
176
217
|
pushedCommits: number;
|
|
177
218
|
pullRounds: number;
|
|
178
219
|
pullResponse: SyncPullResponse;
|
|
220
|
+
pushResults: PushResultInfo[];
|
|
179
221
|
}
|
|
180
222
|
|
|
181
223
|
/**
|
|
@@ -276,6 +318,7 @@ async function syncOnceCombined<DB extends SyncClientDb>(
|
|
|
276
318
|
|
|
277
319
|
// Process push response
|
|
278
320
|
let pushedCommits = 0;
|
|
321
|
+
const pushResults: PushResultInfo[] = [];
|
|
279
322
|
if (outbox && pushRequest) {
|
|
280
323
|
let pushRes = wsPushResponse ?? combined.push;
|
|
281
324
|
if (!pushRes) {
|
|
@@ -303,6 +346,14 @@ async function syncOnceCombined<DB extends SyncClientDb>(
|
|
|
303
346
|
commitSeq: pushRes.commitSeq ?? null,
|
|
304
347
|
responseJson,
|
|
305
348
|
});
|
|
349
|
+
pushResults.push(
|
|
350
|
+
buildPushResult({
|
|
351
|
+
outboxCommitId: outbox.id,
|
|
352
|
+
clientCommitId: outbox.client_commit_id,
|
|
353
|
+
status: pushRes.status,
|
|
354
|
+
response: pushRes,
|
|
355
|
+
})
|
|
356
|
+
);
|
|
306
357
|
pushedCommits = 1;
|
|
307
358
|
} else {
|
|
308
359
|
// Check if all errors are retriable
|
|
@@ -317,6 +368,14 @@ async function syncOnceCombined<DB extends SyncClientDb>(
|
|
|
317
368
|
error: 'Retriable',
|
|
318
369
|
responseJson,
|
|
319
370
|
});
|
|
371
|
+
pushResults.push(
|
|
372
|
+
buildPushResult({
|
|
373
|
+
outboxCommitId: outbox.id,
|
|
374
|
+
clientCommitId: outbox.client_commit_id,
|
|
375
|
+
status: 'retriable',
|
|
376
|
+
response: pushRes,
|
|
377
|
+
})
|
|
378
|
+
);
|
|
320
379
|
pushedCommits = 1;
|
|
321
380
|
} else {
|
|
322
381
|
await upsertConflictsForRejectedCommit(db, {
|
|
@@ -329,6 +388,14 @@ async function syncOnceCombined<DB extends SyncClientDb>(
|
|
|
329
388
|
error: 'REJECTED',
|
|
330
389
|
responseJson,
|
|
331
390
|
});
|
|
391
|
+
pushResults.push(
|
|
392
|
+
buildPushResult({
|
|
393
|
+
outboxCommitId: outbox.id,
|
|
394
|
+
clientCommitId: outbox.client_commit_id,
|
|
395
|
+
status: 'rejected',
|
|
396
|
+
response: pushRes,
|
|
397
|
+
})
|
|
398
|
+
);
|
|
332
399
|
pushedCommits = 1;
|
|
333
400
|
}
|
|
334
401
|
}
|
|
@@ -341,6 +408,7 @@ async function syncOnceCombined<DB extends SyncClientDb>(
|
|
|
341
408
|
maxCommits: (options.maxPushCommits ?? 20) - 1,
|
|
342
409
|
});
|
|
343
410
|
pushedCommits += remaining.pushedCount;
|
|
411
|
+
pushResults.push(...remaining.pushResults);
|
|
344
412
|
}
|
|
345
413
|
|
|
346
414
|
// Process pull response
|
|
@@ -376,7 +444,7 @@ async function syncOnceCombined<DB extends SyncClientDb>(
|
|
|
376
444
|
}
|
|
377
445
|
}
|
|
378
446
|
|
|
379
|
-
return { pushedCommits, pullRounds, pullResponse };
|
|
447
|
+
return { pushedCommits, pullRounds, pullResponse, pushResults };
|
|
380
448
|
}
|
|
381
449
|
|
|
382
450
|
export async function syncOnce<DB extends SyncClientDb>(
|