@evolu/sqlite-wasm 2.2.4 → 3.53.4-build1

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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -218
  3. package/dist/src/CApi.d.ts +2108 -0
  4. package/dist/src/CApi.d.ts.map +1 -0
  5. package/dist/src/CApi.js +1919 -0
  6. package/dist/src/Constants.d.ts +868 -0
  7. package/dist/src/Constants.d.ts.map +1 -0
  8. package/dist/src/Constants.js +602 -0
  9. package/dist/src/Database.d.ts +641 -0
  10. package/dist/src/Database.d.ts.map +1 -0
  11. package/dist/src/Database.js +1177 -0
  12. package/dist/src/Memory.d.ts +119 -0
  13. package/dist/src/Memory.d.ts.map +1 -0
  14. package/dist/src/Memory.js +207 -0
  15. package/dist/src/Pointer.d.ts +100 -0
  16. package/dist/src/Pointer.d.ts.map +1 -0
  17. package/dist/src/Pointer.js +15 -0
  18. package/dist/src/SahPool.d.ts +744 -0
  19. package/dist/src/SahPool.d.ts.map +1 -0
  20. package/dist/src/SahPool.js +1985 -0
  21. package/dist/src/Wasm.d.ts +315 -0
  22. package/dist/src/Wasm.d.ts.map +1 -0
  23. package/dist/src/Wasm.js +756 -0
  24. package/dist/src/WasmUrl.d.ts +21 -0
  25. package/dist/src/WasmUrl.d.ts.map +1 -0
  26. package/dist/src/WasmUrl.js +20 -0
  27. package/dist/src/c-api/index.d.ts +9 -0
  28. package/dist/src/c-api/index.d.ts.map +1 -0
  29. package/dist/src/c-api/index.js +8 -0
  30. package/dist/src/index.d.ts +15 -0
  31. package/dist/src/index.d.ts.map +1 -0
  32. package/dist/src/index.js +13 -0
  33. package/dist/wasm/sqlite3.wasm +0 -0
  34. package/package.json +60 -60
  35. package/src/CApi.test.ts +217 -0
  36. package/src/CApi.ts +3294 -0
  37. package/src/Constants.ts +803 -0
  38. package/src/Database.ts +1714 -0
  39. package/src/Memory.test.ts +319 -0
  40. package/src/Memory.ts +237 -0
  41. package/src/Pointer.ts +121 -0
  42. package/src/SahPool.test.ts +180 -0
  43. package/src/SahPool.ts +2627 -0
  44. package/src/Wasm.test.ts +444 -0
  45. package/src/Wasm.ts +1026 -0
  46. package/src/WasmUrl.ts +26 -0
  47. package/src/c-api/index.ts +9 -0
  48. package/src/index.ts +15 -0
  49. package/bin/index.js +0 -110
  50. package/index.d.ts +0 -8118
  51. package/index.mjs +0 -7
  52. package/node.mjs +0 -3
  53. package/sqlite-wasm/jswasm/sqlite3-bundler-friendly.mjs +0 -13659
  54. package/sqlite-wasm/jswasm/sqlite3-node.mjs +0 -11671
  55. package/sqlite-wasm/jswasm/sqlite3-opfs-async-proxy.js +0 -691
  56. package/sqlite-wasm/jswasm/sqlite3-worker1-bundler-friendly.mjs +0 -35
  57. package/sqlite-wasm/jswasm/sqlite3-worker1-promiser.js +0 -193
  58. package/sqlite-wasm/jswasm/sqlite3-worker1-promiser.mjs +0 -187
  59. package/sqlite-wasm/jswasm/sqlite3-worker1.js +0 -46
  60. package/sqlite-wasm/jswasm/sqlite3.js +0 -13697
  61. package/sqlite-wasm/jswasm/sqlite3.mjs +0 -13661
  62. package/sqlite-wasm/jswasm/sqlite3.wasm +0 -0
