@syncular/client 0.0.6-95 → 0.1.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/README.md +713 -11
- package/dist/auth-leases.d.ts +11 -0
- package/dist/auth-leases.d.ts.map +1 -0
- package/dist/auth-leases.js +140 -0
- package/dist/auth-leases.js.map +1 -0
- package/dist/blob-limits.d.ts +15 -0
- package/dist/blob-limits.d.ts.map +1 -0
- package/dist/blob-limits.js +66 -0
- package/dist/blob-limits.js.map +1 -0
- package/dist/bridge-client.d.ts +69 -0
- package/dist/bridge-client.d.ts.map +1 -0
- package/dist/bridge-client.js +282 -0
- package/dist/bridge-client.js.map +1 -0
- package/dist/client-config.d.ts +6 -0
- package/dist/client-config.d.ts.map +1 -0
- package/dist/client-config.js +18 -0
- package/dist/client-config.js.map +1 -0
- package/dist/client.d.ts +65 -397
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +150 -960
- package/dist/client.js.map +1 -1
- package/dist/command-history.d.ts +35 -0
- package/dist/command-history.d.ts.map +1 -0
- package/dist/command-history.js +378 -0
- package/dist/command-history.js.map +1 -0
- package/dist/console-diagnostics.d.ts +33 -0
- package/dist/console-diagnostics.d.ts.map +1 -0
- package/dist/console-diagnostics.js +496 -0
- package/dist/console-diagnostics.js.map +1 -0
- package/dist/crdt-yjs/index.d.ts +4 -0
- package/dist/crdt-yjs/index.d.ts.map +1 -0
- package/dist/crdt-yjs/index.js +4 -0
- package/dist/crdt-yjs/index.js.map +1 -0
- package/dist/crdt-yjs/webview-host-facade.d.ts +126 -0
- package/dist/crdt-yjs/webview-host-facade.d.ts.map +1 -0
- package/dist/crdt-yjs/webview-host-facade.js +284 -0
- package/dist/crdt-yjs/webview-host-facade.js.map +1 -0
- package/dist/crdt-yjs/yjs-document-field-adapter.d.ts +153 -0
- package/dist/crdt-yjs/yjs-document-field-adapter.d.ts.map +1 -0
- package/dist/crdt-yjs/yjs-document-field-adapter.js +406 -0
- package/dist/crdt-yjs/yjs-document-field-adapter.js.map +1 -0
- package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts +73 -0
- package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts.map +1 -0
- package/dist/crdt-yjs/yjs-prosemirror-bridge.js +169 -0
- package/dist/crdt-yjs/yjs-prosemirror-bridge.js.map +1 -0
- package/dist/database.d.ts +83 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +916 -0
- package/dist/database.js.map +1 -0
- package/dist/diagnostics.d.ts +10 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +83 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +211 -0
- package/dist/errors.js.map +1 -0
- package/dist/generated-bridge.d.ts +365 -0
- package/dist/generated-bridge.d.ts.map +1 -0
- package/dist/generated-bridge.js +250 -0
- package/dist/generated-bridge.js.map +1 -0
- package/dist/index.d.ts +13 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -25
- package/dist/index.js.map +1 -1
- package/dist/mutations.d.ts +4 -72
- package/dist/mutations.d.ts.map +1 -1
- package/dist/mutations.js +2 -568
- package/dist/mutations.js.map +1 -1
- package/dist/network.d.ts +3 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +17 -0
- package/dist/network.js.map +1 -0
- package/dist/react/index.d.ts +169 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +628 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react-native/index.d.ts +35 -0
- package/dist/react-native/index.d.ts.map +1 -0
- package/dist/react-native/index.js +49 -0
- package/dist/react-native/index.js.map +1 -0
- package/dist/runtime-contract.d.ts +13 -0
- package/dist/runtime-contract.d.ts.map +1 -0
- package/dist/runtime-contract.js +24 -0
- package/dist/runtime-contract.js.map +1 -0
- package/dist/rust-client.d.ts +106 -0
- package/dist/rust-client.d.ts.map +1 -0
- package/dist/rust-client.js +936 -0
- package/dist/rust-client.js.map +1 -0
- package/dist/sentry.d.ts +35 -0
- package/dist/sentry.d.ts.map +1 -0
- package/dist/sentry.js +155 -0
- package/dist/sentry.js.map +1 -0
- package/dist/sql-safety.d.ts +3 -0
- package/dist/sql-safety.d.ts.map +1 -0
- package/dist/sql-safety.js +62 -0
- package/dist/sql-safety.js.map +1 -0
- package/dist/syncular-runtime-artifacts.json +61 -0
- package/dist/tauri/index.d.ts +35 -0
- package/dist/tauri/index.d.ts.map +1 -0
- package/dist/tauri/index.js +114 -0
- package/dist/tauri/index.js.map +1 -0
- package/dist/types.d.ts +1059 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/wasm/.syncular-wasm-profile +1 -0
- package/dist/wasm/syncular-runtime-artifact.json +21 -0
- package/dist/wasm/syncular.d.ts +207 -0
- package/dist/wasm/syncular.js +2341 -0
- package/dist/wasm/syncular_bg.wasm +0 -0
- package/dist/wasm/syncular_bg.wasm.d.ts +97 -0
- package/dist/wasm-bindings/runtime-contract.d.ts +22 -0
- package/dist/wasm-bindings/runtime-contract.d.ts.map +1 -0
- package/dist/wasm-bindings/runtime-contract.js +112 -0
- package/dist/wasm-bindings/runtime-contract.js.map +1 -0
- package/dist/wasm-core/.syncular-wasm-profile +1 -0
- package/dist/wasm-core/syncular-runtime-artifact.json +17 -0
- package/dist/wasm-core/syncular.d.ts +162 -0
- package/dist/wasm-core/syncular.js +1847 -0
- package/dist/wasm-core/syncular_bg.wasm +0 -0
- package/dist/wasm-core/syncular_bg.wasm.d.ts +77 -0
- package/dist/wasm-perf/.syncular-wasm-profile +1 -0
- package/dist/wasm-perf/syncular-runtime-artifact.json +21 -0
- package/dist/wasm-perf/syncular.d.ts +207 -0
- package/dist/wasm-perf/syncular.js +2341 -0
- package/dist/wasm-perf/syncular_bg.wasm +0 -0
- package/dist/wasm-perf/syncular_bg.wasm.d.ts +97 -0
- package/dist/wasm-runtime.d.ts +18 -0
- package/dist/wasm-runtime.d.ts.map +1 -0
- package/dist/wasm-runtime.js +28 -0
- package/dist/wasm-runtime.js.map +1 -0
- package/dist/worker-client.d.ts +123 -0
- package/dist/worker-client.d.ts.map +1 -0
- package/dist/worker-client.js +1686 -0
- package/dist/worker-client.js.map +1 -0
- package/dist/worker-entry.d.ts +2 -0
- package/dist/worker-entry.d.ts.map +1 -0
- package/dist/worker-entry.js +479 -0
- package/dist/worker-entry.js.map +1 -0
- package/dist/worker-protocol.d.ts +64 -0
- package/dist/worker-protocol.d.ts.map +1 -0
- package/dist/worker-protocol.js +16 -0
- package/dist/worker-protocol.js.map +1 -0
- package/dist/worker-realtime.d.ts +39 -0
- package/dist/worker-realtime.d.ts.map +1 -0
- package/dist/worker-realtime.js +677 -0
- package/dist/worker-realtime.js.map +1 -0
- package/package.json +124 -17
- package/src/auth-leases.ts +251 -0
- package/src/blob-limits.ts +98 -0
- package/src/bridge-client.ts +512 -0
- package/src/client-config.ts +29 -0
- package/src/client.ts +256 -1414
- package/src/command-history.ts +623 -0
- package/src/console-diagnostics.ts +617 -0
- package/src/crdt-yjs/index.ts +3 -0
- package/src/crdt-yjs/webview-host-facade.ts +477 -0
- package/src/crdt-yjs/yjs-document-field-adapter.ts +733 -0
- package/src/crdt-yjs/yjs-prosemirror-bridge.ts +272 -0
- package/src/database.ts +1342 -0
- package/src/diagnostics.ts +116 -0
- package/src/errors.ts +269 -0
- package/src/generated-bridge.ts +741 -0
- package/src/index.ts +21 -25
- package/src/mutations.ts +12 -821
- package/src/network.ts +32 -0
- package/src/react/index.ts +1036 -0
- package/src/react-native/index.ts +152 -0
- package/src/runtime-contract.ts +48 -0
- package/src/rust-client.ts +1488 -0
- package/src/sentry.ts +215 -0
- package/src/sql-safety.ts +61 -0
- package/src/tauri/index.ts +211 -0
- package/src/types.ts +1356 -0
- package/src/wasm-bindings/generated-wasm-bindings.d.ts +70 -0
- package/src/wasm-bindings/runtime-contract.ts +158 -0
- package/src/wasm-runtime.ts +76 -0
- package/src/worker-client.ts +2215 -0
- package/src/worker-entry.ts +584 -0
- package/src/worker-protocol.ts +109 -0
- package/src/worker-realtime.ts +843 -0
- package/dist/blobs/index.d.ts +0 -7
- package/dist/blobs/index.d.ts.map +0 -1
- package/dist/blobs/index.js +0 -7
- package/dist/blobs/index.js.map +0 -1
- package/dist/blobs/manager.d.ts +0 -345
- package/dist/blobs/manager.d.ts.map +0 -1
- package/dist/blobs/manager.js +0 -749
- package/dist/blobs/manager.js.map +0 -1
- package/dist/blobs/migrate.d.ts +0 -14
- package/dist/blobs/migrate.d.ts.map +0 -1
- package/dist/blobs/migrate.js +0 -59
- package/dist/blobs/migrate.js.map +0 -1
- package/dist/blobs/types.d.ts +0 -62
- package/dist/blobs/types.d.ts.map +0 -1
- package/dist/blobs/types.js +0 -5
- package/dist/blobs/types.js.map +0 -1
- package/dist/conflicts.d.ts +0 -31
- package/dist/conflicts.d.ts.map +0 -1
- package/dist/conflicts.js +0 -112
- package/dist/conflicts.js.map +0 -1
- package/dist/create-client.d.ts +0 -118
- package/dist/create-client.d.ts.map +0 -1
- package/dist/create-client.js +0 -178
- package/dist/create-client.js.map +0 -1
- package/dist/engine/SyncEngine.d.ts +0 -272
- package/dist/engine/SyncEngine.d.ts.map +0 -1
- package/dist/engine/SyncEngine.js +0 -1860
- package/dist/engine/SyncEngine.js.map +0 -1
- package/dist/engine/index.d.ts +0 -6
- package/dist/engine/index.d.ts.map +0 -1
- package/dist/engine/index.js +0 -6
- package/dist/engine/index.js.map +0 -1
- package/dist/engine/types.d.ts +0 -345
- package/dist/engine/types.d.ts.map +0 -1
- package/dist/engine/types.js +0 -7
- package/dist/engine/types.js.map +0 -1
- package/dist/handlers/collection.d.ts +0 -6
- package/dist/handlers/collection.d.ts.map +0 -1
- package/dist/handlers/collection.js +0 -21
- package/dist/handlers/collection.js.map +0 -1
- package/dist/handlers/create-handler.d.ts +0 -120
- package/dist/handlers/create-handler.d.ts.map +0 -1
- package/dist/handlers/create-handler.js +0 -145
- package/dist/handlers/create-handler.js.map +0 -1
- package/dist/handlers/types.d.ts +0 -83
- package/dist/handlers/types.d.ts.map +0 -1
- package/dist/handlers/types.js +0 -5
- package/dist/handlers/types.js.map +0 -1
- package/dist/migrate.d.ts +0 -19
- package/dist/migrate.d.ts.map +0 -1
- package/dist/migrate.js +0 -238
- package/dist/migrate.js.map +0 -1
- package/dist/outbox.d.ts +0 -112
- package/dist/outbox.d.ts.map +0 -1
- package/dist/outbox.js +0 -294
- package/dist/outbox.js.map +0 -1
- package/dist/plugins/incrementing-version.d.ts +0 -34
- package/dist/plugins/incrementing-version.d.ts.map +0 -1
- package/dist/plugins/incrementing-version.js +0 -83
- package/dist/plugins/incrementing-version.js.map +0 -1
- package/dist/plugins/index.d.ts +0 -3
- package/dist/plugins/index.d.ts.map +0 -1
- package/dist/plugins/index.js +0 -3
- package/dist/plugins/index.js.map +0 -1
- package/dist/plugins/types.d.ts +0 -49
- package/dist/plugins/types.d.ts.map +0 -1
- package/dist/plugins/types.js +0 -15
- package/dist/plugins/types.js.map +0 -1
- package/dist/proxy/connection.d.ts +0 -33
- package/dist/proxy/connection.d.ts.map +0 -1
- package/dist/proxy/connection.js +0 -153
- package/dist/proxy/connection.js.map +0 -1
- package/dist/proxy/dialect.d.ts +0 -46
- package/dist/proxy/dialect.d.ts.map +0 -1
- package/dist/proxy/dialect.js +0 -58
- package/dist/proxy/dialect.js.map +0 -1
- package/dist/proxy/driver.d.ts +0 -42
- package/dist/proxy/driver.d.ts.map +0 -1
- package/dist/proxy/driver.js +0 -78
- package/dist/proxy/driver.js.map +0 -1
- package/dist/proxy/index.d.ts +0 -10
- package/dist/proxy/index.d.ts.map +0 -1
- package/dist/proxy/index.js +0 -10
- package/dist/proxy/index.js.map +0 -1
- package/dist/proxy/mutations.d.ts +0 -9
- package/dist/proxy/mutations.d.ts.map +0 -1
- package/dist/proxy/mutations.js +0 -11
- package/dist/proxy/mutations.js.map +0 -1
- package/dist/pull-engine.d.ts +0 -48
- package/dist/pull-engine.d.ts.map +0 -1
- package/dist/pull-engine.js +0 -648
- package/dist/pull-engine.js.map +0 -1
- package/dist/push-engine.d.ts +0 -18
- package/dist/push-engine.d.ts.map +0 -1
- package/dist/push-engine.js +0 -167
- package/dist/push-engine.js.map +0 -1
- package/dist/query/FingerprintCollector.d.ts +0 -18
- package/dist/query/FingerprintCollector.d.ts.map +0 -1
- package/dist/query/FingerprintCollector.js +0 -28
- package/dist/query/FingerprintCollector.js.map +0 -1
- package/dist/query/QueryContext.d.ts +0 -33
- package/dist/query/QueryContext.d.ts.map +0 -1
- package/dist/query/QueryContext.js +0 -16
- package/dist/query/QueryContext.js.map +0 -1
- package/dist/query/fingerprint.d.ts +0 -61
- package/dist/query/fingerprint.d.ts.map +0 -1
- package/dist/query/fingerprint.js +0 -91
- package/dist/query/fingerprint.js.map +0 -1
- package/dist/query/index.d.ts +0 -7
- package/dist/query/index.d.ts.map +0 -1
- package/dist/query/index.js +0 -7
- package/dist/query/index.js.map +0 -1
- package/dist/query/tracked-select.d.ts +0 -18
- package/dist/query/tracked-select.d.ts.map +0 -1
- package/dist/query/tracked-select.js +0 -90
- package/dist/query/tracked-select.js.map +0 -1
- package/dist/schema.d.ts +0 -83
- package/dist/schema.d.ts.map +0 -1
- package/dist/schema.js +0 -7
- package/dist/schema.js.map +0 -1
- package/dist/subscription-state.d.ts +0 -46
- package/dist/subscription-state.d.ts.map +0 -1
- package/dist/subscription-state.js +0 -185
- package/dist/subscription-state.js.map +0 -1
- package/dist/sync-loop.d.ts +0 -34
- package/dist/sync-loop.d.ts.map +0 -1
- package/dist/sync-loop.js +0 -253
- package/dist/sync-loop.js.map +0 -1
- package/dist/sync.d.ts +0 -32
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js +0 -55
- package/dist/sync.js.map +0 -1
- package/dist/utils/id.d.ts +0 -17
- package/dist/utils/id.d.ts.map +0 -1
- package/dist/utils/id.js +0 -46
- package/dist/utils/id.js.map +0 -1
- package/src/blobs/index.ts +0 -7
- package/src/blobs/manager.ts +0 -1027
- package/src/blobs/migrate.ts +0 -67
- package/src/blobs/types.ts +0 -84
- package/src/client.test.ts +0 -417
- package/src/conflicts.ts +0 -171
- package/src/create-client.test.ts +0 -83
- package/src/create-client.ts +0 -330
- package/src/engine/SyncEngine.test.ts +0 -254
- package/src/engine/SyncEngine.ts +0 -2375
- package/src/engine/index.ts +0 -6
- package/src/engine/types.ts +0 -419
- package/src/handlers/collection.ts +0 -36
- package/src/handlers/create-handler.ts +0 -330
- package/src/handlers/types.ts +0 -108
- package/src/migrate.ts +0 -318
- package/src/outbox.ts +0 -383
- package/src/plugins/incrementing-version.ts +0 -133
- package/src/plugins/index.ts +0 -2
- package/src/plugins/types.ts +0 -63
- package/src/proxy/connection.ts +0 -191
- package/src/proxy/dialect.ts +0 -76
- package/src/proxy/driver.ts +0 -126
- package/src/proxy/index.ts +0 -10
- package/src/proxy/mutations.ts +0 -18
- package/src/pull-engine.test.ts +0 -151
- package/src/pull-engine.ts +0 -855
- package/src/push-engine.ts +0 -216
- package/src/query/FingerprintCollector.ts +0 -29
- package/src/query/QueryContext.ts +0 -54
- package/src/query/fingerprint.ts +0 -109
- package/src/query/index.ts +0 -10
- package/src/query/tracked-select.ts +0 -139
- package/src/schema.ts +0 -94
- package/src/subscription-state.ts +0 -259
- package/src/sync-loop.ts +0 -379
- package/src/sync.ts +0 -170
- package/src/utils/id.ts +0 -55
package/dist/pull-engine.js
DELETED
|
@@ -1,648 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/client - Sync pull engine
|
|
3
|
-
*/
|
|
4
|
-
import { decodeSnapshotRows } from '@syncular/core';
|
|
5
|
-
import { sql } from 'kysely';
|
|
6
|
-
import { getClientHandlerOrThrow, } from './handlers/collection.js';
|
|
7
|
-
// Simple JSON serialization cache to avoid repeated stringification
|
|
8
|
-
// of the same objects during pull operations
|
|
9
|
-
const jsonCache = new WeakMap();
|
|
10
|
-
const jsonCacheStats = { hits: 0, misses: 0 };
|
|
11
|
-
const SNAPSHOT_CHUNK_CONCURRENCY = 8;
|
|
12
|
-
const SNAPSHOT_APPLY_BATCH_ROWS = 500;
|
|
13
|
-
const SNAPSHOT_ROW_FRAME_MAGIC = new Uint8Array([0x53, 0x52, 0x46, 0x31]); // "SRF1"
|
|
14
|
-
const FRAME_LENGTH_BYTES = 4;
|
|
15
|
-
function serializeJsonCached(obj) {
|
|
16
|
-
if (obj === null || typeof obj !== 'object') {
|
|
17
|
-
return JSON.stringify(obj);
|
|
18
|
-
}
|
|
19
|
-
const cached = jsonCache.get(obj);
|
|
20
|
-
if (cached !== undefined) {
|
|
21
|
-
jsonCacheStats.hits++;
|
|
22
|
-
return cached;
|
|
23
|
-
}
|
|
24
|
-
jsonCacheStats.misses++;
|
|
25
|
-
const serialized = JSON.stringify(obj);
|
|
26
|
-
// Only cache objects that are likely to be reused (not one-off empty objects)
|
|
27
|
-
if (Object.keys(obj).length > 0) {
|
|
28
|
-
jsonCache.set(obj, serialized);
|
|
29
|
-
}
|
|
30
|
-
return serialized;
|
|
31
|
-
}
|
|
32
|
-
function isGzipBytes(bytes) {
|
|
33
|
-
return bytes.length >= 2 && bytes[0] === 0x1f && bytes[1] === 0x8b;
|
|
34
|
-
}
|
|
35
|
-
function bytesToReadableStream(bytes) {
|
|
36
|
-
return new ReadableStream({
|
|
37
|
-
start(controller) {
|
|
38
|
-
controller.enqueue(bytes);
|
|
39
|
-
controller.close();
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
function concatBytes(chunks) {
|
|
44
|
-
if (chunks.length === 1) {
|
|
45
|
-
return chunks[0] ?? new Uint8Array();
|
|
46
|
-
}
|
|
47
|
-
let total = 0;
|
|
48
|
-
for (const chunk of chunks)
|
|
49
|
-
total += chunk.length;
|
|
50
|
-
const out = new Uint8Array(total);
|
|
51
|
-
let offset = 0;
|
|
52
|
-
for (const chunk of chunks) {
|
|
53
|
-
out.set(chunk, offset);
|
|
54
|
-
offset += chunk.length;
|
|
55
|
-
}
|
|
56
|
-
return out;
|
|
57
|
-
}
|
|
58
|
-
function appendBytes(base, next) {
|
|
59
|
-
if (base.length === 0)
|
|
60
|
-
return next;
|
|
61
|
-
if (next.length === 0)
|
|
62
|
-
return base;
|
|
63
|
-
const out = new Uint8Array(base.length + next.length);
|
|
64
|
-
out.set(base, 0);
|
|
65
|
-
out.set(next, base.length);
|
|
66
|
-
return out;
|
|
67
|
-
}
|
|
68
|
-
function toOwnedUint8Array(chunk) {
|
|
69
|
-
const out = new ArrayBuffer(chunk.byteLength);
|
|
70
|
-
const bytes = new Uint8Array(out);
|
|
71
|
-
bytes.set(chunk);
|
|
72
|
-
return bytes;
|
|
73
|
-
}
|
|
74
|
-
async function streamToBytes(stream) {
|
|
75
|
-
const reader = stream.getReader();
|
|
76
|
-
try {
|
|
77
|
-
const chunks = [];
|
|
78
|
-
while (true) {
|
|
79
|
-
const { done, value } = await reader.read();
|
|
80
|
-
if (done)
|
|
81
|
-
break;
|
|
82
|
-
if (!value || value.length === 0)
|
|
83
|
-
continue;
|
|
84
|
-
chunks.push(value);
|
|
85
|
-
}
|
|
86
|
-
return concatBytes(chunks);
|
|
87
|
-
}
|
|
88
|
-
finally {
|
|
89
|
-
reader.releaseLock();
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async function maybeGunzipStream(stream) {
|
|
93
|
-
const reader = stream.getReader();
|
|
94
|
-
const prefetched = [];
|
|
95
|
-
let prefetchedBytes = 0;
|
|
96
|
-
while (prefetchedBytes < 2) {
|
|
97
|
-
const { done, value } = await reader.read();
|
|
98
|
-
if (done)
|
|
99
|
-
break;
|
|
100
|
-
if (!value || value.length === 0)
|
|
101
|
-
continue;
|
|
102
|
-
prefetched.push(value);
|
|
103
|
-
prefetchedBytes += value.length;
|
|
104
|
-
}
|
|
105
|
-
const prefetchedCombined = concatBytes(prefetched);
|
|
106
|
-
const gzip = isGzipBytes(prefetchedCombined);
|
|
107
|
-
const replayStream = new ReadableStream({
|
|
108
|
-
start(controller) {
|
|
109
|
-
if (prefetchedCombined.length > 0) {
|
|
110
|
-
controller.enqueue(toOwnedUint8Array(prefetchedCombined));
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
async pull(controller) {
|
|
114
|
-
const { done, value } = await reader.read();
|
|
115
|
-
if (done) {
|
|
116
|
-
controller.close();
|
|
117
|
-
reader.releaseLock();
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (!value || value.length === 0)
|
|
121
|
-
return;
|
|
122
|
-
controller.enqueue(toOwnedUint8Array(value));
|
|
123
|
-
},
|
|
124
|
-
async cancel(reason) {
|
|
125
|
-
await reader.cancel(reason);
|
|
126
|
-
reader.releaseLock();
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
if (!gzip)
|
|
130
|
-
return replayStream;
|
|
131
|
-
if (typeof DecompressionStream !== 'undefined') {
|
|
132
|
-
return replayStream.pipeThrough(new DecompressionStream('gzip'));
|
|
133
|
-
}
|
|
134
|
-
throw new Error('Snapshot chunk appears gzip-compressed but gzip decompression is not available in this runtime');
|
|
135
|
-
}
|
|
136
|
-
async function maybeGunzip(bytes) {
|
|
137
|
-
if (!isGzipBytes(bytes))
|
|
138
|
-
return bytes;
|
|
139
|
-
const decompressedStream = await maybeGunzipStream(bytesToReadableStream(bytes));
|
|
140
|
-
return streamToBytes(decompressedStream);
|
|
141
|
-
}
|
|
142
|
-
async function* decodeSnapshotRowStreamBatches(stream, batchSize) {
|
|
143
|
-
const reader = stream.getReader();
|
|
144
|
-
const decoder = new TextDecoder();
|
|
145
|
-
let pending = new Uint8Array(0);
|
|
146
|
-
let headerValidated = false;
|
|
147
|
-
let rows = [];
|
|
148
|
-
try {
|
|
149
|
-
while (true) {
|
|
150
|
-
const { done, value } = await reader.read();
|
|
151
|
-
if (done)
|
|
152
|
-
break;
|
|
153
|
-
if (!value || value.length === 0)
|
|
154
|
-
continue;
|
|
155
|
-
pending = appendBytes(pending, value);
|
|
156
|
-
if (!headerValidated) {
|
|
157
|
-
if (pending.length < SNAPSHOT_ROW_FRAME_MAGIC.length) {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
for (let index = 0; index < SNAPSHOT_ROW_FRAME_MAGIC.length; index++) {
|
|
161
|
-
if (pending[index] !== SNAPSHOT_ROW_FRAME_MAGIC[index]) {
|
|
162
|
-
throw new Error('Unexpected snapshot chunk format');
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
pending = pending.subarray(SNAPSHOT_ROW_FRAME_MAGIC.length);
|
|
166
|
-
headerValidated = true;
|
|
167
|
-
}
|
|
168
|
-
while (pending.length >= FRAME_LENGTH_BYTES) {
|
|
169
|
-
const view = new DataView(pending.buffer, pending.byteOffset, pending.byteLength);
|
|
170
|
-
const payloadLength = view.getUint32(0, false);
|
|
171
|
-
if (pending.length < FRAME_LENGTH_BYTES + payloadLength) {
|
|
172
|
-
break;
|
|
173
|
-
}
|
|
174
|
-
const payload = pending.subarray(FRAME_LENGTH_BYTES, FRAME_LENGTH_BYTES + payloadLength);
|
|
175
|
-
rows.push(JSON.parse(decoder.decode(payload)));
|
|
176
|
-
pending = pending.subarray(FRAME_LENGTH_BYTES + payloadLength);
|
|
177
|
-
if (rows.length >= batchSize) {
|
|
178
|
-
yield rows;
|
|
179
|
-
rows = [];
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if (!headerValidated) {
|
|
184
|
-
throw new Error('Snapshot chunk payload is too small');
|
|
185
|
-
}
|
|
186
|
-
if (pending.length > 0) {
|
|
187
|
-
if (pending.length < FRAME_LENGTH_BYTES) {
|
|
188
|
-
throw new Error('Snapshot chunk payload ended mid-frame header');
|
|
189
|
-
}
|
|
190
|
-
const view = new DataView(pending.buffer, pending.byteOffset, pending.byteLength);
|
|
191
|
-
const payloadLength = view.getUint32(0, false);
|
|
192
|
-
if (pending.length < FRAME_LENGTH_BYTES + payloadLength) {
|
|
193
|
-
throw new Error('Snapshot chunk payload ended mid-frame body');
|
|
194
|
-
}
|
|
195
|
-
while (pending.length >= FRAME_LENGTH_BYTES) {
|
|
196
|
-
const nextView = new DataView(pending.buffer, pending.byteOffset, pending.byteLength);
|
|
197
|
-
const nextLength = nextView.getUint32(0, false);
|
|
198
|
-
if (pending.length < FRAME_LENGTH_BYTES + nextLength) {
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
const payload = pending.subarray(FRAME_LENGTH_BYTES, FRAME_LENGTH_BYTES + nextLength);
|
|
202
|
-
rows.push(JSON.parse(decoder.decode(payload)));
|
|
203
|
-
pending = pending.subarray(FRAME_LENGTH_BYTES + nextLength);
|
|
204
|
-
if (rows.length >= batchSize) {
|
|
205
|
-
yield rows;
|
|
206
|
-
rows = [];
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (pending.length > 0) {
|
|
210
|
-
throw new Error('Snapshot chunk payload ended mid-frame body');
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
if (rows.length > 0) {
|
|
214
|
-
yield rows;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
finally {
|
|
218
|
-
reader.releaseLock();
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
async function computeSha256Hex(bytes, sha256Override) {
|
|
222
|
-
// Use injected implementation if provided (e.g. expo-crypto on React Native)
|
|
223
|
-
if (sha256Override) {
|
|
224
|
-
return sha256Override(bytes);
|
|
225
|
-
}
|
|
226
|
-
// Use crypto.subtle if available (browsers, modern Node/Bun)
|
|
227
|
-
if (typeof crypto !== 'undefined' && crypto.subtle) {
|
|
228
|
-
// Create a fresh ArrayBuffer to satisfy crypto.subtle's type requirements
|
|
229
|
-
const buffer = new ArrayBuffer(bytes.length);
|
|
230
|
-
new Uint8Array(buffer).set(bytes);
|
|
231
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
|
|
232
|
-
const hashArray = new Uint8Array(hashBuffer);
|
|
233
|
-
return Array.from(hashArray, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
234
|
-
}
|
|
235
|
-
throw new Error('No crypto implementation available for SHA-256. ' +
|
|
236
|
-
'Provide a sha256 function via options or ensure crypto.subtle is available.');
|
|
237
|
-
}
|
|
238
|
-
async function fetchSnapshotChunkStream(transport, chunkId) {
|
|
239
|
-
if (transport.fetchSnapshotChunkStream) {
|
|
240
|
-
return transport.fetchSnapshotChunkStream({ chunkId });
|
|
241
|
-
}
|
|
242
|
-
const bytes = await transport.fetchSnapshotChunk({ chunkId });
|
|
243
|
-
return bytesToReadableStream(bytes);
|
|
244
|
-
}
|
|
245
|
-
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
246
|
-
if (items.length === 0)
|
|
247
|
-
return [];
|
|
248
|
-
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
249
|
-
const results = new Array(items.length);
|
|
250
|
-
let nextIndex = 0;
|
|
251
|
-
async function worker() {
|
|
252
|
-
while (nextIndex < items.length) {
|
|
253
|
-
const index = nextIndex;
|
|
254
|
-
nextIndex += 1;
|
|
255
|
-
const item = items[index];
|
|
256
|
-
if (item === undefined)
|
|
257
|
-
continue;
|
|
258
|
-
results[index] = await mapper(item, index);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
262
|
-
return results;
|
|
263
|
-
}
|
|
264
|
-
async function materializeChunkedSnapshots(transport, response, sha256Override) {
|
|
265
|
-
const chunkCache = new Map();
|
|
266
|
-
const subscriptions = await Promise.all(response.subscriptions.map(async (sub) => {
|
|
267
|
-
if (!sub.bootstrap)
|
|
268
|
-
return sub;
|
|
269
|
-
if (!sub.snapshots || sub.snapshots.length === 0)
|
|
270
|
-
return sub;
|
|
271
|
-
const snapshots = await mapWithConcurrency(sub.snapshots, SNAPSHOT_CHUNK_CONCURRENCY, async (snapshot) => {
|
|
272
|
-
const chunks = snapshot.chunks ?? [];
|
|
273
|
-
if (chunks.length === 0) {
|
|
274
|
-
return snapshot;
|
|
275
|
-
}
|
|
276
|
-
const parsedRowsByChunk = await mapWithConcurrency(chunks, SNAPSHOT_CHUNK_CONCURRENCY, async (chunk) => {
|
|
277
|
-
const promise = chunkCache.get(chunk.id) ??
|
|
278
|
-
transport.fetchSnapshotChunk({ chunkId: chunk.id });
|
|
279
|
-
chunkCache.set(chunk.id, promise);
|
|
280
|
-
const raw = await promise;
|
|
281
|
-
const bytes = await maybeGunzip(raw);
|
|
282
|
-
// Verify chunk integrity using sha256 hash
|
|
283
|
-
if (chunk.sha256) {
|
|
284
|
-
const actualHash = await computeSha256Hex(bytes, sha256Override);
|
|
285
|
-
if (actualHash !== chunk.sha256) {
|
|
286
|
-
throw new Error(`Snapshot chunk integrity check failed: expected sha256 ${chunk.sha256}, got ${actualHash}`);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return decodeSnapshotRows(bytes);
|
|
290
|
-
});
|
|
291
|
-
const rows = [];
|
|
292
|
-
for (const parsedRows of parsedRowsByChunk) {
|
|
293
|
-
rows.push(...parsedRows);
|
|
294
|
-
}
|
|
295
|
-
return { ...snapshot, rows, chunks: undefined };
|
|
296
|
-
});
|
|
297
|
-
return { ...sub, snapshots };
|
|
298
|
-
}));
|
|
299
|
-
// Clear chunk cache after processing to prevent memory accumulation
|
|
300
|
-
chunkCache.clear();
|
|
301
|
-
return { ...response, subscriptions };
|
|
302
|
-
}
|
|
303
|
-
async function applyChunkedSnapshot(transport, handler, trx, snapshot) {
|
|
304
|
-
const chunks = snapshot.chunks ?? [];
|
|
305
|
-
if (chunks.length === 0) {
|
|
306
|
-
await handler.applySnapshot({ trx }, snapshot);
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
let nextIsFirstPage = snapshot.isFirstPage;
|
|
310
|
-
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex += 1) {
|
|
311
|
-
const chunk = chunks[chunkIndex];
|
|
312
|
-
if (!chunk)
|
|
313
|
-
continue;
|
|
314
|
-
const rawStream = await fetchSnapshotChunkStream(transport, chunk.id);
|
|
315
|
-
const decodedStream = await maybeGunzipStream(rawStream);
|
|
316
|
-
const rowBatchIterator = decodeSnapshotRowStreamBatches(decodedStream, SNAPSHOT_APPLY_BATCH_ROWS);
|
|
317
|
-
let pendingBatch = null;
|
|
318
|
-
// eslint-disable-next-line no-await-in-loop
|
|
319
|
-
for await (const batch of rowBatchIterator) {
|
|
320
|
-
if (pendingBatch) {
|
|
321
|
-
// eslint-disable-next-line no-await-in-loop
|
|
322
|
-
await handler.applySnapshot({ trx }, {
|
|
323
|
-
...snapshot,
|
|
324
|
-
rows: pendingBatch,
|
|
325
|
-
chunks: undefined,
|
|
326
|
-
isFirstPage: nextIsFirstPage,
|
|
327
|
-
isLastPage: false,
|
|
328
|
-
});
|
|
329
|
-
nextIsFirstPage = false;
|
|
330
|
-
}
|
|
331
|
-
pendingBatch = batch;
|
|
332
|
-
}
|
|
333
|
-
if (pendingBatch) {
|
|
334
|
-
const isLastChunk = chunkIndex === chunks.length - 1;
|
|
335
|
-
// eslint-disable-next-line no-await-in-loop
|
|
336
|
-
await handler.applySnapshot({ trx }, {
|
|
337
|
-
...snapshot,
|
|
338
|
-
rows: pendingBatch,
|
|
339
|
-
chunks: undefined,
|
|
340
|
-
isFirstPage: nextIsFirstPage,
|
|
341
|
-
isLastPage: isLastChunk ? snapshot.isLastPage : false,
|
|
342
|
-
});
|
|
343
|
-
nextIsFirstPage = false;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
function parseBootstrapState(value) {
|
|
348
|
-
if (!value)
|
|
349
|
-
return null;
|
|
350
|
-
try {
|
|
351
|
-
// Handle both string (raw JSON) and object (already deserialized by a DB plugin/driver)
|
|
352
|
-
const parsed = typeof value === 'string'
|
|
353
|
-
? JSON.parse(value)
|
|
354
|
-
: value;
|
|
355
|
-
if (!parsed || typeof parsed !== 'object')
|
|
356
|
-
return null;
|
|
357
|
-
if (typeof parsed.asOfCommitSeq !== 'number')
|
|
358
|
-
return null;
|
|
359
|
-
if (!Array.isArray(parsed.tables))
|
|
360
|
-
return null;
|
|
361
|
-
if (typeof parsed.tableIndex !== 'number')
|
|
362
|
-
return null;
|
|
363
|
-
if (parsed.rowCursor !== null && typeof parsed.rowCursor !== 'string')
|
|
364
|
-
return null;
|
|
365
|
-
return parsed;
|
|
366
|
-
}
|
|
367
|
-
catch {
|
|
368
|
-
return null;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Build a pull request from subscription state. Exported for use
|
|
373
|
-
* by the combined sync path in sync-loop.ts.
|
|
374
|
-
*/
|
|
375
|
-
export async function buildPullRequest(db, options) {
|
|
376
|
-
const stateId = options.stateId ?? 'default';
|
|
377
|
-
const existingResult = await sql `
|
|
378
|
-
select
|
|
379
|
-
${sql.ref('state_id')},
|
|
380
|
-
${sql.ref('subscription_id')},
|
|
381
|
-
${sql.ref('table')},
|
|
382
|
-
${sql.ref('scopes_json')},
|
|
383
|
-
${sql.ref('params_json')},
|
|
384
|
-
${sql.ref('cursor')},
|
|
385
|
-
${sql.ref('bootstrap_state_json')},
|
|
386
|
-
${sql.ref('status')},
|
|
387
|
-
${sql.ref('created_at')},
|
|
388
|
-
${sql.ref('updated_at')}
|
|
389
|
-
from ${sql.table('sync_subscription_state')}
|
|
390
|
-
where ${sql.ref('state_id')} = ${sql.val(stateId)}
|
|
391
|
-
`.execute(db);
|
|
392
|
-
const existing = existingResult.rows;
|
|
393
|
-
const existingById = new Map();
|
|
394
|
-
for (const row of existing)
|
|
395
|
-
existingById.set(row.subscription_id, row);
|
|
396
|
-
const request = {
|
|
397
|
-
clientId: options.clientId,
|
|
398
|
-
limitCommits: options.limitCommits ?? 50,
|
|
399
|
-
limitSnapshotRows: options.limitSnapshotRows ?? 1000,
|
|
400
|
-
maxSnapshotPages: options.maxSnapshotPages ?? 4,
|
|
401
|
-
dedupeRows: options.dedupeRows,
|
|
402
|
-
subscriptions: (options.subscriptions ?? []).map((sub) => ({
|
|
403
|
-
...sub,
|
|
404
|
-
cursor: Math.max(-1, existingById.get(sub.id)?.cursor ?? -1),
|
|
405
|
-
bootstrapState: parseBootstrapState(existingById.get(sub.id)?.bootstrap_state_json),
|
|
406
|
-
})),
|
|
407
|
-
};
|
|
408
|
-
return { request, existing, existingById, stateId };
|
|
409
|
-
}
|
|
410
|
-
export function createFollowupPullState(pullState, response) {
|
|
411
|
-
const responseById = new Map();
|
|
412
|
-
for (const sub of response.subscriptions ?? []) {
|
|
413
|
-
responseById.set(sub.id, sub);
|
|
414
|
-
}
|
|
415
|
-
const now = Date.now();
|
|
416
|
-
const nextExisting = [];
|
|
417
|
-
const nextExistingById = new Map();
|
|
418
|
-
for (const sub of pullState.request.subscriptions ?? []) {
|
|
419
|
-
const res = responseById.get(sub.id);
|
|
420
|
-
if (res?.status === 'revoked') {
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
const nextCursor = res ? Math.max(-1, res.nextCursor) : (sub.cursor ?? -1);
|
|
424
|
-
const nextBootstrapState = res
|
|
425
|
-
? res.bootstrap
|
|
426
|
-
? (res.bootstrapState ?? null)
|
|
427
|
-
: null
|
|
428
|
-
: (sub.bootstrapState ?? null);
|
|
429
|
-
const prev = pullState.existingById.get(sub.id);
|
|
430
|
-
const nextRow = {
|
|
431
|
-
state_id: pullState.stateId,
|
|
432
|
-
subscription_id: sub.id,
|
|
433
|
-
table: sub.table,
|
|
434
|
-
scopes_json: serializeJsonCached(sub.scopes ?? {}),
|
|
435
|
-
params_json: serializeJsonCached(sub.params ?? {}),
|
|
436
|
-
cursor: nextCursor,
|
|
437
|
-
bootstrap_state_json: nextBootstrapState
|
|
438
|
-
? serializeJsonCached(nextBootstrapState)
|
|
439
|
-
: null,
|
|
440
|
-
status: 'active',
|
|
441
|
-
created_at: prev?.created_at ?? now,
|
|
442
|
-
updated_at: now,
|
|
443
|
-
};
|
|
444
|
-
nextExisting.push(nextRow);
|
|
445
|
-
nextExistingById.set(nextRow.subscription_id, nextRow);
|
|
446
|
-
}
|
|
447
|
-
const nextRequest = {
|
|
448
|
-
...pullState.request,
|
|
449
|
-
subscriptions: (pullState.request.subscriptions ?? []).map((sub) => {
|
|
450
|
-
const row = nextExistingById.get(sub.id);
|
|
451
|
-
return {
|
|
452
|
-
...sub,
|
|
453
|
-
cursor: Math.max(-1, row?.cursor ?? -1),
|
|
454
|
-
bootstrapState: parseBootstrapState(row?.bootstrap_state_json),
|
|
455
|
-
};
|
|
456
|
-
}),
|
|
457
|
-
};
|
|
458
|
-
return {
|
|
459
|
-
request: nextRequest,
|
|
460
|
-
existing: nextExisting,
|
|
461
|
-
existingById: nextExistingById,
|
|
462
|
-
stateId: pullState.stateId,
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Apply a pull response (run plugins + write to local DB).
|
|
467
|
-
* Exported for use by the combined sync path in sync-loop.ts.
|
|
468
|
-
*/
|
|
469
|
-
export async function applyPullResponse(db, transport, handlers, options, pullState, rawResponse) {
|
|
470
|
-
const { request, existing, existingById, stateId } = pullState;
|
|
471
|
-
const ctx = {
|
|
472
|
-
actorId: options.actorId ?? 'unknown',
|
|
473
|
-
clientId: options.clientId,
|
|
474
|
-
};
|
|
475
|
-
const plugins = options.plugins ?? [];
|
|
476
|
-
const requiresMaterializedSnapshots = plugins.some((plugin) => !!plugin.afterPull);
|
|
477
|
-
let responseToApply = requiresMaterializedSnapshots
|
|
478
|
-
? await materializeChunkedSnapshots(transport, rawResponse, options.sha256)
|
|
479
|
-
: rawResponse;
|
|
480
|
-
for (const plugin of plugins) {
|
|
481
|
-
if (!plugin.afterPull)
|
|
482
|
-
continue;
|
|
483
|
-
responseToApply = await plugin.afterPull(ctx, {
|
|
484
|
-
request,
|
|
485
|
-
response: responseToApply,
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
await db.transaction().execute(async (trx) => {
|
|
489
|
-
const desiredIds = new Set((options.subscriptions ?? []).map((s) => s.id));
|
|
490
|
-
// Remove local data for subscriptions that are no longer desired.
|
|
491
|
-
for (const row of existing) {
|
|
492
|
-
if (desiredIds.has(row.subscription_id))
|
|
493
|
-
continue;
|
|
494
|
-
// Clear data for this table matching the subscription's scopes
|
|
495
|
-
if (row.table) {
|
|
496
|
-
try {
|
|
497
|
-
const scopes = row.scopes_json
|
|
498
|
-
? typeof row.scopes_json === 'string'
|
|
499
|
-
? JSON.parse(row.scopes_json)
|
|
500
|
-
: row.scopes_json
|
|
501
|
-
: {};
|
|
502
|
-
await getClientHandlerOrThrow(handlers, row.table).clearAll({
|
|
503
|
-
trx,
|
|
504
|
-
scopes,
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
catch {
|
|
508
|
-
// ignore missing table handler
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
await sql `
|
|
512
|
-
delete from ${sql.table('sync_subscription_state')}
|
|
513
|
-
where ${sql.ref('state_id')} = ${sql.val(stateId)}
|
|
514
|
-
and ${sql.ref('subscription_id')} = ${sql.val(row.subscription_id)}
|
|
515
|
-
`.execute(trx);
|
|
516
|
-
}
|
|
517
|
-
const subsById = new Map();
|
|
518
|
-
for (const s of options.subscriptions ?? [])
|
|
519
|
-
subsById.set(s.id, s);
|
|
520
|
-
for (const sub of responseToApply.subscriptions) {
|
|
521
|
-
const def = subsById.get(sub.id);
|
|
522
|
-
const prev = existingById.get(sub.id);
|
|
523
|
-
// Revoked: clear data and drop the subscription row.
|
|
524
|
-
if (sub.status === 'revoked') {
|
|
525
|
-
if (prev?.table) {
|
|
526
|
-
try {
|
|
527
|
-
const scopes = prev.scopes_json
|
|
528
|
-
? typeof prev.scopes_json === 'string'
|
|
529
|
-
? JSON.parse(prev.scopes_json)
|
|
530
|
-
: prev.scopes_json
|
|
531
|
-
: {};
|
|
532
|
-
await getClientHandlerOrThrow(handlers, prev.table).clearAll({
|
|
533
|
-
trx,
|
|
534
|
-
scopes,
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
catch {
|
|
538
|
-
// ignore missing handler
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
await sql `
|
|
542
|
-
delete from ${sql.table('sync_subscription_state')}
|
|
543
|
-
where ${sql.ref('state_id')} = ${sql.val(stateId)}
|
|
544
|
-
and ${sql.ref('subscription_id')} = ${sql.val(sub.id)}
|
|
545
|
-
`.execute(trx);
|
|
546
|
-
continue;
|
|
547
|
-
}
|
|
548
|
-
// Apply snapshots (bootstrap mode)
|
|
549
|
-
if (sub.bootstrap) {
|
|
550
|
-
for (const snapshot of sub.snapshots ?? []) {
|
|
551
|
-
const handler = getClientHandlerOrThrow(handlers, snapshot.table);
|
|
552
|
-
const hasChunkRefs = Array.isArray(snapshot.chunks) && snapshot.chunks.length > 0;
|
|
553
|
-
// Call onSnapshotStart hook when starting a new snapshot
|
|
554
|
-
if (snapshot.isFirstPage && handler.onSnapshotStart) {
|
|
555
|
-
await handler.onSnapshotStart({
|
|
556
|
-
trx,
|
|
557
|
-
table: snapshot.table,
|
|
558
|
-
scopes: sub.scopes,
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
if (hasChunkRefs) {
|
|
562
|
-
await applyChunkedSnapshot(transport, handler, trx, snapshot);
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
await handler.applySnapshot({ trx }, snapshot);
|
|
566
|
-
}
|
|
567
|
-
// Call onSnapshotEnd hook when snapshot is complete
|
|
568
|
-
if (snapshot.isLastPage && handler.onSnapshotEnd) {
|
|
569
|
-
await handler.onSnapshotEnd({
|
|
570
|
-
trx,
|
|
571
|
-
table: snapshot.table,
|
|
572
|
-
scopes: sub.scopes,
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
else {
|
|
578
|
-
// Apply incremental changes
|
|
579
|
-
for (const commit of sub.commits) {
|
|
580
|
-
for (const change of commit.changes) {
|
|
581
|
-
const handler = getClientHandlerOrThrow(handlers, change.table);
|
|
582
|
-
await handler.applyChange({ trx }, change);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
// Persist subscription cursor + metadata.
|
|
587
|
-
// Use cached JSON serialization to avoid repeated stringification
|
|
588
|
-
const now = Date.now();
|
|
589
|
-
const paramsJson = serializeJsonCached(def?.params ?? {});
|
|
590
|
-
const scopesJson = serializeJsonCached(def?.scopes ?? {});
|
|
591
|
-
const bootstrapStateJson = sub.bootstrap
|
|
592
|
-
? sub.bootstrapState
|
|
593
|
-
? serializeJsonCached(sub.bootstrapState)
|
|
594
|
-
: null
|
|
595
|
-
: null;
|
|
596
|
-
const table = def?.table ?? 'unknown';
|
|
597
|
-
await sql `
|
|
598
|
-
insert into ${sql.table('sync_subscription_state')} (
|
|
599
|
-
${sql.join([
|
|
600
|
-
sql.ref('state_id'),
|
|
601
|
-
sql.ref('subscription_id'),
|
|
602
|
-
sql.ref('table'),
|
|
603
|
-
sql.ref('scopes_json'),
|
|
604
|
-
sql.ref('params_json'),
|
|
605
|
-
sql.ref('cursor'),
|
|
606
|
-
sql.ref('bootstrap_state_json'),
|
|
607
|
-
sql.ref('status'),
|
|
608
|
-
sql.ref('created_at'),
|
|
609
|
-
sql.ref('updated_at'),
|
|
610
|
-
])}
|
|
611
|
-
) values (
|
|
612
|
-
${sql.join([
|
|
613
|
-
sql.val(stateId),
|
|
614
|
-
sql.val(sub.id),
|
|
615
|
-
sql.val(table),
|
|
616
|
-
sql.val(scopesJson),
|
|
617
|
-
sql.val(paramsJson),
|
|
618
|
-
sql.val(sub.nextCursor),
|
|
619
|
-
sql.val(bootstrapStateJson),
|
|
620
|
-
sql.val('active'),
|
|
621
|
-
sql.val(now),
|
|
622
|
-
sql.val(now),
|
|
623
|
-
])}
|
|
624
|
-
)
|
|
625
|
-
on conflict (${sql.join([sql.ref('state_id'), sql.ref('subscription_id')])})
|
|
626
|
-
do update set
|
|
627
|
-
${sql.ref('table')} = ${sql.val(table)},
|
|
628
|
-
${sql.ref('scopes_json')} = ${sql.val(scopesJson)},
|
|
629
|
-
${sql.ref('params_json')} = ${sql.val(paramsJson)},
|
|
630
|
-
${sql.ref('cursor')} = ${sql.val(sub.nextCursor)},
|
|
631
|
-
${sql.ref('bootstrap_state_json')} = ${sql.val(bootstrapStateJson)},
|
|
632
|
-
${sql.ref('status')} = ${sql.val('active')},
|
|
633
|
-
${sql.ref('updated_at')} = ${sql.val(now)}
|
|
634
|
-
`.execute(trx);
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
return responseToApply;
|
|
638
|
-
}
|
|
639
|
-
export async function syncPullOnce(db, transport, handlers, options, pullStateOverride) {
|
|
640
|
-
const pullState = pullStateOverride ?? (await buildPullRequest(db, options));
|
|
641
|
-
const { clientId, ...pullBody } = pullState.request;
|
|
642
|
-
const combined = await transport.sync({ clientId, pull: pullBody });
|
|
643
|
-
if (!combined.pull) {
|
|
644
|
-
return { ok: true, subscriptions: [] };
|
|
645
|
-
}
|
|
646
|
-
return applyPullResponse(db, transport, handlers, options, pullState, combined.pull);
|
|
647
|
-
}
|
|
648
|
-
//# sourceMappingURL=pull-engine.js.map
|