@syncular/client 0.0.6-96 → 0.1.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 +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 +183 -957
- 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 +954 -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 +12 -23
- 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 +938 -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 +1096 -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 +23 -0
- package/dist/wasm-runtime.d.ts.map +1 -0
- package/dist/wasm-runtime.js +69 -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 +1735 -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 +293 -1407
- 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 +1396 -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 +1491 -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 +1397 -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 +145 -0
- package/src/worker-client.ts +2289 -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/client.js
CHANGED
|
@@ -1,993 +1,219 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// Client Class
|
|
19
|
-
// ============================================================================
|
|
20
|
-
/**
|
|
21
|
-
* Unified sync client.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```typescript
|
|
25
|
-
* import { Client } from '@syncular/client';
|
|
26
|
-
* import { createHttpTransport } from '@syncular/transport-http';
|
|
27
|
-
*
|
|
28
|
-
* const client = new Client({
|
|
29
|
-
* db,
|
|
30
|
-
* transport: createHttpTransport({ baseUrl: '/api/sync', getHeaders }),
|
|
31
|
-
* tableHandlers,
|
|
32
|
-
* clientId: 'device-123',
|
|
33
|
-
* actorId: 'user-456',
|
|
34
|
-
* subscriptions: [{ id: 'tasks', table: 'tasks', scopes: { user_id: 'user-456' } }],
|
|
35
|
-
* });
|
|
36
|
-
*
|
|
37
|
-
* await client.start();
|
|
38
|
-
*
|
|
39
|
-
* // Mutations
|
|
40
|
-
* await client.mutations.tasks.insert({ title: 'New task' });
|
|
41
|
-
*
|
|
42
|
-
* // Events
|
|
43
|
-
* client.on('sync:complete', () => console.log('synced'));
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
export class Client {
|
|
47
|
-
options;
|
|
48
|
-
engine = null;
|
|
49
|
-
started = false;
|
|
50
|
-
destroyed = false;
|
|
51
|
-
emittedConflictIds = new Set();
|
|
52
|
-
eventListeners = new Map();
|
|
53
|
-
outboxStats = {
|
|
54
|
-
pending: 0,
|
|
55
|
-
sending: 0,
|
|
56
|
-
failed: 0,
|
|
57
|
-
acked: 0,
|
|
58
|
-
total: 0,
|
|
1
|
+
import { isSyncularOfflineError } from './errors.js';
|
|
2
|
+
import { browserSyncularNetworkStatusSource } from './network.js';
|
|
3
|
+
export function getSyncularClientStatus(client) {
|
|
4
|
+
const lifecycle = client.lifecycleState();
|
|
5
|
+
const connection = client.connectionState();
|
|
6
|
+
const outbox = lifecycle.outbox ?? null;
|
|
7
|
+
const conflicts = lifecycle.conflicts ?? null;
|
|
8
|
+
return {
|
|
9
|
+
lifecycle,
|
|
10
|
+
connection,
|
|
11
|
+
outbox,
|
|
12
|
+
conflicts,
|
|
13
|
+
isConnected: connection.realtime === 'connected' && !connection.closed,
|
|
14
|
+
isSyncing: lifecycle.phase === 'syncing' || lifecycle.phase === 'recovering',
|
|
15
|
+
hasPendingMutations: (outbox?.pending ?? 0) + (outbox?.sending ?? 0) > 0,
|
|
16
|
+
hasConflicts: (conflicts?.unresolved ?? 0) > 0,
|
|
17
|
+
requiresAction: lifecycle.requiresAction,
|
|
59
18
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
19
|
+
}
|
|
20
|
+
export class SyncularClientLifecycle {
|
|
21
|
+
client;
|
|
22
|
+
options;
|
|
23
|
+
#started = false;
|
|
24
|
+
#pollTimer;
|
|
25
|
+
#unsubscribeDiagnostics;
|
|
26
|
+
#unsubscribeNetwork;
|
|
27
|
+
#syncInFlight;
|
|
28
|
+
#queuedSyncWaiters = [];
|
|
29
|
+
#hasConnectedRealtime = false;
|
|
30
|
+
#realtimeStarted = false;
|
|
31
|
+
#network;
|
|
32
|
+
constructor(client, options = {}) {
|
|
33
|
+
this.client = client;
|
|
65
34
|
this.options = options;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
versionColumn: options.versionColumn ?? 'server_version',
|
|
71
|
-
omitColumns: options.omitColumns ?? [],
|
|
72
|
-
codecs: options.codecs,
|
|
73
|
-
codecDialect: options.codecDialect,
|
|
74
|
-
});
|
|
75
|
-
this.mutations = createMutationsApi(commitFn);
|
|
76
|
-
// Create blob client if storage provided
|
|
77
|
-
if (options.blobStorage && options.transport.blobs) {
|
|
78
|
-
this.blobs = this.createBlobClient(options.blobStorage, options.transport);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
// ===========================================================================
|
|
82
|
-
// Identity Getters
|
|
83
|
-
// ===========================================================================
|
|
84
|
-
/** Client ID */
|
|
85
|
-
get clientId() {
|
|
86
|
-
return this.options.clientId;
|
|
87
|
-
}
|
|
88
|
-
/** Actor ID */
|
|
89
|
-
get actorId() {
|
|
90
|
-
return this.options.actorId;
|
|
35
|
+
this.#network =
|
|
36
|
+
options.network === false
|
|
37
|
+
? undefined
|
|
38
|
+
: (options.network ?? browserSyncularNetworkStatusSource());
|
|
91
39
|
}
|
|
92
|
-
/** Database instance */
|
|
93
|
-
get db() {
|
|
94
|
-
return this.options.db;
|
|
95
|
-
}
|
|
96
|
-
// ===========================================================================
|
|
97
|
-
// Lifecycle
|
|
98
|
-
// ===========================================================================
|
|
99
|
-
/**
|
|
100
|
-
* Start the client.
|
|
101
|
-
* Runs migrations and starts sync engine.
|
|
102
|
-
*/
|
|
103
40
|
async start() {
|
|
104
|
-
if (this
|
|
105
|
-
throw new Error('Client has been destroyed');
|
|
106
|
-
}
|
|
107
|
-
if (this.started) {
|
|
41
|
+
if (this.#started)
|
|
108
42
|
return;
|
|
43
|
+
this.#started = true;
|
|
44
|
+
this.#hasConnectedRealtime =
|
|
45
|
+
this.client.connectionState().realtime === 'connected';
|
|
46
|
+
this.#realtimeStarted = this.#hasConnectedRealtime;
|
|
47
|
+
this.#unsubscribeDiagnostics = this.client.addDiagnosticListener((event) => this.#handleDiagnostic(event));
|
|
48
|
+
this.#unsubscribeNetwork = this.#subscribeNetworkEvents();
|
|
49
|
+
try {
|
|
50
|
+
if (this.options.subscriptions) {
|
|
51
|
+
await this.client.setSubscriptions(this.options.subscriptions);
|
|
52
|
+
}
|
|
53
|
+
if (this.options.initialSync !== false && this.#isOnline()) {
|
|
54
|
+
await this.#syncForLifecycle();
|
|
55
|
+
}
|
|
56
|
+
if (this.options.realtime !== false && this.#isOnline()) {
|
|
57
|
+
await this.#startRealtimeForLifecycle();
|
|
58
|
+
}
|
|
59
|
+
this.#startPolling();
|
|
109
60
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
await ensureClientBlobSchema(this.options.db);
|
|
61
|
+
catch (error) {
|
|
62
|
+
await this.stop().catch(() => undefined);
|
|
63
|
+
throw error;
|
|
114
64
|
}
|
|
115
|
-
// Create and start engine
|
|
116
|
-
this.engine = new SyncEngine({
|
|
117
|
-
db: this.options.db,
|
|
118
|
-
transport: this.options.transport,
|
|
119
|
-
handlers: this.options.tableHandlers,
|
|
120
|
-
clientId: this.options.clientId,
|
|
121
|
-
actorId: this.options.actorId,
|
|
122
|
-
subscriptions: this.options.subscriptions.map((s) => ({
|
|
123
|
-
id: s.id,
|
|
124
|
-
table: s.table,
|
|
125
|
-
scopes: s.scopes ?? {},
|
|
126
|
-
params: s.params ?? {},
|
|
127
|
-
})),
|
|
128
|
-
plugins: this.options.plugins,
|
|
129
|
-
realtimeEnabled: this.options.realtimeEnabled,
|
|
130
|
-
pollIntervalMs: this.options.pollIntervalMs,
|
|
131
|
-
stateId: this.options.stateId,
|
|
132
|
-
migrate: undefined, // We already ran migrations
|
|
133
|
-
});
|
|
134
|
-
// Wire up engine events to client events
|
|
135
|
-
this.wireEngineEvents();
|
|
136
|
-
await this.engine.start();
|
|
137
|
-
this.started = true;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Stop the client (can be restarted).
|
|
141
|
-
*/
|
|
142
|
-
stop() {
|
|
143
|
-
this.engine?.stop();
|
|
144
65
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
this
|
|
150
|
-
this
|
|
151
|
-
this
|
|
66
|
+
async stop() {
|
|
67
|
+
if (!this.#started)
|
|
68
|
+
return;
|
|
69
|
+
this.#started = false;
|
|
70
|
+
this.#stopPolling();
|
|
71
|
+
this.#unsubscribeDiagnostics?.();
|
|
72
|
+
this.#unsubscribeDiagnostics = undefined;
|
|
73
|
+
this.#unsubscribeNetwork?.();
|
|
74
|
+
this.#unsubscribeNetwork = undefined;
|
|
75
|
+
this.#rejectQueuedSyncWaiters(new Error('Syncular lifecycle stopped before queued sync could run'));
|
|
76
|
+
if (this.options.realtime !== false) {
|
|
77
|
+
await this.client.stopRealtime();
|
|
78
|
+
}
|
|
79
|
+
this.#realtimeStarted = false;
|
|
152
80
|
}
|
|
153
|
-
// ===========================================================================
|
|
154
|
-
// Sync
|
|
155
|
-
// ===========================================================================
|
|
156
|
-
/**
|
|
157
|
-
* Trigger a manual sync.
|
|
158
|
-
*/
|
|
159
81
|
async sync() {
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
// ===========================================================================
|
|
166
|
-
// Subscriptions
|
|
167
|
-
// ===========================================================================
|
|
168
|
-
/**
|
|
169
|
-
* Update subscriptions.
|
|
170
|
-
*/
|
|
171
|
-
updateSubscriptions(subscriptions) {
|
|
172
|
-
this.options.subscriptions = subscriptions;
|
|
173
|
-
if (this.engine) {
|
|
174
|
-
this.engine.updateSubscriptions(subscriptions.map((s) => ({
|
|
175
|
-
id: s.id,
|
|
176
|
-
table: s.table,
|
|
177
|
-
scopes: s.scopes ?? {},
|
|
178
|
-
params: s.params ?? {},
|
|
179
|
-
})));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Get current subscriptions.
|
|
184
|
-
*/
|
|
185
|
-
getSubscriptions() {
|
|
186
|
-
return this.options.subscriptions.map((s) => ({
|
|
187
|
-
id: s.id,
|
|
188
|
-
table: s.table,
|
|
189
|
-
scopes: s.scopes ?? {},
|
|
190
|
-
params: s.params ?? {},
|
|
191
|
-
}));
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* List persisted subscription metadata rows.
|
|
195
|
-
*/
|
|
196
|
-
async listSubscriptionStates(args) {
|
|
197
|
-
if (!this.engine)
|
|
198
|
-
return [];
|
|
199
|
-
return this.engine.listSubscriptionStates(args);
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Read one persisted subscription metadata row.
|
|
203
|
-
*/
|
|
204
|
-
async getSubscriptionState(subscriptionId, options) {
|
|
205
|
-
if (!this.engine)
|
|
206
|
-
return null;
|
|
207
|
-
return this.engine.getSubscriptionState(subscriptionId, options);
|
|
208
|
-
}
|
|
209
|
-
// ===========================================================================
|
|
210
|
-
// State
|
|
211
|
-
// ===========================================================================
|
|
212
|
-
/**
|
|
213
|
-
* Get current client state.
|
|
214
|
-
*/
|
|
215
|
-
getState() {
|
|
216
|
-
const engineState = this.engine?.getState() ?? this.createInitialEngineState();
|
|
217
|
-
return {
|
|
218
|
-
clientId: this.options.clientId,
|
|
219
|
-
actorId: this.options.actorId,
|
|
220
|
-
enabled: engineState.enabled,
|
|
221
|
-
isSyncing: engineState.isSyncing,
|
|
222
|
-
connectionState: engineState.connectionState,
|
|
223
|
-
lastSyncAt: engineState.lastSyncAt,
|
|
224
|
-
error: engineState.error
|
|
225
|
-
? { code: engineState.error.code, message: engineState.error.message }
|
|
226
|
-
: null,
|
|
227
|
-
outbox: this.outboxStats,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Get current transport health details.
|
|
232
|
-
*/
|
|
233
|
-
getTransportHealth() {
|
|
234
|
-
if (!this.engine)
|
|
235
|
-
return null;
|
|
236
|
-
return this.engine.getTransportHealth();
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Get computed sync progress across subscriptions.
|
|
240
|
-
*/
|
|
241
|
-
async getProgress() {
|
|
242
|
-
if (!this.engine)
|
|
243
|
-
return null;
|
|
244
|
-
return this.engine.getProgress();
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Get a diagnostics snapshot for support/debug flows.
|
|
248
|
-
*/
|
|
249
|
-
async getDiagnostics() {
|
|
250
|
-
if (!this.engine)
|
|
251
|
-
return null;
|
|
252
|
-
return this.engine.getDiagnostics();
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Get a serializable inspector snapshot for in-app debug tooling.
|
|
256
|
-
*/
|
|
257
|
-
async getInspectorSnapshot(options) {
|
|
258
|
-
if (!this.engine)
|
|
259
|
-
return null;
|
|
260
|
-
return this.engine.getInspectorSnapshot(options);
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Reset local sync metadata (and optionally synced app rows/outbox/conflicts).
|
|
264
|
-
*/
|
|
265
|
-
async reset(options) {
|
|
266
|
-
if (!this.engine) {
|
|
267
|
-
return {
|
|
268
|
-
deletedSubscriptionStates: 0,
|
|
269
|
-
deletedOutboxCommits: 0,
|
|
270
|
-
deletedConflicts: 0,
|
|
271
|
-
clearedTables: [],
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
return this.engine.reset(options);
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Run a built-in repair flow for common corruption scenarios.
|
|
278
|
-
*/
|
|
279
|
-
async repair(options) {
|
|
280
|
-
if (!this.engine) {
|
|
281
|
-
return {
|
|
282
|
-
deletedSubscriptionStates: 0,
|
|
283
|
-
deletedOutboxCommits: 0,
|
|
284
|
-
deletedConflicts: 0,
|
|
285
|
-
clearedTables: [],
|
|
286
|
-
};
|
|
82
|
+
if (this.#syncInFlight) {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
this.#queuedSyncWaiters.push({ resolve, reject });
|
|
85
|
+
});
|
|
287
86
|
}
|
|
288
|
-
|
|
87
|
+
this.#syncInFlight = this.#startSyncCycle();
|
|
88
|
+
return this.#syncInFlight;
|
|
289
89
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (!this.engine)
|
|
295
|
-
return null;
|
|
296
|
-
return this.engine.awaitPhase(phase, options);
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Wait until bootstrap completes for the default state or a specific subscription.
|
|
300
|
-
*/
|
|
301
|
-
async awaitBootstrapComplete(options = {}) {
|
|
302
|
-
if (!this.engine)
|
|
303
|
-
return null;
|
|
304
|
-
return this.engine.awaitBootstrapComplete(options);
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Subscribe to state changes (for useSyncExternalStore).
|
|
308
|
-
*/
|
|
309
|
-
subscribe(callback) {
|
|
310
|
-
if (!this.engine) {
|
|
311
|
-
// Return no-op unsubscribe before engine is started
|
|
312
|
-
return () => { };
|
|
90
|
+
#startSyncCycle() {
|
|
91
|
+
let sync;
|
|
92
|
+
try {
|
|
93
|
+
sync = Promise.resolve(this.client.syncOnce());
|
|
313
94
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
// ===========================================================================
|
|
317
|
-
// Events
|
|
318
|
-
// ===========================================================================
|
|
319
|
-
/**
|
|
320
|
-
* Subscribe to client events.
|
|
321
|
-
*/
|
|
322
|
-
on(event, handler) {
|
|
323
|
-
if (!this.eventListeners.has(event)) {
|
|
324
|
-
this.eventListeners.set(event, new Set());
|
|
95
|
+
catch (error) {
|
|
96
|
+
sync = Promise.reject(error);
|
|
325
97
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
emit(event, payload) {
|
|
332
|
-
const listeners = this.eventListeners.get(event);
|
|
333
|
-
if (listeners) {
|
|
334
|
-
for (const listener of listeners) {
|
|
335
|
-
try {
|
|
336
|
-
listener(payload);
|
|
337
|
-
}
|
|
338
|
-
catch (err) {
|
|
339
|
-
console.error(`[Client] Error in ${event} listener:`, err);
|
|
340
|
-
}
|
|
98
|
+
const finish = () => {
|
|
99
|
+
if (this.#syncInFlight === sync) {
|
|
100
|
+
this.#syncInFlight = undefined;
|
|
341
101
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const resolvedConflict = pendingBeforeResolve.find((c) => c.id === id);
|
|
364
|
-
// For 'keep-local' and 'keep-server', we just mark it resolved
|
|
365
|
-
// For 'custom', we would need to apply the payload - but that requires
|
|
366
|
-
// creating a new mutation, which the user should do separately
|
|
367
|
-
const resolutionStr = resolution.strategy === 'custom'
|
|
368
|
-
? `custom:${JSON.stringify(resolution.payload)}`
|
|
369
|
-
: resolution.strategy;
|
|
370
|
-
await resolveConflict(this.options.db, { id, resolution: resolutionStr });
|
|
371
|
-
countSyncMetric('sync.conflicts.resolved', 1, {
|
|
372
|
-
attributes: {
|
|
373
|
-
strategy: resolution.strategy,
|
|
374
|
-
},
|
|
102
|
+
const waiters = this.#queuedSyncWaiters.splice(0);
|
|
103
|
+
if (waiters.length === 0)
|
|
104
|
+
return;
|
|
105
|
+
if (!this.#started) {
|
|
106
|
+
const error = new Error('Syncular lifecycle stopped before queued sync could run');
|
|
107
|
+
for (const waiter of waiters)
|
|
108
|
+
waiter.reject(error);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const queued = this.#startSyncCycle();
|
|
112
|
+
this.#syncInFlight = queued;
|
|
113
|
+
queued.then((result) => {
|
|
114
|
+
for (const waiter of waiters)
|
|
115
|
+
waiter.resolve(result);
|
|
116
|
+
}, (error) => {
|
|
117
|
+
for (const waiter of waiters)
|
|
118
|
+
waiter.reject(error);
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
void sync.then(finish, finish).catch((error) => {
|
|
122
|
+
this.#rejectQueuedSyncWaiters(error);
|
|
375
123
|
});
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
124
|
+
return sync;
|
|
125
|
+
}
|
|
126
|
+
#rejectQueuedSyncWaiters(error) {
|
|
127
|
+
const waiters = this.#queuedSyncWaiters.splice(0);
|
|
128
|
+
for (const waiter of waiters)
|
|
129
|
+
waiter.reject(error);
|
|
130
|
+
}
|
|
131
|
+
#handleDiagnostic(event) {
|
|
132
|
+
if (event.source === 'sync' && event.details?.resyncRequired === true) {
|
|
133
|
+
void this.client
|
|
134
|
+
.forceSubscriptionsBootstrap()
|
|
135
|
+
.then(() => this.sync())
|
|
136
|
+
.catch(() => undefined);
|
|
137
|
+
return;
|
|
379
138
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Get outbox statistics.
|
|
386
|
-
*/
|
|
387
|
-
async getOutboxStats() {
|
|
388
|
-
if (!this.engine) {
|
|
389
|
-
return this.outboxStats;
|
|
139
|
+
if (event.source !== 'realtime' ||
|
|
140
|
+
event.code !== 'realtime.state' ||
|
|
141
|
+
event.details?.state == null) {
|
|
142
|
+
return;
|
|
390
143
|
}
|
|
391
|
-
|
|
392
|
-
|
|
144
|
+
const state = event.details.state;
|
|
145
|
+
if (state !== 'connected')
|
|
146
|
+
return;
|
|
147
|
+
const wasReconnect = this.#hasConnectedRealtime;
|
|
148
|
+
this.#hasConnectedRealtime = true;
|
|
149
|
+
const shouldSync = this.options.syncOnRealtimeConnect !== false &&
|
|
150
|
+
(wasReconnect || this.options.initialSync === false);
|
|
151
|
+
if (!shouldSync)
|
|
152
|
+
return;
|
|
153
|
+
void this.sync().catch(() => undefined);
|
|
393
154
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
155
|
+
#subscribeNetworkEvents() {
|
|
156
|
+
const network = this.#network;
|
|
157
|
+
if (!network?.addEventListener || !network.removeEventListener)
|
|
158
|
+
return;
|
|
159
|
+
const handleOnline = () => {
|
|
160
|
+
if (!this.#started)
|
|
161
|
+
return;
|
|
162
|
+
void this.#resumeOnline().catch(() => undefined);
|
|
163
|
+
};
|
|
164
|
+
network.addEventListener('online', handleOnline);
|
|
165
|
+
return () => {
|
|
166
|
+
network.removeEventListener?.('online', handleOnline);
|
|
167
|
+
};
|
|
402
168
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const result = await sql `
|
|
409
|
-
update ${sql.table('sync_outbox_commits')}
|
|
410
|
-
set
|
|
411
|
-
${sql.ref('status')} = ${sql.val('pending')},
|
|
412
|
-
${sql.ref('attempt_count')} = ${sql.val(0)},
|
|
413
|
-
${sql.ref('error')} = ${sql.val(null)}
|
|
414
|
-
where ${sql.ref('status')} = ${sql.val('failed')}
|
|
415
|
-
`.execute(this.options.db);
|
|
416
|
-
const count = Number(result.numAffectedRows ?? 0n);
|
|
417
|
-
if (count > 0 && this.engine) {
|
|
418
|
-
await this.engine.refreshOutboxStats();
|
|
419
|
-
await this.engine.sync();
|
|
169
|
+
async #resumeOnline() {
|
|
170
|
+
if (!this.#started || !this.#isOnline())
|
|
171
|
+
return;
|
|
172
|
+
if (this.options.initialSync !== false) {
|
|
173
|
+
await this.#syncForLifecycle();
|
|
420
174
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
// ===========================================================================
|
|
424
|
-
// Presence
|
|
425
|
-
// ===========================================================================
|
|
426
|
-
/**
|
|
427
|
-
* Get presence for a scope.
|
|
428
|
-
*/
|
|
429
|
-
getPresence(scopeKey) {
|
|
430
|
-
if (!this.engine) {
|
|
431
|
-
return [];
|
|
175
|
+
if (this.options.realtime !== false && !this.#realtimeStarted) {
|
|
176
|
+
await this.#startRealtimeForLifecycle();
|
|
432
177
|
}
|
|
433
|
-
return this.engine.getPresence(scopeKey);
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* Join presence for a scope key.
|
|
437
|
-
*/
|
|
438
|
-
joinPresence(scopeKey, metadata) {
|
|
439
|
-
this.engine?.joinPresence(scopeKey, metadata);
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* Leave presence for a scope key.
|
|
443
|
-
*/
|
|
444
|
-
leavePresence(scopeKey) {
|
|
445
|
-
this.engine?.leavePresence(scopeKey);
|
|
446
178
|
}
|
|
447
|
-
|
|
448
|
-
* Update presence metadata for a scope key.
|
|
449
|
-
*/
|
|
450
|
-
updatePresenceMetadata(scopeKey, metadata) {
|
|
451
|
-
this.engine?.updatePresenceMetadata(scopeKey, metadata);
|
|
452
|
-
}
|
|
453
|
-
// ===========================================================================
|
|
454
|
-
// Migration Info
|
|
455
|
-
// ===========================================================================
|
|
456
|
-
/**
|
|
457
|
-
* Get migration info.
|
|
458
|
-
*/
|
|
459
|
-
async getMigrationInfo() {
|
|
460
|
-
// Check if sync tables exist
|
|
461
|
-
let syncMigrated = false;
|
|
179
|
+
async #syncForLifecycle() {
|
|
462
180
|
try {
|
|
463
|
-
await this.
|
|
464
|
-
.selectFrom('sync_outbox_commits')
|
|
465
|
-
.selectAll()
|
|
466
|
-
.limit(1)
|
|
467
|
-
.execute();
|
|
468
|
-
syncMigrated = true;
|
|
469
|
-
}
|
|
470
|
-
catch {
|
|
471
|
-
syncMigrated = false;
|
|
181
|
+
await this.sync();
|
|
472
182
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
await this.options.db
|
|
477
|
-
.selectFrom('sync_blob_cache')
|
|
478
|
-
.selectAll()
|
|
479
|
-
.limit(1)
|
|
480
|
-
.execute();
|
|
481
|
-
blobsMigrated = true;
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (!isSyncularOfflineError(error))
|
|
185
|
+
throw error;
|
|
482
186
|
}
|
|
483
|
-
catch {
|
|
484
|
-
blobsMigrated = false;
|
|
485
|
-
}
|
|
486
|
-
return { syncMigrated, blobsMigrated };
|
|
487
187
|
}
|
|
488
|
-
|
|
489
|
-
* Static: Check if migrations are needed.
|
|
490
|
-
*/
|
|
491
|
-
static async checkMigrations(db) {
|
|
492
|
-
let syncMigrated = false;
|
|
493
|
-
let blobsMigrated = false;
|
|
494
|
-
try {
|
|
495
|
-
await db.selectFrom('sync_outbox_commits').selectAll().limit(1).execute();
|
|
496
|
-
syncMigrated = true;
|
|
497
|
-
}
|
|
498
|
-
catch {
|
|
499
|
-
syncMigrated = false;
|
|
500
|
-
}
|
|
188
|
+
async #startRealtimeForLifecycle() {
|
|
501
189
|
try {
|
|
502
|
-
await
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
catch {
|
|
506
|
-
blobsMigrated = false;
|
|
190
|
+
await this.client.startRealtime(this.options.realtime);
|
|
191
|
+
this.#realtimeStarted = true;
|
|
507
192
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
/**
|
|
515
|
-
* Static: Run migrations.
|
|
516
|
-
*/
|
|
517
|
-
static async migrate(db, options) {
|
|
518
|
-
await ensureClientSyncSchema(db);
|
|
519
|
-
if (options?.blobs) {
|
|
520
|
-
await ensureClientBlobSchema(db);
|
|
193
|
+
catch (error) {
|
|
194
|
+
this.#realtimeStarted = false;
|
|
195
|
+
if (!isSyncularOfflineError(error))
|
|
196
|
+
throw error;
|
|
521
197
|
}
|
|
522
198
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
// ===========================================================================
|
|
526
|
-
createInitialEngineState() {
|
|
527
|
-
return {
|
|
528
|
-
enabled: false,
|
|
529
|
-
isSyncing: false,
|
|
530
|
-
connectionState: 'disconnected',
|
|
531
|
-
transportMode: 'polling',
|
|
532
|
-
lastSyncAt: null,
|
|
533
|
-
error: null,
|
|
534
|
-
pendingCount: 0,
|
|
535
|
-
retryCount: 0,
|
|
536
|
-
isRetrying: false,
|
|
537
|
-
};
|
|
199
|
+
#isOnline() {
|
|
200
|
+
return this.#network?.isOnline() !== false;
|
|
538
201
|
}
|
|
539
|
-
|
|
540
|
-
|
|
202
|
+
#startPolling() {
|
|
203
|
+
const interval = this.options.pollIntervalMs;
|
|
204
|
+
if (interval === false || interval === undefined || interval <= 0)
|
|
541
205
|
return;
|
|
542
|
-
this
|
|
543
|
-
this
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
this.engine.on('sync:live', (payload) => {
|
|
554
|
-
this.emit('sync:live', payload);
|
|
555
|
-
});
|
|
556
|
-
this.engine.on('sync:error', (error) => {
|
|
557
|
-
this.emit('sync:error', { code: error.code, message: error.message });
|
|
558
|
-
// Check for new conflicts after sync error
|
|
559
|
-
this.checkForNewConflicts();
|
|
560
|
-
});
|
|
561
|
-
this.engine.on('bootstrap:start', (payload) => {
|
|
562
|
-
this.emit('bootstrap:start', payload);
|
|
563
|
-
});
|
|
564
|
-
this.engine.on('bootstrap:progress', (payload) => {
|
|
565
|
-
this.emit('bootstrap:progress', payload);
|
|
566
|
-
});
|
|
567
|
-
this.engine.on('bootstrap:complete', (payload) => {
|
|
568
|
-
this.emit('bootstrap:complete', payload);
|
|
569
|
-
});
|
|
570
|
-
this.engine.on('connection:change', (payload) => {
|
|
571
|
-
this.emit('connection:change', payload);
|
|
572
|
-
});
|
|
573
|
-
this.engine.on('data:change', (payload) => {
|
|
574
|
-
this.emit('data:change', payload);
|
|
575
|
-
});
|
|
576
|
-
this.engine.on('outbox:change', (payload) => {
|
|
577
|
-
this.outboxStats = {
|
|
578
|
-
pending: payload.pendingCount,
|
|
579
|
-
sending: payload.sendingCount,
|
|
580
|
-
failed: payload.failedCount,
|
|
581
|
-
acked: payload.ackedCount ?? 0,
|
|
582
|
-
total: payload.pendingCount +
|
|
583
|
-
payload.sendingCount +
|
|
584
|
-
payload.failedCount +
|
|
585
|
-
(payload.ackedCount ?? 0),
|
|
586
|
-
};
|
|
587
|
-
this.emit('outbox:change', this.outboxStats);
|
|
588
|
-
});
|
|
589
|
-
this.engine.on('presence:change', (payload) => {
|
|
590
|
-
this.emit('presence:change', payload);
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
async checkForNewConflicts() {
|
|
594
|
-
const conflicts = await this.getConflicts();
|
|
595
|
-
const activeIds = new Set(conflicts.map((conflict) => conflict.id));
|
|
596
|
-
for (const id of this.emittedConflictIds) {
|
|
597
|
-
if (!activeIds.has(id)) {
|
|
598
|
-
this.emittedConflictIds.delete(id);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
for (const conflict of conflicts) {
|
|
602
|
-
if (this.emittedConflictIds.has(conflict.id)) {
|
|
603
|
-
continue;
|
|
604
|
-
}
|
|
605
|
-
this.emittedConflictIds.add(conflict.id);
|
|
606
|
-
this.emit('conflict:new', conflict);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
mapConflictInfo(info) {
|
|
610
|
-
let serverPayload = null;
|
|
611
|
-
if (info.serverRowJson) {
|
|
612
|
-
try {
|
|
613
|
-
serverPayload = JSON.parse(info.serverRowJson);
|
|
614
|
-
}
|
|
615
|
-
catch {
|
|
616
|
-
serverPayload = null;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
return {
|
|
620
|
-
id: info.id,
|
|
621
|
-
table: info.table,
|
|
622
|
-
rowId: info.rowId,
|
|
623
|
-
opIndex: info.opIndex,
|
|
624
|
-
localPayload: info.localPayload,
|
|
625
|
-
serverPayload,
|
|
626
|
-
serverVersion: info.serverVersion,
|
|
627
|
-
message: info.message,
|
|
628
|
-
code: info.code,
|
|
629
|
-
createdAt: info.createdAt,
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
createBlobClient(storage, transport) {
|
|
633
|
-
const db = this.options.db;
|
|
634
|
-
const blobs = transport.blobs;
|
|
635
|
-
const staleUploadingTimeoutMs = 30_000;
|
|
636
|
-
const maxUploadRetries = 3;
|
|
637
|
-
return {
|
|
638
|
-
async store(data, options) {
|
|
639
|
-
const bytes = await toUint8Array(data);
|
|
640
|
-
const mimeType = data instanceof Blob
|
|
641
|
-
? data.type
|
|
642
|
-
: (options?.mimeType ?? 'application/octet-stream');
|
|
643
|
-
// Compute hash
|
|
644
|
-
const hashHex = await computeSha256Hex(bytes);
|
|
645
|
-
const hash = `sha256:${hashHex}`;
|
|
646
|
-
// Store locally
|
|
647
|
-
await storage.write(hash, bytes);
|
|
648
|
-
// Store metadata
|
|
649
|
-
const now = Date.now();
|
|
650
|
-
await sql `
|
|
651
|
-
insert into ${sql.table('sync_blob_cache')} (
|
|
652
|
-
${sql.join([
|
|
653
|
-
sql.ref('hash'),
|
|
654
|
-
sql.ref('size'),
|
|
655
|
-
sql.ref('mime_type'),
|
|
656
|
-
sql.ref('cached_at'),
|
|
657
|
-
sql.ref('last_accessed_at'),
|
|
658
|
-
sql.ref('encrypted'),
|
|
659
|
-
sql.ref('key_id'),
|
|
660
|
-
sql.ref('body'),
|
|
661
|
-
])}
|
|
662
|
-
) values (
|
|
663
|
-
${sql.join([
|
|
664
|
-
sql.val(hash),
|
|
665
|
-
sql.val(bytes.length),
|
|
666
|
-
sql.val(mimeType),
|
|
667
|
-
sql.val(now),
|
|
668
|
-
sql.val(now),
|
|
669
|
-
sql.val(0),
|
|
670
|
-
sql.val(null),
|
|
671
|
-
sql.val(bytes),
|
|
672
|
-
])}
|
|
673
|
-
)
|
|
674
|
-
on conflict (${sql.ref('hash')}) do nothing
|
|
675
|
-
`.execute(db);
|
|
676
|
-
// Queue for upload or upload immediately
|
|
677
|
-
if (options?.immediate) {
|
|
678
|
-
// Initiate upload
|
|
679
|
-
const initResult = await blobs.initiateUpload({
|
|
680
|
-
hash,
|
|
681
|
-
size: bytes.length,
|
|
682
|
-
mimeType,
|
|
683
|
-
});
|
|
684
|
-
if (!initResult.exists && initResult.uploadUrl) {
|
|
685
|
-
// Upload to presigned URL
|
|
686
|
-
const uploadResponse = await fetch(initResult.uploadUrl, {
|
|
687
|
-
method: initResult.uploadMethod ?? 'PUT',
|
|
688
|
-
body: bytes.buffer,
|
|
689
|
-
headers: initResult.uploadHeaders,
|
|
690
|
-
});
|
|
691
|
-
if (!uploadResponse.ok) {
|
|
692
|
-
throw new Error(`Upload failed: ${uploadResponse.statusText}`);
|
|
693
|
-
}
|
|
694
|
-
// Complete upload
|
|
695
|
-
await blobs.completeUpload(hash);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
else {
|
|
699
|
-
// Queue for later upload
|
|
700
|
-
await sql `
|
|
701
|
-
insert into ${sql.table('sync_blob_outbox')} (
|
|
702
|
-
${sql.join([
|
|
703
|
-
sql.ref('hash'),
|
|
704
|
-
sql.ref('size'),
|
|
705
|
-
sql.ref('mime_type'),
|
|
706
|
-
sql.ref('status'),
|
|
707
|
-
sql.ref('created_at'),
|
|
708
|
-
sql.ref('updated_at'),
|
|
709
|
-
sql.ref('attempt_count'),
|
|
710
|
-
sql.ref('error'),
|
|
711
|
-
sql.ref('encrypted'),
|
|
712
|
-
sql.ref('key_id'),
|
|
713
|
-
sql.ref('body'),
|
|
714
|
-
])}
|
|
715
|
-
) values (
|
|
716
|
-
${sql.join([
|
|
717
|
-
sql.val(hash),
|
|
718
|
-
sql.val(bytes.length),
|
|
719
|
-
sql.val(mimeType),
|
|
720
|
-
sql.val('pending'),
|
|
721
|
-
sql.val(now),
|
|
722
|
-
sql.val(now),
|
|
723
|
-
sql.val(0),
|
|
724
|
-
sql.val(null),
|
|
725
|
-
sql.val(0),
|
|
726
|
-
sql.val(null),
|
|
727
|
-
sql.val(bytes),
|
|
728
|
-
])}
|
|
729
|
-
)
|
|
730
|
-
on conflict (${sql.ref('hash')}) do nothing
|
|
731
|
-
`.execute(db);
|
|
732
|
-
}
|
|
733
|
-
return {
|
|
734
|
-
hash,
|
|
735
|
-
size: bytes.length,
|
|
736
|
-
mimeType,
|
|
737
|
-
};
|
|
738
|
-
},
|
|
739
|
-
async retrieve(ref) {
|
|
740
|
-
// Check local storage first
|
|
741
|
-
const local = await storage.read(ref.hash);
|
|
742
|
-
if (local) {
|
|
743
|
-
// Update access time
|
|
744
|
-
await sql `
|
|
745
|
-
update ${sql.table('sync_blob_cache')}
|
|
746
|
-
set ${sql.ref('last_accessed_at')} = ${sql.val(Date.now())}
|
|
747
|
-
where ${sql.ref('hash')} = ${sql.val(ref.hash)}
|
|
748
|
-
`.execute(db);
|
|
749
|
-
return local;
|
|
750
|
-
}
|
|
751
|
-
// Fetch from server
|
|
752
|
-
const { url } = await blobs.getDownloadUrl(ref.hash);
|
|
753
|
-
const response = await fetch(url);
|
|
754
|
-
if (!response.ok) {
|
|
755
|
-
throw new Error(`Download failed: ${response.statusText}`);
|
|
756
|
-
}
|
|
757
|
-
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
758
|
-
// Cache locally
|
|
759
|
-
await storage.write(ref.hash, bytes);
|
|
760
|
-
const now = Date.now();
|
|
761
|
-
await sql `
|
|
762
|
-
insert into ${sql.table('sync_blob_cache')} (
|
|
763
|
-
${sql.join([
|
|
764
|
-
sql.ref('hash'),
|
|
765
|
-
sql.ref('size'),
|
|
766
|
-
sql.ref('mime_type'),
|
|
767
|
-
sql.ref('cached_at'),
|
|
768
|
-
sql.ref('last_accessed_at'),
|
|
769
|
-
sql.ref('encrypted'),
|
|
770
|
-
sql.ref('key_id'),
|
|
771
|
-
sql.ref('body'),
|
|
772
|
-
])}
|
|
773
|
-
) values (
|
|
774
|
-
${sql.join([
|
|
775
|
-
sql.val(ref.hash),
|
|
776
|
-
sql.val(bytes.length),
|
|
777
|
-
sql.val(ref.mimeType),
|
|
778
|
-
sql.val(now),
|
|
779
|
-
sql.val(now),
|
|
780
|
-
sql.val(0),
|
|
781
|
-
sql.val(null),
|
|
782
|
-
sql.val(bytes),
|
|
783
|
-
])}
|
|
784
|
-
)
|
|
785
|
-
on conflict (${sql.ref('hash')}) do nothing
|
|
786
|
-
`.execute(db);
|
|
787
|
-
return bytes;
|
|
788
|
-
},
|
|
789
|
-
async isLocal(hash) {
|
|
790
|
-
return storage.exists(hash);
|
|
791
|
-
},
|
|
792
|
-
async preload(refs) {
|
|
793
|
-
await Promise.all(refs.map((ref) => this.retrieve(ref)));
|
|
794
|
-
},
|
|
795
|
-
async processUploadQueue() {
|
|
796
|
-
let uploaded = 0;
|
|
797
|
-
let failed = 0;
|
|
798
|
-
const now = Date.now();
|
|
799
|
-
const staleThreshold = now - staleUploadingTimeoutMs;
|
|
800
|
-
await sql `
|
|
801
|
-
update ${sql.table('sync_blob_outbox')}
|
|
802
|
-
set
|
|
803
|
-
${sql.ref('status')} = ${sql.val('failed')},
|
|
804
|
-
${sql.ref('attempt_count')} = ${sql.ref('attempt_count')} + ${sql.val(1)},
|
|
805
|
-
${sql.ref('error')} = ${sql.val('Upload timed out while in uploading state')},
|
|
806
|
-
${sql.ref('updated_at')} = ${sql.val(now)}
|
|
807
|
-
where ${sql.ref('status')} = ${sql.val('uploading')}
|
|
808
|
-
and ${sql.ref('updated_at')} < ${sql.val(staleThreshold)}
|
|
809
|
-
and ${sql.ref('attempt_count')} + ${sql.val(1)} >= ${sql.val(maxUploadRetries)}
|
|
810
|
-
`.execute(db);
|
|
811
|
-
await sql `
|
|
812
|
-
update ${sql.table('sync_blob_outbox')}
|
|
813
|
-
set
|
|
814
|
-
${sql.ref('status')} = ${sql.val('pending')},
|
|
815
|
-
${sql.ref('attempt_count')} = ${sql.ref('attempt_count')} + ${sql.val(1)},
|
|
816
|
-
${sql.ref('error')} = ${sql.val('Upload timed out while in uploading state; retrying')},
|
|
817
|
-
${sql.ref('updated_at')} = ${sql.val(now)}
|
|
818
|
-
where ${sql.ref('status')} = ${sql.val('uploading')}
|
|
819
|
-
and ${sql.ref('updated_at')} < ${sql.val(staleThreshold)}
|
|
820
|
-
and ${sql.ref('attempt_count')} + ${sql.val(1)} < ${sql.val(maxUploadRetries)}
|
|
821
|
-
`.execute(db);
|
|
822
|
-
const pendingResult = await sql `
|
|
823
|
-
select
|
|
824
|
-
${sql.ref('hash')},
|
|
825
|
-
${sql.ref('size')},
|
|
826
|
-
${sql.ref('mime_type')},
|
|
827
|
-
${sql.ref('body')},
|
|
828
|
-
${sql.ref('attempt_count')}
|
|
829
|
-
from ${sql.table('sync_blob_outbox')}
|
|
830
|
-
where ${sql.ref('status')} = ${sql.val('pending')}
|
|
831
|
-
and ${sql.ref('attempt_count')} < ${sql.val(maxUploadRetries)}
|
|
832
|
-
limit ${sql.val(10)}
|
|
833
|
-
`.execute(db);
|
|
834
|
-
const pending = pendingResult.rows;
|
|
835
|
-
for (const item of pending) {
|
|
836
|
-
const nextAttemptCount = item.attempt_count + 1;
|
|
837
|
-
try {
|
|
838
|
-
// Mark as uploading
|
|
839
|
-
await sql `
|
|
840
|
-
update ${sql.table('sync_blob_outbox')}
|
|
841
|
-
set
|
|
842
|
-
${sql.ref('status')} = ${sql.val('uploading')},
|
|
843
|
-
${sql.ref('attempt_count')} = ${sql.val(nextAttemptCount)},
|
|
844
|
-
${sql.ref('error')} = ${sql.val(null)},
|
|
845
|
-
${sql.ref('updated_at')} = ${sql.val(Date.now())}
|
|
846
|
-
where ${sql.ref('hash')} = ${sql.val(item.hash)}
|
|
847
|
-
and ${sql.ref('status')} = ${sql.val('pending')}
|
|
848
|
-
`.execute(db);
|
|
849
|
-
// Initiate upload
|
|
850
|
-
const initResult = await blobs.initiateUpload({
|
|
851
|
-
hash: item.hash,
|
|
852
|
-
size: item.size,
|
|
853
|
-
mimeType: item.mime_type,
|
|
854
|
-
});
|
|
855
|
-
if (!initResult.exists && initResult.uploadUrl && item.body) {
|
|
856
|
-
const uploadBody = new ArrayBuffer(item.body.byteLength);
|
|
857
|
-
new Uint8Array(uploadBody).set(item.body);
|
|
858
|
-
// Upload
|
|
859
|
-
const uploadResponse = await fetch(initResult.uploadUrl, {
|
|
860
|
-
method: initResult.uploadMethod ?? 'PUT',
|
|
861
|
-
body: uploadBody,
|
|
862
|
-
headers: initResult.uploadHeaders,
|
|
863
|
-
});
|
|
864
|
-
if (!uploadResponse.ok) {
|
|
865
|
-
throw new Error(`Upload failed: ${uploadResponse.statusText}`);
|
|
866
|
-
}
|
|
867
|
-
// Complete
|
|
868
|
-
const completeResult = await blobs.completeUpload(item.hash);
|
|
869
|
-
if (!completeResult.ok) {
|
|
870
|
-
throw new Error(completeResult.error ?? 'Failed to complete blob upload');
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
// Mark as complete
|
|
874
|
-
await sql `
|
|
875
|
-
delete from ${sql.table('sync_blob_outbox')}
|
|
876
|
-
where ${sql.ref('hash')} = ${sql.val(item.hash)}
|
|
877
|
-
`.execute(db);
|
|
878
|
-
uploaded++;
|
|
879
|
-
}
|
|
880
|
-
catch (err) {
|
|
881
|
-
const nextStatus = nextAttemptCount >= maxUploadRetries ? 'failed' : 'pending';
|
|
882
|
-
await sql `
|
|
883
|
-
update ${sql.table('sync_blob_outbox')}
|
|
884
|
-
set
|
|
885
|
-
${sql.ref('status')} = ${sql.val(nextStatus)},
|
|
886
|
-
${sql.ref('error')} = ${sql.val(err instanceof Error ? err.message : 'Unknown error')},
|
|
887
|
-
${sql.ref('updated_at')} = ${sql.val(Date.now())}
|
|
888
|
-
where ${sql.ref('hash')} = ${sql.val(item.hash)}
|
|
889
|
-
`.execute(db);
|
|
890
|
-
if (nextStatus === 'failed') {
|
|
891
|
-
failed++;
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
return { uploaded, failed };
|
|
896
|
-
},
|
|
897
|
-
async getUploadQueueStats() {
|
|
898
|
-
const rowsResult = await sql `
|
|
899
|
-
select
|
|
900
|
-
${sql.ref('status')} as status,
|
|
901
|
-
count(${sql.ref('hash')}) as count
|
|
902
|
-
from ${sql.table('sync_blob_outbox')}
|
|
903
|
-
group by ${sql.ref('status')}
|
|
904
|
-
`.execute(db);
|
|
905
|
-
const stats = { pending: 0, uploading: 0, failed: 0 };
|
|
906
|
-
for (const row of rowsResult.rows) {
|
|
907
|
-
if (row.status === 'pending')
|
|
908
|
-
stats.pending = Number(row.count);
|
|
909
|
-
if (row.status === 'uploading')
|
|
910
|
-
stats.uploading = Number(row.count);
|
|
911
|
-
if (row.status === 'failed')
|
|
912
|
-
stats.failed = Number(row.count);
|
|
913
|
-
}
|
|
914
|
-
return stats;
|
|
915
|
-
},
|
|
916
|
-
async getCacheStats() {
|
|
917
|
-
const result = await sql `
|
|
918
|
-
select
|
|
919
|
-
count(${sql.ref('hash')}) as count,
|
|
920
|
-
sum(${sql.ref('size')}) as totalBytes
|
|
921
|
-
from ${sql.table('sync_blob_cache')}
|
|
922
|
-
`.execute(db);
|
|
923
|
-
const row = result.rows[0];
|
|
924
|
-
return {
|
|
925
|
-
count: Number(row?.count ?? 0),
|
|
926
|
-
totalBytes: Number(row?.totalBytes ?? 0),
|
|
927
|
-
};
|
|
928
|
-
},
|
|
929
|
-
async pruneCache(maxBytes) {
|
|
930
|
-
if (!maxBytes)
|
|
931
|
-
return 0;
|
|
932
|
-
// Get current size
|
|
933
|
-
const stats = await this.getCacheStats();
|
|
934
|
-
if (stats.totalBytes <= maxBytes)
|
|
935
|
-
return 0;
|
|
936
|
-
// Get oldest entries to delete
|
|
937
|
-
const toFree = stats.totalBytes - maxBytes;
|
|
938
|
-
let freed = 0;
|
|
939
|
-
const oldEntriesResult = await sql `
|
|
940
|
-
select ${sql.ref('hash')}, ${sql.ref('size')}
|
|
941
|
-
from ${sql.table('sync_blob_cache')}
|
|
942
|
-
order by ${sql.ref('last_accessed_at')} asc
|
|
943
|
-
`.execute(db);
|
|
944
|
-
const oldEntries = oldEntriesResult.rows;
|
|
945
|
-
for (const entry of oldEntries) {
|
|
946
|
-
if (freed >= toFree)
|
|
947
|
-
break;
|
|
948
|
-
await storage.delete(entry.hash);
|
|
949
|
-
await sql `
|
|
950
|
-
delete from ${sql.table('sync_blob_cache')}
|
|
951
|
-
where ${sql.ref('hash')} = ${sql.val(entry.hash)}
|
|
952
|
-
`.execute(db);
|
|
953
|
-
freed += entry.size;
|
|
954
|
-
}
|
|
955
|
-
return freed;
|
|
956
|
-
},
|
|
957
|
-
async clearCache() {
|
|
958
|
-
if (storage.clear) {
|
|
959
|
-
await storage.clear();
|
|
960
|
-
}
|
|
961
|
-
else {
|
|
962
|
-
// Delete each entry individually
|
|
963
|
-
const entriesResult = await sql `
|
|
964
|
-
select ${sql.ref('hash')}
|
|
965
|
-
from ${sql.table('sync_blob_cache')}
|
|
966
|
-
`.execute(db);
|
|
967
|
-
for (const entry of entriesResult.rows) {
|
|
968
|
-
await storage.delete(entry.hash);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
await sql `delete from ${sql.table('sync_blob_cache')}`.execute(db);
|
|
972
|
-
},
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
// ============================================================================
|
|
977
|
-
// Helpers
|
|
978
|
-
// ============================================================================
|
|
979
|
-
async function toUint8Array(data) {
|
|
980
|
-
if (data instanceof Uint8Array) {
|
|
981
|
-
return data;
|
|
206
|
+
this.#pollTimer = setInterval(() => {
|
|
207
|
+
if (!this.#isOnline())
|
|
208
|
+
return;
|
|
209
|
+
void this.#syncForLifecycle();
|
|
210
|
+
}, interval);
|
|
211
|
+
}
|
|
212
|
+
#stopPolling() {
|
|
213
|
+
if (!this.#pollTimer)
|
|
214
|
+
return;
|
|
215
|
+
clearInterval(this.#pollTimer);
|
|
216
|
+
this.#pollTimer = undefined;
|
|
982
217
|
}
|
|
983
|
-
const buffer = await data.arrayBuffer();
|
|
984
|
-
return new Uint8Array(buffer);
|
|
985
|
-
}
|
|
986
|
-
async function computeSha256Hex(data) {
|
|
987
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data.buffer);
|
|
988
|
-
const hashArray = new Uint8Array(hashBuffer);
|
|
989
|
-
return Array.from(hashArray)
|
|
990
|
-
.map((b) => b.toString(16).padStart(2, '0'))
|
|
991
|
-
.join('');
|
|
992
218
|
}
|
|
993
219
|
//# sourceMappingURL=client.js.map
|