@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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ClientDatabase } from './database.js';
|
|
2
|
+
/**
|
|
3
|
+
* EXPLICIT ephemeral mode: an in-memory sqlite-wasm database. For tests,
|
|
4
|
+
* demos and SSR only — nothing persists. The persistent mode is
|
|
5
|
+
* `openPersistentWasmDatabase` inside a worker; there is no fallback from
|
|
6
|
+
* one to the other (Direction decision 2).
|
|
7
|
+
*/
|
|
8
|
+
export declare function openWasmDatabase(): Promise<ClientDatabase>;
|
|
9
|
+
export interface PersistentWasmDatabaseOptions {
|
|
10
|
+
/**
|
|
11
|
+
* OPFS directory holding this database's SAH pool (default
|
|
12
|
+
* `.syncular/<name>`). One pool per concurrent user: the sahpool VFS
|
|
13
|
+
* forbids two live instances on the same directory, so two cores on one
|
|
14
|
+
* origin (e.g. the demo's two panes) need distinct names/directories.
|
|
15
|
+
*/
|
|
16
|
+
readonly directory?: string;
|
|
17
|
+
/** Initial SAH pool capacity (sqlite-wasm default: 6). */
|
|
18
|
+
readonly initialCapacity?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* THE persistent browser mode: a named database on OPFS via the
|
|
22
|
+
* `opfs-sahpool` VFS. Worker-context only — not because SAHPool requires
|
|
23
|
+
* it (it uses `FileSystemSyncAccessHandle`, no `Atomics.wait`, and could
|
|
24
|
+
* technically run on the main thread), but because the persistent mode IS
|
|
25
|
+
* whole-core-in-a-worker (REVISE Direction decision 2, 2026-07-03) and
|
|
26
|
+
* this factory enforces that decision. No COOP/COEP headers required.
|
|
27
|
+
*
|
|
28
|
+
* Support floor: no OPFS → a loud `ClientSyncError`, never a fallback.
|
|
29
|
+
*/
|
|
30
|
+
export declare function openPersistentWasmDatabase(name: string, options?: PersistentWasmDatabaseOptions): Promise<ClientDatabase>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ClientDatabase` on @sqlite.org/sqlite-wasm (Direction decision 2,
|
|
3
|
+
* 2026-07-03). Two modes, no ladder between them:
|
|
4
|
+
*
|
|
5
|
+
* - `openPersistentWasmDatabase(name)` — THE persistent browser mode:
|
|
6
|
+
* OPFS via the `opfs-sahpool` VFS, restricted to Web Worker contexts
|
|
7
|
+
* because the whole client core runs in a worker by design. SAHPool
|
|
8
|
+
* needs **no COOP/COEP headers and no SharedArrayBuffer** (it is built
|
|
9
|
+
* on `FileSystemSyncAccessHandle`, not the Atomics-based `opfs` VFS
|
|
10
|
+
* proxy — the COOP/COEP requirement documented by sqlite-wasm applies
|
|
11
|
+
* only to `oo1.OpfsDb`, which this binding no longer uses). Browsers
|
|
12
|
+
* without OPFS are unsupported (support floor ~2023+): the factory
|
|
13
|
+
* fails loud. Never IndexedDB, never a silent in-memory fallback.
|
|
14
|
+
* - `openWasmDatabase()` — EXPLICIT ephemeral: an in-memory database for
|
|
15
|
+
* tests, demos and SSR. Nothing survives a reload, on purpose.
|
|
16
|
+
*
|
|
17
|
+
* A thin binding over the sqlite3 `oo1` API: opening is async (wasm
|
|
18
|
+
* init), everything after is synchronous like every other
|
|
19
|
+
* `ClientDatabase`. Browser-only — exercised by the demo, never imported
|
|
20
|
+
* by bun tests (subpath export `@syncular/client/wasm`).
|
|
21
|
+
*/
|
|
22
|
+
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
|
|
23
|
+
import { assertImageAlias, runTransaction, } from './database.js';
|
|
24
|
+
import { ClientSyncError } from './errors.js';
|
|
25
|
+
function coerceParams(params) {
|
|
26
|
+
return params.map((value) => {
|
|
27
|
+
if (typeof value === 'boolean')
|
|
28
|
+
return value ? 1 : 0;
|
|
29
|
+
return value;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
class WasmClientDatabase {
|
|
33
|
+
#db;
|
|
34
|
+
#sqlite3;
|
|
35
|
+
#tx = { depth: 0 };
|
|
36
|
+
constructor(db, sqlite3) {
|
|
37
|
+
this.#db = db;
|
|
38
|
+
this.#sqlite3 = sqlite3;
|
|
39
|
+
}
|
|
40
|
+
exec(sql, params = []) {
|
|
41
|
+
this.#db.exec({
|
|
42
|
+
sql,
|
|
43
|
+
...(params.length > 0 ? { bind: coerceParams(params) } : {}),
|
|
44
|
+
rowMode: 'object',
|
|
45
|
+
resultRows: [],
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
query(sql, params = []) {
|
|
49
|
+
const resultRows = [];
|
|
50
|
+
this.#db.exec({
|
|
51
|
+
sql,
|
|
52
|
+
...(params.length > 0 ? { bind: coerceParams(params) } : {}),
|
|
53
|
+
rowMode: 'object',
|
|
54
|
+
resultRows,
|
|
55
|
+
});
|
|
56
|
+
return resultRows;
|
|
57
|
+
}
|
|
58
|
+
transaction(fn) {
|
|
59
|
+
return runTransaction(this.#tx, (sql) => this.exec(sql), fn);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* §5.3 image import at near-file-copy speed: attach an empty in-memory
|
|
63
|
+
* schema, then `sqlite3_deserialize` the image bytes into it (the
|
|
64
|
+
* documented sqlite-wasm import path — no OPFS round-trip, no SQL-level
|
|
65
|
+
* row shuttling before the single INSERT…SELECT the caller runs).
|
|
66
|
+
*/
|
|
67
|
+
withSqliteImage(bytes, alias, fn) {
|
|
68
|
+
assertImageAlias(alias);
|
|
69
|
+
const pointer = this.#db.pointer;
|
|
70
|
+
if (pointer === undefined) {
|
|
71
|
+
throw new ClientSyncError('sync.invalid_request', 'sqlite-wasm database exposes no native handle for image import');
|
|
72
|
+
}
|
|
73
|
+
const { capi, wasm } = this.#sqlite3;
|
|
74
|
+
this.exec(`ATTACH ':memory:' AS ${alias}`);
|
|
75
|
+
try {
|
|
76
|
+
const data = wasm.allocFromTypedArray(bytes);
|
|
77
|
+
const rc = capi.sqlite3_deserialize(pointer, alias, data, bytes.length, bytes.length,
|
|
78
|
+
// FREEONCLOSE: sqlite owns the allocation; READONLY: images are
|
|
79
|
+
// immutable inputs (§5.1).
|
|
80
|
+
capi.SQLITE_DESERIALIZE_FREEONCLOSE | capi.SQLITE_DESERIALIZE_READONLY);
|
|
81
|
+
if (rc !== 0) {
|
|
82
|
+
throw new ClientSyncError('sync.invalid_request', `sqlite3_deserialize failed with code ${rc} (§5.3)`);
|
|
83
|
+
}
|
|
84
|
+
return fn();
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
this.exec(`DETACH ${alias}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
close() {
|
|
91
|
+
this.#db.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
let sqlite3Init;
|
|
95
|
+
function initSqlite3() {
|
|
96
|
+
if (sqlite3Init === undefined) {
|
|
97
|
+
const init = sqlite3InitModule;
|
|
98
|
+
sqlite3Init = init({
|
|
99
|
+
print: () => { },
|
|
100
|
+
printErr: () => { },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return sqlite3Init;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* EXPLICIT ephemeral mode: an in-memory sqlite-wasm database. For tests,
|
|
107
|
+
* demos and SSR only — nothing persists. The persistent mode is
|
|
108
|
+
* `openPersistentWasmDatabase` inside a worker; there is no fallback from
|
|
109
|
+
* one to the other (Direction decision 2).
|
|
110
|
+
*/
|
|
111
|
+
export async function openWasmDatabase() {
|
|
112
|
+
const sqlite3 = await initSqlite3();
|
|
113
|
+
return new WasmClientDatabase(new sqlite3.oo1.DB(':memory:', 'c'), sqlite3);
|
|
114
|
+
}
|
|
115
|
+
function inWorkerContext() {
|
|
116
|
+
const scope = globalThis;
|
|
117
|
+
return (typeof scope.WorkerGlobalScope === 'function' &&
|
|
118
|
+
globalThis instanceof scope.WorkerGlobalScope);
|
|
119
|
+
}
|
|
120
|
+
/** One registered VFS per pool directory, reused across opens. */
|
|
121
|
+
const sahPools = new Map();
|
|
122
|
+
/**
|
|
123
|
+
* THE persistent browser mode: a named database on OPFS via the
|
|
124
|
+
* `opfs-sahpool` VFS. Worker-context only — not because SAHPool requires
|
|
125
|
+
* it (it uses `FileSystemSyncAccessHandle`, no `Atomics.wait`, and could
|
|
126
|
+
* technically run on the main thread), but because the persistent mode IS
|
|
127
|
+
* whole-core-in-a-worker (REVISE Direction decision 2, 2026-07-03) and
|
|
128
|
+
* this factory enforces that decision. No COOP/COEP headers required.
|
|
129
|
+
*
|
|
130
|
+
* Support floor: no OPFS → a loud `ClientSyncError`, never a fallback.
|
|
131
|
+
*/
|
|
132
|
+
export async function openPersistentWasmDatabase(name, options) {
|
|
133
|
+
if (!/^[A-Za-z0-9._-]+$/.test(name)) {
|
|
134
|
+
throw new ClientSyncError('sync.invalid_request', `invalid persistent database name ${JSON.stringify(name)}`);
|
|
135
|
+
}
|
|
136
|
+
if (!inWorkerContext()) {
|
|
137
|
+
throw new ClientSyncError('sync.invalid_request', 'openPersistentWasmDatabase() must run inside a Web Worker: the ' +
|
|
138
|
+
'persistent mode is whole-core-in-a-worker by design (Direction ' +
|
|
139
|
+
'decision 2, 2026-07-03). Use the worker handle from the main ' +
|
|
140
|
+
'thread, or openWasmDatabase() for explicitly ephemeral state.');
|
|
141
|
+
}
|
|
142
|
+
const storage = globalThis.navigator?.storage;
|
|
143
|
+
if (typeof storage?.getDirectory !== 'function') {
|
|
144
|
+
throw new ClientSyncError('sync.invalid_request', 'OPFS is unavailable in this browser — the syncular support floor ' +
|
|
145
|
+
'requires OPFS (~2023+ browsers). There is no IndexedDB or ' +
|
|
146
|
+
'in-memory fallback for persistent mode.');
|
|
147
|
+
}
|
|
148
|
+
const sqlite3 = await initSqlite3();
|
|
149
|
+
const directory = options?.directory ?? `.syncular/${name}`;
|
|
150
|
+
let pool = sahPools.get(directory);
|
|
151
|
+
if (pool === undefined) {
|
|
152
|
+
pool = sqlite3
|
|
153
|
+
.installOpfsSAHPoolVfs({
|
|
154
|
+
// VFS registration names must be unique per directory.
|
|
155
|
+
name: `syncular-sahpool-${directory.replace(/[^A-Za-z0-9]+/g, '-')}`,
|
|
156
|
+
directory,
|
|
157
|
+
...(options?.initialCapacity !== undefined
|
|
158
|
+
? { initialCapacity: options.initialCapacity }
|
|
159
|
+
: {}),
|
|
160
|
+
})
|
|
161
|
+
.catch((error) => {
|
|
162
|
+
sahPools.delete(directory);
|
|
163
|
+
throw new ClientSyncError('sync.invalid_request', 'opfs-sahpool VFS failed to initialize (is OPFS available, and ' +
|
|
164
|
+
`is another live instance using directory ${JSON.stringify(directory)}?): ${String(error)}`);
|
|
165
|
+
});
|
|
166
|
+
sahPools.set(directory, pool);
|
|
167
|
+
}
|
|
168
|
+
const util = await pool;
|
|
169
|
+
return new WasmClientDatabase(new util.OpfsSAHPoolDb(`/${name}.db`), sqlite3);
|
|
170
|
+
}
|
package/dist/window.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windowed subscriptions (SPEC.md §4.8 / DESIGN-eviction.md W1).
|
|
3
|
+
*
|
|
4
|
+
* A window is a partial local replica keyed by scope VALUES: the client
|
|
5
|
+
* holds rows for a chosen set of units (one scope value each) of a window
|
|
6
|
+
* BASE (a table + one scope variable + the fixed remainder of the scope
|
|
7
|
+
* map). Windowing is a set-difference on §4 subscriptions — never a
|
|
8
|
+
* mutation of one — so it needs zero wire or server changes.
|
|
9
|
+
*
|
|
10
|
+
* This module owns the registry bookkeeping and the deterministic
|
|
11
|
+
* per-unit subscription-id derivation (§4.1 guidance). The eviction that
|
|
12
|
+
* fuses with a shrink (E1–E4) lives on the client, which holds the DB
|
|
13
|
+
* transaction and the invalidation choke point.
|
|
14
|
+
*/
|
|
15
|
+
import { type ScopeMap } from '@syncular/core';
|
|
16
|
+
import type { ClientDatabase } from './database.js';
|
|
17
|
+
/**
|
|
18
|
+
* A window base: one table, one variable whose values are the window
|
|
19
|
+
* units, and any FIXED scopes every unit shares (other variables pinned
|
|
20
|
+
* to a constant set). `params` is host-opaque, carried onto every unit's
|
|
21
|
+
* subscription verbatim.
|
|
22
|
+
*/
|
|
23
|
+
export interface WindowBase {
|
|
24
|
+
readonly table: string;
|
|
25
|
+
/** The scope variable whose values are the window units. */
|
|
26
|
+
readonly variable: string;
|
|
27
|
+
/** Scopes shared by every unit (other variables), if any. */
|
|
28
|
+
readonly fixedScopes?: ScopeMap;
|
|
29
|
+
readonly params?: string;
|
|
30
|
+
}
|
|
31
|
+
/** A live unit in the registry: its value and the subscription it drives. */
|
|
32
|
+
export interface WindowUnit {
|
|
33
|
+
readonly unit: string;
|
|
34
|
+
readonly subId: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A stable, server-opaque key for a window base — table + variable +
|
|
38
|
+
* canonical fixed scopes. Two `setWindow` calls with the same base
|
|
39
|
+
* address the same registry rows.
|
|
40
|
+
*/
|
|
41
|
+
export declare function windowBaseKey(base: WindowBase): string;
|
|
42
|
+
/** The full requested scope map for one unit (fixed scopes + the unit). */
|
|
43
|
+
export declare function unitScopes(base: WindowBase, unit: string): ScopeMap;
|
|
44
|
+
/**
|
|
45
|
+
* Deterministic per-unit subscription id (§4.1 guidance):
|
|
46
|
+
* `w:<table>:<sha256(canonical scope map, §11.2)[0..16]>`. Ids are echoed
|
|
47
|
+
* not interpreted by the server (§4.1), so the exact hash is pure client
|
|
48
|
+
* convention; SHA-256 matches the SPEC's worked example.
|
|
49
|
+
*/
|
|
50
|
+
export declare function deriveSubId(base: WindowBase, unit: string): Promise<string>;
|
|
51
|
+
/** Live units for a base, ordered by unit value. */
|
|
52
|
+
export declare function loadWindowUnits(db: ClientDatabase, baseKey: string): WindowUnit[];
|
|
53
|
+
export declare function insertWindowUnit(db: ClientDatabase, baseKey: string, unit: string, subId: string): void;
|
|
54
|
+
export declare function deleteWindowUnit(db: ClientDatabase, baseKey: string, unit: string): void;
|
|
55
|
+
/** Is a single scope value windowed-in for this base? (the oracle, I3) */
|
|
56
|
+
export declare function isUnitLive(db: ClientDatabase, baseKey: string, unit: string): boolean;
|
|
57
|
+
export interface PendingEviction {
|
|
58
|
+
readonly subId: string;
|
|
59
|
+
readonly table: string;
|
|
60
|
+
readonly effective: ScopeMap;
|
|
61
|
+
}
|
|
62
|
+
export declare function savePendingEviction(db: ClientDatabase, subId: string, table: string, effective: ScopeMap): void;
|
|
63
|
+
export declare function deletePendingEviction(db: ClientDatabase, subId: string): void;
|
|
64
|
+
export declare function loadPendingEvictions(db: ClientDatabase): PendingEviction[];
|
package/dist/window.js
ADDED
|
Binary file
|
package/dist/worker-entry.d.ts
CHANGED
|
@@ -1,2 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { ClientDatabase } from './database.js';
|
|
2
|
+
import type { RealtimeConnector, SegmentDownloader, SyncTransport } from './transport.js';
|
|
3
|
+
import { type WorkerInitConfig } from './worker-protocol.js';
|
|
4
|
+
export interface SyncWorkerOverrides {
|
|
5
|
+
/** Database factory indirection: tests inject bun:sqlite here. */
|
|
6
|
+
readonly openDatabase?: (config: WorkerInitConfig) => Promise<ClientDatabase> | ClientDatabase;
|
|
7
|
+
readonly createTransport?: (config: WorkerInitConfig) => SyncTransport;
|
|
8
|
+
readonly createSegments?: (config: WorkerInitConfig) => SegmentDownloader | undefined;
|
|
9
|
+
readonly createRealtime?: (config: WorkerInitConfig, clientId: string) => RealtimeConnector | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Register the RPC server on the current worker scope. Call this once
|
|
13
|
+
* from a worker entry script; the main-thread handle drives everything
|
|
14
|
+
* else through `init`/`call` messages.
|
|
15
|
+
*/
|
|
16
|
+
export declare function startSyncWorker(overrides?: SyncWorkerOverrides): void;
|