@syncular/client 0.0.6-232 → 0.0.6-239
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/engine/SyncEngine.d.ts.map +1 -1
- package/dist/engine/SyncEngine.js +1 -0
- package/dist/engine/SyncEngine.js.map +1 -1
- package/dist/engine/types.d.ts +7 -0
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/handlers/create-handler.d.ts.map +1 -1
- package/dist/handlers/create-handler.js +3 -0
- package/dist/handlers/create-handler.js.map +1 -1
- package/dist/handlers/types.d.ts +6 -0
- package/dist/handlers/types.d.ts.map +1 -1
- package/dist/pull-engine.d.ts +8 -0
- package/dist/pull-engine.d.ts.map +1 -1
- package/dist/pull-engine.js +28 -7
- package/dist/pull-engine.js.map +1 -1
- package/dist/sync-loop.d.ts +1 -0
- package/dist/sync-loop.d.ts.map +1 -1
- package/dist/sync-loop.js +18 -4
- package/dist/sync-loop.js.map +1 -1
- package/package.json +3 -3
- package/src/engine/SyncEngine.test.ts +144 -0
- package/src/engine/SyncEngine.ts +1 -0
- package/src/engine/types.ts +7 -0
- package/src/handlers/create-handler.ts +3 -0
- package/src/handlers/types.ts +6 -0
- package/src/pull-engine.ts +48 -4
- package/src/sync-loop.ts +31 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/client",
|
|
3
|
-
"version": "0.0.6-
|
|
3
|
+
"version": "0.0.6-239",
|
|
4
4
|
"description": "Client-side sync engine with offline-first support, outbox, and conflict resolution",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -119,8 +119,8 @@
|
|
|
119
119
|
"release": "bunx syncular-publish"
|
|
120
120
|
},
|
|
121
121
|
"dependencies": {
|
|
122
|
-
"@syncular/core": "0.0.6-
|
|
123
|
-
"@syncular/transport-http": "0.0.6-
|
|
122
|
+
"@syncular/core": "0.0.6-239",
|
|
123
|
+
"@syncular/transport-http": "0.0.6-239"
|
|
124
124
|
},
|
|
125
125
|
"peerDependencies": {
|
|
126
126
|
"kysely": "*"
|
|
@@ -924,6 +924,150 @@ describe('SyncEngine WS inline apply', () => {
|
|
|
924
924
|
expect(state.error?.retryable).toBe(false);
|
|
925
925
|
});
|
|
926
926
|
|
|
927
|
+
it('pulls newly eligible bootstrap phases in the same sync cycle', async () => {
|
|
928
|
+
const handlers: ClientHandlerCollection<TestDb> = [
|
|
929
|
+
{
|
|
930
|
+
table: 'tasks',
|
|
931
|
+
async applySnapshot() {},
|
|
932
|
+
async clearAll() {},
|
|
933
|
+
async applyChange() {},
|
|
934
|
+
},
|
|
935
|
+
];
|
|
936
|
+
|
|
937
|
+
const syncRequests: string[][] = [];
|
|
938
|
+
const transport: SyncTransport = {
|
|
939
|
+
async sync(request) {
|
|
940
|
+
const ids = request.pull?.subscriptions.map((subscription) => {
|
|
941
|
+
return subscription.id;
|
|
942
|
+
}) ?? ['unexpected'];
|
|
943
|
+
syncRequests.push(ids);
|
|
944
|
+
|
|
945
|
+
if (syncRequests.length === 1) {
|
|
946
|
+
expect(ids).toEqual(['catalog-meta', 'catalog-codes']);
|
|
947
|
+
return {
|
|
948
|
+
pull: {
|
|
949
|
+
ok: true,
|
|
950
|
+
subscriptions: [
|
|
951
|
+
{
|
|
952
|
+
id: 'catalog-meta',
|
|
953
|
+
status: 'active',
|
|
954
|
+
scopes: {},
|
|
955
|
+
bootstrap: false,
|
|
956
|
+
nextCursor: 1,
|
|
957
|
+
commits: [],
|
|
958
|
+
snapshots: [],
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
id: 'catalog-codes',
|
|
962
|
+
status: 'active',
|
|
963
|
+
scopes: {},
|
|
964
|
+
bootstrap: false,
|
|
965
|
+
nextCursor: 1,
|
|
966
|
+
commits: [],
|
|
967
|
+
snapshots: [],
|
|
968
|
+
},
|
|
969
|
+
],
|
|
970
|
+
},
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
expect(ids).toEqual([
|
|
975
|
+
'catalog-meta',
|
|
976
|
+
'catalog-codes',
|
|
977
|
+
'catalog-relations',
|
|
978
|
+
]);
|
|
979
|
+
return {
|
|
980
|
+
pull: {
|
|
981
|
+
ok: true,
|
|
982
|
+
subscriptions: [
|
|
983
|
+
{
|
|
984
|
+
id: 'catalog-meta',
|
|
985
|
+
status: 'active',
|
|
986
|
+
scopes: {},
|
|
987
|
+
bootstrap: false,
|
|
988
|
+
nextCursor: 1,
|
|
989
|
+
commits: [],
|
|
990
|
+
snapshots: [],
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
id: 'catalog-codes',
|
|
994
|
+
status: 'active',
|
|
995
|
+
scopes: {},
|
|
996
|
+
bootstrap: false,
|
|
997
|
+
nextCursor: 1,
|
|
998
|
+
commits: [],
|
|
999
|
+
snapshots: [],
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
id: 'catalog-relations',
|
|
1003
|
+
status: 'active',
|
|
1004
|
+
scopes: {},
|
|
1005
|
+
bootstrap: false,
|
|
1006
|
+
nextCursor: 1,
|
|
1007
|
+
commits: [],
|
|
1008
|
+
snapshots: [],
|
|
1009
|
+
},
|
|
1010
|
+
],
|
|
1011
|
+
},
|
|
1012
|
+
};
|
|
1013
|
+
},
|
|
1014
|
+
async fetchSnapshotChunk() {
|
|
1015
|
+
return new Uint8Array();
|
|
1016
|
+
},
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1019
|
+
const engine = new SyncEngine<TestDb>({
|
|
1020
|
+
db,
|
|
1021
|
+
transport,
|
|
1022
|
+
handlers,
|
|
1023
|
+
actorId: 'catalog-public',
|
|
1024
|
+
clientId: 'client-bootstrap-phases',
|
|
1025
|
+
subscriptions: [
|
|
1026
|
+
{
|
|
1027
|
+
id: 'catalog-meta',
|
|
1028
|
+
table: 'tasks',
|
|
1029
|
+
scopes: {},
|
|
1030
|
+
bootstrapPhase: 0,
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
id: 'catalog-codes',
|
|
1034
|
+
table: 'tasks',
|
|
1035
|
+
scopes: {},
|
|
1036
|
+
bootstrapPhase: 0,
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
id: 'catalog-relations',
|
|
1040
|
+
table: 'tasks',
|
|
1041
|
+
scopes: {},
|
|
1042
|
+
bootstrapPhase: 1,
|
|
1043
|
+
},
|
|
1044
|
+
],
|
|
1045
|
+
stateId: 'default',
|
|
1046
|
+
pollIntervalMs: 60_000,
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
await engine.start();
|
|
1050
|
+
engine.stop();
|
|
1051
|
+
|
|
1052
|
+
expect(syncRequests).toEqual([
|
|
1053
|
+
['catalog-meta', 'catalog-codes'],
|
|
1054
|
+
['catalog-meta', 'catalog-codes', 'catalog-relations'],
|
|
1055
|
+
]);
|
|
1056
|
+
|
|
1057
|
+
const relationState = await db
|
|
1058
|
+
.selectFrom('sync_subscription_state')
|
|
1059
|
+
.select(['subscription_id', 'status', 'cursor'])
|
|
1060
|
+
.where('state_id', '=', 'default')
|
|
1061
|
+
.where('subscription_id', '=', 'catalog-relations')
|
|
1062
|
+
.executeTakeFirst();
|
|
1063
|
+
|
|
1064
|
+
expect(relationState).toEqual({
|
|
1065
|
+
subscription_id: 'catalog-relations',
|
|
1066
|
+
status: 'active',
|
|
1067
|
+
cursor: 1,
|
|
1068
|
+
});
|
|
1069
|
+
});
|
|
1070
|
+
|
|
927
1071
|
it('skips outbox and conflict refresh after a read-only successful sync', async () => {
|
|
928
1072
|
const handlers: ClientHandlerCollection<TestDb> = [
|
|
929
1073
|
{
|
package/src/engine/SyncEngine.ts
CHANGED
|
@@ -2172,6 +2172,7 @@ export class SyncEngine<DB extends SyncClientDb = SyncClientDb> {
|
|
|
2172
2172
|
limitCommits: this.config.limitCommits,
|
|
2173
2173
|
limitSnapshotRows: this.config.limitSnapshotRows,
|
|
2174
2174
|
maxSnapshotPages: this.config.maxSnapshotPages,
|
|
2175
|
+
snapshotApplyYieldMs: this.config.snapshotApplyYieldMs,
|
|
2175
2176
|
dedupeRows: this.config.dedupeRows,
|
|
2176
2177
|
stateId: this.config.stateId,
|
|
2177
2178
|
sha256: this.config.sha256,
|
package/src/engine/types.ts
CHANGED
|
@@ -371,6 +371,13 @@ export interface SyncEngineConfig<DB extends SyncClientDb = SyncClientDb> {
|
|
|
371
371
|
limitSnapshotRows?: number;
|
|
372
372
|
/** Bootstrap snapshot pages per pull */
|
|
373
373
|
maxSnapshotPages?: number;
|
|
374
|
+
/**
|
|
375
|
+
* Yield delay between large bootstrap apply batches.
|
|
376
|
+
* - `0`: yield on the next macrotask
|
|
377
|
+
* - `false`: disable yielding
|
|
378
|
+
* - omitted: use transport/runtime defaults
|
|
379
|
+
*/
|
|
380
|
+
snapshotApplyYieldMs?: number | false;
|
|
374
381
|
/** Deduplicate rows in pull responses on the server */
|
|
375
382
|
dedupeRows?: boolean;
|
|
376
383
|
/** Optional state row id (multi-profile support) */
|
|
@@ -278,6 +278,7 @@ export function createClientHandler<
|
|
|
278
278
|
)}
|
|
279
279
|
on conflict (${sql.ref(primaryKey)}) ${onConflict}
|
|
280
280
|
`.execute(ctx.trx);
|
|
281
|
+
await ctx.yieldToMainThread?.();
|
|
281
282
|
}
|
|
282
283
|
};
|
|
283
284
|
|
|
@@ -359,6 +360,7 @@ export function createClientHandler<
|
|
|
359
360
|
delete from ${sql.table(table)}
|
|
360
361
|
where ${sql.ref(primaryKey)} in ${sql`(${sql.join(batchIds.map((rowId) => sql.val(rowId)))})`}
|
|
361
362
|
`.execute(ctx.trx);
|
|
363
|
+
await ctx.yieldToMainThread?.();
|
|
362
364
|
}
|
|
363
365
|
};
|
|
364
366
|
|
|
@@ -404,6 +406,7 @@ export function createClientHandler<
|
|
|
404
406
|
)}
|
|
405
407
|
on conflict (${sql.ref(primaryKey)}) ${onConflict}
|
|
406
408
|
`.execute(ctx.trx);
|
|
409
|
+
await ctx.yieldToMainThread?.();
|
|
407
410
|
}
|
|
408
411
|
};
|
|
409
412
|
|
package/src/handlers/types.ts
CHANGED
|
@@ -16,6 +16,12 @@ import type { Transaction } from 'kysely';
|
|
|
16
16
|
export interface ClientHandlerContext<DB> {
|
|
17
17
|
/** Database transaction */
|
|
18
18
|
trx: Transaction<DB>;
|
|
19
|
+
/**
|
|
20
|
+
* Yields control back to the runtime scheduler between large apply batches.
|
|
21
|
+
* Useful on React Native / Hermes where long bootstrap writes can otherwise
|
|
22
|
+
* monopolize the JS thread.
|
|
23
|
+
*/
|
|
24
|
+
yieldToMainThread?: () => Promise<void>;
|
|
19
25
|
/**
|
|
20
26
|
* Commit metadata for server-delivered changes.
|
|
21
27
|
* Undefined for local optimistic changes.
|
package/src/pull-engine.ts
CHANGED
|
@@ -639,6 +639,7 @@ async function applyChunkedSnapshot<DB extends SyncClientDb>(
|
|
|
639
639
|
snapshot: SyncSnapshot,
|
|
640
640
|
scopeValues: ScopeValues,
|
|
641
641
|
sha256Override?: (bytes: Uint8Array) => Promise<string>,
|
|
642
|
+
yieldToMainThread?: () => Promise<void>,
|
|
642
643
|
trace?: {
|
|
643
644
|
stateId: string;
|
|
644
645
|
subscriptionId: string;
|
|
@@ -647,7 +648,7 @@ async function applyChunkedSnapshot<DB extends SyncClientDb>(
|
|
|
647
648
|
): Promise<void> {
|
|
648
649
|
const chunks = snapshot.chunks ?? [];
|
|
649
650
|
if (chunks.length === 0) {
|
|
650
|
-
await handler.applySnapshot({ trx }, snapshot);
|
|
651
|
+
await handler.applySnapshot({ trx, yieldToMainThread }, snapshot);
|
|
651
652
|
return;
|
|
652
653
|
}
|
|
653
654
|
|
|
@@ -713,7 +714,7 @@ async function applyChunkedSnapshot<DB extends SyncClientDb>(
|
|
|
713
714
|
// eslint-disable-next-line no-await-in-loop
|
|
714
715
|
try {
|
|
715
716
|
await handler.applySnapshot(
|
|
716
|
-
{ trx },
|
|
717
|
+
{ trx, yieldToMainThread },
|
|
717
718
|
{
|
|
718
719
|
...snapshot,
|
|
719
720
|
rows: pendingBatch,
|
|
@@ -745,7 +746,7 @@ async function applyChunkedSnapshot<DB extends SyncClientDb>(
|
|
|
745
746
|
// eslint-disable-next-line no-await-in-loop
|
|
746
747
|
try {
|
|
747
748
|
await handler.applySnapshot(
|
|
748
|
-
{ trx },
|
|
749
|
+
{ trx, yieldToMainThread },
|
|
749
750
|
{
|
|
750
751
|
...snapshot,
|
|
751
752
|
rows: pendingBatch,
|
|
@@ -1046,6 +1047,13 @@ export interface SyncPullOnceOptions {
|
|
|
1046
1047
|
limitCommits?: number;
|
|
1047
1048
|
limitSnapshotRows?: number;
|
|
1048
1049
|
maxSnapshotPages?: number;
|
|
1050
|
+
/**
|
|
1051
|
+
* Yield delay between heavy bootstrap apply batches.
|
|
1052
|
+
* - `0`: yield on the next macrotask
|
|
1053
|
+
* - `false`: disable yielding
|
|
1054
|
+
* - omitted: use transport/runtime defaults
|
|
1055
|
+
*/
|
|
1056
|
+
snapshotApplyYieldMs?: number | false;
|
|
1049
1057
|
dedupeRows?: boolean;
|
|
1050
1058
|
stateId?: string;
|
|
1051
1059
|
/**
|
|
@@ -1087,6 +1095,26 @@ function emitTrace(
|
|
|
1087
1095
|
});
|
|
1088
1096
|
}
|
|
1089
1097
|
|
|
1098
|
+
function resolveSnapshotApplyYieldToMainThread(
|
|
1099
|
+
options: SyncPullOnceOptions,
|
|
1100
|
+
capabilities?: SyncTransportCapabilities
|
|
1101
|
+
): (() => Promise<void>) | undefined {
|
|
1102
|
+
const configuredDelay =
|
|
1103
|
+
options.snapshotApplyYieldMs ??
|
|
1104
|
+
capabilities?.preferredSnapshotApplyYieldMs ??
|
|
1105
|
+
false;
|
|
1106
|
+
|
|
1107
|
+
if (configuredDelay === false) {
|
|
1108
|
+
return undefined;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const delay = Math.max(0, Math.trunc(configuredDelay));
|
|
1112
|
+
return () =>
|
|
1113
|
+
new Promise<void>((resolve) => {
|
|
1114
|
+
setTimeout(resolve, delay);
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1090
1118
|
function countSubscriptionRows(
|
|
1091
1119
|
subscription: SyncPullSubscriptionResponse
|
|
1092
1120
|
): number | undefined {
|
|
@@ -1247,10 +1275,12 @@ export async function applyIncrementalCommitChanges<DB extends SyncClientDb>(
|
|
|
1247
1275
|
commitSeq?: number | null;
|
|
1248
1276
|
actorId?: string | null;
|
|
1249
1277
|
createdAt?: string | null;
|
|
1278
|
+
yieldToMainThread?: () => Promise<void>;
|
|
1250
1279
|
}
|
|
1251
1280
|
): Promise<void> {
|
|
1252
1281
|
const ctx = {
|
|
1253
1282
|
trx,
|
|
1283
|
+
yieldToMainThread: args.yieldToMainThread,
|
|
1254
1284
|
commitSeq: args.commitSeq ?? null,
|
|
1255
1285
|
actorId: args.actorId ?? null,
|
|
1256
1286
|
createdAt: args.createdAt ?? null,
|
|
@@ -1311,6 +1341,10 @@ export async function applyPullResponse<DB extends SyncClientDb>(
|
|
|
1311
1341
|
rawResponse,
|
|
1312
1342
|
transport.capabilities
|
|
1313
1343
|
);
|
|
1344
|
+
const yieldToMainThread = resolveSnapshotApplyYieldToMainThread(
|
|
1345
|
+
options,
|
|
1346
|
+
transport.capabilities
|
|
1347
|
+
);
|
|
1314
1348
|
|
|
1315
1349
|
let responseToApply = requiresMaterializedSnapshots
|
|
1316
1350
|
? await materializeChunkedSnapshots(
|
|
@@ -1365,6 +1399,7 @@ export async function applyPullResponse<DB extends SyncClientDb>(
|
|
|
1365
1399
|
existingById,
|
|
1366
1400
|
subsById,
|
|
1367
1401
|
sub,
|
|
1402
|
+
yieldToMainThread,
|
|
1368
1403
|
});
|
|
1369
1404
|
});
|
|
1370
1405
|
emitTrace(options.onTrace, {
|
|
@@ -1409,6 +1444,7 @@ export async function applyPullResponse<DB extends SyncClientDb>(
|
|
|
1409
1444
|
existingById,
|
|
1410
1445
|
subsById,
|
|
1411
1446
|
sub,
|
|
1447
|
+
yieldToMainThread,
|
|
1412
1448
|
});
|
|
1413
1449
|
}
|
|
1414
1450
|
});
|
|
@@ -1528,6 +1564,7 @@ async function applySubscriptionResponse<DB extends SyncClientDb>(args: {
|
|
|
1528
1564
|
existingById: Map<string, SyncSubscriptionStateTable>;
|
|
1529
1565
|
subsById: Map<string, SyncClientSubscription | undefined>;
|
|
1530
1566
|
sub: SyncPullSubscriptionResponse;
|
|
1567
|
+
yieldToMainThread?: () => Promise<void>;
|
|
1531
1568
|
}): Promise<void> {
|
|
1532
1569
|
const {
|
|
1533
1570
|
trx,
|
|
@@ -1538,6 +1575,7 @@ async function applySubscriptionResponse<DB extends SyncClientDb>(args: {
|
|
|
1538
1575
|
existingById,
|
|
1539
1576
|
subsById,
|
|
1540
1577
|
sub,
|
|
1578
|
+
yieldToMainThread,
|
|
1541
1579
|
} = args;
|
|
1542
1580
|
const def = subsById.get(sub.id);
|
|
1543
1581
|
const prev = existingById.get(sub.id);
|
|
@@ -1603,6 +1641,7 @@ async function applySubscriptionResponse<DB extends SyncClientDb>(args: {
|
|
|
1603
1641
|
const scopes = parseScopeValuesJson(prev.scopes_json);
|
|
1604
1642
|
await getClientHandlerOrThrow(handlers, prev.table).clearAll({
|
|
1605
1643
|
trx,
|
|
1644
|
+
yieldToMainThread,
|
|
1606
1645
|
scopes,
|
|
1607
1646
|
});
|
|
1608
1647
|
} catch {
|
|
@@ -1644,6 +1683,7 @@ async function applySubscriptionResponse<DB extends SyncClientDb>(args: {
|
|
|
1644
1683
|
if (clearScopes !== 'none') {
|
|
1645
1684
|
await getClientHandlerOrThrow(handlers, prev.table).clearAll({
|
|
1646
1685
|
trx,
|
|
1686
|
+
yieldToMainThread,
|
|
1647
1687
|
scopes: clearScopes ?? previousScopes,
|
|
1648
1688
|
});
|
|
1649
1689
|
}
|
|
@@ -1662,6 +1702,7 @@ async function applySubscriptionResponse<DB extends SyncClientDb>(args: {
|
|
|
1662
1702
|
try {
|
|
1663
1703
|
await handler.onSnapshotStart({
|
|
1664
1704
|
trx,
|
|
1705
|
+
yieldToMainThread,
|
|
1665
1706
|
table: snapshot.table,
|
|
1666
1707
|
scopes: sub.scopes,
|
|
1667
1708
|
});
|
|
@@ -1687,6 +1728,7 @@ async function applySubscriptionResponse<DB extends SyncClientDb>(args: {
|
|
|
1687
1728
|
snapshot,
|
|
1688
1729
|
sub.scopes,
|
|
1689
1730
|
options.sha256,
|
|
1731
|
+
yieldToMainThread,
|
|
1690
1732
|
{
|
|
1691
1733
|
stateId,
|
|
1692
1734
|
subscriptionId: sub.id,
|
|
@@ -1695,7 +1737,7 @@ async function applySubscriptionResponse<DB extends SyncClientDb>(args: {
|
|
|
1695
1737
|
);
|
|
1696
1738
|
} else {
|
|
1697
1739
|
try {
|
|
1698
|
-
await handler.applySnapshot({ trx }, snapshot);
|
|
1740
|
+
await handler.applySnapshot({ trx, yieldToMainThread }, snapshot);
|
|
1699
1741
|
} catch (error) {
|
|
1700
1742
|
throw wrapSyncClientStageError(
|
|
1701
1743
|
error,
|
|
@@ -1714,6 +1756,7 @@ async function applySubscriptionResponse<DB extends SyncClientDb>(args: {
|
|
|
1714
1756
|
try {
|
|
1715
1757
|
await handler.onSnapshotEnd({
|
|
1716
1758
|
trx,
|
|
1759
|
+
yieldToMainThread,
|
|
1717
1760
|
table: snapshot.table,
|
|
1718
1761
|
scopes: sub.scopes,
|
|
1719
1762
|
});
|
|
@@ -1739,6 +1782,7 @@ async function applySubscriptionResponse<DB extends SyncClientDb>(args: {
|
|
|
1739
1782
|
commitSeq: commit.commitSeq ?? null,
|
|
1740
1783
|
actorId: commit.actorId ?? null,
|
|
1741
1784
|
createdAt: commit.createdAt ?? null,
|
|
1785
|
+
yieldToMainThread,
|
|
1742
1786
|
});
|
|
1743
1787
|
} catch (error) {
|
|
1744
1788
|
throw wrapSyncClientStageError(
|
package/src/sync-loop.ts
CHANGED
|
@@ -459,6 +459,21 @@ function needsAnotherPull(
|
|
|
459
459
|
return totalCommits >= limitCommits;
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
+
function hasNewlyEligibleBootstrapSubscriptions(
|
|
463
|
+
currentPullState: SyncPullRequestState,
|
|
464
|
+
nextPullState: SyncPullRequestState
|
|
465
|
+
): boolean {
|
|
466
|
+
const currentIds = new Set(
|
|
467
|
+
(currentPullState.request.subscriptions ?? []).map((subscription) => {
|
|
468
|
+
return subscription.id;
|
|
469
|
+
})
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
return (nextPullState.request.subscriptions ?? []).some((subscription) => {
|
|
473
|
+
return !currentIds.has(subscription.id);
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
462
477
|
function mergePullResponse(
|
|
463
478
|
targetBySubId: Map<string, SyncPullSubscriptionResponse>,
|
|
464
479
|
res: SyncPullResponse
|
|
@@ -545,8 +560,14 @@ async function syncPullUntilSettled<DB extends SyncClientDb>(
|
|
|
545
560
|
const res = await syncPullOnce(db, transport, handlers, options, pullState);
|
|
546
561
|
mergePullResponse(aggregatedBySubId, res);
|
|
547
562
|
|
|
548
|
-
|
|
549
|
-
|
|
563
|
+
const nextPullState = createFollowupPullState(pullState, res);
|
|
564
|
+
if (
|
|
565
|
+
!needsAnotherPull(res, pullState.request.limitCommits) &&
|
|
566
|
+
!hasNewlyEligibleBootstrapSubscriptions(pullState, nextPullState)
|
|
567
|
+
) {
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
pullState = nextPullState;
|
|
550
571
|
}
|
|
551
572
|
|
|
552
573
|
return {
|
|
@@ -568,6 +589,7 @@ export interface SyncOnceOptions {
|
|
|
568
589
|
limitCommits?: number;
|
|
569
590
|
limitSnapshotRows?: number;
|
|
570
591
|
maxSnapshotPages?: number;
|
|
592
|
+
snapshotApplyYieldMs?: number | false;
|
|
571
593
|
dedupeRows?: boolean;
|
|
572
594
|
stateId?: string;
|
|
573
595
|
maxPushCommits?: number;
|
|
@@ -612,6 +634,7 @@ async function syncOnceCombined<DB extends SyncClientDb>(
|
|
|
612
634
|
limitCommits: options.limitCommits,
|
|
613
635
|
limitSnapshotRows: options.limitSnapshotRows,
|
|
614
636
|
maxSnapshotPages: options.maxSnapshotPages,
|
|
637
|
+
snapshotApplyYieldMs: options.snapshotApplyYieldMs,
|
|
615
638
|
dedupeRows: options.dedupeRows,
|
|
616
639
|
stateId: options.stateId,
|
|
617
640
|
sha256: options.sha256,
|
|
@@ -868,14 +891,18 @@ async function syncOnceCombined<DB extends SyncClientDb>(
|
|
|
868
891
|
pullRounds = 1;
|
|
869
892
|
|
|
870
893
|
// Continue pulling if more data
|
|
871
|
-
|
|
894
|
+
const nextPullState = createFollowupPullState(pullState, pullResponse);
|
|
895
|
+
if (
|
|
896
|
+
needsAnotherPull(pullResponse, pullState.request.limitCommits) ||
|
|
897
|
+
hasNewlyEligibleBootstrapSubscriptions(pullState, nextPullState)
|
|
898
|
+
) {
|
|
872
899
|
const aggregatedBySubId = new Map<string, SyncPullSubscriptionResponse>();
|
|
873
900
|
mergePullResponse(aggregatedBySubId, pullResponse);
|
|
874
901
|
|
|
875
902
|
const more = await syncPullUntilSettled(db, transport, handlers, {
|
|
876
903
|
...pullOpts,
|
|
877
904
|
maxRounds: (options.maxPullRounds ?? 20) - 1,
|
|
878
|
-
initialPullState:
|
|
905
|
+
initialPullState: nextPullState,
|
|
879
906
|
});
|
|
880
907
|
pullRounds += more.rounds;
|
|
881
908
|
mergePullResponse(aggregatedBySubId, more.response);
|