@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,279 @@
|
|
|
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 {
|
|
24
|
+
assertImageAlias,
|
|
25
|
+
type ClientDatabase,
|
|
26
|
+
runTransaction,
|
|
27
|
+
type SqlRow,
|
|
28
|
+
type SqlValue,
|
|
29
|
+
} from './database';
|
|
30
|
+
import { ClientSyncError } from './errors';
|
|
31
|
+
|
|
32
|
+
/** Structural view of the sqlite3 oo1 surface this binding uses. */
|
|
33
|
+
interface Oo1Database {
|
|
34
|
+
/** Native db handle, used by `sqlite3_deserialize` (§5.3 image import). */
|
|
35
|
+
readonly pointer?: number;
|
|
36
|
+
exec(options: {
|
|
37
|
+
sql: string;
|
|
38
|
+
bind?: readonly unknown[];
|
|
39
|
+
rowMode: 'object';
|
|
40
|
+
resultRows: unknown[];
|
|
41
|
+
}): unknown;
|
|
42
|
+
close(): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface SahPoolUtil {
|
|
46
|
+
OpfsSAHPoolDb: new (filename: string) => Oo1Database;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface Sqlite3Static {
|
|
50
|
+
oo1: {
|
|
51
|
+
DB: new (filename: string, flags?: string) => Oo1Database;
|
|
52
|
+
};
|
|
53
|
+
capi: {
|
|
54
|
+
SQLITE_DESERIALIZE_FREEONCLOSE: number;
|
|
55
|
+
SQLITE_DESERIALIZE_READONLY: number;
|
|
56
|
+
sqlite3_deserialize(
|
|
57
|
+
db: number,
|
|
58
|
+
schema: string,
|
|
59
|
+
data: number,
|
|
60
|
+
dataLength: number,
|
|
61
|
+
bufferLength: number,
|
|
62
|
+
flags: number,
|
|
63
|
+
): number;
|
|
64
|
+
};
|
|
65
|
+
wasm: {
|
|
66
|
+
allocFromTypedArray(bytes: Uint8Array): number;
|
|
67
|
+
};
|
|
68
|
+
installOpfsSAHPoolVfs(options: {
|
|
69
|
+
name?: string;
|
|
70
|
+
directory?: string;
|
|
71
|
+
initialCapacity?: number;
|
|
72
|
+
}): Promise<SahPoolUtil>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function coerceParams(params: readonly SqlValue[]): unknown[] {
|
|
76
|
+
return params.map((value) => {
|
|
77
|
+
if (typeof value === 'boolean') return value ? 1 : 0;
|
|
78
|
+
return value;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class WasmClientDatabase implements ClientDatabase {
|
|
83
|
+
readonly #db: Oo1Database;
|
|
84
|
+
readonly #sqlite3: Sqlite3Static;
|
|
85
|
+
#tx = { depth: 0 };
|
|
86
|
+
|
|
87
|
+
constructor(db: Oo1Database, sqlite3: Sqlite3Static) {
|
|
88
|
+
this.#db = db;
|
|
89
|
+
this.#sqlite3 = sqlite3;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
exec(sql: string, params: readonly SqlValue[] = []): void {
|
|
93
|
+
this.#db.exec({
|
|
94
|
+
sql,
|
|
95
|
+
...(params.length > 0 ? { bind: coerceParams(params) } : {}),
|
|
96
|
+
rowMode: 'object',
|
|
97
|
+
resultRows: [],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
query(sql: string, params: readonly SqlValue[] = []): SqlRow[] {
|
|
102
|
+
const resultRows: unknown[] = [];
|
|
103
|
+
this.#db.exec({
|
|
104
|
+
sql,
|
|
105
|
+
...(params.length > 0 ? { bind: coerceParams(params) } : {}),
|
|
106
|
+
rowMode: 'object',
|
|
107
|
+
resultRows,
|
|
108
|
+
});
|
|
109
|
+
return resultRows as SqlRow[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
transaction<T>(fn: () => T): T {
|
|
113
|
+
return runTransaction(this.#tx, (sql) => this.exec(sql), fn);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* §5.3 image import at near-file-copy speed: attach an empty in-memory
|
|
118
|
+
* schema, then `sqlite3_deserialize` the image bytes into it (the
|
|
119
|
+
* documented sqlite-wasm import path — no OPFS round-trip, no SQL-level
|
|
120
|
+
* row shuttling before the single INSERT…SELECT the caller runs).
|
|
121
|
+
*/
|
|
122
|
+
withSqliteImage<T>(bytes: Uint8Array, alias: string, fn: () => T): T {
|
|
123
|
+
assertImageAlias(alias);
|
|
124
|
+
const pointer = this.#db.pointer;
|
|
125
|
+
if (pointer === undefined) {
|
|
126
|
+
throw new ClientSyncError(
|
|
127
|
+
'sync.invalid_request',
|
|
128
|
+
'sqlite-wasm database exposes no native handle for image import',
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const { capi, wasm } = this.#sqlite3;
|
|
132
|
+
this.exec(`ATTACH ':memory:' AS ${alias}`);
|
|
133
|
+
try {
|
|
134
|
+
const data = wasm.allocFromTypedArray(bytes);
|
|
135
|
+
const rc = capi.sqlite3_deserialize(
|
|
136
|
+
pointer,
|
|
137
|
+
alias,
|
|
138
|
+
data,
|
|
139
|
+
bytes.length,
|
|
140
|
+
bytes.length,
|
|
141
|
+
// FREEONCLOSE: sqlite owns the allocation; READONLY: images are
|
|
142
|
+
// immutable inputs (§5.1).
|
|
143
|
+
capi.SQLITE_DESERIALIZE_FREEONCLOSE | capi.SQLITE_DESERIALIZE_READONLY,
|
|
144
|
+
);
|
|
145
|
+
if (rc !== 0) {
|
|
146
|
+
throw new ClientSyncError(
|
|
147
|
+
'sync.invalid_request',
|
|
148
|
+
`sqlite3_deserialize failed with code ${rc} (§5.3)`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return fn();
|
|
152
|
+
} finally {
|
|
153
|
+
this.exec(`DETACH ${alias}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
close(): void {
|
|
158
|
+
this.#db.close();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let sqlite3Init: Promise<Sqlite3Static> | undefined;
|
|
163
|
+
|
|
164
|
+
function initSqlite3(): Promise<Sqlite3Static> {
|
|
165
|
+
if (sqlite3Init === undefined) {
|
|
166
|
+
const init = sqlite3InitModule as unknown as (config?: {
|
|
167
|
+
print?: (...args: unknown[]) => void;
|
|
168
|
+
printErr?: (...args: unknown[]) => void;
|
|
169
|
+
}) => Promise<unknown>;
|
|
170
|
+
sqlite3Init = init({
|
|
171
|
+
print: () => {},
|
|
172
|
+
printErr: () => {},
|
|
173
|
+
}) as Promise<Sqlite3Static>;
|
|
174
|
+
}
|
|
175
|
+
return sqlite3Init;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* EXPLICIT ephemeral mode: an in-memory sqlite-wasm database. For tests,
|
|
180
|
+
* demos and SSR only — nothing persists. The persistent mode is
|
|
181
|
+
* `openPersistentWasmDatabase` inside a worker; there is no fallback from
|
|
182
|
+
* one to the other (Direction decision 2).
|
|
183
|
+
*/
|
|
184
|
+
export async function openWasmDatabase(): Promise<ClientDatabase> {
|
|
185
|
+
const sqlite3 = await initSqlite3();
|
|
186
|
+
return new WasmClientDatabase(new sqlite3.oo1.DB(':memory:', 'c'), sqlite3);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface PersistentWasmDatabaseOptions {
|
|
190
|
+
/**
|
|
191
|
+
* OPFS directory holding this database's SAH pool (default
|
|
192
|
+
* `.syncular/<name>`). One pool per concurrent user: the sahpool VFS
|
|
193
|
+
* forbids two live instances on the same directory, so two cores on one
|
|
194
|
+
* origin (e.g. the demo's two panes) need distinct names/directories.
|
|
195
|
+
*/
|
|
196
|
+
readonly directory?: string;
|
|
197
|
+
/** Initial SAH pool capacity (sqlite-wasm default: 6). */
|
|
198
|
+
readonly initialCapacity?: number;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function inWorkerContext(): boolean {
|
|
202
|
+
const scope = globalThis as { WorkerGlobalScope?: unknown };
|
|
203
|
+
return (
|
|
204
|
+
typeof scope.WorkerGlobalScope === 'function' &&
|
|
205
|
+
globalThis instanceof (scope.WorkerGlobalScope as new () => object)
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** One registered VFS per pool directory, reused across opens. */
|
|
210
|
+
const sahPools = new Map<string, Promise<SahPoolUtil>>();
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* THE persistent browser mode: a named database on OPFS via the
|
|
214
|
+
* `opfs-sahpool` VFS. Worker-context only — not because SAHPool requires
|
|
215
|
+
* it (it uses `FileSystemSyncAccessHandle`, no `Atomics.wait`, and could
|
|
216
|
+
* technically run on the main thread), but because the persistent mode IS
|
|
217
|
+
* whole-core-in-a-worker (REVISE Direction decision 2, 2026-07-03) and
|
|
218
|
+
* this factory enforces that decision. No COOP/COEP headers required.
|
|
219
|
+
*
|
|
220
|
+
* Support floor: no OPFS → a loud `ClientSyncError`, never a fallback.
|
|
221
|
+
*/
|
|
222
|
+
export async function openPersistentWasmDatabase(
|
|
223
|
+
name: string,
|
|
224
|
+
options?: PersistentWasmDatabaseOptions,
|
|
225
|
+
): Promise<ClientDatabase> {
|
|
226
|
+
if (!/^[A-Za-z0-9._-]+$/.test(name)) {
|
|
227
|
+
throw new ClientSyncError(
|
|
228
|
+
'sync.invalid_request',
|
|
229
|
+
`invalid persistent database name ${JSON.stringify(name)}`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
if (!inWorkerContext()) {
|
|
233
|
+
throw new ClientSyncError(
|
|
234
|
+
'sync.invalid_request',
|
|
235
|
+
'openPersistentWasmDatabase() must run inside a Web Worker: the ' +
|
|
236
|
+
'persistent mode is whole-core-in-a-worker by design (Direction ' +
|
|
237
|
+
'decision 2, 2026-07-03). Use the worker handle from the main ' +
|
|
238
|
+
'thread, or openWasmDatabase() for explicitly ephemeral state.',
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const storage = (
|
|
242
|
+
globalThis as { navigator?: { storage?: { getDirectory?: unknown } } }
|
|
243
|
+
).navigator?.storage;
|
|
244
|
+
if (typeof storage?.getDirectory !== 'function') {
|
|
245
|
+
throw new ClientSyncError(
|
|
246
|
+
'sync.invalid_request',
|
|
247
|
+
'OPFS is unavailable in this browser — the syncular support floor ' +
|
|
248
|
+
'requires OPFS (~2023+ browsers). There is no IndexedDB or ' +
|
|
249
|
+
'in-memory fallback for persistent mode.',
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
const sqlite3 = await initSqlite3();
|
|
253
|
+
const directory = options?.directory ?? `.syncular/${name}`;
|
|
254
|
+
let pool = sahPools.get(directory);
|
|
255
|
+
if (pool === undefined) {
|
|
256
|
+
pool = sqlite3
|
|
257
|
+
.installOpfsSAHPoolVfs({
|
|
258
|
+
// VFS registration names must be unique per directory.
|
|
259
|
+
name: `syncular-sahpool-${directory.replace(/[^A-Za-z0-9]+/g, '-')}`,
|
|
260
|
+
directory,
|
|
261
|
+
...(options?.initialCapacity !== undefined
|
|
262
|
+
? { initialCapacity: options.initialCapacity }
|
|
263
|
+
: {}),
|
|
264
|
+
})
|
|
265
|
+
.catch((error: unknown) => {
|
|
266
|
+
sahPools.delete(directory);
|
|
267
|
+
throw new ClientSyncError(
|
|
268
|
+
'sync.invalid_request',
|
|
269
|
+
'opfs-sahpool VFS failed to initialize (is OPFS available, and ' +
|
|
270
|
+
`is another live instance using directory ${JSON.stringify(
|
|
271
|
+
directory,
|
|
272
|
+
)}?): ${String(error)}`,
|
|
273
|
+
);
|
|
274
|
+
});
|
|
275
|
+
sahPools.set(directory, pool);
|
|
276
|
+
}
|
|
277
|
+
const util = await pool;
|
|
278
|
+
return new WasmClientDatabase(new util.OpfsSAHPoolDb(`/${name}.db`), sqlite3);
|
|
279
|
+
}
|
package/src/window.ts
ADDED
|
Binary file
|