@@ -0,0 +1,444 @@
1
+ import { test } from "node:test";
2
+
3
+ import {
4
+ assertEqual,
5
+ assertErr,
6
+ assertFalse,
7
+ assertInstanceOf,
8
+ assertOk,
9
+ assertSame,
10
+ testCreateDeps,
11
+ trySync,
12
+ } from "@evolu/common";
13
+ import type { SqliteCExports } from "./CApi.ts";
14
+ import {
15
+ SQLITE_BUSY,
16
+ SQLITE_ERROR,
17
+ SQLITE_MISUSE,
18
+ SQLITE_NOMEM,
19
+ SQLITE_OK,
20
+ } from "./Constants.ts";
21
+ import type { SqliteFunctionPtr } from "./Pointer.ts";
22
+ import {
23
+ initializeSqliteWasm,
24
+ installWasmFunctions,
25
+ type SqliteWasm,
26
+ type SqliteWasmFunction,
27
+ } from "./Wasm.ts";
28
+
29
+ /**
30
+ * Fake dependencies whose instance has only a real function table, with its
31
+ * NULL slot, so installed functions are called through it from JavaScript, and
32
+ * a broken flag the test sets.
33
+ */
34
+ const setupFakeSqliteWasm = () => {
35
+ const functionTable = new WebAssembly.Table({
36
+ initial: 1,
37
+ element: "anyfunc",
38
+ });
39
+ let broken = false;
40
+ const callFunction = (pointer: SqliteFunctionPtr) =>
41
+ functionTable.get(pointer) as (...args: ReadonlyArray<unknown>) => unknown;
42
+ return {
43
+ ...testCreateDeps(),
44
+ sqliteWasm: { functionTable, isBroken: () => broken } as SqliteWasm,
45
+ functionTable,
46
+ callFunction,
47
+ breakInstance: () => {
48
+ broken = true;
49
+ },
50
+ };
51
+ };
52
+
53
+ test("installWasmFunctions installs functions into the table, returning their pointers in order", () => {
54
+ const deps = setupFakeSqliteWasm();
55
+ const pushed: Array<number> = [];
56
+
57
+ const { pointers } = installWasmFunctions(deps)([
58
+ { signature: "i(ii)", fn: (a: number, b: number) => a + b },
59
+ {
60
+ signature: "v(i)",
61
+ fn: (value: number) => {
62
+ pushed.push(value);
63
+ },
64
+ },
65
+ ]);
66
+
67
+ assertEqual(deps.callFunction(pointers[0])(2, 3), 5);
68
+ assertEqual(deps.callFunction(pointers[1])(7), undefined);
69
+ assertEqual(pushed, [7]);
70
+ });
71
+
72
+ test("installWasmFunctions installs more functions than one byte counts in one module", () => {
73
+ const deps = setupFakeSqliteWasm();
74
+ const functions = Array.from({ length: 300 }, (_, index) => ({
75
+ signature: "i()",
76
+ fn: () => index,
77
+ }));
78
+
79
+ const { pointers } = installWasmFunctions(deps)([
80
+ { signature: "i()", fn: () => -1 },
81
+ ...functions,
82
+ ]);
83
+
84
+ assertEqual(
85
+ pointers.map((pointer) => deps.callFunction(pointer)()),
86
+ [-1, ...functions.map((_, index) => index)],
87
+ );
88
+ });
89
+
90
+ test("installWasmFunctions passes i, p and s as i32, j as a bigint and d as a double", () => {
91
+ const deps = setupFakeSqliteWasm();
92
+ const received: Array<ReadonlyArray<unknown>> = [];
93
+
94
+ const { pointers } = installWasmFunctions(deps)([
95
+ {
96
+ signature: "v(ipsjd)",
97
+ fn: (...args: ReadonlyArray<unknown>) => {
98
+ received.push(args);
99
+ },
100
+ },
101
+ ]);
102
+ // An f32 would round 0.1 to 0.10000000149011612.
103
+ deps.callFunction(pointers[0])(2 ** 32 + 1, -1, 3.7, 2n ** 64n + 5n, 0.1);
104
+
105
+ assertEqual(received, [[1, -1, 3, 5n, 0.1]]);
106
+ });
107
+
108
+ test("installWasmFunctions reports what a function throws as a defect and returns SQLITE_ERROR, or nothing without a result", () => {
109
+ const deps = setupFakeSqliteWasm();
110
+ const error = new Error("from a callback");
111
+ const thrown = "a string";
112
+
113
+ const { pointers } = installWasmFunctions(deps)([
114
+ {
115
+ signature: "i(i)",
116
+ fn: () => {
117
+ throw error;
118
+ },
119
+ },
120
+ {
121
+ signature: "v()",
122
+ fn: () => {
123
+ // oxlint-disable-next-line typescript/only-throw-error -- Anything can be thrown.
124
+ throw thrown;
125
+ },
126
+ },
127
+ ]);
128
+
129
+ assertEqual(deps.callFunction(pointers[0])(1), SQLITE_ERROR);
130
+ assertEqual(deps.callFunction(pointers[1])(), undefined);
131
+ const defects = deps.reportDefect.getDefects();
132
+ assertEqual(defects.length, 2);
133
+ assertSame(defects[0], error);
134
+ assertSame(defects[1], thrown);
135
+ });
136
+
137
+ test("installWasmFunctions reports an int result that is not a number as a defect and returns SQLITE_ERROR", () => {
138
+ const deps = setupFakeSqliteWasm();
139
+ // A function typed to return nothing may return anything, and converting a
140
+ // bigint to i32 at the wasm boundary would throw into wasm.
141
+ // oxlint-disable-next-line typescript/strict-void-return -- The case under test.
142
+ const fn: () => void = () => 1n;
143
+
144
+ const { pointers } = installWasmFunctions(deps)([{ signature: "i()", fn }]);
145
+
146
+ assertEqual(deps.callFunction(pointers[0])(), SQLITE_ERROR);
147
+ const defects = deps.reportDefect.getDefects();
148
+ assertEqual(defects.length, 1);
149
+ assertInstanceOf(defects[0], TypeError);
150
+ });
151
+
152
+ test("installWasmFunctions reports a ReportDefect failure from a microtask and still returns SQLITE_ERROR", (t) => {
153
+ // reportDefectAfterMicrotask throws from a microtask, which would fail the
154
+ // test file, so the microtask is captured and run here.
155
+ const microtasks: Array<VoidFunction> = [];
156
+ t.mock.method(globalThis, "queueMicrotask", (callback: VoidFunction) => {
157
+ microtasks.push(callback);
158
+ });
159
+ const error = new Error("from a callback");
160
+ const reporterError = new Error("from ReportDefect");
161
+ const deps = {
162
+ ...setupFakeSqliteWasm(),
163
+ reportDefect: () => {
164
+ throw reporterError;
165
+ },
166
+ };
167
+
168
+ const { pointers } = installWasmFunctions(deps)([
169
+ {
170
+ signature: "i()",
171
+ fn: () => {
172
+ throw error;
173
+ },
174
+ },
175
+ ]);
176
+
177
+ assertEqual(deps.callFunction(pointers[0])(), SQLITE_ERROR);
178
+ assertEqual(microtasks.length, 1);
179
+ const uncaught = trySync(microtasks[0], (thrown) => thrown);
180
+ assertErr(uncaught);
181
+ assertInstanceOf(uncaught.error, AggregateError);
182
+ assertEqual(
183
+ uncaught.error.message,
184
+ "ReportDefect failed while reporting a defect",
185
+ );
186
+ assertSame(uncaught.error.errors[0], error);
187
+ assertSame(uncaught.error.errors[1], reporterError);
188
+ });
189
+
190
+ test("installWasmFunctions lets a WebAssembly.RuntimeError through, because the instance is broken", () => {
191
+ const deps = setupFakeSqliteWasm();
192
+ // A trap in wasm called from the function has unwound C frames without
193
+ // cleanup, so returning to C would run on corrupt state.
194
+ const trap = new WebAssembly.RuntimeError("unreachable");
195
+
196
+ const { pointers } = installWasmFunctions(deps)([
197
+ {
198
+ signature: "i()",
199
+ fn: () => {
200
+ throw trap;
201
+ },
202
+ },
203
+ ]);
204
+ const result = trySync(
205
+ () => deps.callFunction(pointers[0])(),
206
+ (error) => error,
207
+ );
208
+
209
+ assertErr(result);
210
+ assertSame(result.error, trap);
211
+ assertEqual(deps.reportDefect.getDefects(), []);
212
+ });
213
+
214
+ test("installWasmFunctions lets anything through once the instance is broken, such as a stack overflow from a nested call that broke it", () => {
215
+ const deps = setupFakeSqliteWasm();
216
+ // A nested call through SqliteWasm.call broke the instance and rethrew.
217
+ const overflow = new RangeError("Maximum call stack size exceeded");
218
+
219
+ const { pointers } = installWasmFunctions(deps)([
220
+ {
221
+ signature: "i()",
222
+ fn: () => {
223
+ deps.breakInstance();
224
+ throw overflow;
225
+ },
226
+ },
227
+ ]);
228
+ const result = trySync(
229
+ () => deps.callFunction(pointers[0])(),
230
+ (error) => error,
231
+ );
232
+
233
+ assertErr(result);
234
+ assertSame(result.error, overflow);
235
+ assertEqual(deps.reportDefect.getDefects(), []);
236
+ });
237
+
238
+ test("disposing installed functions empties their slots, which later installs reuse", () => {
239
+ const deps = setupFakeSqliteWasm();
240
+ const install = installWasmFunctions(deps);
241
+ const first = install([
242
+ { signature: "i()", fn: () => 1 },
243
+ { signature: "i()", fn: () => 2 },
244
+ ]);
245
+ const tableLength = deps.functionTable.length;
246
+
247
+ first[Symbol.dispose]();
248
+
249
+ for (const pointer of first.pointers)
250
+ assertEqual(deps.functionTable.get(pointer), null);
251
+ const second = install([
252
+ { signature: "i()", fn: () => 3 },
253
+ { signature: "i()", fn: () => 4 },
254
+ ]);
255
+ assertEqual(deps.functionTable.length, tableLength);
256
+ assertEqual(
257
+ [...second.pointers].toSorted((a, b) => a - b),
258
+ [...first.pointers].toSorted((a, b) => a - b),
259
+ );
260
+ assertEqual(
261
+ second.pointers.map((pointer) => deps.callFunction(pointer)()),
262
+ [3, 4],
263
+ );
264
+ });
265
+
266
+ test("disposing installed functions twice empties their slots once", () => {
267
+ const deps = setupFakeSqliteWasm();
268
+ const install = installWasmFunctions(deps);
269
+ const installed = install([{ signature: "i()", fn: () => 1 }]);
270
+
271
+ installed[Symbol.dispose]();
272
+ installed[Symbol.dispose]();
273
+
274
+ const { pointers } = install([
275
+ { signature: "i()", fn: () => 2 },
276
+ { signature: "i()", fn: () => 3 },
277
+ ]);
278
+ assertEqual(new Set(pointers).size, 2);
279
+ assertEqual(
280
+ pointers.map((pointer) => deps.callFunction(pointer)()),
281
+ [2, 3],
282
+ );
283
+ });
284
+
285
+ test("each instance reuses only its own emptied slots", () => {
286
+ const first = setupFakeSqliteWasm();
287
+ const second = setupFakeSqliteWasm();
288
+ const fn = { signature: "i()", fn: () => 1 } as const;
289
+ const installed = installWasmFunctions(first)([fn, fn, fn]);
290
+ installed[Symbol.dispose]();
291
+
292
+ const other = installWasmFunctions(second)([fn]);
293
+
294
+ assertEqual(other.pointers, [1]);
295
+ assertEqual(second.callFunction(other.pointers[0])(), 1);
296
+ const reused = installWasmFunctions(first)([fn, fn, fn]);
297
+ assertEqual(
298
+ [...reused.pointers].toSorted((a, b) => a - b),
299
+ [...installed.pointers].toSorted((a, b) => a - b),
300
+ );
301
+ });
302
+
303
+ test("a SqliteWasmFunction returns an int or nothing", () => {
304
+ const functions: Array<SqliteWasmFunction> = [];
305
+
306
+ functions.push({ signature: "i()", fn: () => 1 });
307
+ functions.push({ signature: "v(j)", fn: (_value: bigint) => {} });
308
+ // @ts-expect-error The signature's result is i or v, so never a bigint.
309
+ functions.push({ signature: "i()", fn: () => 1n });
310
+ });
311
+
312
+ test("installWasmFunctions throws for a signature it does not support, installing nothing", () => {
313
+ const deps = setupFakeSqliteWasm();
314
+
315
+ for (const signature of [
316
+ "x()",
317
+ "i(x)",
318
+ "j()",
319
+ "d(i)",
320
+ "p(i)",
321
+ "i(i",
322
+ "i()v",
323
+ ]) {
324
+ const result = trySync(
325
+ () =>
326
+ installWasmFunctions(deps)([
327
+ { signature: "i()", fn: () => 0 },
328
+ { signature, fn: () => 0 },
329
+ ]),
330
+ (error) => error,
331
+ );
332
+
333
+ assertErr(result);
334
+ assertInstanceOf(result.error, Error);
335
+ assertEqual(
336
+ result.error.message,
337
+ `Invalid wasm function signature: ${signature}`,
338
+ );
339
+ }
340
+ assertEqual(deps.functionTable.length, 1);
341
+ });
342
+
343
+ /**
344
+ * Fake dependencies whose library records the calls {@link initializeSqliteWasm}
345
+ * makes and returns the given results, with a heap for the names it allocates.
346
+ */
347
+ const setupFakeLibrary = ({
348
+ initialize = SQLITE_OK,
349
+ malloc = 64,
350
+ find = 1024,
351
+ findKvvfs = 2048,
352
+ register = SQLITE_OK,
353
+ }: {
354
+ initialize?: number;
355
+ malloc?: number;
356
+ find?: number;
357
+ findKvvfs?: number;
358
+ register?: number;
359
+ } = {}) => {
360
+ const heap = new Uint8Array(4096);
361
+ const calls: Array<ReadonlyArray<unknown>> = [];
362
+ const exports = {
363
+ sqlite3_initialize: () => {
364
+ calls.push(["sqlite3_initialize"]);
365
+ return initialize;
366
+ },
367
+ sqlite3_malloc: (byteLength: number) => {
368
+ calls.push(["sqlite3_malloc", byteLength]);
369
+ return malloc;
370
+ },
371
+ sqlite3_vfs_find: (zVfsName: number) => {
372
+ const name = new TextDecoder().decode(
373
+ heap.subarray(zVfsName, heap.indexOf(0, zVfsName)),
374
+ );
375
+ calls.push(["sqlite3_vfs_find", name]);
376
+ return name === "kvvfs" ? findKvvfs : find;
377
+ },
378
+ sqlite3_vfs_unregister: (vfs: number) => {
379
+ calls.push(["sqlite3_vfs_unregister", vfs]);
380
+ return SQLITE_OK;
381
+ },
382
+ sqlite3_free: (ptr: number) => {
383
+ calls.push(["sqlite3_free", ptr]);
384
+ },
385
+ sqlite3_vfs_register: (vfs: number, makeDflt: number) => {
386
+ calls.push(["sqlite3_vfs_register", vfs, makeDflt]);
387
+ return register;
388
+ },
389
+ sqlite3_randomness: (byteLength: number, ptr: number) => {
390
+ calls.push(["sqlite3_randomness", byteLength, ptr]);
391
+ },
392
+ };
393
+ return {
394
+ sqliteWasm: {
395
+ exports: exports as unknown as SqliteCExports,
396
+ getHeapU8: () => heap,
397
+ call: (fn) => fn(),
398
+ } as SqliteWasm,
399
+ calls,
400
+ };
401
+ };
402
+
403
+ test("initializeSqliteWasm initializes the library, unregisters kvvfs, makes evolu-memory the default VFS and resets the random number generator", () => {
404
+ const { calls, ...deps } = setupFakeLibrary();
405
+
406
+ assertOk(initializeSqliteWasm(deps)());
407
+
408
+ assertEqual(calls, [
409
+ ["sqlite3_initialize"],
410
+ ["sqlite3_malloc", 19],
411
+ ["sqlite3_vfs_find", "evolu-memory"],
412
+ ["sqlite3_vfs_find", "kvvfs"],
413
+ ["sqlite3_free", 64],
414
+ ["sqlite3_vfs_unregister", 2048],
415
+ ["sqlite3_vfs_register", 1024, 1],
416
+ ["sqlite3_randomness", 0, 0],
417
+ ]);
418
+ });
419
+
420
+ test("initializeSqliteWasm unregisters nothing when kvvfs is not registered, as after an earlier call without sqlite3_shutdown", () => {
421
+ const { calls, ...deps } = setupFakeLibrary({ findKvvfs: 0 });
422
+
423
+ assertOk(initializeSqliteWasm(deps)());
424
+
425
+ assertFalse(calls.some(([name]) => name === "sqlite3_vfs_unregister"));
426
+ });
427
+
428
+ test("initializeSqliteWasm fails with SqliteWasmInitializeError at the first step that fails", () => {
429
+ for (const [options, code, lastCall] of [
430
+ [{ initialize: SQLITE_ERROR }, SQLITE_ERROR, "sqlite3_initialize"],
431
+ [{ malloc: 0 }, SQLITE_NOMEM, "sqlite3_malloc"],
432
+ // Only sqlite3_vfs_unregister removes evolu-memory.
433
+ [{ find: 0 }, SQLITE_MISUSE, "sqlite3_free"],
434
+ [{ register: SQLITE_BUSY }, SQLITE_BUSY, "sqlite3_vfs_register"],
435
+ ] as const) {
436
+ const { calls, ...deps } = setupFakeLibrary(options);
437
+
438
+ assertErr(initializeSqliteWasm(deps)(), {
439
+ type: "SqliteWasmInitializeError",
440
+ code,
441
+ });
442
+ assertEqual(calls.at(-1)?.[0], lastCall);
443
+ }
444
+ });