@syncular/client 0.1.3 → 0.2.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 +227 -709
- package/dist/apply.d.ts +80 -0
- package/dist/apply.js +301 -0
- package/dist/blob.d.ts +127 -0
- package/dist/blob.js +201 -0
- package/dist/bun-database.d.ts +22 -0
- package/dist/bun-database.js +61 -0
- package/dist/client.d.ts +320 -69
- package/dist/client.js +1627 -163
- package/dist/content-type.d.ts +2 -0
- package/dist/content-type.js +2 -0
- package/dist/database.d.ts +35 -80
- package/dist/database.js +32 -947
- package/dist/encryption.d.ts +47 -0
- package/dist/encryption.js +75 -0
- package/dist/errors.d.ts +8 -22
- package/dist/errors.js +10 -207
- package/dist/http.d.ts +32 -0
- package/dist/http.js +196 -0
- package/dist/index.d.ts +28 -16
- package/dist/index.js +25 -12
- package/dist/invalidation.d.ts +69 -0
- package/dist/invalidation.js +84 -0
- package/dist/leader-lock.d.ts +28 -0
- package/dist/leader-lock.js +38 -0
- package/dist/multi-tab.d.ts +134 -0
- package/dist/multi-tab.js +399 -0
- package/dist/node-database.d.ts +41 -0
- package/dist/node-database.js +133 -0
- package/dist/outbox.d.ts +56 -0
- package/dist/outbox.js +140 -0
- package/dist/schema.d.ts +107 -0
- package/dist/schema.js +267 -0
- package/dist/state.d.ts +40 -0
- package/dist/state.js +77 -0
- package/dist/transport.d.ts +44 -0
- package/dist/transport.js +7 -0
- package/dist/wasm-database.d.ts +30 -0
- package/dist/wasm-database.js +170 -0
- package/dist/window.d.ts +64 -0
- package/dist/window.js +0 -0
- package/dist/worker-entry.d.ts +16 -2
- package/dist/worker-entry.js +300 -456
- package/dist/worker-host.d.ts +174 -0
- package/dist/worker-host.js +536 -0
- package/dist/worker-protocol.d.ts +173 -61
- package/dist/worker-protocol.js +7 -16
- package/package.json +45 -112
- package/src/apply.ts +435 -0
- package/src/blob.ts +333 -0
- package/src/bun-database.ts +75 -0
- package/src/client.ts +2247 -277
- package/src/content-type.ts +2 -0
- package/src/database.ts +63 -1389
- package/src/encryption.ts +123 -0
- package/src/errors.ts +11 -265
- package/src/http.ts +254 -0
- package/src/index.ts +25 -20
- package/src/invalidation.ts +128 -0
- package/src/leader-lock.ts +68 -0
- package/src/multi-tab.ts +550 -0
- package/src/node-database.ts +180 -0
- package/src/outbox.ts +206 -0
- package/src/schema.ts +397 -0
- package/src/state.ts +126 -0
- package/src/transport.ts +52 -0
- package/src/wasm-database.ts +279 -0
- package/src/window.ts +0 -0
- package/src/worker-entry.ts +391 -545
- package/src/worker-host.ts +800 -0
- package/src/worker-protocol.ts +204 -99
- package/dist/auth-leases.d.ts +0 -11
- package/dist/auth-leases.d.ts.map +0 -1
- package/dist/auth-leases.js +0 -140
- package/dist/auth-leases.js.map +0 -1
- package/dist/blob-limits.d.ts +0 -15
- package/dist/blob-limits.d.ts.map +0 -1
- package/dist/blob-limits.js +0 -66
- package/dist/blob-limits.js.map +0 -1
- package/dist/bridge-client.d.ts +0 -69
- package/dist/bridge-client.d.ts.map +0 -1
- package/dist/bridge-client.js +0 -282
- package/dist/bridge-client.js.map +0 -1
- package/dist/client-config.d.ts +0 -6
- package/dist/client-config.d.ts.map +0 -1
- package/dist/client-config.js +0 -18
- package/dist/client-config.js.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/command-history.d.ts +0 -35
- package/dist/command-history.d.ts.map +0 -1
- package/dist/command-history.js +0 -378
- package/dist/command-history.js.map +0 -1
- package/dist/console-diagnostics.d.ts +0 -33
- package/dist/console-diagnostics.d.ts.map +0 -1
- package/dist/console-diagnostics.js +0 -496
- package/dist/console-diagnostics.js.map +0 -1
- package/dist/crdt-yjs/index.d.ts +0 -4
- package/dist/crdt-yjs/index.d.ts.map +0 -1
- package/dist/crdt-yjs/index.js +0 -4
- package/dist/crdt-yjs/index.js.map +0 -1
- package/dist/crdt-yjs/webview-host-facade.d.ts +0 -126
- package/dist/crdt-yjs/webview-host-facade.d.ts.map +0 -1
- package/dist/crdt-yjs/webview-host-facade.js +0 -284
- package/dist/crdt-yjs/webview-host-facade.js.map +0 -1
- package/dist/crdt-yjs/yjs-document-field-adapter.d.ts +0 -153
- package/dist/crdt-yjs/yjs-document-field-adapter.d.ts.map +0 -1
- package/dist/crdt-yjs/yjs-document-field-adapter.js +0 -406
- package/dist/crdt-yjs/yjs-document-field-adapter.js.map +0 -1
- package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts +0 -73
- package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts.map +0 -1
- package/dist/crdt-yjs/yjs-prosemirror-bridge.js +0 -169
- package/dist/crdt-yjs/yjs-prosemirror-bridge.js.map +0 -1
- package/dist/database.d.ts.map +0 -1
- package/dist/database.js.map +0 -1
- package/dist/diagnostics.d.ts +0 -10
- package/dist/diagnostics.d.ts.map +0 -1
- package/dist/diagnostics.js +0 -83
- package/dist/diagnostics.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/generated-bridge.d.ts +0 -365
- package/dist/generated-bridge.d.ts.map +0 -1
- package/dist/generated-bridge.js +0 -250
- package/dist/generated-bridge.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mutations.d.ts +0 -72
- package/dist/mutations.d.ts.map +0 -1
- package/dist/mutations.js +0 -63
- package/dist/mutations.js.map +0 -1
- package/dist/network.d.ts +0 -3
- package/dist/network.d.ts.map +0 -1
- package/dist/network.js +0 -17
- package/dist/network.js.map +0 -1
- package/dist/react/index.d.ts +0 -169
- package/dist/react/index.d.ts.map +0 -1
- package/dist/react/index.js +0 -628
- package/dist/react/index.js.map +0 -1
- package/dist/react-native/index.d.ts +0 -35
- package/dist/react-native/index.d.ts.map +0 -1
- package/dist/react-native/index.js +0 -49
- package/dist/react-native/index.js.map +0 -1
- package/dist/runtime-contract.d.ts +0 -13
- package/dist/runtime-contract.d.ts.map +0 -1
- package/dist/runtime-contract.js +0 -24
- package/dist/runtime-contract.js.map +0 -1
- package/dist/rust-client.d.ts +0 -106
- package/dist/rust-client.d.ts.map +0 -1
- package/dist/rust-client.js +0 -938
- package/dist/rust-client.js.map +0 -1
- package/dist/sentry.d.ts +0 -35
- package/dist/sentry.d.ts.map +0 -1
- package/dist/sentry.js +0 -155
- package/dist/sentry.js.map +0 -1
- package/dist/sql-safety.d.ts +0 -3
- package/dist/sql-safety.d.ts.map +0 -1
- package/dist/sql-safety.js +0 -62
- package/dist/sql-safety.js.map +0 -1
- package/dist/syncular-runtime-artifacts.json +0 -61
- package/dist/tauri/index.d.ts +0 -35
- package/dist/tauri/index.d.ts.map +0 -1
- package/dist/tauri/index.js +0 -114
- package/dist/tauri/index.js.map +0 -1
- package/dist/types.d.ts +0 -1096
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/wasm/.syncular-wasm-profile +0 -1
- package/dist/wasm/syncular-runtime-artifact.json +0 -21
- package/dist/wasm/syncular.d.ts +0 -207
- package/dist/wasm/syncular.js +0 -2341
- package/dist/wasm/syncular_bg.wasm +0 -0
- package/dist/wasm/syncular_bg.wasm.d.ts +0 -97
- package/dist/wasm-bindings/runtime-contract.d.ts +0 -22
- package/dist/wasm-bindings/runtime-contract.d.ts.map +0 -1
- package/dist/wasm-bindings/runtime-contract.js +0 -112
- package/dist/wasm-bindings/runtime-contract.js.map +0 -1
- package/dist/wasm-core/.syncular-wasm-profile +0 -1
- package/dist/wasm-core/syncular-runtime-artifact.json +0 -17
- package/dist/wasm-core/syncular.d.ts +0 -162
- package/dist/wasm-core/syncular.js +0 -1847
- package/dist/wasm-core/syncular_bg.wasm +0 -0
- package/dist/wasm-core/syncular_bg.wasm.d.ts +0 -77
- package/dist/wasm-perf/.syncular-wasm-profile +0 -1
- package/dist/wasm-perf/syncular-runtime-artifact.json +0 -21
- package/dist/wasm-perf/syncular.d.ts +0 -207
- package/dist/wasm-perf/syncular.js +0 -2341
- package/dist/wasm-perf/syncular_bg.wasm +0 -0
- package/dist/wasm-perf/syncular_bg.wasm.d.ts +0 -97
- package/dist/wasm-runtime.d.ts +0 -23
- package/dist/wasm-runtime.d.ts.map +0 -1
- package/dist/wasm-runtime.js +0 -69
- package/dist/wasm-runtime.js.map +0 -1
- package/dist/worker-client.d.ts +0 -123
- package/dist/worker-client.d.ts.map +0 -1
- package/dist/worker-client.js +0 -1735
- package/dist/worker-client.js.map +0 -1
- package/dist/worker-entry.d.ts.map +0 -1
- package/dist/worker-entry.js.map +0 -1
- package/dist/worker-protocol.d.ts.map +0 -1
- package/dist/worker-protocol.js.map +0 -1
- package/dist/worker-realtime.d.ts +0 -39
- package/dist/worker-realtime.d.ts.map +0 -1
- package/dist/worker-realtime.js +0 -677
- package/dist/worker-realtime.js.map +0 -1
- package/src/auth-leases.ts +0 -251
- package/src/blob-limits.ts +0 -98
- package/src/bridge-client.ts +0 -512
- package/src/client-config.ts +0 -29
- package/src/command-history.ts +0 -623
- package/src/console-diagnostics.ts +0 -617
- package/src/crdt-yjs/index.ts +0 -3
- package/src/crdt-yjs/webview-host-facade.ts +0 -477
- package/src/crdt-yjs/yjs-document-field-adapter.ts +0 -733
- package/src/crdt-yjs/yjs-prosemirror-bridge.ts +0 -272
- package/src/diagnostics.ts +0 -116
- package/src/generated-bridge.ts +0 -741
- package/src/mutations.ts +0 -168
- package/src/network.ts +0 -32
- package/src/react/index.ts +0 -1036
- package/src/react-native/index.ts +0 -152
- package/src/runtime-contract.ts +0 -48
- package/src/rust-client.ts +0 -1491
- package/src/sentry.ts +0 -215
- package/src/sql-safety.ts +0 -61
- package/src/tauri/index.ts +0 -211
- package/src/types.ts +0 -1397
- package/src/wasm-bindings/generated-wasm-bindings.d.ts +0 -70
- package/src/wasm-bindings/runtime-contract.ts +0 -158
- package/src/wasm-runtime.ts +0 -145
- package/src/worker-client.ts +0 -2289
- package/src/worker-realtime.ts +0 -843
package/dist/rust-client.js
DELETED
|
@@ -1,938 +0,0 @@
|
|
|
1
|
-
import { issueSyncularAuthLease } from './auth-leases.js';
|
|
2
|
-
import { assertSyncularBlobPayloadLimit } from './blob-limits.js';
|
|
3
|
-
import { resolveSyncularClientConfig } from './client-config.js';
|
|
4
|
-
import { appendSyncularDiagnosticEvent, appendSyncularSyncTimings, createSyncularSyncAttempt, summarizeSyncularDiagnosticSubscriptions, syncularDiagnosticAttemptFields, syncularSyncAttemptHeaders, } from './diagnostics.js';
|
|
5
|
-
import { SyncularClientError, toSyncularClientError } from './errors.js';
|
|
6
|
-
import { createSyncularRuntimeInfo } from './runtime-contract.js';
|
|
7
|
-
import { assertSyncularReadonlySql } from './sql-safety.js';
|
|
8
|
-
import { getSyncularWasmGlueUrl, getSyncularWasmUrl, loadSyncularWasmGlue, prepareSyncularWasmModuleInput, readSyncularRustRuntimeInfo, } from './wasm-runtime.js';
|
|
9
|
-
export async function openSyncularRustClient(options) {
|
|
10
|
-
const wasmGlueUrl = options.runtime?.wasmGlueUrl ??
|
|
11
|
-
options.wasmGlueUrl ??
|
|
12
|
-
getSyncularWasmGlueUrl();
|
|
13
|
-
const mod = await (options.module ??
|
|
14
|
-
(options.runtime?.wasmGlueUrl || options.wasmGlueUrl
|
|
15
|
-
? loadSyncularWasmGlueFromUrl(wasmGlueUrl)
|
|
16
|
-
: loadSyncularWasmGlue()));
|
|
17
|
-
const wasmUrl = options.runtime?.wasmUrl ?? options.wasmUrl ?? getSyncularWasmUrl();
|
|
18
|
-
const config = resolveSyncularClientConfig(options.config);
|
|
19
|
-
await mod.default({
|
|
20
|
-
module_or_path: await prepareSyncularWasmModuleInput(wasmUrl),
|
|
21
|
-
});
|
|
22
|
-
const rustRuntimeInfo = readSyncularRustRuntimeInfo(mod);
|
|
23
|
-
return new SyncularRustClient(await mod.openSyncularRustOwnedSqliteClient(config), createSyncularRuntimeInfo({
|
|
24
|
-
storage: config.storage,
|
|
25
|
-
wasmGlueUrl,
|
|
26
|
-
wasmUrl,
|
|
27
|
-
rust: rustRuntimeInfo,
|
|
28
|
-
}), config, config.pull, options.blobLimits);
|
|
29
|
-
}
|
|
30
|
-
function loadSyncularWasmGlueFromUrl(wasmGlueUrl) {
|
|
31
|
-
const href = wasmGlueUrl instanceof URL ? wasmGlueUrl.href : wasmGlueUrl;
|
|
32
|
-
return import(/* @vite-ignore */ href);
|
|
33
|
-
}
|
|
34
|
-
export class SyncularRustClient {
|
|
35
|
-
raw;
|
|
36
|
-
runtime;
|
|
37
|
-
config;
|
|
38
|
-
pullOptions;
|
|
39
|
-
blobLimits;
|
|
40
|
-
#rowsChangedListeners = new Set();
|
|
41
|
-
#diagnosticListeners = new Set();
|
|
42
|
-
#recentDiagnostics = [];
|
|
43
|
-
#recentSyncTimings = [];
|
|
44
|
-
#subscriptions = [];
|
|
45
|
-
#authHeaders = {};
|
|
46
|
-
#bootstrapById = new Map();
|
|
47
|
-
#closed = false;
|
|
48
|
-
constructor(raw, runtime, config, pullOptions, blobLimits) {
|
|
49
|
-
this.raw = raw;
|
|
50
|
-
this.runtime = runtime;
|
|
51
|
-
this.config = config;
|
|
52
|
-
this.pullOptions = pullOptions;
|
|
53
|
-
this.blobLimits = blobLimits;
|
|
54
|
-
}
|
|
55
|
-
#assertRuntimeFeature(feature, apiName) {
|
|
56
|
-
if (this.runtime.rust?.features.includes(feature))
|
|
57
|
-
return;
|
|
58
|
-
throw new Error(`Syncular runtime feature "${feature}" is required for ${apiName}. ` +
|
|
59
|
-
'Select the full browser WASM runtime artifact.');
|
|
60
|
-
}
|
|
61
|
-
setSubscriptions(subscriptions) {
|
|
62
|
-
this.#subscriptions = [...subscriptions];
|
|
63
|
-
this.#bootstrapById.clear();
|
|
64
|
-
this.raw.setSubscriptionsJson(JSON.stringify(subscriptions));
|
|
65
|
-
this.#emitDiagnostic({
|
|
66
|
-
at: Date.now(),
|
|
67
|
-
level: 'info',
|
|
68
|
-
source: 'client',
|
|
69
|
-
code: 'client.subscriptions.updated',
|
|
70
|
-
message: 'Syncular subscriptions updated',
|
|
71
|
-
details: { subscriptionCount: subscriptions.length },
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
async forceSubscriptionsBootstrap(subscriptionIds = []) {
|
|
75
|
-
const count = parseJson(await this.raw.forceSubscriptionsBootstrapJson(JSON.stringify(subscriptionIds)));
|
|
76
|
-
for (const id of subscriptionIds.length > 0
|
|
77
|
-
? subscriptionIds
|
|
78
|
-
: this.#subscriptions.map((subscription) => subscription.id)) {
|
|
79
|
-
this.#bootstrapById.delete(id);
|
|
80
|
-
}
|
|
81
|
-
return count;
|
|
82
|
-
}
|
|
83
|
-
setAuthHeaders(headers) {
|
|
84
|
-
this.#authHeaders = { ...headers };
|
|
85
|
-
this.raw.setAuthHeadersJson(JSON.stringify(this.#authHeaders));
|
|
86
|
-
}
|
|
87
|
-
async issueAuthLease(request) {
|
|
88
|
-
const lease = await issueSyncularAuthLease({
|
|
89
|
-
baseUrl: this.config.baseUrl,
|
|
90
|
-
headers: this.#authHeaders,
|
|
91
|
-
request,
|
|
92
|
-
appSchema: this.config.appSchema,
|
|
93
|
-
});
|
|
94
|
-
await this.upsertAuthLease(lease);
|
|
95
|
-
this.#emitDiagnostic({
|
|
96
|
-
at: Date.now(),
|
|
97
|
-
level: 'info',
|
|
98
|
-
source: 'auth',
|
|
99
|
-
code: 'auth_lease.issued',
|
|
100
|
-
message: 'Syncular auth lease issued and stored',
|
|
101
|
-
details: {
|
|
102
|
-
leaseId: lease.leaseId,
|
|
103
|
-
expiresAtMs: lease.expiresAtMs,
|
|
104
|
-
schemaVersion: lease.schemaVersion,
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
return lease;
|
|
108
|
-
}
|
|
109
|
-
async upsertAuthLease(lease) {
|
|
110
|
-
this.raw.upsertAuthLeaseJson(JSON.stringify(lease));
|
|
111
|
-
}
|
|
112
|
-
async authLease(leaseId) {
|
|
113
|
-
return parseJson(this.raw.authLeaseJson(leaseId));
|
|
114
|
-
}
|
|
115
|
-
async activeAuthLeases(actorId, nowMs = Date.now()) {
|
|
116
|
-
return parseJson(this.raw.activeAuthLeasesJson(actorId ?? null, BigInt(Math.trunc(nowMs))));
|
|
117
|
-
}
|
|
118
|
-
setFieldEncryption(config) {
|
|
119
|
-
if (config == null && !this.runtime.rust?.features.includes('e2ee'))
|
|
120
|
-
return;
|
|
121
|
-
this.#assertRuntimeFeature('e2ee', 'setFieldEncryption');
|
|
122
|
-
this.raw.setFieldEncryptionJson(config == null
|
|
123
|
-
? 'null'
|
|
124
|
-
: JSON.stringify(normalizeFieldEncryptionConfig(config)));
|
|
125
|
-
}
|
|
126
|
-
setEncryptedCrdt(config) {
|
|
127
|
-
if (config == null && !this.runtime.rust?.features.includes('e2ee'))
|
|
128
|
-
return;
|
|
129
|
-
this.#assertRuntimeFeature('e2ee', 'setEncryptedCrdt');
|
|
130
|
-
this.raw.setEncryptedCrdtJson(config == null
|
|
131
|
-
? 'null'
|
|
132
|
-
: JSON.stringify(normalizeEncryptedCrdtConfig(config)));
|
|
133
|
-
}
|
|
134
|
-
setBlobEncryption(config) {
|
|
135
|
-
if (config == null && !this.runtime.rust?.features.includes('e2ee'))
|
|
136
|
-
return;
|
|
137
|
-
this.#assertRuntimeFeature('e2ee', 'setBlobEncryption');
|
|
138
|
-
this.raw.setBlobEncryptionJson(config == null
|
|
139
|
-
? 'null'
|
|
140
|
-
: JSON.stringify(normalizeBlobEncryptionConfig(config)));
|
|
141
|
-
}
|
|
142
|
-
setAbortSignal(signal) {
|
|
143
|
-
this.raw.setAbortSignal(signal ?? null);
|
|
144
|
-
}
|
|
145
|
-
async applyMutation(operation, localRow) {
|
|
146
|
-
const commitId = await this.raw.applyMutationJson(JSON.stringify(operation), localRow == null ? null : JSON.stringify(localRow));
|
|
147
|
-
this.#drainAndEmitRowsChanged();
|
|
148
|
-
return commitId;
|
|
149
|
-
}
|
|
150
|
-
async applyLeasedMutation(operation, localRow) {
|
|
151
|
-
const commitId = await this.raw.applyLeasedMutationJson(JSON.stringify(operation), localRow == null ? null : JSON.stringify(localRow));
|
|
152
|
-
this.#drainAndEmitRowsChanged();
|
|
153
|
-
return commitId;
|
|
154
|
-
}
|
|
155
|
-
async applyMutationsBatch(operations) {
|
|
156
|
-
const commitIds = parseJson(await this.raw.applyMutationsBatchJson(JSON.stringify(operations)));
|
|
157
|
-
this.#drainAndEmitRowsChanged();
|
|
158
|
-
return commitIds;
|
|
159
|
-
}
|
|
160
|
-
async applyMutationsCommit(operations) {
|
|
161
|
-
const commitId = parseJson(await this.raw.applyMutationsCommitJson(JSON.stringify(operations)));
|
|
162
|
-
this.#drainAndEmitRowsChanged();
|
|
163
|
-
return commitId;
|
|
164
|
-
}
|
|
165
|
-
async applyLeasedMutationsCommit(operations) {
|
|
166
|
-
const commitId = parseJson(await this.raw.applyLeasedMutationsCommitJson(JSON.stringify(operations)));
|
|
167
|
-
this.#drainAndEmitRowsChanged();
|
|
168
|
-
return commitId;
|
|
169
|
-
}
|
|
170
|
-
async syncPull(options = {}) {
|
|
171
|
-
const result = await this.#runTracedSync('syncPull', options.syncAttempt, async () => this.#decorateSyncResult(parseSyncResult(await this.raw.syncPullJson())));
|
|
172
|
-
this.#captureSyncTimings(result);
|
|
173
|
-
this.#emitRowsChanged('remotePull', result);
|
|
174
|
-
return result;
|
|
175
|
-
}
|
|
176
|
-
async applyRealtimeSyncPack(bytes) {
|
|
177
|
-
const result = this.#decorateSyncResult(parseSyncResult(await this.raw.applyRealtimeSyncPackBytes(bytes)));
|
|
178
|
-
this.#captureSyncTimings(result);
|
|
179
|
-
this.#emitRowsChanged('remotePull', result);
|
|
180
|
-
return result;
|
|
181
|
-
}
|
|
182
|
-
async syncPush(options = {}) {
|
|
183
|
-
try {
|
|
184
|
-
const result = await this.#runTracedSync('syncPush', options.syncAttempt, async () => this.#decorateSyncResult(parseSyncResult(await this.raw.syncPushJson())));
|
|
185
|
-
this.#captureSyncTimings(result);
|
|
186
|
-
this.#emitRowsChanged('localWrite', result);
|
|
187
|
-
return result;
|
|
188
|
-
}
|
|
189
|
-
catch (error) {
|
|
190
|
-
this.raw.recoverSyncPushErrorJson(String(error));
|
|
191
|
-
throw error;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
async syncOnce(options = {}) {
|
|
195
|
-
try {
|
|
196
|
-
const result = await this.#runTracedSync('syncOnce', options.syncAttempt, async () => this.#decorateSyncResult(parseSyncResult(await this.raw.syncOnceJson())));
|
|
197
|
-
this.#captureSyncTimings(result);
|
|
198
|
-
this.#emitRowsChanged('remotePull', result);
|
|
199
|
-
return result;
|
|
200
|
-
}
|
|
201
|
-
catch (error) {
|
|
202
|
-
this.raw.recoverSyncPushErrorJson(String(error));
|
|
203
|
-
throw error;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
resumeFromBackground(options = {}) {
|
|
207
|
-
return this.syncOnce(options);
|
|
208
|
-
}
|
|
209
|
-
transportStats() {
|
|
210
|
-
return parseJson(this.raw.transportStatsJson());
|
|
211
|
-
}
|
|
212
|
-
resetTransportStats() {
|
|
213
|
-
this.raw.resetTransportStats();
|
|
214
|
-
}
|
|
215
|
-
async conflictSummaries() {
|
|
216
|
-
return parseConflictSummaries(await this.raw.conflictSummariesJson());
|
|
217
|
-
}
|
|
218
|
-
retryConflictKeepLocal(id) {
|
|
219
|
-
return this.raw.retryConflictKeepLocal(id);
|
|
220
|
-
}
|
|
221
|
-
resolveConflict(id, resolution) {
|
|
222
|
-
return this.raw.resolveConflict(id, resolution);
|
|
223
|
-
}
|
|
224
|
-
executeSql(sql, params = []) {
|
|
225
|
-
assertSyncularReadonlySql(sql);
|
|
226
|
-
return this.raw.executeSqlValue(sql, params);
|
|
227
|
-
}
|
|
228
|
-
executeUnsafeSql(sql, params = []) {
|
|
229
|
-
return this.raw.executeUnsafeSqlValue(sql, params);
|
|
230
|
-
}
|
|
231
|
-
subscribeQuery(sql, params, tables, hints = []) {
|
|
232
|
-
return parseJson(this.raw.subscribeQueryJson(sql, stringifyParams(params), JSON.stringify(tables), JSON.stringify(hints)));
|
|
233
|
-
}
|
|
234
|
-
unsubscribeQuery(id) {
|
|
235
|
-
this.raw.unsubscribeQuery(id);
|
|
236
|
-
}
|
|
237
|
-
drainLiveQueryEvents() {
|
|
238
|
-
return parseJson(this.raw.drainLiveQueryEventsJson());
|
|
239
|
-
}
|
|
240
|
-
liveQueryDiagnostics() {
|
|
241
|
-
return parseJson(this.raw.liveQueryDiagnosticsJson());
|
|
242
|
-
}
|
|
243
|
-
addRowsChangedListener(listener) {
|
|
244
|
-
this.#rowsChangedListeners.add(listener);
|
|
245
|
-
return () => {
|
|
246
|
-
this.#rowsChangedListeners.delete(listener);
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
async listTable(table) {
|
|
250
|
-
return parseJson(await this.raw.listTableJson(table));
|
|
251
|
-
}
|
|
252
|
-
async storeBlob(data, options = {}) {
|
|
253
|
-
assertSyncularBlobPayloadLimit({
|
|
254
|
-
operation: 'store',
|
|
255
|
-
size: data.byteLength,
|
|
256
|
-
limits: this.blobLimits,
|
|
257
|
-
options,
|
|
258
|
-
diagnostics: (event) => this.#emitDiagnostic(event),
|
|
259
|
-
});
|
|
260
|
-
return parseJson(await this.raw.storeBlobJson(data, JSON.stringify(options)));
|
|
261
|
-
}
|
|
262
|
-
async retrieveBlob(ref) {
|
|
263
|
-
assertSyncularBlobPayloadLimit({
|
|
264
|
-
operation: 'retrieve',
|
|
265
|
-
size: ref.size,
|
|
266
|
-
limits: this.blobLimits,
|
|
267
|
-
refHash: ref.hash,
|
|
268
|
-
diagnostics: (event) => this.#emitDiagnostic(event),
|
|
269
|
-
});
|
|
270
|
-
const wasLocal = this.#hasDiagnosticListeners()
|
|
271
|
-
? this.raw.isBlobLocal(ref.hash)
|
|
272
|
-
: undefined;
|
|
273
|
-
try {
|
|
274
|
-
const bytes = await this.raw.retrieveBlob(JSON.stringify(ref));
|
|
275
|
-
if (wasLocal !== undefined) {
|
|
276
|
-
this.#emitDiagnostic({
|
|
277
|
-
at: Date.now(),
|
|
278
|
-
level: 'info',
|
|
279
|
-
source: 'blob',
|
|
280
|
-
code: wasLocal ? 'blob.cache_hit' : 'blob.cache_miss',
|
|
281
|
-
message: wasLocal
|
|
282
|
-
? 'Syncular blob served from local cache'
|
|
283
|
-
: 'Syncular blob fetched after local cache miss',
|
|
284
|
-
details: {
|
|
285
|
-
hash: ref.hash,
|
|
286
|
-
size: ref.size,
|
|
287
|
-
mimeType: ref.mimeType,
|
|
288
|
-
encrypted: ref.encrypted === true,
|
|
289
|
-
...(ref.keyId ? { keyId: ref.keyId } : {}),
|
|
290
|
-
},
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
return bytes;
|
|
294
|
-
}
|
|
295
|
-
catch (error) {
|
|
296
|
-
if (this.#hasDiagnosticListeners()) {
|
|
297
|
-
this.#emitDiagnostic({
|
|
298
|
-
at: Date.now(),
|
|
299
|
-
level: 'warn',
|
|
300
|
-
source: 'blob',
|
|
301
|
-
code: 'blob.download_failed',
|
|
302
|
-
message: `Syncular blob download failed: ${String(error)}`,
|
|
303
|
-
details: {
|
|
304
|
-
hash: ref.hash,
|
|
305
|
-
size: ref.size,
|
|
306
|
-
mimeType: ref.mimeType,
|
|
307
|
-
encrypted: ref.encrypted === true,
|
|
308
|
-
...(ref.keyId ? { keyId: ref.keyId } : {}),
|
|
309
|
-
},
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
throw error;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
isBlobLocal(hash) {
|
|
316
|
-
const local = this.raw.isBlobLocal(hash);
|
|
317
|
-
if (this.#hasDiagnosticListeners()) {
|
|
318
|
-
this.#emitDiagnostic({
|
|
319
|
-
at: Date.now(),
|
|
320
|
-
level: 'debug',
|
|
321
|
-
source: 'blob',
|
|
322
|
-
code: local ? 'blob.cache_hit' : 'blob.cache_miss',
|
|
323
|
-
message: local
|
|
324
|
-
? 'Syncular blob is available in local cache'
|
|
325
|
-
: 'Syncular blob is not available in local cache',
|
|
326
|
-
details: { hash },
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
return local;
|
|
330
|
-
}
|
|
331
|
-
async processBlobUploadQueue(options) {
|
|
332
|
-
const result = parseJson(await this.raw.processBlobUploadQueueJson(options?.retryNow === true));
|
|
333
|
-
if (this.#hasDiagnosticListeners()) {
|
|
334
|
-
this.#emitDiagnostic({
|
|
335
|
-
at: Date.now(),
|
|
336
|
-
level: result.failed > 0 ? 'warn' : 'info',
|
|
337
|
-
source: 'blob',
|
|
338
|
-
code: 'blob.upload_queue_processed',
|
|
339
|
-
message: 'Syncular blob upload queue processed',
|
|
340
|
-
details: result,
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
return result;
|
|
344
|
-
}
|
|
345
|
-
blobUploadQueueStats() {
|
|
346
|
-
return parseJson(this.raw.blobUploadQueueStatsJson());
|
|
347
|
-
}
|
|
348
|
-
blobCacheStats() {
|
|
349
|
-
return parseJson(this.raw.blobCacheStatsJson());
|
|
350
|
-
}
|
|
351
|
-
pruneBlobCache(maxBytes = 0) {
|
|
352
|
-
const prunedBytes = Number(this.raw.pruneBlobCache(BigInt(maxBytes)));
|
|
353
|
-
if (this.#hasDiagnosticListeners()) {
|
|
354
|
-
this.#emitDiagnostic({
|
|
355
|
-
at: Date.now(),
|
|
356
|
-
level: 'info',
|
|
357
|
-
source: 'blob',
|
|
358
|
-
code: 'blob.cache_pruned',
|
|
359
|
-
message: 'Syncular blob cache pruned',
|
|
360
|
-
details: { prunedBytes, maxBytes },
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
return prunedBytes;
|
|
364
|
-
}
|
|
365
|
-
clearBlobCache() {
|
|
366
|
-
this.raw.clearBlobCache();
|
|
367
|
-
if (this.#hasDiagnosticListeners()) {
|
|
368
|
-
this.#emitDiagnostic({
|
|
369
|
-
at: Date.now(),
|
|
370
|
-
level: 'info',
|
|
371
|
-
source: 'blob',
|
|
372
|
-
code: 'blob.cache_cleared',
|
|
373
|
-
message: 'Syncular blob cache cleared',
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
compactStorage(options = {}) {
|
|
378
|
-
return parseJson(this.raw.compactStorageJson(JSON.stringify(options)));
|
|
379
|
-
}
|
|
380
|
-
generatedSchemaState() {
|
|
381
|
-
return parseJson(this.raw.generatedSchemaStateJson());
|
|
382
|
-
}
|
|
383
|
-
async localHealthCheck() {
|
|
384
|
-
return parseJson(await this.raw.localHealthCheckJson());
|
|
385
|
-
}
|
|
386
|
-
async exportLocalSupportBundle() {
|
|
387
|
-
return parseJson(await this.raw.exportLocalSupportBundleJson());
|
|
388
|
-
}
|
|
389
|
-
async importLocalSupportBundle(bundle) {
|
|
390
|
-
const bundleJson = typeof bundle === 'string' ? bundle : JSON.stringify(bundle);
|
|
391
|
-
return parseJson(await this.raw.importLocalSupportBundleJson(bundleJson));
|
|
392
|
-
}
|
|
393
|
-
async repairLocalHealth(request) {
|
|
394
|
-
const normalized = {
|
|
395
|
-
action: request.action,
|
|
396
|
-
subscriptionIds: [...(request.subscriptionIds ?? [])],
|
|
397
|
-
tables: [...(request.tables ?? [])],
|
|
398
|
-
};
|
|
399
|
-
return parseJson(await this.raw.repairLocalHealthJson(JSON.stringify(normalized)));
|
|
400
|
-
}
|
|
401
|
-
async resetLocalSyncState(request = {}) {
|
|
402
|
-
const normalized = {
|
|
403
|
-
subscriptionIds: [...(request.subscriptionIds ?? [])],
|
|
404
|
-
clearSyncedRows: request.clearSyncedRows === true,
|
|
405
|
-
};
|
|
406
|
-
const result = parseJson(await this.raw.resetLocalSyncStateJson(JSON.stringify(normalized)));
|
|
407
|
-
for (const id of normalized.subscriptionIds.length > 0
|
|
408
|
-
? normalized.subscriptionIds
|
|
409
|
-
: this.#subscriptions.map((subscription) => subscription.id)) {
|
|
410
|
-
this.#bootstrapById.delete(id);
|
|
411
|
-
}
|
|
412
|
-
return result;
|
|
413
|
-
}
|
|
414
|
-
buildYjsTextUpdate(args) {
|
|
415
|
-
this.#assertRuntimeFeature('crdt-yjs', 'buildYjsTextUpdate');
|
|
416
|
-
return parseJson(this.raw.buildYjsTextUpdateJson(JSON.stringify(args)));
|
|
417
|
-
}
|
|
418
|
-
applyYjsTextUpdates(args) {
|
|
419
|
-
this.#assertRuntimeFeature('crdt-yjs', 'applyYjsTextUpdates');
|
|
420
|
-
return parseJson(this.raw.applyYjsTextUpdatesJson(JSON.stringify(args)));
|
|
421
|
-
}
|
|
422
|
-
applyYjsEnvelopeToPayload(args) {
|
|
423
|
-
this.#assertRuntimeFeature('crdt-yjs', 'applyYjsEnvelopeToPayload');
|
|
424
|
-
const result = parseJson(this.raw.applyYjsEnvelopeToPayloadJson(JSON.stringify(args)));
|
|
425
|
-
return result.payload;
|
|
426
|
-
}
|
|
427
|
-
async openCrdtField(request) {
|
|
428
|
-
this.#assertRuntimeFeature('crdt-yjs', 'openCrdtField');
|
|
429
|
-
return parseJson(this.raw.openCrdtFieldJson(JSON.stringify(request)));
|
|
430
|
-
}
|
|
431
|
-
async applyCrdtFieldText(request) {
|
|
432
|
-
this.#assertRuntimeFeature('crdt-yjs', 'applyCrdtFieldText');
|
|
433
|
-
const receipt = parseJson(this.raw.applyCrdtFieldTextJson(JSON.stringify(request)));
|
|
434
|
-
this.#drainAndEmitRowsChanged();
|
|
435
|
-
return receipt;
|
|
436
|
-
}
|
|
437
|
-
async applyCrdtFieldYjsUpdate(request) {
|
|
438
|
-
this.#assertRuntimeFeature('crdt-yjs', 'applyCrdtFieldYjsUpdate');
|
|
439
|
-
const receipt = parseJson(this.raw.applyCrdtFieldYjsUpdateJson(JSON.stringify(request)));
|
|
440
|
-
this.#drainAndEmitRowsChanged();
|
|
441
|
-
return receipt;
|
|
442
|
-
}
|
|
443
|
-
async materializeCrdtField(request) {
|
|
444
|
-
this.#assertRuntimeFeature('crdt-yjs', 'materializeCrdtField');
|
|
445
|
-
return parseJson(this.raw.materializeCrdtFieldJson(JSON.stringify(request)));
|
|
446
|
-
}
|
|
447
|
-
async crdtDocumentSnapshot(request) {
|
|
448
|
-
this.#assertRuntimeFeature('crdt-yjs', 'crdtDocumentSnapshot');
|
|
449
|
-
return parseJson(this.raw.crdtDocumentSnapshotJson(JSON.stringify(request)));
|
|
450
|
-
}
|
|
451
|
-
async crdtUpdateLog(request) {
|
|
452
|
-
this.#assertRuntimeFeature('crdt-yjs', 'crdtUpdateLog');
|
|
453
|
-
return parseJson(this.raw.crdtUpdateLogJson(JSON.stringify(request)));
|
|
454
|
-
}
|
|
455
|
-
async snapshotCrdtFieldStateVector(request) {
|
|
456
|
-
this.#assertRuntimeFeature('crdt-yjs', 'snapshotCrdtFieldStateVector');
|
|
457
|
-
return parseJson(this.raw.snapshotCrdtFieldStateVectorJson(JSON.stringify(request)));
|
|
458
|
-
}
|
|
459
|
-
async compactCrdtField(request) {
|
|
460
|
-
this.#assertRuntimeFeature('crdt-yjs', 'compactCrdtField');
|
|
461
|
-
const receipt = parseJson(this.raw.compactCrdtFieldJson(JSON.stringify(request)));
|
|
462
|
-
this.#drainAndEmitRowsChanged();
|
|
463
|
-
return receipt;
|
|
464
|
-
}
|
|
465
|
-
encryptionHelper(method, args = {}) {
|
|
466
|
-
this.#assertRuntimeFeature('e2ee', 'encryptionHelper');
|
|
467
|
-
return parseJson(this.raw.encryptionHelperJson(method, JSON.stringify(args)));
|
|
468
|
-
}
|
|
469
|
-
async runtimeInfo() {
|
|
470
|
-
return this.runtime;
|
|
471
|
-
}
|
|
472
|
-
connectionState() {
|
|
473
|
-
const lastDiagnostic = this.#recentDiagnostics[this.#recentDiagnostics.length - 1];
|
|
474
|
-
return {
|
|
475
|
-
closed: this.#closed,
|
|
476
|
-
pendingRequests: 0,
|
|
477
|
-
realtime: 'disconnected',
|
|
478
|
-
...(lastDiagnostic ? { lastDiagnostic } : {}),
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
lifecycleState() {
|
|
482
|
-
const bootstrap = buildBootstrapStatus(this.#subscriptions, this.#bootstrapById, this.pullOptions);
|
|
483
|
-
const lastDiagnostic = this.#recentDiagnostics[this.#recentDiagnostics.length - 1];
|
|
484
|
-
return {
|
|
485
|
-
phase: this.#closed
|
|
486
|
-
? 'closed'
|
|
487
|
-
: bootstrap.complete
|
|
488
|
-
? 'complete'
|
|
489
|
-
: 'offline',
|
|
490
|
-
realtime: 'disconnected',
|
|
491
|
-
online: false,
|
|
492
|
-
requiresAction: false,
|
|
493
|
-
pendingRequests: 0,
|
|
494
|
-
bootstrap: {
|
|
495
|
-
complete: bootstrap.complete,
|
|
496
|
-
criticalReady: bootstrap.criticalReady,
|
|
497
|
-
interactiveReady: bootstrap.interactiveReady,
|
|
498
|
-
isBootstrapping: bootstrap.isBootstrapping,
|
|
499
|
-
progressPercent: bootstrap.progressPercent,
|
|
500
|
-
},
|
|
501
|
-
...(lastDiagnostic ? { lastDiagnostic } : {}),
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
async diagnosticSnapshot() {
|
|
505
|
-
const bootstrap = buildBootstrapStatus(this.#subscriptions, this.#bootstrapById, this.pullOptions);
|
|
506
|
-
return {
|
|
507
|
-
generatedAt: Date.now(),
|
|
508
|
-
runtime: this.runtime,
|
|
509
|
-
connection: this.connectionState(),
|
|
510
|
-
subscriptions: summarizeSyncularDiagnosticSubscriptions(this.#subscriptions, bootstrap),
|
|
511
|
-
recentDiagnostics: [...this.#recentDiagnostics],
|
|
512
|
-
recentSyncTimings: [...this.#recentSyncTimings],
|
|
513
|
-
bootstrap,
|
|
514
|
-
transportStats: this.transportStats(),
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
addDiagnosticListener(listener) {
|
|
518
|
-
this.#diagnosticListeners.add(listener);
|
|
519
|
-
return () => {
|
|
520
|
-
this.#diagnosticListeners.delete(listener);
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
#decorateSyncResult(result) {
|
|
524
|
-
for (const subscription of result.subscriptions) {
|
|
525
|
-
this.#bootstrapById.set(subscription.id, {
|
|
526
|
-
id: subscription.id,
|
|
527
|
-
table: subscription.table,
|
|
528
|
-
status: subscription.status,
|
|
529
|
-
ready: subscription.ready,
|
|
530
|
-
phase: subscription.phase,
|
|
531
|
-
progressPercent: subscription.progressPercent,
|
|
532
|
-
cursor: subscription.nextCursor,
|
|
533
|
-
bootstrapState: subscription.bootstrapState,
|
|
534
|
-
bootstrapPhase: subscription.bootstrapPhase,
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
return {
|
|
538
|
-
...result,
|
|
539
|
-
bootstrap: buildBootstrapStatus(this.#subscriptions, this.#bootstrapById, this.pullOptions),
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
#emitRowsChanged(source, result) {
|
|
543
|
-
if (this.#rowsChangedListeners.size === 0 ||
|
|
544
|
-
(result.changedTables.length === 0 && result.changedRows.length === 0)) {
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
for (const listener of this.#rowsChangedListeners) {
|
|
548
|
-
try {
|
|
549
|
-
listener({
|
|
550
|
-
source,
|
|
551
|
-
changedTables: result.changedTables,
|
|
552
|
-
changedRows: result.changedRows,
|
|
553
|
-
changedRowsTruncated: result.changedRowsTruncated,
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
catch {
|
|
557
|
-
// Row-change listeners must never break client control flow.
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
#captureSyncTimings(result) {
|
|
562
|
-
appendSyncularSyncTimings(this.#recentSyncTimings, result.timings);
|
|
563
|
-
}
|
|
564
|
-
async #runTracedSync(requestType, providedAttempt, run) {
|
|
565
|
-
const syncAttempt = providedAttempt ?? createSyncularSyncAttempt();
|
|
566
|
-
const startedAt = Date.now();
|
|
567
|
-
this.#emitDiagnostic({
|
|
568
|
-
at: startedAt,
|
|
569
|
-
level: 'debug',
|
|
570
|
-
source: 'sync',
|
|
571
|
-
code: `sync.${requestType}.started`,
|
|
572
|
-
message: `Syncular ${requestType} started`,
|
|
573
|
-
...syncularDiagnosticAttemptFields(syncAttempt),
|
|
574
|
-
});
|
|
575
|
-
this.raw.setAuthHeadersJson(JSON.stringify({
|
|
576
|
-
...this.#authHeaders,
|
|
577
|
-
...syncularSyncAttemptHeaders(syncAttempt),
|
|
578
|
-
}));
|
|
579
|
-
try {
|
|
580
|
-
const result = await run();
|
|
581
|
-
this.#emitDiagnostic({
|
|
582
|
-
at: Date.now(),
|
|
583
|
-
level: 'info',
|
|
584
|
-
source: 'sync',
|
|
585
|
-
code: `sync.${requestType}.completed`,
|
|
586
|
-
message: `Syncular ${requestType} completed`,
|
|
587
|
-
...syncularDiagnosticAttemptFields(syncAttempt),
|
|
588
|
-
details: { durationMs: Date.now() - startedAt },
|
|
589
|
-
});
|
|
590
|
-
if (isSyncularSyncResult(result)) {
|
|
591
|
-
this.#emitScopeRevokedDiagnostic(requestType, result, syncAttempt);
|
|
592
|
-
}
|
|
593
|
-
return result;
|
|
594
|
-
}
|
|
595
|
-
catch (error) {
|
|
596
|
-
const classifiedError = toSyncularClientError(error);
|
|
597
|
-
const classified = classifiedError instanceof SyncularClientError ? classifiedError : null;
|
|
598
|
-
this.#emitDiagnostic({
|
|
599
|
-
at: Date.now(),
|
|
600
|
-
level: 'warn',
|
|
601
|
-
source: 'sync',
|
|
602
|
-
code: classified?.code ?? `sync.${requestType}.failed`,
|
|
603
|
-
message: `Syncular ${requestType} failed`,
|
|
604
|
-
...syncularDiagnosticAttemptFields(syncAttempt),
|
|
605
|
-
details: {
|
|
606
|
-
durationMs: Date.now() - startedAt,
|
|
607
|
-
error: classifiedError instanceof Error
|
|
608
|
-
? classifiedError.message
|
|
609
|
-
: String(classifiedError),
|
|
610
|
-
...(classified
|
|
611
|
-
? {
|
|
612
|
-
errorCode: classified.code,
|
|
613
|
-
category: classified.category,
|
|
614
|
-
retryable: classified.retryable,
|
|
615
|
-
recommendedAction: classified.recommendedAction,
|
|
616
|
-
...(classified.details ?? {}),
|
|
617
|
-
}
|
|
618
|
-
: {}),
|
|
619
|
-
},
|
|
620
|
-
});
|
|
621
|
-
throw classifiedError;
|
|
622
|
-
}
|
|
623
|
-
finally {
|
|
624
|
-
this.raw.setAuthHeadersJson(JSON.stringify(this.#authHeaders));
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
#emitDiagnostic(event) {
|
|
628
|
-
appendSyncularDiagnosticEvent(this.#recentDiagnostics, event);
|
|
629
|
-
if (this.#diagnosticListeners.size === 0)
|
|
630
|
-
return;
|
|
631
|
-
for (const listener of this.#diagnosticListeners) {
|
|
632
|
-
try {
|
|
633
|
-
listener(event);
|
|
634
|
-
}
|
|
635
|
-
catch {
|
|
636
|
-
// Diagnostics must never break client control flow.
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
#hasDiagnosticListeners() {
|
|
641
|
-
return this.#diagnosticListeners.size > 0;
|
|
642
|
-
}
|
|
643
|
-
#drainAndEmitRowsChanged() {
|
|
644
|
-
let events;
|
|
645
|
-
try {
|
|
646
|
-
events = parseJson(this.raw.drainRowsChangedEventsJson());
|
|
647
|
-
}
|
|
648
|
-
catch {
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
if (this.#rowsChangedListeners.size === 0)
|
|
652
|
-
return;
|
|
653
|
-
for (const event of events) {
|
|
654
|
-
if (event.changedTables.length === 0 && event.changedRows.length === 0) {
|
|
655
|
-
continue;
|
|
656
|
-
}
|
|
657
|
-
for (const listener of this.#rowsChangedListeners) {
|
|
658
|
-
try {
|
|
659
|
-
listener(event);
|
|
660
|
-
}
|
|
661
|
-
catch {
|
|
662
|
-
// Row-change listeners must never break client control flow.
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
close() {
|
|
668
|
-
this.#closed = true;
|
|
669
|
-
this.raw.close();
|
|
670
|
-
}
|
|
671
|
-
#emitScopeRevokedDiagnostic(requestType, result, syncAttempt) {
|
|
672
|
-
const revokedSubscriptionIds = result.subscriptions
|
|
673
|
-
.filter((subscription) => subscription.status === 'revoked')
|
|
674
|
-
.map((subscription) => subscription.id);
|
|
675
|
-
if (revokedSubscriptionIds.length === 0)
|
|
676
|
-
return;
|
|
677
|
-
this.#emitDiagnostic({
|
|
678
|
-
at: Date.now(),
|
|
679
|
-
level: 'warn',
|
|
680
|
-
source: 'sync',
|
|
681
|
-
code: 'sync.scope_revoked',
|
|
682
|
-
message: 'Syncular subscription scope revoked',
|
|
683
|
-
...syncularDiagnosticAttemptFields(syncAttempt),
|
|
684
|
-
details: {
|
|
685
|
-
requestType,
|
|
686
|
-
revokedSubscriptionIds,
|
|
687
|
-
revokedSubscriptionCount: revokedSubscriptionIds.length,
|
|
688
|
-
},
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
function isSyncularSyncResult(value) {
|
|
693
|
-
return (value !== null &&
|
|
694
|
-
typeof value === 'object' &&
|
|
695
|
-
Array.isArray(value.subscriptions));
|
|
696
|
-
}
|
|
697
|
-
function parseSyncResult(value) {
|
|
698
|
-
const raw = parseJson(value);
|
|
699
|
-
return {
|
|
700
|
-
changedTables: raw.changed_tables ?? [],
|
|
701
|
-
changedRows: raw.changed_rows ?? [],
|
|
702
|
-
changedRowsTruncated: raw.changed_rows_truncated ?? false,
|
|
703
|
-
subscriptions: (raw.subscriptions ?? []).map((subscription) => ({
|
|
704
|
-
id: subscription.id,
|
|
705
|
-
table: subscription.table,
|
|
706
|
-
status: subscription.status,
|
|
707
|
-
scopes: subscription.scopes,
|
|
708
|
-
nextCursor: subscription.next_cursor,
|
|
709
|
-
bootstrapPhase: subscription.bootstrap_phase ?? 0,
|
|
710
|
-
bootstrapState: parseBootstrapState(subscription.bootstrap_state),
|
|
711
|
-
ready: subscription.ready === true,
|
|
712
|
-
phase: subscription.phase ?? 'pending',
|
|
713
|
-
progressPercent: subscription.progress_percent ?? 0,
|
|
714
|
-
snapshotRows: subscription.snapshot_rows ?? [],
|
|
715
|
-
commits: subscription.commits ?? [],
|
|
716
|
-
})),
|
|
717
|
-
bootstrap: parseBootstrapStatus(raw.bootstrap),
|
|
718
|
-
pushedCommits: raw.pushed_commits ?? 0,
|
|
719
|
-
timings: {
|
|
720
|
-
totalMs: raw.timings?.total_ms ?? 0,
|
|
721
|
-
pushMs: raw.timings?.push_ms ?? 0,
|
|
722
|
-
pullMs: raw.timings?.pull_ms ?? 0,
|
|
723
|
-
pullRequestMs: raw.timings?.pull_request_ms ?? 0,
|
|
724
|
-
syncPackDecodeMs: raw.timings?.sync_pack_decode_ms ?? 0,
|
|
725
|
-
pullTransformMs: raw.timings?.pull_transform_ms ?? 0,
|
|
726
|
-
integrityVerifyMs: raw.timings?.integrity_verify_ms ?? 0,
|
|
727
|
-
snapshotFetchMs: raw.timings?.snapshot_fetch_ms ?? 0,
|
|
728
|
-
pullApplyMs: raw.timings?.pull_apply_ms ?? 0,
|
|
729
|
-
scopeClearMs: raw.timings?.scope_clear_ms ?? 0,
|
|
730
|
-
snapshotRowApplyMs: raw.timings?.snapshot_row_apply_ms ?? 0,
|
|
731
|
-
snapshotArtifactApplyMs: raw.timings?.snapshot_artifact_apply_ms ?? 0,
|
|
732
|
-
snapshotArtifactCheckpointMs: raw.timings?.snapshot_artifact_checkpoint_ms ?? 0,
|
|
733
|
-
snapshotArtifactCheckpointCount: raw.timings?.snapshot_artifact_checkpoint_count ?? 0,
|
|
734
|
-
snapshotChunkApplyMs: raw.timings?.snapshot_chunk_apply_ms ?? 0,
|
|
735
|
-
snapshotChunkMaterializeMs: raw.timings?.snapshot_chunk_materialize_ms ?? 0,
|
|
736
|
-
snapshotChunkResetMs: raw.timings?.snapshot_chunk_reset_ms ?? 0,
|
|
737
|
-
snapshotChunkBindMs: raw.timings?.snapshot_chunk_bind_ms ?? 0,
|
|
738
|
-
snapshotChunkStepMs: raw.timings?.snapshot_chunk_step_ms ?? 0,
|
|
739
|
-
commitApplyMs: raw.timings?.commit_apply_ms ?? 0,
|
|
740
|
-
subscriptionStateMs: raw.timings?.subscription_state_ms ?? 0,
|
|
741
|
-
notifyMs: raw.timings?.notify_ms ?? 0,
|
|
742
|
-
},
|
|
743
|
-
};
|
|
744
|
-
}
|
|
745
|
-
function parseBootstrapState(state) {
|
|
746
|
-
if (!state)
|
|
747
|
-
return null;
|
|
748
|
-
return {
|
|
749
|
-
asOfCommitSeq: state.asOfCommitSeq ?? 0,
|
|
750
|
-
tables: state.tables ?? [],
|
|
751
|
-
tableIndex: state.tableIndex ?? 0,
|
|
752
|
-
rowCursor: state.rowCursor ?? null,
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
function parseBootstrapStatus(raw) {
|
|
756
|
-
return {
|
|
757
|
-
channelPhase: raw?.channel_phase ?? 'idle',
|
|
758
|
-
progressPercent: raw?.progress_percent ?? 100,
|
|
759
|
-
isBootstrapping: raw?.is_bootstrapping === true,
|
|
760
|
-
criticalReady: raw?.critical_ready ?? true,
|
|
761
|
-
interactiveReady: raw?.interactive_ready ?? true,
|
|
762
|
-
complete: raw?.complete ?? true,
|
|
763
|
-
activePhase: raw?.active_phase ?? null,
|
|
764
|
-
expectedSubscriptionIds: raw?.expected_subscription_ids ?? [],
|
|
765
|
-
readySubscriptionIds: raw?.ready_subscription_ids ?? [],
|
|
766
|
-
pendingSubscriptionIds: raw?.pending_subscription_ids ?? [],
|
|
767
|
-
subscriptions: (raw?.subscriptions ?? []).map((subscription) => ({
|
|
768
|
-
id: subscription.id,
|
|
769
|
-
table: subscription.table,
|
|
770
|
-
expected: subscription.expected ?? true,
|
|
771
|
-
ready: subscription.ready === true,
|
|
772
|
-
status: subscription.status ?? null,
|
|
773
|
-
phase: subscription.phase ?? 'pending',
|
|
774
|
-
progressPercent: subscription.progress_percent ?? 0,
|
|
775
|
-
cursor: subscription.cursor ?? null,
|
|
776
|
-
bootstrapState: parseBootstrapState(subscription.bootstrap_state),
|
|
777
|
-
bootstrapPhase: subscription.bootstrap_phase ?? 0,
|
|
778
|
-
})),
|
|
779
|
-
phases: (raw?.phases ?? []).map((phase) => ({
|
|
780
|
-
phase: phase.phase ?? 0,
|
|
781
|
-
expectedSubscriptionIds: phase.expected_subscription_ids ?? [],
|
|
782
|
-
readySubscriptionIds: phase.ready_subscription_ids ?? [],
|
|
783
|
-
pendingSubscriptionIds: phase.pending_subscription_ids ?? [],
|
|
784
|
-
isReady: phase.is_ready === true,
|
|
785
|
-
progressPercent: phase.progress_percent ?? 0,
|
|
786
|
-
})),
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
function buildBootstrapStatus(configuredSubscriptions, cachedById, pullOptions) {
|
|
790
|
-
const criticalPhase = normalizeBootstrapPhase(pullOptions?.criticalBootstrapPhase);
|
|
791
|
-
const interactivePhase = Math.max(criticalPhase, normalizeBootstrapPhase(pullOptions?.interactiveBootstrapPhase ?? 1));
|
|
792
|
-
const subscriptions = configuredSubscriptions.map((spec) => {
|
|
793
|
-
const cached = cachedById.get(spec.id);
|
|
794
|
-
const bootstrapPhase = normalizeBootstrapPhase(spec.bootstrapPhase);
|
|
795
|
-
return {
|
|
796
|
-
id: spec.id,
|
|
797
|
-
table: spec.table,
|
|
798
|
-
expected: true,
|
|
799
|
-
ready: cached?.ready ?? false,
|
|
800
|
-
status: cached?.status ?? null,
|
|
801
|
-
phase: cached?.phase ?? 'pending',
|
|
802
|
-
progressPercent: cached?.progressPercent ?? 0,
|
|
803
|
-
cursor: cached?.cursor ?? null,
|
|
804
|
-
bootstrapState: cached?.bootstrapState ?? null,
|
|
805
|
-
bootstrapPhase,
|
|
806
|
-
};
|
|
807
|
-
});
|
|
808
|
-
const expectedSubscriptionIds = subscriptions.map((subscription) => subscription.id);
|
|
809
|
-
const readySubscriptionIds = subscriptions
|
|
810
|
-
.filter((subscription) => subscription.ready)
|
|
811
|
-
.map((subscription) => subscription.id);
|
|
812
|
-
const pendingSubscriptionIds = subscriptions
|
|
813
|
-
.filter((subscription) => !subscription.ready)
|
|
814
|
-
.map((subscription) => subscription.id);
|
|
815
|
-
const complete = pendingSubscriptionIds.length === 0;
|
|
816
|
-
const criticalReady = subscriptions.every((subscription) => subscription.bootstrapPhase > criticalPhase || subscription.ready);
|
|
817
|
-
const interactiveReady = subscriptions.every((subscription) => subscription.bootstrapPhase > interactivePhase || subscription.ready);
|
|
818
|
-
const activePhase = subscriptions
|
|
819
|
-
.filter((subscription) => !subscription.ready)
|
|
820
|
-
.reduce((lowest, subscription) => lowest === null || subscription.bootstrapPhase < lowest
|
|
821
|
-
? subscription.bootstrapPhase
|
|
822
|
-
: lowest, null) ?? null;
|
|
823
|
-
const hasError = subscriptions.some((subscription) => subscription.phase === 'error');
|
|
824
|
-
const isBootstrapping = subscriptions.some((subscription) => !subscription.ready && subscription.phase !== 'error');
|
|
825
|
-
const channelPhase = hasError
|
|
826
|
-
? 'error'
|
|
827
|
-
: isBootstrapping
|
|
828
|
-
? 'bootstrapping'
|
|
829
|
-
: complete && expectedSubscriptionIds.length > 0
|
|
830
|
-
? 'live'
|
|
831
|
-
: 'idle';
|
|
832
|
-
const progressPercent = subscriptions.length === 0
|
|
833
|
-
? 100
|
|
834
|
-
: Math.round(subscriptions.reduce((sum, subscription) => sum + subscription.progressPercent, 0) / subscriptions.length);
|
|
835
|
-
const phaseMap = new Map();
|
|
836
|
-
for (const subscription of subscriptions) {
|
|
837
|
-
const phase = phaseMap.get(subscription.bootstrapPhase) ?? {
|
|
838
|
-
expectedSubscriptionIds: [],
|
|
839
|
-
readySubscriptionIds: [],
|
|
840
|
-
pendingSubscriptionIds: [],
|
|
841
|
-
progressPercent: 0,
|
|
842
|
-
};
|
|
843
|
-
phase.expectedSubscriptionIds.push(subscription.id);
|
|
844
|
-
phase.progressPercent += subscription.progressPercent;
|
|
845
|
-
if (subscription.ready) {
|
|
846
|
-
phase.readySubscriptionIds.push(subscription.id);
|
|
847
|
-
}
|
|
848
|
-
else {
|
|
849
|
-
phase.pendingSubscriptionIds.push(subscription.id);
|
|
850
|
-
}
|
|
851
|
-
phaseMap.set(subscription.bootstrapPhase, phase);
|
|
852
|
-
}
|
|
853
|
-
const phases = [...phaseMap.entries()]
|
|
854
|
-
.sort(([left], [right]) => left - right)
|
|
855
|
-
.map(([phase, summary]) => ({
|
|
856
|
-
phase,
|
|
857
|
-
expectedSubscriptionIds: summary.expectedSubscriptionIds,
|
|
858
|
-
readySubscriptionIds: summary.readySubscriptionIds,
|
|
859
|
-
pendingSubscriptionIds: summary.pendingSubscriptionIds,
|
|
860
|
-
isReady: summary.pendingSubscriptionIds.length === 0,
|
|
861
|
-
progressPercent: summary.expectedSubscriptionIds.length === 0
|
|
862
|
-
? 100
|
|
863
|
-
: Math.round(summary.progressPercent / summary.expectedSubscriptionIds.length),
|
|
864
|
-
}));
|
|
865
|
-
return {
|
|
866
|
-
channelPhase,
|
|
867
|
-
progressPercent,
|
|
868
|
-
isBootstrapping,
|
|
869
|
-
criticalReady,
|
|
870
|
-
interactiveReady,
|
|
871
|
-
complete,
|
|
872
|
-
activePhase,
|
|
873
|
-
expectedSubscriptionIds,
|
|
874
|
-
readySubscriptionIds,
|
|
875
|
-
pendingSubscriptionIds,
|
|
876
|
-
subscriptions,
|
|
877
|
-
phases,
|
|
878
|
-
};
|
|
879
|
-
}
|
|
880
|
-
function normalizeBootstrapPhase(value) {
|
|
881
|
-
return Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
|
|
882
|
-
}
|
|
883
|
-
function parseConflictSummaries(value) {
|
|
884
|
-
return parseJson(value).map((conflict) => ({
|
|
885
|
-
id: conflict.id,
|
|
886
|
-
clientCommitId: conflict.client_commit_id,
|
|
887
|
-
opIndex: conflict.op_index,
|
|
888
|
-
resultStatus: conflict.result_status,
|
|
889
|
-
message: conflict.message,
|
|
890
|
-
code: conflict.code,
|
|
891
|
-
serverVersion: conflict.server_version,
|
|
892
|
-
resolvedAt: conflict.resolved_at,
|
|
893
|
-
resolution: conflict.resolution,
|
|
894
|
-
}));
|
|
895
|
-
}
|
|
896
|
-
function parseJson(value) {
|
|
897
|
-
return JSON.parse(value);
|
|
898
|
-
}
|
|
899
|
-
function stringifyParams(params) {
|
|
900
|
-
return JSON.stringify(params, (_key, value) => {
|
|
901
|
-
if (typeof value === 'bigint')
|
|
902
|
-
return Number(value);
|
|
903
|
-
if (value instanceof Uint8Array)
|
|
904
|
-
return Array.from(value);
|
|
905
|
-
return value;
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
function normalizeFieldEncryptionConfig(config) {
|
|
909
|
-
const keys = {};
|
|
910
|
-
for (const [kid, value] of Object.entries(config.keys)) {
|
|
911
|
-
keys[kid] = value instanceof Uint8Array ? bytesToBase64Url(value) : value;
|
|
912
|
-
}
|
|
913
|
-
return { ...config, keys };
|
|
914
|
-
}
|
|
915
|
-
function normalizeEncryptedCrdtConfig(config) {
|
|
916
|
-
const keys = {};
|
|
917
|
-
for (const [kid, value] of Object.entries(config.keys)) {
|
|
918
|
-
keys[kid] = value instanceof Uint8Array ? bytesToBase64Url(value) : value;
|
|
919
|
-
}
|
|
920
|
-
return { ...config, keys };
|
|
921
|
-
}
|
|
922
|
-
function normalizeBlobEncryptionConfig(config) {
|
|
923
|
-
const keys = {};
|
|
924
|
-
for (const [kid, value] of Object.entries(config.keys)) {
|
|
925
|
-
keys[kid] = value instanceof Uint8Array ? bytesToBase64Url(value) : value;
|
|
926
|
-
}
|
|
927
|
-
return { ...config, keys };
|
|
928
|
-
}
|
|
929
|
-
function bytesToBase64Url(bytes) {
|
|
930
|
-
let binary = '';
|
|
931
|
-
for (const byte of bytes)
|
|
932
|
-
binary += String.fromCharCode(byte);
|
|
933
|
-
return btoa(binary)
|
|
934
|
-
.replace(/\+/g, '-')
|
|
935
|
-
.replace(/\//g, '_')
|
|
936
|
-
.replace(/=+$/g, '');
|
|
937
|
-
}
|
|
938
|
-
//# sourceMappingURL=rust-client.js.map
|