@powersync/web 0.0.0-dev-20241119081147 → 0.0.0-dev-20250127153955

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 (67) hide show
  1. package/README.md +33 -0
  2. package/dist/3cb48be086dd9edd02ff.wasm +0 -0
  3. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js-_powersync_co-780aa20.index.umd.js +335 -0
  4. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js-_powersync_co-780aa20.index.umd.js.map +1 -0
  5. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js-_powersync_co-780aa21.index.umd.js +335 -0
  6. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js-_powersync_co-780aa21.index.umd.js.map +1 -0
  7. package/dist/df958358cadf945bd0fe.wasm +0 -0
  8. package/dist/f9c8ada26c59f5bf4339.wasm +0 -0
  9. package/dist/fe5693c7678cf12e05ac.wasm +0 -0
  10. package/dist/index.umd.js +3716 -626
  11. package/dist/index.umd.js.map +1 -1
  12. package/dist/worker/SharedSyncImplementation.umd.js +275 -2200
  13. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  14. package/dist/worker/WASQLiteDB.umd.js +956 -395
  15. package/dist/worker/WASQLiteDB.umd.js.map +1 -1
  16. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js +45 -0
  17. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js.map +1 -0
  18. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js +45 -0
  19. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js.map +1 -0
  20. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js +2 -132
  21. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js.map +1 -1
  22. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +45 -0
  23. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -0
  24. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js +1509 -0
  25. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js.map +1 -0
  26. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js +1746 -1372
  27. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js.map +1 -1
  28. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js +1641 -0
  29. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js.map +1 -0
  30. package/lib/package.json +6 -6
  31. package/lib/src/db/PowerSyncDatabase.d.ts +10 -2
  32. package/lib/src/db/PowerSyncDatabase.js +20 -4
  33. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.d.ts +2 -0
  34. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.js +3 -0
  35. package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +26 -0
  36. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +82 -0
  37. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +239 -0
  38. package/lib/src/db/adapters/WebDBAdapter.d.ts +17 -0
  39. package/lib/src/db/adapters/WebDBAdapter.js +1 -0
  40. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +39 -0
  41. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +46 -0
  42. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +127 -0
  43. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +343 -0
  44. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.d.ts +12 -43
  45. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +36 -209
  46. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +12 -0
  47. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +81 -4
  48. package/lib/src/db/adapters/web-sql-flags.d.ts +26 -0
  49. package/lib/src/db/adapters/web-sql-flags.js +5 -0
  50. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +9 -2
  51. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +17 -12
  52. package/lib/src/db/sync/WebStreamingSyncImplementation.d.ts +0 -5
  53. package/lib/src/index.d.ts +8 -7
  54. package/lib/src/index.js +8 -7
  55. package/lib/src/worker/db/WASQLiteDB.worker.js +38 -20
  56. package/lib/src/worker/db/open-worker-database.d.ts +5 -4
  57. package/lib/src/worker/db/open-worker-database.js +5 -3
  58. package/lib/src/worker/sync/AbstractSharedSyncClientProvider.d.ts +1 -0
  59. package/lib/src/worker/sync/SharedSyncImplementation.d.ts +20 -3
  60. package/lib/src/worker/sync/SharedSyncImplementation.js +40 -11
  61. package/lib/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +7 -7
  63. package/dist/5fe5ed837a91c836c24f.wasm +0 -0
  64. package/lib/src/shared/open-db.d.ts +0 -5
  65. package/lib/src/shared/open-db.js +0 -192
  66. package/lib/src/shared/types.d.ts +0 -22
  67. /package/lib/src/{shared/types.js → db/adapters/AsyncDatabaseConnection.js} +0 -0
@@ -1,6 +1,568 @@
1
1
  "use strict";
2
2
  (self["webpackChunksdk_web"] = self["webpackChunksdk_web"] || []).push([["node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js"],{
3
3
 
4
+ /***/ "../../node_modules/@journeyapps/wa-sqlite/src/FacadeVFS.js":
5
+ /*!******************************************************************!*\
6
+ !*** ../../node_modules/@journeyapps/wa-sqlite/src/FacadeVFS.js ***!
7
+ \******************************************************************/
8
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
9
+
10
+ __webpack_require__.r(__webpack_exports__);
11
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
12
+ /* harmony export */ FacadeVFS: () => (/* binding */ FacadeVFS)
13
+ /* harmony export */ });
14
+ /* harmony import */ var _VFS_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./VFS.js */ "../../node_modules/@journeyapps/wa-sqlite/src/VFS.js");
15
+ // Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
16
+
17
+
18
+ const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
19
+
20
+ // Milliseconds since Julian epoch as a BigInt.
21
+ // https://github.com/sqlite/sqlite/blob/e57527c14f7b7cfa6e32eeab5c549d50c4fa3674/src/os_unix.c#L6872-L6882
22
+ const UNIX_EPOCH = 24405875n * 8640000n;
23
+
24
+ // Convenience base class for a JavaScript VFS.
25
+ // The raw xOpen, xRead, etc. function signatures receive only C primitives
26
+ // which aren't easy to work with. This class provides corresponding calls
27
+ // like jOpen, jRead, etc., which receive JavaScript-friendlier arguments
28
+ // such as string, Uint8Array, and DataView.
29
+ class FacadeVFS extends _VFS_js__WEBPACK_IMPORTED_MODULE_0__.Base {
30
+ /**
31
+ * @param {string} name
32
+ * @param {object} module
33
+ */
34
+ constructor(name, module) {
35
+ super(name, module);
36
+ }
37
+
38
+ /**
39
+ * Override to indicate which methods are asynchronous.
40
+ * @param {string} methodName
41
+ * @returns {boolean}
42
+ */
43
+ hasAsyncMethod(methodName) {
44
+ // The input argument is a string like "xOpen", so convert to "jOpen".
45
+ // Then check if the method exists and is async.
46
+ const jMethodName = `j${methodName.slice(1)}`;
47
+ return this[jMethodName] instanceof AsyncFunction;
48
+ }
49
+
50
+ /**
51
+ * Return the filename for a file id for use by mixins.
52
+ * @param {number} pFile
53
+ * @returns {string}
54
+ */
55
+ getFilename(pFile) {
56
+ throw new Error('unimplemented');
57
+ }
58
+
59
+ /**
60
+ * @param {string?} filename
61
+ * @param {number} pFile
62
+ * @param {number} flags
63
+ * @param {DataView} pOutFlags
64
+ * @returns {number|Promise<number>}
65
+ */
66
+ jOpen(filename, pFile, flags, pOutFlags) {
67
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_CANTOPEN;
68
+ }
69
+
70
+ /**
71
+ * @param {string} filename
72
+ * @param {number} syncDir
73
+ * @returns {number|Promise<number>}
74
+ */
75
+ jDelete(filename, syncDir) {
76
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
77
+ }
78
+
79
+ /**
80
+ * @param {string} filename
81
+ * @param {number} flags
82
+ * @param {DataView} pResOut
83
+ * @returns {number|Promise<number>}
84
+ */
85
+ jAccess(filename, flags, pResOut) {
86
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
87
+ }
88
+
89
+ /**
90
+ * @param {string} filename
91
+ * @param {Uint8Array} zOut
92
+ * @returns {number|Promise<number>}
93
+ */
94
+ jFullPathname(filename, zOut) {
95
+ // Copy the filename to the output buffer.
96
+ const { read, written } = new TextEncoder().encodeInto(filename, zOut);
97
+ if (read < filename.length) return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
98
+ if (written >= zOut.length) return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
99
+ zOut[written] = 0;
100
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
101
+ }
102
+
103
+ /**
104
+ * @param {Uint8Array} zBuf
105
+ * @returns {number|Promise<number>}
106
+ */
107
+ jGetLastError(zBuf) {
108
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
109
+ }
110
+
111
+ /**
112
+ * @param {number} pFile
113
+ * @returns {number|Promise<number>}
114
+ */
115
+ jClose(pFile) {
116
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
117
+ }
118
+
119
+ /**
120
+ * @param {number} pFile
121
+ * @param {Uint8Array} pData
122
+ * @param {number} iOffset
123
+ * @returns {number|Promise<number>}
124
+ */
125
+ jRead(pFile, pData, iOffset) {
126
+ pData.fill(0);
127
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR_SHORT_READ;
128
+ }
129
+
130
+ /**
131
+ * @param {number} pFile
132
+ * @param {Uint8Array} pData
133
+ * @param {number} iOffset
134
+ * @returns {number|Promise<number>}
135
+ */
136
+ jWrite(pFile, pData, iOffset) {
137
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR_WRITE;
138
+ }
139
+
140
+ /**
141
+ * @param {number} pFile
142
+ * @param {number} size
143
+ * @returns {number|Promise<number>}
144
+ */
145
+ jTruncate(pFile, size) {
146
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
147
+ }
148
+
149
+ /**
150
+ * @param {number} pFile
151
+ * @param {number} flags
152
+ * @returns {number|Promise<number>}
153
+ */
154
+ jSync(pFile, flags) {
155
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
156
+ }
157
+
158
+ /**
159
+ * @param {number} pFile
160
+ * @param {DataView} pSize
161
+ * @returns {number|Promise<number>}
162
+ */
163
+ jFileSize(pFile, pSize) {
164
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
165
+ }
166
+
167
+ /**
168
+ * @param {number} pFile
169
+ * @param {number} lockType
170
+ * @returns {number|Promise<number>}
171
+ */
172
+ jLock(pFile, lockType) {
173
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
174
+ }
175
+
176
+ /**
177
+ * @param {number} pFile
178
+ * @param {number} lockType
179
+ * @returns {number|Promise<number>}
180
+ */
181
+ jUnlock(pFile, lockType) {
182
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
183
+ }
184
+
185
+ /**
186
+ * @param {number} pFile
187
+ * @param {DataView} pResOut
188
+ * @returns {number|Promise<number>}
189
+ */
190
+ jCheckReservedLock(pFile, pResOut) {
191
+ pResOut.setInt32(0, 0, true);
192
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
193
+ }
194
+
195
+ /**
196
+ * @param {number} pFile
197
+ * @param {number} op
198
+ * @param {DataView} pArg
199
+ * @returns {number|Promise<number>}
200
+ */
201
+ jFileControl(pFile, op, pArg) {
202
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_NOTFOUND;
203
+ }
204
+
205
+ /**
206
+ * @param {number} pFile
207
+ * @returns {number|Promise<number>}
208
+ */
209
+ jSectorSize(pFile) {
210
+ return super.xSectorSize(pFile);
211
+ }
212
+
213
+ /**
214
+ * @param {number} pFile
215
+ * @returns {number|Promise<number>}
216
+ */
217
+ jDeviceCharacteristics(pFile) {
218
+ return 0;
219
+ }
220
+
221
+ /**
222
+ * @param {number} pVfs
223
+ * @param {number} zName
224
+ * @param {number} pFile
225
+ * @param {number} flags
226
+ * @param {number} pOutFlags
227
+ * @returns {number|Promise<number>}
228
+ */
229
+ xOpen(pVfs, zName, pFile, flags, pOutFlags) {
230
+ const filename = this.#decodeFilename(zName, flags);
231
+ const pOutFlagsView = this.#makeTypedDataView('Int32', pOutFlags);
232
+ this['log']?.('jOpen', filename, pFile, '0x' + flags.toString(16));
233
+ return this.jOpen(filename, pFile, flags, pOutFlagsView);
234
+ }
235
+
236
+ /**
237
+ * @param {number} pVfs
238
+ * @param {number} nByte
239
+ * @param {number} pCharOut
240
+ * @returns {number|Promise<number>}
241
+ */
242
+ xRandomness(pVfs, nByte, pCharOut) {
243
+ const randomArray = new Uint8Array(nByte);
244
+ crypto.getRandomValues(randomArray);
245
+ // Copy randomArray to the WebAssembly memory
246
+ const buffer = pCharOut; // Pointer to memory in WebAssembly
247
+ this._module.HEAPU8.set(randomArray, buffer); // Copy randomArray into memory starting at buffer
248
+ return nByte;
249
+ }
250
+
251
+ /**
252
+ * Gets the current time as milliseconds since Unix epoch
253
+ * @param {number} pVfs pointer to the VFS
254
+ * @param {number} pTime pointer to write the time value
255
+ * @returns {number} SQLite error code
256
+ */
257
+ xCurrentTimeInt64(pVfs, pTime) {
258
+ // Create a DataView to write the current time
259
+ const timeView = this.#makeTypedDataView('BigInt64', pTime);
260
+
261
+ const currentTime = BigInt(Date.now());
262
+ // Convert the current time to milliseconds since Unix epoch
263
+ const value = UNIX_EPOCH + currentTime;
264
+
265
+ // Write the time value to the pointer location
266
+ timeView.setBigInt64(0, value, true);
267
+
268
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
269
+ }
270
+
271
+ /**
272
+ * @param {number} pVfs
273
+ * @param {number} zName
274
+ * @param {number} syncDir
275
+ * @returns {number|Promise<number>}
276
+ */
277
+ xDelete(pVfs, zName, syncDir) {
278
+ const filename = this._module.UTF8ToString(zName);
279
+ this['log']?.('jDelete', filename, syncDir);
280
+ return this.jDelete(filename, syncDir);
281
+ }
282
+
283
+ /**
284
+ * @param {number} pVfs
285
+ * @param {number} zName
286
+ * @param {number} flags
287
+ * @param {number} pResOut
288
+ * @returns {number|Promise<number>}
289
+ */
290
+ xAccess(pVfs, zName, flags, pResOut) {
291
+ const filename = this._module.UTF8ToString(zName);
292
+ const pResOutView = this.#makeTypedDataView('Int32', pResOut);
293
+ this['log']?.('jAccess', filename, flags);
294
+ return this.jAccess(filename, flags, pResOutView);
295
+ }
296
+
297
+ /**
298
+ * @param {number} pVfs
299
+ * @param {number} zName
300
+ * @param {number} nOut
301
+ * @param {number} zOut
302
+ * @returns {number|Promise<number>}
303
+ */
304
+ xFullPathname(pVfs, zName, nOut, zOut) {
305
+ const filename = this._module.UTF8ToString(zName);
306
+ const zOutArray = this._module.HEAPU8.subarray(zOut, zOut + nOut);
307
+ this['log']?.('jFullPathname', filename, nOut);
308
+ return this.jFullPathname(filename, zOutArray);
309
+ }
310
+
311
+ /**
312
+ * @param {number} pVfs
313
+ * @param {number} nBuf
314
+ * @param {number} zBuf
315
+ * @returns {number|Promise<number>}
316
+ */
317
+ xGetLastError(pVfs, nBuf, zBuf) {
318
+ const zBufArray = this._module.HEAPU8.subarray(zBuf, zBuf + nBuf);
319
+ this['log']?.('jGetLastError', nBuf);
320
+ return this.jGetLastError(zBufArray);
321
+ }
322
+
323
+ /**
324
+ * @param {number} pFile
325
+ * @returns {number|Promise<number>}
326
+ */
327
+ xClose(pFile) {
328
+ this['log']?.('jClose', pFile);
329
+ return this.jClose(pFile);
330
+ }
331
+
332
+ /**
333
+ * @param {number} pFile
334
+ * @param {number} pData
335
+ * @param {number} iAmt
336
+ * @param {number} iOffsetLo
337
+ * @param {number} iOffsetHi
338
+ * @returns {number|Promise<number>}
339
+ */
340
+ xRead(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {
341
+ const pDataArray = this.#makeDataArray(pData, iAmt);
342
+ const iOffset = delegalize(iOffsetLo, iOffsetHi);
343
+ this['log']?.('jRead', pFile, iAmt, iOffset);
344
+ return this.jRead(pFile, pDataArray, iOffset);
345
+ }
346
+
347
+ /**
348
+ * @param {number} pFile
349
+ * @param {number} pData
350
+ * @param {number} iAmt
351
+ * @param {number} iOffsetLo
352
+ * @param {number} iOffsetHi
353
+ * @returns {number|Promise<number>}
354
+ */
355
+ xWrite(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {
356
+ const pDataArray = this.#makeDataArray(pData, iAmt);
357
+ const iOffset = delegalize(iOffsetLo, iOffsetHi);
358
+ this['log']?.('jWrite', pFile, pDataArray, iOffset);
359
+ return this.jWrite(pFile, pDataArray, iOffset);
360
+ }
361
+
362
+ /**
363
+ * @param {number} pFile
364
+ * @param {number} sizeLo
365
+ * @param {number} sizeHi
366
+ * @returns {number|Promise<number>}
367
+ */
368
+ xTruncate(pFile, sizeLo, sizeHi) {
369
+ const size = delegalize(sizeLo, sizeHi);
370
+ this['log']?.('jTruncate', pFile, size);
371
+ return this.jTruncate(pFile, size);
372
+ }
373
+
374
+ /**
375
+ * @param {number} pFile
376
+ * @param {number} flags
377
+ * @returns {number|Promise<number>}
378
+ */
379
+ xSync(pFile, flags) {
380
+ this['log']?.('jSync', pFile, flags);
381
+ return this.jSync(pFile, flags);
382
+ }
383
+
384
+ /**
385
+ *
386
+ * @param {number} pFile
387
+ * @param {number} pSize
388
+ * @returns {number|Promise<number>}
389
+ */
390
+ xFileSize(pFile, pSize) {
391
+ const pSizeView = this.#makeTypedDataView('BigInt64', pSize);
392
+ this['log']?.('jFileSize', pFile);
393
+ return this.jFileSize(pFile, pSizeView);
394
+ }
395
+
396
+ /**
397
+ * @param {number} pFile
398
+ * @param {number} lockType
399
+ * @returns {number|Promise<number>}
400
+ */
401
+ xLock(pFile, lockType) {
402
+ this['log']?.('jLock', pFile, lockType);
403
+ return this.jLock(pFile, lockType);
404
+ }
405
+
406
+ /**
407
+ * @param {number} pFile
408
+ * @param {number} lockType
409
+ * @returns {number|Promise<number>}
410
+ */
411
+ xUnlock(pFile, lockType) {
412
+ this['log']?.('jUnlock', pFile, lockType);
413
+ return this.jUnlock(pFile, lockType);
414
+ }
415
+
416
+ /**
417
+ * @param {number} pFile
418
+ * @param {number} pResOut
419
+ * @returns {number|Promise<number>}
420
+ */
421
+ xCheckReservedLock(pFile, pResOut) {
422
+ const pResOutView = this.#makeTypedDataView('Int32', pResOut);
423
+ this['log']?.('jCheckReservedLock', pFile);
424
+ return this.jCheckReservedLock(pFile, pResOutView);
425
+ }
426
+
427
+ /**
428
+ * @param {number} pFile
429
+ * @param {number} op
430
+ * @param {number} pArg
431
+ * @returns {number|Promise<number>}
432
+ */
433
+ xFileControl(pFile, op, pArg) {
434
+ const pArgView = new DataView(
435
+ this._module.HEAPU8.buffer,
436
+ this._module.HEAPU8.byteOffset + pArg);
437
+ this['log']?.('jFileControl', pFile, op, pArgView);
438
+ return this.jFileControl(pFile, op, pArgView);
439
+ }
440
+
441
+ /**
442
+ * @param {number} pFile
443
+ * @returns {number|Promise<number>}
444
+ */
445
+ xSectorSize(pFile) {
446
+ this['log']?.('jSectorSize', pFile);
447
+ return this.jSectorSize(pFile);
448
+ }
449
+
450
+ /**
451
+ * @param {number} pFile
452
+ * @returns {number|Promise<number>}
453
+ */
454
+ xDeviceCharacteristics(pFile) {
455
+ this['log']?.('jDeviceCharacteristics', pFile);
456
+ return this.jDeviceCharacteristics(pFile);
457
+ }
458
+
459
+ /**
460
+ * Wrapped DataView for pointer arguments.
461
+ * Pointers to a single value are passed using DataView. A Proxy
462
+ * wrapper prevents use of incorrect type or endianness.
463
+ * @param {'Int32'|'BigInt64'} type
464
+ * @param {number} byteOffset
465
+ * @returns {DataView}
466
+ */
467
+ #makeTypedDataView(type, byteOffset) {
468
+ const byteLength = type === 'Int32' ? 4 : 8;
469
+ const getter = `get${type}`;
470
+ const setter = `set${type}`;
471
+ const makeDataView = () => new DataView(
472
+ this._module.HEAPU8.buffer,
473
+ this._module.HEAPU8.byteOffset + byteOffset,
474
+ byteLength);
475
+ let dataView = makeDataView();
476
+ return new Proxy(dataView, {
477
+ get(_, prop) {
478
+ if (dataView.buffer.byteLength === 0) {
479
+ // WebAssembly memory resize detached the buffer.
480
+ dataView = makeDataView();
481
+ }
482
+ if (prop === getter) {
483
+ return function(byteOffset, littleEndian) {
484
+ if (!littleEndian) throw new Error('must be little endian');
485
+ return dataView[prop](byteOffset, littleEndian);
486
+ }
487
+ }
488
+ if (prop === setter) {
489
+ return function(byteOffset, value, littleEndian) {
490
+ if (!littleEndian) throw new Error('must be little endian');
491
+ return dataView[prop](byteOffset, value, littleEndian);
492
+ }
493
+ }
494
+ if (typeof prop === 'string' && (prop.match(/^(get)|(set)/))) {
495
+ throw new Error('invalid type');
496
+ }
497
+ const result = dataView[prop];
498
+ return typeof result === 'function' ? result.bind(dataView) : result;
499
+ }
500
+ });
501
+ }
502
+
503
+ /**
504
+ * @param {number} byteOffset
505
+ * @param {number} byteLength
506
+ */
507
+ #makeDataArray(byteOffset, byteLength) {
508
+ let target = this._module.HEAPU8.subarray(byteOffset, byteOffset + byteLength);
509
+ return new Proxy(target, {
510
+ get: (_, prop, receiver) => {
511
+ if (target.buffer.byteLength === 0) {
512
+ // WebAssembly memory resize detached the buffer.
513
+ target = this._module.HEAPU8.subarray(byteOffset, byteOffset + byteLength);
514
+ }
515
+ const result = target[prop];
516
+ return typeof result === 'function' ? result.bind(target) : result;
517
+ }
518
+ });
519
+ }
520
+
521
+ #decodeFilename(zName, flags) {
522
+ if (flags & _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OPEN_URI) {
523
+ // The first null-terminated string is the URI path. Subsequent
524
+ // strings are query parameter keys and values.
525
+ // https://www.sqlite.org/c3ref/open.html#urifilenamesinsqlite3open
526
+ let pName = zName;
527
+ let state = 1;
528
+ const charCodes = [];
529
+ while (state) {
530
+ const charCode = this._module.HEAPU8[pName++];
531
+ if (charCode) {
532
+ charCodes.push(charCode);
533
+ } else {
534
+ if (!this._module.HEAPU8[pName]) state = null;
535
+ switch (state) {
536
+ case 1: // path
537
+ charCodes.push('?'.charCodeAt(0));
538
+ state = 2;
539
+ break;
540
+ case 2: // key
541
+ charCodes.push('='.charCodeAt(0));
542
+ state = 3;
543
+ break;
544
+ case 3: // value
545
+ charCodes.push('&'.charCodeAt(0));
546
+ state = 2;
547
+ break;
548
+ }
549
+ }
550
+ }
551
+ return new TextDecoder().decode(new Uint8Array(charCodes));
552
+ }
553
+ return zName ? this._module.UTF8ToString(zName) : null;
554
+ }
555
+ }
556
+
557
+ // Emscripten "legalizes" 64-bit integer arguments by passing them as
558
+ // two 32-bit signed integers.
559
+ function delegalize(lo32, hi32) {
560
+ return (hi32 * 0x100000000) + lo32 + (lo32 < 0 ? 2**32 : 0);
561
+ }
562
+
563
+
564
+ /***/ }),
565
+
4
566
  /***/ "../../node_modules/@journeyapps/wa-sqlite/src/VFS.js":
5
567
  /*!************************************************************!*\
6
568
  !*** ../../node_modules/@journeyapps/wa-sqlite/src/VFS.js ***!
@@ -215,6 +777,9 @@ __webpack_require__.r(__webpack_exports__);
215
777
  /* harmony export */ SQLITE_OPEN_WAL: () => (/* reexport safe */ _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OPEN_WAL),
216
778
  /* harmony export */ SQLITE_PERM: () => (/* reexport safe */ _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_PERM),
217
779
  /* harmony export */ SQLITE_PRAGMA: () => (/* reexport safe */ _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_PRAGMA),
780
+ /* harmony export */ SQLITE_PREPARE_NORMALIZED: () => (/* reexport safe */ _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_PREPARE_NORMALIZED),
781
+ /* harmony export */ SQLITE_PREPARE_NO_VTAB: () => (/* reexport safe */ _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_PREPARE_NO_VTAB),
782
+ /* harmony export */ SQLITE_PREPARE_PERSISTENT: () => (/* reexport safe */ _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_PREPARE_PERSISTENT),
218
783
  /* harmony export */ SQLITE_PROTOCOL: () => (/* reexport safe */ _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_PROTOCOL),
219
784
  /* harmony export */ SQLITE_RANGE: () => (/* reexport safe */ _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_RANGE),
220
785
  /* harmony export */ SQLITE_READ: () => (/* reexport safe */ _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_READ),
@@ -242,166 +807,215 @@ __webpack_require__.r(__webpack_exports__);
242
807
  /* harmony export */ SQLITE_WARNING: () => (/* reexport safe */ _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_WARNING)
243
808
  /* harmony export */ });
244
809
  /* harmony import */ var _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./sqlite-constants.js */ "../../node_modules/@journeyapps/wa-sqlite/src/sqlite-constants.js");
245
- // Copyright 2022 Roy T. Hashimoto. All Rights Reserved.
810
+ // Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
246
811
 
247
812
 
248
813
 
814
+ const DEFAULT_SECTOR_SIZE = 512;
815
+
249
816
  // Base class for a VFS.
250
817
  class Base {
251
- mxPathName = 64;
818
+ name;
819
+ mxPathname = 64;
820
+ _module;
252
821
 
253
822
  /**
254
- * @param {number} fileId
255
- * @returns {number}
823
+ * @param {string} name
824
+ * @param {object} module
256
825
  */
257
- xClose(fileId) {
258
- return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
826
+ constructor(name, module) {
827
+ this.name = name;
828
+ this._module = module;
259
829
  }
260
830
 
261
831
  /**
262
- * @param {number} fileId
263
- * @param {Uint8Array} pData
264
- * @param {number} iOffset
265
- * @returns {number}
832
+ * @returns {void|Promise<void>}
266
833
  */
267
- xRead(fileId, pData, iOffset) {
268
- return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
834
+ close() {
269
835
  }
270
836
 
271
837
  /**
272
- * @param {number} fileId
273
- * @param {Uint8Array} pData
274
- * @param {number} iOffset
275
- * @returns {number}
838
+ * @returns {boolean|Promise<boolean>}
276
839
  */
277
- xWrite(fileId, pData, iOffset) {
278
- return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
840
+ isReady() {
841
+ return true;
279
842
  }
280
843
 
281
844
  /**
282
- * @param {number} fileId
283
- * @param {number} iSize
284
- * @returns {number}
845
+ * Overload in subclasses to indicate which methods are asynchronous.
846
+ * @param {string} methodName
847
+ * @returns {boolean}
285
848
  */
286
- xTruncate(fileId, iSize) {
287
- return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
849
+ hasAsyncMethod(methodName) {
850
+ return false;
288
851
  }
289
852
 
290
853
  /**
291
- * @param {number} fileId
292
- * @param {*} flags
293
- * @returns {number}
854
+ * @param {number} pVfs
855
+ * @param {number} zName
856
+ * @param {number} pFile
857
+ * @param {number} flags
858
+ * @param {number} pOutFlags
859
+ * @returns {number|Promise<number>}
294
860
  */
295
- xSync(fileId, flags) {
296
- return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
861
+ xOpen(pVfs, zName, pFile, flags, pOutFlags) {
862
+ return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_CANTOPEN;
297
863
  }
298
864
 
299
865
  /**
300
- * @param {number} fileId
301
- * @param {DataView} pSize64
302
- * @returns {number}
866
+ * @param {number} pVfs
867
+ * @param {number} zName
868
+ * @param {number} syncDir
869
+ * @returns {number|Promise<number>}
303
870
  */
304
- xFileSize(fileId, pSize64) {
305
- return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
871
+ xDelete(pVfs, zName, syncDir) {
872
+ return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
306
873
  }
307
874
 
308
875
  /**
309
- * @param {number} fileId
876
+ * @param {number} pVfs
877
+ * @param {number} zName
310
878
  * @param {number} flags
311
- * @returns {number}
879
+ * @param {number} pResOut
880
+ * @returns {number|Promise<number>}
312
881
  */
313
- xLock(fileId, flags) {
882
+ xAccess(pVfs, zName, flags, pResOut) {
314
883
  return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
315
884
  }
316
885
 
317
886
  /**
318
- * @param {number} fileId
319
- * @param {number} flags
320
- * @returns {number}
887
+ * @param {number} pVfs
888
+ * @param {number} zName
889
+ * @param {number} nOut
890
+ * @param {number} zOut
891
+ * @returns {number|Promise<number>}
321
892
  */
322
- xUnlock(fileId, flags) {
893
+ xFullPathname(pVfs, zName, nOut, zOut) {
323
894
  return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
324
895
  }
325
896
 
326
897
  /**
327
- * @param {number} fileId
328
- * @param {DataView} pResOut
329
- * @returns {number}
898
+ * @param {number} pVfs
899
+ * @param {number} nBuf
900
+ * @param {number} zBuf
901
+ * @returns {number|Promise<number>}
330
902
  */
331
- xCheckReservedLock(fileId, pResOut) {
332
- pResOut.setInt32(0, 0, true);
903
+ xGetLastError(pVfs, nBuf, zBuf) {
333
904
  return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
334
905
  }
335
906
 
336
907
  /**
337
- * @param {number} fileId
338
- * @param {number} op
339
- * @param {DataView} pArg
340
- * @returns {number}
908
+ * @param {number} pFile
909
+ * @returns {number|Promise<number>}
341
910
  */
342
- xFileControl(fileId, op, pArg) {
343
- return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_NOTFOUND;
911
+ xClose(pFile) {
912
+ return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
344
913
  }
345
914
 
346
915
  /**
347
- * @param {number} fileId
348
- * @returns {number}
916
+ * @param {number} pFile
917
+ * @param {number} pData
918
+ * @param {number} iAmt
919
+ * @param {number} iOffsetLo
920
+ * @param {number} iOffsetHi
921
+ * @returns {number|Promise<number>}
922
+ */
923
+ xRead(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {
924
+ return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
925
+ }
926
+
927
+ /**
928
+ * @param {number} pFile
929
+ * @param {number} pData
930
+ * @param {number} iAmt
931
+ * @param {number} iOffsetLo
932
+ * @param {number} iOffsetHi
933
+ * @returns {number|Promise<number>}
349
934
  */
350
- xSectorSize(fileId) {
351
- return 512;
935
+ xWrite(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {
936
+ return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
352
937
  }
353
938
 
354
939
  /**
355
- * @param {number} fileId
356
- * @returns {number}
940
+ * @param {number} pFile
941
+ * @param {number} sizeLo
942
+ * @param {number} sizeHi
943
+ * @returns {number|Promise<number>}
357
944
  */
358
- xDeviceCharacteristics(fileId) {
359
- return 0;
945
+ xTruncate(pFile, sizeLo, sizeHi) {
946
+ return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
360
947
  }
361
948
 
362
949
  /**
363
- * @param {string?} name
364
- * @param {number} fileId
950
+ * @param {number} pFile
365
951
  * @param {number} flags
366
- * @param {DataView} pOutFlags
367
- * @returns {number}
952
+ * @returns {number|Promise<number>}
368
953
  */
369
- xOpen(name, fileId, flags, pOutFlags) {
370
- return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_CANTOPEN;
954
+ xSync(pFile, flags) {
955
+ return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
371
956
  }
372
957
 
373
958
  /**
374
- * @param {string} name
375
- * @param {number} syncDir
376
- * @returns {number}
959
+ *
960
+ * @param {number} pFile
961
+ * @param {number} pSize
962
+ * @returns {number|Promise<number>}
377
963
  */
378
- xDelete(name, syncDir) {
379
- return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
964
+ xFileSize(pFile, pSize) {
965
+ return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
380
966
  }
381
967
 
382
968
  /**
383
- * @param {string} name
384
- * @param {number} flags
385
- * @param {DataView} pResOut
386
- * @returns {number}
969
+ * @param {number} pFile
970
+ * @param {number} lockType
971
+ * @returns {number|Promise<number>}
387
972
  */
388
- xAccess(name, flags, pResOut) {
389
- return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
973
+ xLock(pFile, lockType) {
974
+ return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
390
975
  }
391
976
 
392
977
  /**
393
- * Handle asynchronous operation. This implementation will be overriden on
394
- * registration by an Asyncify build.
395
- * @param {function(): Promise<number>} f
396
- * @returns {number}
978
+ * @param {number} pFile
979
+ * @param {number} lockType
980
+ * @returns {number|Promise<number>}
981
+ */
982
+ xUnlock(pFile, lockType) {
983
+ return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
984
+ }
985
+
986
+ /**
987
+ * @param {number} pFile
988
+ * @param {number} pResOut
989
+ * @returns {number|Promise<number>}
990
+ */
991
+ xCheckReservedLock(pFile, pResOut) {
992
+ return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
993
+ }
994
+
995
+ /**
996
+ * @param {number} pFile
997
+ * @param {number} op
998
+ * @param {number} pArg
999
+ * @returns {number|Promise<number>}
1000
+ */
1001
+ xFileControl(pFile, op, pArg) {
1002
+ return _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_NOTFOUND;
1003
+ }
1004
+
1005
+ /**
1006
+ * @param {number} pFile
1007
+ * @returns {number|Promise<number>}
397
1008
  */
398
- handleAsync(f) {
399
- // This default implementation deliberately does not match the
400
- // declared signature. It will be used in testing VFS classes
401
- // separately from SQLite. This will work acceptably for methods
402
- // that simply return the handleAsync() result without using it.
403
- // @ts-ignore
404
- return f();
1009
+ xSectorSize(pFile) {
1010
+ return DEFAULT_SECTOR_SIZE;
1011
+ }
1012
+
1013
+ /**
1014
+ * @param {number} pFile
1015
+ * @returns {number|Promise<number>}
1016
+ */
1017
+ xDeviceCharacteristics(pFile) {
1018
+ return 0;
405
1019
  }
406
1020
  }
407
1021
 
@@ -412,1514 +1026,1274 @@ const FILE_TYPE_MASK = [
412
1026
  _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OPEN_TEMP_JOURNAL,
413
1027
  _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OPEN_TRANSIENT_DB,
414
1028
  _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OPEN_SUBJOURNAL,
415
- _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OPEN_SUPER_JOURNAL
1029
+ _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OPEN_SUPER_JOURNAL,
1030
+ _sqlite_constants_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OPEN_WAL
416
1031
  ].reduce((mask, element) => mask | element);
417
1032
 
418
1033
  /***/ }),
419
1034
 
420
- /***/ "../../node_modules/@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js":
421
- /*!***********************************************************************************!*\
422
- !*** ../../node_modules/@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js ***!
423
- \***********************************************************************************/
1035
+ /***/ "../../node_modules/@journeyapps/wa-sqlite/src/WebLocksMixin.js":
1036
+ /*!**********************************************************************!*\
1037
+ !*** ../../node_modules/@journeyapps/wa-sqlite/src/WebLocksMixin.js ***!
1038
+ \**********************************************************************/
424
1039
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
425
1040
 
426
1041
  __webpack_require__.r(__webpack_exports__);
427
1042
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
428
- /* harmony export */ IDBBatchAtomicVFS: () => (/* binding */ IDBBatchAtomicVFS)
1043
+ /* harmony export */ WebLocksMixin: () => (/* binding */ WebLocksMixin)
429
1044
  /* harmony export */ });
430
- /* harmony import */ var _VFS_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../VFS.js */ "../../node_modules/@journeyapps/wa-sqlite/src/VFS.js");
431
- /* harmony import */ var _WebLocks_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./WebLocks.js */ "../../node_modules/@journeyapps/wa-sqlite/src/examples/WebLocks.js");
432
- /* harmony import */ var _IDBContext_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./IDBContext.js */ "../../node_modules/@journeyapps/wa-sqlite/src/examples/IDBContext.js");
433
- // Copyright 2022 Roy T. Hashimoto. All Rights Reserved.
434
-
1045
+ /* harmony import */ var _VFS_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./VFS.js */ "../../node_modules/@journeyapps/wa-sqlite/src/VFS.js");
435
1046
 
436
1047
 
1048
+ // Options for navigator.locks.request().
1049
+ /** @type {LockOptions} */ const SHARED = { mode: 'shared' };
1050
+ /** @type {LockOptions} */ const POLL_SHARED = { ifAvailable: true, mode: 'shared' };
1051
+ /** @type {LockOptions} */ const POLL_EXCLUSIVE = { ifAvailable: true, mode: 'exclusive' };
437
1052
 
438
- const SECTOR_SIZE = 512;
439
- const MAX_TASK_MILLIS = 3000;
1053
+ const POLICIES = ['exclusive', 'shared', 'shared+hint'];
440
1054
 
441
1055
  /**
442
- * @typedef VFSOptions
443
- * @property {"default"|"strict"|"relaxed"} [durability]
444
- * @property {"deferred"|"manual"} [purge]
445
- * @property {number} [purgeAtLeast]
1056
+ * @typedef LockState
1057
+ * @property {string} baseName
1058
+ * @property {number} type
1059
+ * @property {boolean} writeHint
1060
+ *
1061
+ * These properties are functions that release a specific lock.
1062
+ * @property {(() => void)?} [gate]
1063
+ * @property {(() => void)?} [access]
1064
+ * @property {(() => void)?} [reserved]
1065
+ * @property {(() => void)?} [hint]
446
1066
  */
447
1067
 
448
- /** @type {VFSOptions} */
449
- const DEFAULT_OPTIONS = {
450
- durability: "default",
451
- purge: "deferred",
452
- purgeAtLeast: 16
453
- };
454
-
455
- function log(...args) {
456
- // console.debug(...args);
457
- }
458
-
459
- /**
460
- * @typedef FileBlock IndexedDB object with key [path, offset, version]
461
- * @property {string} path
462
- * @property {number} offset negative of position in file
463
- * @property {number} version
464
- * @property {Uint8Array} data
465
- *
466
- * @property {number} [fileSize] Only present on block 0
467
- */
468
-
469
1068
  /**
470
- * @typedef OpenedFileEntry
471
- * @property {string} path
472
- * @property {number} flags
473
- * @property {FileBlock} block0
474
- * @property {boolean} isMetadataChanged
475
- * @property {WebLocks} locks
476
- *
477
- * @property {Set<number>} [changedPages]
478
- * @property {boolean} [overwrite]
1069
+ * Mix-in for FacadeVFS that implements the SQLite VFS locking protocol.
1070
+ * @param {*} superclass FacadeVFS (or subclass)
1071
+ * @returns
479
1072
  */
480
-
481
- // This sample VFS stores optionally versioned writes to IndexedDB, which
482
- // it uses with the SQLite xFileControl() batch atomic write feature.
483
- class IDBBatchAtomicVFS extends _VFS_js__WEBPACK_IMPORTED_MODULE_0__.Base {
484
- #options;
485
- /** @type {Map<number, OpenedFileEntry>} */ #mapIdToFile = new Map();
486
-
487
- /** @type {IDBContext} */ #idb;
488
- /** @type {Set<string>} */ #pendingPurges = new Set();
489
-
490
- #taskTimestamp = performance.now();
491
- #pendingAsync = new Set();
492
-
493
- // Asyncify can grow WebAssembly memory during an asynchronous call.
494
- // If this happens, then any array buffer arguments will be detached.
495
- // The workaround is when finding a detached buffer, set this handler
496
- // function to process the new buffer outside handlerAsync().
497
- #growthHandler = null;
498
-
499
- constructor(idbDatabaseName = 'wa-sqlite', options = DEFAULT_OPTIONS) {
500
- super();
501
- this.name = idbDatabaseName;
502
- this.#options = Object.assign({}, DEFAULT_OPTIONS, options);
503
- this.#idb = new _IDBContext_js__WEBPACK_IMPORTED_MODULE_2__.IDBContext(openDatabase(idbDatabaseName), {
504
- durability: this.#options.durability
505
- });
506
- }
507
-
508
- async close() {
509
- for (const fileId of this.#mapIdToFile.keys()) {
510
- await this.xClose(fileId);
1073
+ const WebLocksMixin = superclass => class extends superclass {
1074
+ #options = {
1075
+ lockPolicy: 'exclusive',
1076
+ lockTimeout: Infinity
1077
+ };
1078
+
1079
+ /** @type {Map<number, LockState>} */ #mapIdToState = new Map();
1080
+
1081
+ constructor(name, module, options) {
1082
+ super(name, module, options);
1083
+ Object.assign(this.#options, options);
1084
+ if (POLICIES.indexOf(this.#options.lockPolicy) === -1) {
1085
+ throw new Error(`WebLocksMixin: invalid lock mode: ${options.lockPolicy}`);
511
1086
  }
512
-
513
- await this.#idb?.close();
514
- this.#idb = null;
515
1087
  }
516
1088
 
517
1089
  /**
518
- * @param {string?} name
519
1090
  * @param {number} fileId
520
- * @param {number} flags
521
- * @param {DataView} pOutFlags
522
- * @returns {number}
1091
+ * @param {number} lockType
1092
+ * @returns {Promise<number>}
523
1093
  */
524
- xOpen(name, fileId, flags, pOutFlags) {
525
- const result = this.handleAsync(async () => {
526
- if (name === null) name = `null_${fileId}`;
527
- log(`xOpen ${name} 0x${fileId.toString(16)} 0x${flags.toString(16)}`);
528
-
529
- try {
530
- // Filenames can be URLs, possibly with query parameters.
531
- const url = new URL(name, 'http://localhost/');
532
- /** @type {OpenedFileEntry} */ const file = {
533
- path: url.pathname,
534
- flags,
535
- block0: null,
536
- isMetadataChanged: true,
537
- locks: new _WebLocks_js__WEBPACK_IMPORTED_MODULE_1__.WebLocksExclusive(url.pathname)
1094
+ async jLock(fileId, lockType) {
1095
+ try {
1096
+ // Create state on first lock.
1097
+ if (!this.#mapIdToState.has(fileId)) {
1098
+ const name = this.getFilename(fileId);
1099
+ const state = {
1100
+ baseName: name,
1101
+ type: _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_NONE,
1102
+ writeHint: false
538
1103
  };
539
- this.#mapIdToFile.set(fileId, file);
540
-
541
- // Read the first block, which also contains the file metadata.
542
- await this.#idb.run('readwrite', async ({blocks}) => {
543
- file.block0 = await blocks.get(this.#bound(file, 0));
544
- if (!file.block0) {
545
- if (flags & _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OPEN_CREATE) {
546
- file.block0 = {
547
- path: file.path,
548
- offset: 0,
549
- version: 0,
550
- data: new Uint8Array(0),
551
- fileSize: 0
552
- };
553
- blocks.put(file.block0);
554
- } else {
555
- throw new Error(`file not found: ${file.path}`);
556
- }
557
- }
558
- });
559
-
560
- // @ts-ignore
561
- if (pOutFlags.buffer.detached || !pOutFlags.buffer.byteLength) {
562
- pOutFlags = new DataView(new ArrayBuffer(4));
563
- this.#growthHandler = (pOutFlagsNew) => {
564
- pOutFlagsNew.setInt32(0, pOutFlags.getInt32(0, true), true);
565
- };
566
- }
567
- pOutFlags.setInt32(0, flags & _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OPEN_READONLY, true);
568
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
569
- } catch (e) {
570
- console.error(e);
571
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_CANTOPEN;
1104
+ this.#mapIdToState.set(fileId, state);
572
1105
  }
573
- });
574
1106
 
575
- this.#growthHandler?.(pOutFlags);
576
- this.#growthHandler = null;
577
- return result;
1107
+ const lockState = this.#mapIdToState.get(fileId);
1108
+ if (lockType <= lockState.type) return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
1109
+
1110
+ switch (this.#options.lockPolicy) {
1111
+ case 'exclusive':
1112
+ return await this.#lockExclusive(lockState, lockType);
1113
+ case 'shared':
1114
+ case 'shared+hint':
1115
+ return await this.#lockShared(lockState, lockType);
1116
+ }
1117
+ } catch (e) {
1118
+ console.error('WebLocksMixin: lock error', e);
1119
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR_LOCK;
1120
+ }
578
1121
  }
579
-
1122
+
580
1123
  /**
581
1124
  * @param {number} fileId
582
- * @returns {number}
1125
+ * @param {number} lockType
1126
+ * @returns {Promise<number>}
583
1127
  */
584
- xClose(fileId) {
585
- return this.handleAsync(async () => {
586
- try {
587
- const file = this.#mapIdToFile.get(fileId);
588
- if (file) {
589
- log(`xClose ${file.path}`);
590
-
591
- this.#mapIdToFile.delete(fileId);
592
- if (file.flags & _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OPEN_DELETEONCLOSE) {
593
- this.#idb.run('readwrite', ({blocks}) => {
594
- blocks.delete(IDBKeyRange.bound([file.path], [file.path, []]));
595
- });
596
- }
597
- }
598
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
599
- } catch (e) {
600
- console.error(e);
601
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
1128
+ async jUnlock(fileId, lockType) {
1129
+ try {
1130
+ const lockState = this.#mapIdToState.get(fileId);
1131
+ if (lockType >= lockState.type) return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
1132
+
1133
+ switch (this.#options.lockPolicy) {
1134
+ case 'exclusive':
1135
+ return await this.#unlockExclusive(lockState, lockType);
1136
+ case 'shared':
1137
+ case 'shared+hint':
1138
+ return await this.#unlockShared(lockState, lockType);
602
1139
  }
603
- });
1140
+ } catch (e) {
1141
+ console.error('WebLocksMixin: unlock error', e);
1142
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR_UNLOCK;
1143
+ }
604
1144
  }
605
1145
 
606
1146
  /**
607
1147
  * @param {number} fileId
608
- * @param {Uint8Array} pData
609
- * @param {number} iOffset
610
- * @returns {number}
1148
+ * @param {DataView} pResOut
1149
+ * @returns {Promise<number>}
611
1150
  */
612
- xRead(fileId, pData, iOffset) {
613
- const byteLength = pData.byteLength;
614
- const result = this.handleAsync(async () => {
615
- const file = this.#mapIdToFile.get(fileId);
616
- log(`xRead ${file.path} ${pData.byteLength} ${iOffset}`);
617
-
618
- try {
619
- // Read as many blocks as necessary to satisfy the read request.
620
- // Usually a read fits within a single write but there is at least
621
- // one case - rollback after journal spill - where reads cross
622
- // write boundaries so we have to allow for that.
623
- const result = await this.#idb.run('readonly', async ({blocks}) => {
624
- // @ts-ignore
625
- if (pData.buffer.detached || !pData.buffer.byteLength) {
626
- // WebAssembly memory has grown, invalidating our buffer. Use
627
- // a temporary buffer and copy after this asynchronous call
628
- // completes.
629
- pData = new Uint8Array(byteLength);
630
- this.#growthHandler = (pDataNew) => pDataNew.set(pData);
631
- }
632
-
633
- let pDataOffset = 0;
634
- while (pDataOffset < pData.byteLength) {
635
- // Fetch the IndexedDB block for this file location.
636
- const fileOffset = iOffset + pDataOffset;
637
- /** @type {FileBlock} */
638
- const block = fileOffset < file.block0.data.byteLength ?
639
- file.block0 :
640
- await blocks.get(this.#bound(file, -fileOffset));
641
-
642
- if (!block || block.data.byteLength - block.offset <= fileOffset) {
643
- pData.fill(0, pDataOffset);
644
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR_SHORT_READ;
645
- }
646
-
647
- const buffer = pData.subarray(pDataOffset);
648
- const blockOffset = fileOffset + block.offset;
649
- const nBytesToCopy = Math.min(
650
- Math.max(block.data.byteLength - blockOffset, 0), // source bytes
651
- buffer.byteLength); // destination bytes
652
- buffer.set(block.data.subarray(blockOffset, blockOffset + nBytesToCopy));
653
- pDataOffset += nBytesToCopy;
654
- }
655
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
656
- });
657
- return result;
658
- } catch (e) {
659
- console.error(e);
660
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
1151
+ async jCheckReservedLock(fileId, pResOut) {
1152
+ try {
1153
+ const lockState = this.#mapIdToState.get(fileId);
1154
+ switch (this.#options.lockPolicy) {
1155
+ case 'exclusive':
1156
+ return this.#checkReservedExclusive(lockState, pResOut);
1157
+ case 'shared':
1158
+ case 'shared+hint':
1159
+ return await this.#checkReservedShared(lockState, pResOut);
661
1160
  }
662
- });
663
-
664
- this.#growthHandler?.(pData);
665
- this.#growthHandler = null;
666
- return result;
1161
+ } catch (e) {
1162
+ console.error('WebLocksMixin: check reserved lock error', e);
1163
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR_CHECKRESERVEDLOCK;
1164
+ }
1165
+ pResOut.setInt32(0, 0, true);
1166
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
667
1167
  }
668
1168
 
669
1169
  /**
670
- * @param {number} fileId
671
- * @param {Uint8Array} pData
672
- * @param {number} iOffset
673
- * @returns {number}
1170
+ * @param {number} pFile
1171
+ * @param {number} op
1172
+ * @param {DataView} pArg
1173
+ * @returns {number|Promise<number>}
674
1174
  */
675
- xWrite(fileId, pData, iOffset) {
676
- // Handle asynchronously every MAX_TASK_MILLIS milliseconds. This is
677
- // tricky because Asyncify calls asynchronous methods twice: once
678
- // to initiate the call and unwinds the stack, then rewinds the
679
- // stack and calls again to retrieve the completed result.
680
- const rewound = this.#pendingAsync.has(fileId);
681
- if (rewound || performance.now() - this.#taskTimestamp > MAX_TASK_MILLIS) {
682
- const result = this.handleAsync(async () => {
683
- if (this.handleAsync !== super.handleAsync) {
684
- this.#pendingAsync.add(fileId);
685
- }
686
- await new Promise(resolve => setTimeout(resolve));
687
-
688
- const result = this.#xWriteHelper(fileId, pData.slice(), iOffset);
689
- this.#taskTimestamp = performance.now();
690
- return result;
691
- });
692
-
693
- if (rewound) this.#pendingAsync.delete(fileId);
694
- return result;
1175
+ jFileControl(pFile, op, pArg) {
1176
+ const lockState = this.#mapIdToState.get(pFile) ??
1177
+ (() => {
1178
+ // Call jLock() to create the lock state.
1179
+ this.jLock(pFile, _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_NONE);
1180
+ return this.#mapIdToState.get(pFile);
1181
+ })();
1182
+ if (op === WebLocksMixin.WRITE_HINT_OP_CODE &&
1183
+ this.#options.lockPolicy === 'shared+hint'){
1184
+ lockState.writeHint = true;
695
1185
  }
696
- return this.#xWriteHelper(fileId, pData, iOffset);
1186
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_NOTFOUND;
697
1187
  }
698
1188
 
699
1189
  /**
700
- * @param {number} fileId
701
- * @param {Uint8Array} pData
702
- * @param {number} iOffset
703
- * @returns {number}
1190
+ * @param {LockState} lockState
1191
+ * @param {number} lockType
1192
+ * @returns
704
1193
  */
705
- #xWriteHelper(fileId, pData, iOffset) {
706
- const file = this.#mapIdToFile.get(fileId);
707
- log(`xWrite ${file.path} ${pData.byteLength} ${iOffset}`);
708
-
709
- try {
710
- // Update file size if appending.
711
- const prevFileSize = file.block0.fileSize;
712
- if (file.block0.fileSize < iOffset + pData.byteLength) {
713
- file.block0.fileSize = iOffset + pData.byteLength;
714
- file.isMetadataChanged = true;
715
- }
716
-
717
- // Convert the write directly into an IndexedDB object. Our assumption
718
- // is that SQLite will only overwrite data with an xWrite of the same
719
- // offset and size unless the database page size changes, except when
720
- // changing database page size which is handled by #reblockIfNeeded().
721
- const block = iOffset === 0 ? file.block0 : {
722
- path: file.path,
723
- offset: -iOffset,
724
- version: file.block0.version,
725
- data: null
726
- };
727
- block.data = pData.slice();
728
-
729
- if (file.changedPages) {
730
- // This write is part of a batch atomic write. All writes in the
731
- // batch have a new version, so update the changed list to allow
732
- // old versions to be eventually deleted.
733
- if (prevFileSize === file.block0.fileSize) {
734
- file.changedPages.add(-iOffset);
735
- }
736
-
737
- // Defer writing block 0 to IndexedDB until batch commit.
738
- if (iOffset !== 0) {
739
- this.#idb.run('readwrite', ({blocks}) => blocks.put(block));
740
- }
741
- } else {
742
- // Not a batch atomic write so write through.
743
- this.#idb.run('readwrite', ({blocks}) => blocks.put(block));
1194
+ async #lockExclusive(lockState, lockType) {
1195
+ if (!lockState.access) {
1196
+ if (!await this.#acquire(lockState, 'access')) {
1197
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_BUSY;
744
1198
  }
745
-
746
- // Clear dirty flag if page 0 was written.
747
- file.isMetadataChanged = iOffset === 0 ? false : file.isMetadataChanged;
748
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
749
- } catch (e) {
750
- console.error(e);
751
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
1199
+ console.assert(!!lockState.access);
752
1200
  }
1201
+ lockState.type = lockType;
1202
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
753
1203
  }
754
1204
 
755
1205
  /**
756
- * @param {number} fileId
757
- * @param {number} iSize
1206
+ * @param {LockState} lockState
1207
+ * @param {number} lockType
758
1208
  * @returns {number}
759
1209
  */
760
- xTruncate(fileId, iSize) {
761
- const file = this.#mapIdToFile.get(fileId);
762
- log(`xTruncate ${file.path} ${iSize}`);
763
-
764
- try {
765
- Object.assign(file.block0, {
766
- fileSize: iSize,
767
- data: file.block0.data.slice(0, iSize)
768
- });
769
-
770
- // Delete all blocks beyond the file size and update metadata.
771
- // This is never called within a transaction.
772
- const block0 = Object.assign({}, file.block0);
773
- this.#idb.run('readwrite', ({blocks})=> {
774
- blocks.delete(this.#bound(file, -Infinity, -iSize));
775
- blocks.put(block0);
776
- });
777
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
778
- } catch (e) {
779
- console.error(e);
780
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
1210
+ #unlockExclusive(lockState, lockType) {
1211
+ if (lockType === _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_NONE) {
1212
+ lockState.access?.();
1213
+ console.assert(!lockState.access);
781
1214
  }
1215
+ lockState.type = lockType;
1216
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
782
1217
  }
783
1218
 
784
1219
  /**
785
- * @param {number} fileId
786
- * @param {number} flags
1220
+ * @param {LockState} lockState
1221
+ * @param {DataView} pResOut
787
1222
  * @returns {number}
788
1223
  */
789
- xSync(fileId, flags) {
790
- // Skip IndexedDB sync if durability is relaxed and the last
791
- // sync was recent enough.
792
- const rewound = this.#pendingAsync.has(fileId);
793
- if (rewound || this.#options.durability !== 'relaxed' ||
794
- performance.now() - this.#taskTimestamp > MAX_TASK_MILLIS) {
795
- const result = this.handleAsync(async () => {
796
- if (this.handleAsync !== super.handleAsync) {
797
- this.#pendingAsync.add(fileId);
1224
+ #checkReservedExclusive(lockState, pResOut) {
1225
+ pResOut.setInt32(0, 0, true);
1226
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
1227
+ }
1228
+
1229
+ /**
1230
+ * @param {LockState} lockState
1231
+ * @param {number} lockType
1232
+ * @returns
1233
+ */
1234
+ async #lockShared(lockState, lockType) {
1235
+ switch (lockState.type) {
1236
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_NONE:
1237
+ switch (lockType) {
1238
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_SHARED:
1239
+ if (lockState.writeHint) {
1240
+ // xFileControl() has hinted that this transaction will
1241
+ // write. Acquire the hint lock, which is required to reach
1242
+ // the RESERVED state.
1243
+ if (!await this.#acquire(lockState, 'hint')) {
1244
+ // Timeout before lock acquired.
1245
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_BUSY;
1246
+ }
1247
+ }
1248
+
1249
+ // Must have the gate lock to request the access lock.
1250
+ if (!await this.#acquire(lockState, 'gate', SHARED)) {
1251
+ // Timeout before lock acquired.
1252
+ lockState.hint?.();
1253
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_BUSY;
1254
+ }
1255
+ await this.#acquire(lockState, 'access', SHARED);
1256
+ lockState.gate();
1257
+ console.assert(!lockState.gate);
1258
+ console.assert(!!lockState.access);
1259
+ console.assert(!lockState.reserved);
1260
+ break;
1261
+
1262
+ default:
1263
+ throw new Error('unsupported lock transition');
798
1264
  }
1265
+ break;
1266
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_SHARED:
1267
+ switch (lockType) {
1268
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_RESERVED:
1269
+ if (this.#options.lockPolicy === 'shared+hint') {
1270
+ // Ideally we should already have the hint lock, but if not
1271
+ // poll for it here.
1272
+ if (!lockState.hint &&
1273
+ !await this.#acquire(lockState, 'hint', POLL_EXCLUSIVE)) {
1274
+ // Another connection has the hint lock so this is a
1275
+ // deadlock. This connection must retry.
1276
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_BUSY;
1277
+ }
1278
+ }
799
1279
 
800
- const result = await this.#xSyncHelper(fileId, flags);
801
- this.#taskTimestamp = performance.now();
802
- return result;
803
- });
1280
+ // Poll for the reserved lock. This should always succeed
1281
+ // if all clients use the 'shared+hint' policy.
1282
+ if (!await this.#acquire(lockState, 'reserved', POLL_EXCLUSIVE)) {
1283
+ // This is a deadlock. The connection holding the reserved
1284
+ // lock blocks us, and it can't acquire an exclusive access
1285
+ // lock because we hold a shared access lock. This connection
1286
+ // must retry.
1287
+ lockState.hint?.();
1288
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_BUSY;
1289
+ }
1290
+ lockState.access();
1291
+ console.assert(!lockState.gate);
1292
+ console.assert(!lockState.access);
1293
+ console.assert(!!lockState.reserved);
1294
+ break;
804
1295
 
805
- if (rewound) this.#pendingAsync.delete(fileId);
806
- return result;
807
- }
1296
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_EXCLUSIVE:
1297
+ // Jumping directly from SHARED to EXCLUSIVE without passing
1298
+ // through RESERVED is only done with a hot journal.
1299
+ if (!await this.#acquire(lockState, 'gate')) {
1300
+ // Timeout before lock acquired.
1301
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_BUSY;
1302
+ }
1303
+ lockState.access();
1304
+ if (!await this.#acquire(lockState, 'access')) {
1305
+ // Timeout before lock acquired.
1306
+ lockState.gate();
1307
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_BUSY;
1308
+ }
1309
+ console.assert(!!lockState.gate);
1310
+ console.assert(!!lockState.access);
1311
+ console.assert(!lockState.reserved);
1312
+ break;
1313
+
1314
+ default:
1315
+ throw new Error('unsupported lock transition');
1316
+ }
1317
+ break;
1318
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_RESERVED:
1319
+ switch (lockType) {
1320
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_EXCLUSIVE:
1321
+ // Prevent other connections from entering the SHARED state.
1322
+ if (!await this.#acquire(lockState, 'gate')) {
1323
+ // Timeout before lock acquired.
1324
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_BUSY;
1325
+ }
1326
+
1327
+ // Block until all other connections exit the SHARED state.
1328
+ if (!await this.#acquire(lockState, 'access')) {
1329
+ // Timeout before lock acquired.
1330
+ lockState.gate();
1331
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_BUSY;
1332
+ }
1333
+ console.assert(!!lockState.gate);
1334
+ console.assert(!!lockState.access);
1335
+ console.assert(!!lockState.reserved);
1336
+ break;
808
1337
 
809
- const file = this.#mapIdToFile.get(fileId);
810
- log(`xSync ${file.path} ${flags}`);
1338
+ default:
1339
+ throw new Error('unsupported lock transition');
1340
+ }
1341
+ break;
1342
+ }
1343
+ lockState.type = lockType;
811
1344
  return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
812
1345
  }
813
1346
 
814
1347
  /**
815
- * @param {number} fileId
816
- * @param {number} flags
817
- * @returns {Promise<number>}
1348
+ * @param {LockState} lockState
1349
+ * @param {number} lockType
1350
+ * @returns
818
1351
  */
819
- async #xSyncHelper(fileId, flags) {
820
- const file = this.#mapIdToFile.get(fileId);
821
- log(`xSync ${file.path} ${flags}`);
822
- try {
823
- if (file.isMetadataChanged) {
824
- // Metadata has changed so write block 0 to IndexedDB.
825
- this.#idb.run('readwrite', async ({blocks}) => {
826
- await blocks.put(file.block0);
827
- });
828
- file.isMetadataChanged = false;
1352
+ async #unlockShared(lockState, lockType) {
1353
+ // lockType can only be SQLITE_LOCK_SHARED or SQLITE_LOCK_NONE.
1354
+ if (lockType === _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_NONE) {
1355
+ lockState.access?.();
1356
+ lockState.gate?.();
1357
+ lockState.reserved?.();
1358
+ lockState.hint?.();
1359
+ lockState.writeHint = false;
1360
+ console.assert(!lockState.access);
1361
+ console.assert(!lockState.gate);
1362
+ console.assert(!lockState.reserved);
1363
+ console.assert(!lockState.hint);
1364
+ } else { // lockType === VFS.SQLITE_LOCK_SHARED
1365
+ switch (lockState.type) {
1366
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_EXCLUSIVE:
1367
+ // Release our exclusive access lock and reacquire it with a
1368
+ // shared lock. This should always succeed because we hold
1369
+ // the gate lock.
1370
+ lockState.access();
1371
+ await this.#acquire(lockState, 'access', SHARED);
1372
+
1373
+ // Release our gate and reserved locks. We might not have a
1374
+ // reserved lock if we were handling a hot journal.
1375
+ lockState.gate();
1376
+ lockState.reserved?.();
1377
+ lockState.hint?.();
1378
+ console.assert(!!lockState.access);
1379
+ console.assert(!lockState.gate);
1380
+ console.assert(!lockState.reserved);
1381
+ break;
1382
+
1383
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_RESERVED:
1384
+ // This transition is rare, probably only on an I/O error
1385
+ // while writing to a journal file.
1386
+ await this.#acquire(lockState, 'access', SHARED);
1387
+ lockState.reserved();
1388
+ lockState.hint?.();
1389
+ console.assert(!!lockState.access);
1390
+ console.assert(!lockState.gate);
1391
+ console.assert(!lockState.reserved);
1392
+ break;
829
1393
  }
830
- await this.#idb.sync();
831
- } catch (e) {
832
- console.error(e);
833
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
834
1394
  }
1395
+ lockState.type = lockType;
835
1396
  return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
836
1397
  }
837
1398
 
838
1399
  /**
839
- * @param {number} fileId
840
- * @param {DataView} pSize64
841
- * @returns {number}
1400
+ * @param {LockState} lockState
1401
+ * @param {DataView} pResOut
1402
+ * @returns {Promise<number>}
842
1403
  */
843
- xFileSize(fileId, pSize64) {
844
- const file = this.#mapIdToFile.get(fileId);
845
- log(`xFileSize ${file.path}`);
846
-
847
- pSize64.setBigInt64(0, BigInt(file.block0.fileSize), true)
1404
+ async #checkReservedShared(lockState, pResOut) {
1405
+ if (await this.#acquire(lockState, 'reserved', POLL_SHARED)) {
1406
+ // We were able to get the lock so it was not reserved.
1407
+ lockState.reserved();
1408
+ pResOut.setInt32(0, 0, true);
1409
+ } else {
1410
+ pResOut.setInt32(0, 1, true);
1411
+ }
848
1412
  return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
849
1413
  }
850
1414
 
851
1415
  /**
852
- * @param {number} fileId
853
- * @param {number} flags
854
- * @returns {number}
1416
+ * @param {LockState} lockState
1417
+ * @param {'gate'|'access'|'reserved'|'hint'} name
1418
+ * @param {LockOptions} options
1419
+ * @returns {Promise<boolean>}
855
1420
  */
856
- xLock(fileId, flags) {
857
- return this.handleAsync(async () => {
858
- const file = this.#mapIdToFile.get(fileId);
859
- log(`xLock ${file.path} ${flags}`);
1421
+ #acquire(lockState, name, options = {}) {
1422
+ console.assert(!lockState[name]);
1423
+ return new Promise(resolve => {
1424
+ if (!options.ifAvailable && this.#options.lockTimeout < Infinity) {
1425
+ // Add a timeout to the lock request.
1426
+ const controller = new AbortController();
1427
+ options = Object.assign({}, options, { signal: controller.signal });
1428
+ setTimeout(() => {
1429
+ controller.abort();
1430
+ resolve?.(false);
1431
+ }, this.#options.lockTimeout);
1432
+ }
860
1433
 
861
- try {
862
- // Acquire the lock.
863
- const result = await file.locks.lock(flags);
864
- if (result === _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK && file.locks.state === _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_SHARED) {
865
- // Update block 0 in case another connection changed it.
866
- file.block0 = await this.#idb.run('readonly', ({blocks}) => {
867
- return blocks.get(this.#bound(file, 0));
1434
+ const lockName = `lock##${lockState.baseName}##${name}`;
1435
+ navigator.locks.request(lockName, options, lock => {
1436
+ if (lock) {
1437
+ return new Promise(release => {
1438
+ lockState[name] = () => {
1439
+ release();
1440
+ lockState[name] = null;
1441
+ };
1442
+ resolve(true);
1443
+ resolve = null;
868
1444
  });
1445
+ } else {
1446
+ lockState[name] = null;
1447
+ resolve(false);
1448
+ resolve = null;
869
1449
  }
870
- return result;
871
- } catch (e) {
872
- console.error(e);
873
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
874
- }
1450
+ }).catch(e => {
1451
+ if (e.name !== 'AbortError') throw e;
1452
+ });
875
1453
  });
876
1454
  }
1455
+ }
877
1456
 
878
- /**
879
- * @param {number} fileId
880
- * @param {number} flags
881
- * @returns {number}
882
- */
883
- xUnlock(fileId, flags) {
884
- return this.handleAsync(async () => {
885
- const file = this.#mapIdToFile.get(fileId);
886
- log(`xUnlock ${file.path} ${flags}`);
887
-
888
- try {
889
- return file.locks.unlock(flags);
890
- } catch(e) {
891
- console.error(e);
892
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
893
- }
894
- });
895
- }
1457
+ WebLocksMixin.WRITE_HINT_OP_CODE = -9999;
896
1458
 
897
- /**
898
- * @param {number} fileId
899
- * @param {DataView} pResOut
900
- * @returns {number}
901
- */
902
- xCheckReservedLock(fileId, pResOut) {
903
- const result = this.handleAsync(async () => {
904
- const file = this.#mapIdToFile.get(fileId);
905
- log(`xCheckReservedLock ${file.path}`);
1459
+ /***/ }),
906
1460
 
907
- const isReserved = await file.locks.isSomewhereReserved();
908
- function setOutput(pResOut) {
909
- };
1461
+ /***/ "../../node_modules/@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js":
1462
+ /*!***********************************************************************************!*\
1463
+ !*** ../../node_modules/@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js ***!
1464
+ \***********************************************************************************/
1465
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
910
1466
 
911
- // @ts-ignore
912
- if (pResOut.buffer.detached || !pResOut.buffer.byteLength) {
913
- pResOut = new DataView(new ArrayBuffer(4));
914
- this.#growthHandler = (pResOutNew) => {
915
- pResOutNew.setInt32(0, pResOut.getInt32(0, true), true);
916
- };
917
- }
918
- pResOut.setInt32(0, isReserved ? 1 : 0, true);
919
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
920
- });
1467
+ __webpack_require__.r(__webpack_exports__);
1468
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
1469
+ /* harmony export */ IDBBatchAtomicVFS: () => (/* binding */ IDBBatchAtomicVFS),
1470
+ /* harmony export */ IDBContext: () => (/* binding */ IDBContext)
1471
+ /* harmony export */ });
1472
+ /* harmony import */ var _FacadeVFS_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../FacadeVFS.js */ "../../node_modules/@journeyapps/wa-sqlite/src/FacadeVFS.js");
1473
+ /* harmony import */ var _VFS_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../VFS.js */ "../../node_modules/@journeyapps/wa-sqlite/src/VFS.js");
1474
+ /* harmony import */ var _WebLocksMixin_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../WebLocksMixin.js */ "../../node_modules/@journeyapps/wa-sqlite/src/WebLocksMixin.js");
1475
+ // Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
921
1476
 
922
- this.#growthHandler?.(pResOut);
923
- this.#growthHandler = null;
924
- return result;
925
- }
926
1477
 
927
- /**
928
- * @param {number} fileId
929
- * @returns {number}
930
- */
931
- xSectorSize(fileId) {
932
- log('xSectorSize');
933
- return SECTOR_SIZE;
934
- }
935
1478
 
936
- /**
937
- * @param {number} fileId
938
- * @returns {number}
939
- */
940
- xDeviceCharacteristics(fileId) {
941
- log('xDeviceCharacteristics');
942
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOCAP_BATCH_ATOMIC |
943
- _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOCAP_SAFE_APPEND |
944
- _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOCAP_SEQUENTIAL |
945
- _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
946
- }
947
1479
 
948
- /**
949
- * @param {number} fileId
950
- * @param {number} op
951
- * @param {DataView} pArg
952
- * @returns {number}
953
- */
954
- xFileControl(fileId, op, pArg) {
955
- const file = this.#mapIdToFile.get(fileId);
956
- log(`xFileControl ${file.path} ${op}`);
957
-
958
- switch (op) {
959
- case 11: //SQLITE_FCNTL_OVERWRITE
960
- // This called on VACUUM. Set a flag so we know whether to check
961
- // later if the page size changed.
962
- file.overwrite = true;
963
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
964
-
965
- case 21: // SQLITE_FCNTL_SYNC
966
- // This is called at the end of each database transaction, whether
967
- // it is batch atomic or not. Handle page size changes here.
968
- if (file.overwrite) {
969
- // As an optimization we only check for and handle a page file
970
- // changes if we know a VACUUM has been done because handleAsync()
971
- // has to unwind and rewind the stack. We must be sure to follow
972
- // the same conditional path in both calls.
973
- try {
974
- return this.handleAsync(async () => {
975
- await this.#reblockIfNeeded(file);
976
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
977
- });
978
- } catch (e) {
979
- console.error(e);
980
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
981
- }
982
- }
1480
+ /**
1481
+ * @typedef Metadata
1482
+ * @property {string} name
1483
+ * @property {number} fileSize
1484
+ * @property {number} version
1485
+ * @property {number} [pendingVersion]
1486
+ */
983
1487
 
984
- if (file.isMetadataChanged) {
985
- // Metadata has changed so write block 0 to IndexedDB.
986
- try {
987
- this.#idb.run('readwrite', async ({blocks}) => {
988
- await blocks.put(file.block0);
989
- });
990
- file.isMetadataChanged = false;
991
- } catch (e) {
992
- console.error(e);
993
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
994
- }
995
- }
996
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
997
-
998
- case 22: // SQLITE_FCNTL_COMMIT_PHASETWO
999
- // This is called after a commit is completed.
1000
- file.overwrite = false;
1001
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
1002
-
1003
- case 31: // SQLITE_FCNTL_BEGIN_ATOMIC_WRITE
1004
- return this.handleAsync(async () => {
1005
- try {
1006
- // Prepare a new version for IndexedDB blocks.
1007
- file.block0.version--;
1008
- file.changedPages = new Set();
1009
-
1010
- // Clear blocks from abandoned transactions that would conflict
1011
- // with the new transaction.
1012
- this.#idb.run('readwrite', async ({blocks}) => {
1013
- const keys = await blocks.index('version').getAllKeys(IDBKeyRange.bound(
1014
- [file.path],
1015
- [file.path, file.block0.version]));
1016
- for (const key of keys) {
1017
- blocks.delete(key);
1018
- }
1019
- });
1020
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
1021
- } catch (e) {
1022
- console.error(e);
1023
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
1024
- }
1025
- });
1488
+ class File {
1489
+ /** @type {string} */ path;
1490
+ /** @type {number} */ flags;
1026
1491
 
1027
- case 32: // SQLITE_FCNTL_COMMIT_ATOMIC_WRITE
1028
- try {
1029
- const block0 = Object.assign({}, file.block0);
1030
- block0.data = block0.data.slice();
1031
- const changedPages = file.changedPages;
1032
- file.changedPages = null;
1033
- file.isMetadataChanged = false;
1034
- this.#idb.run('readwrite', async ({blocks})=> {
1035
- // Write block 0 to commit the new version.
1036
- blocks.put(block0);
1037
-
1038
- // Blocks to purge are saved in a special IndexedDB object with
1039
- // an "index" of "purge". Add pages changed by this transaction.
1040
- const purgeBlock = await blocks.get([file.path, 'purge', 0]) ?? {
1041
- path: file.path,
1042
- offset: 'purge',
1043
- version: 0,
1044
- data: new Map(),
1045
- count: 0
1046
- };
1492
+ /** @type {Metadata} */ metadata;
1493
+ /** @type {number} */ fileSize = 0;
1047
1494
 
1048
- purgeBlock.count += changedPages.size;
1049
- for (const pageIndex of changedPages) {
1050
- purgeBlock.data.set(pageIndex, block0.version);
1051
- }
1495
+ /** @type {boolean} */ needsMetadataSync = false;
1496
+ /** @type {Metadata} */ rollback = null;
1497
+ /** @type {Set<number>} */ changedPages = new Set();
1052
1498
 
1053
- blocks.put(purgeBlock);
1054
- this.#maybePurge(file.path, purgeBlock.count);
1055
- });
1056
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
1057
- } catch (e) {
1058
- console.error(e);
1059
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
1060
- }
1499
+ /** @type {string} */ synchronous = 'full';
1500
+ /** @type {IDBTransactionOptions} */ txOptions = { durability: 'strict' };
1061
1501
 
1062
- case 33: // SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE
1063
- return this.handleAsync(async () => {
1064
- try {
1065
- // Restore original state. Objects for the abandoned version will
1066
- // be left in IndexedDB to be removed by the next atomic write
1067
- // transaction.
1068
- file.changedPages = null;
1069
- file.isMetadataChanged = false;
1070
- file.block0 = await this.#idb.run('readonly', ({blocks}) => {
1071
- return blocks.get([file.path, 0, file.block0.version + 1]);
1072
- });
1073
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
1074
- } catch (e) {
1075
- console.error(e);
1076
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
1077
- }
1078
- });
1079
-
1080
- default:
1081
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_NOTFOUND;
1082
- }
1502
+ constructor(path, flags, metadata) {
1503
+ this.path = path;
1504
+ this.flags = flags;
1505
+ this.metadata = metadata;
1083
1506
  }
1507
+ }
1084
1508
 
1085
- /**
1086
- * @param {string} name
1087
- * @param {number} flags
1088
- * @param {DataView} pResOut
1089
- * @returns {number}
1090
- */
1091
- xAccess(name, flags, pResOut) {
1092
- const result = this.handleAsync(async () => {
1093
- try {
1094
- const path = new URL(name, 'file://localhost/').pathname;
1095
- log(`xAccess ${path} ${flags}`);
1509
+ class IDBBatchAtomicVFS extends (0,_WebLocksMixin_js__WEBPACK_IMPORTED_MODULE_2__.WebLocksMixin)(_FacadeVFS_js__WEBPACK_IMPORTED_MODULE_0__.FacadeVFS) {
1510
+ /** @type {Map<number, File>} */ mapIdToFile = new Map();
1511
+ lastError = null;
1096
1512
 
1097
- // Check if block 0 exists.
1098
- const key = await this.#idb.run('readonly', ({blocks}) => {
1099
- return blocks.getKey(this.#bound({path}, 0));
1100
- });
1513
+ log = null; // console.log
1101
1514
 
1102
- // @ts-ignore
1103
- if (pResOut.buffer.detached || !pResOut.buffer.byteLength) {
1104
- pResOut = new DataView(new ArrayBuffer(4));
1105
- this.#growthHandler = (pResOutNew) => {
1106
- pResOutNew.setInt32(0, pResOut.getInt32(0, true), true);
1107
- }
1108
- }
1109
- pResOut.setInt32(0, key ? 1 : 0, true);
1110
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
1111
- } catch (e) {
1112
- console.error(e);
1113
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
1114
- }
1115
- });
1515
+ /** @type {Promise} */ #isReady;
1516
+ /** @type {IDBContext} */ #idb;
1116
1517
 
1117
- this.#growthHandler?.(pResOut);
1118
- this.#growthHandler = null;
1119
- return result;
1518
+ static async create(name, module, options) {
1519
+ const vfs = new IDBBatchAtomicVFS(name, module, options);
1520
+ await vfs.isReady();
1521
+ return vfs;
1120
1522
  }
1121
1523
 
1122
- /**
1123
- * @param {string} name
1124
- * @param {number} syncDir
1125
- * @returns {number}
1126
- */
1127
- xDelete(name, syncDir) {
1128
- return this.handleAsync(async () => {
1129
- const path = new URL(name, 'file://localhost/').pathname;
1130
- log(`xDelete ${path} ${syncDir}`);
1131
-
1132
- try {
1133
- this.#idb.run('readwrite', ({blocks}) => {
1134
- return blocks.delete(IDBKeyRange.bound([path], [path, []]));
1135
- });
1136
- if (syncDir) {
1137
- await this.#idb.sync();
1138
- }
1139
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
1140
- } catch (e) {
1141
- console.error(e);
1142
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR;
1143
- }
1144
- });
1524
+ constructor(name, module, options = {}) {
1525
+ super(name, module, options);
1526
+ this.#isReady = this.#initialize(options.idbName ?? name);
1145
1527
  }
1146
1528
 
1147
- /**
1148
- * Purge obsolete blocks from a database file.
1149
- * @param {string} path
1150
- */
1151
- async purge(path) {
1152
- const start = Date.now();
1153
- await this.#idb.run('readwrite', async ({blocks}) => {
1154
- const purgeBlock = await blocks.get([path, 'purge', 0]);
1155
- if (purgeBlock) {
1156
- for (const [pageOffset, version] of purgeBlock.data) {
1157
- blocks.delete(IDBKeyRange.bound(
1158
- [path, pageOffset, version],
1159
- [path, pageOffset, Infinity],
1160
- true, false));
1161
- }
1162
- await blocks.delete([path, 'purge', 0]);
1163
- }
1164
- log(`purge ${path} ${purgeBlock?.data.size ?? 0} pages in ${Date.now() - start} ms`);
1165
- });
1529
+ async #initialize(name) {
1530
+ this.#idb = await IDBContext.create(name);
1166
1531
  }
1167
1532
 
1168
- /**
1169
- * Conditionally schedule a purge task.
1170
- * @param {string} path
1171
- * @param {number} nPages
1172
- */
1173
- #maybePurge(path, nPages) {
1174
- if (this.#options.purge === 'manual' ||
1175
- this.#pendingPurges.has(path) ||
1176
- nPages < this.#options.purgeAtLeast) {
1177
- // No purge needed.
1178
- return;
1179
- }
1180
-
1181
- if (globalThis.requestIdleCallback) {
1182
- globalThis.requestIdleCallback(() => {
1183
- this.purge(path);
1184
- this.#pendingPurges.delete(path)
1185
- });
1186
- } else {
1187
- setTimeout(() => {
1188
- this.purge(path);
1189
- this.#pendingPurges.delete(path)
1190
- });
1191
- }
1192
- this.#pendingPurges.add(path);
1193
- }
1194
-
1195
- #bound(file, begin, end = 0) {
1196
- // Fetch newest block 0. For other blocks, use block 0 version.
1197
- const version = !begin || -begin < file.block0.data.length ?
1198
- -Infinity :
1199
- file.block0.version;
1200
- return IDBKeyRange.bound(
1201
- [file.path, begin, version],
1202
- [file.path, end, Infinity]);
1203
- }
1204
-
1205
- // The database page size can be changed with PRAGMA page_size and VACUUM.
1206
- // The updated file will be overwritten with a regular transaction using
1207
- // the old page size. After that it will be read and written using the
1208
- // new page size, so the IndexedDB objects must be combined or split
1209
- // appropriately.
1210
- async #reblockIfNeeded(file) {
1211
- const oldPageSize = file.block0.data.length;
1212
- if (oldPageSize < 18) return; // no page size defined
1213
-
1214
- const view = new DataView(file.block0.data.buffer, file.block0.data.byteOffset);
1215
- let newPageSize = view.getUint16(16);
1216
- if (newPageSize === 1) newPageSize = 65536;
1217
- if (newPageSize === oldPageSize) return; // no page size change
1218
-
1219
- const maxPageSize = Math.max(oldPageSize, newPageSize);
1220
- const nOldPages = maxPageSize / oldPageSize;
1221
- const nNewPages = maxPageSize / newPageSize;
1222
-
1223
- const newPageCount = view.getUint32(28);
1224
- const fileSize = newPageCount * newPageSize;
1225
-
1226
- const version = file.block0.version;
1227
- await this.#idb.run('readwrite', async ({blocks}) => {
1228
- // When the block size changes, the entire file is rewritten. Delete
1229
- // all blocks older than block 0 to leave a single version at every
1230
- // offset.
1231
- const keys = await blocks.index('version').getAllKeys(IDBKeyRange.bound(
1232
- [file.path, version + 1],
1233
- [file.path, Infinity]
1234
- ));
1235
- for (const key of keys) {
1236
- blocks.delete(key);
1237
- }
1238
- blocks.delete([file.path, 'purge', 0]);
1239
-
1240
- // Do the conversion in chunks of the larger of the page sizes.
1241
- for (let iOffset = 0; iOffset < fileSize; iOffset += maxPageSize) {
1242
- // Fetch nOldPages. They can be fetched in one request because
1243
- // there is now a single version in the file.
1244
- const oldPages = await blocks.getAll(
1245
- IDBKeyRange.lowerBound([file.path, -(iOffset + maxPageSize), Infinity]),
1246
- nOldPages);
1247
- for (const oldPage of oldPages) {
1248
- blocks.delete([oldPage.path, oldPage.offset, oldPage.version]);
1249
- }
1250
-
1251
- // Convert to new pages.
1252
- if (nNewPages === 1) {
1253
- // Combine nOldPages old pages into a new page.
1254
- const buffer = new Uint8Array(newPageSize);
1255
- for (const oldPage of oldPages) {
1256
- buffer.set(oldPage.data, -(iOffset + oldPage.offset));
1257
- }
1258
- const newPage = {
1259
- path: file.path,
1260
- offset: -iOffset,
1261
- version,
1262
- data: buffer
1263
- };
1264
- if (newPage.offset === 0) {
1265
- newPage.fileSize = fileSize;
1266
- file.block0 = newPage;
1267
- }
1268
- blocks.put(newPage);
1269
- } else {
1270
- // Split an old page into nNewPages new pages.
1271
- const oldPage = oldPages[0];
1272
- for (let i = 0; i < nNewPages; ++i) {
1273
- const offset = -(iOffset + i * newPageSize);
1274
- if (-offset >= fileSize) break;
1275
- const newPage = {
1276
- path: oldPage.path,
1277
- offset,
1278
- version,
1279
- data: oldPage.data.subarray(i * newPageSize, (i + 1) * newPageSize)
1280
- }
1281
- if (newPage.offset === 0) {
1282
- newPage.fileSize = fileSize;
1283
- file.block0 = newPage;
1284
- }
1285
- blocks.put(newPage);
1286
- }
1287
- }
1288
- }
1289
- });
1533
+ close() {
1534
+ this.#idb.close();
1290
1535
  }
1291
- }
1292
-
1293
- function openDatabase(idbDatabaseName) {
1294
- return new Promise((resolve, reject) => {
1295
- const request = globalThis.indexedDB.open(idbDatabaseName, 5);
1296
- request.addEventListener('upgradeneeded', function() {
1297
- const blocks = request.result.createObjectStore('blocks', {
1298
- keyPath: ['path', 'offset', 'version']
1299
- });
1300
- blocks.createIndex('version', ['path', 'version']);
1301
- });
1302
- request.addEventListener('success', () => {
1303
- resolve(request.result);
1304
- });
1305
- request.addEventListener('error', () => {
1306
- reject(request.error);
1307
- });
1308
- });
1309
- }
1310
-
1311
- /***/ }),
1312
-
1313
- /***/ "../../node_modules/@journeyapps/wa-sqlite/src/examples/IDBContext.js":
1314
- /*!****************************************************************************!*\
1315
- !*** ../../node_modules/@journeyapps/wa-sqlite/src/examples/IDBContext.js ***!
1316
- \****************************************************************************/
1317
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
1318
-
1319
- __webpack_require__.r(__webpack_exports__);
1320
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
1321
- /* harmony export */ IDBContext: () => (/* binding */ IDBContext)
1322
- /* harmony export */ });
1323
- // Copyright 2022 Roy T. Hashimoto. All Rights Reserved.
1324
-
1325
- // IndexedDB transactions older than this will be replaced.
1326
- const MAX_TRANSACTION_LIFETIME_MILLIS = 5_000;
1327
-
1328
- // For debugging.
1329
- let nextTxId = 0;
1330
- const mapTxToId = new WeakMap();
1331
- function log(...args) {
1332
- // console.debug(...args);
1333
- }
1334
-
1335
- // This class manages IDBTransaction and IDBRequest instances. It tries
1336
- // to reuse transactions to minimize transaction overhead.
1337
- class IDBContext {
1338
- /** @type {IDBDatabase} */ #db;
1339
- /** @type {Promise<IDBDatabase>} */ #dbReady;
1340
- #txOptions;
1341
-
1342
- /** @type {IDBTransaction} */ #tx = null;
1343
- #txTimestamp = 0;
1344
- #runChain = Promise.resolve();
1345
- #putChain = Promise.resolve();
1346
-
1347
- /**
1348
- * @param {IDBDatabase|Promise<IDBDatabase>} idbDatabase
1349
- */
1350
- constructor(idbDatabase, txOptions = { durability: 'default' }) {
1351
- this.#dbReady = Promise.resolve(idbDatabase).then(db => this.#db = db);
1352
- this.#txOptions = txOptions;
1536
+
1537
+ async isReady() {
1538
+ await super.isReady();
1539
+ await this.#isReady;
1353
1540
  }
1354
1541
 
1355
- async close() {
1356
- const db = this.#db ?? await this.#dbReady;
1357
- await this.#runChain;
1358
- await this.sync();
1359
- db.close();
1542
+ getFilename(fileId) {
1543
+ const pathname = this.mapIdToFile.get(fileId).path;
1544
+ return `IDB(${this.name}):${pathname}`
1360
1545
  }
1361
1546
 
1362
1547
  /**
1363
- * Run a function with the provided object stores. The function
1364
- * should be idempotent in case it is passed an expired transaction.
1365
- * @param {IDBTransactionMode} mode
1366
- * @param {(stores: Object.<string, ObjectStore>) => any} f
1367
- */
1368
- async run(mode, f) {
1369
- // Ensure that functions run sequentially.
1370
- const result = this.#runChain.then(() => this.#run(mode, f));
1371
- this.#runChain = result.catch(() => {});
1372
- return result;
1373
- }
1374
-
1375
- /**
1376
- * @param {IDBTransactionMode} mode
1377
- * @param {(stores: Object.<string, ObjectStore>) => any} f
1378
- * @returns
1548
+ * @param {string?} zName
1549
+ * @param {number} fileId
1550
+ * @param {number} flags
1551
+ * @param {DataView} pOutFlags
1552
+ * @returns {Promise<number>}
1379
1553
  */
1380
- async #run(mode, f) {
1381
- const db = this.#db ?? await this.#dbReady;
1382
- if (mode === 'readwrite' && this.#tx?.mode === 'readonly') {
1383
- // Mode requires a new transaction.
1384
- this.#tx = null;
1385
- } else if (performance.now() - this.#txTimestamp > MAX_TRANSACTION_LIFETIME_MILLIS) {
1386
- // Chrome times out transactions after 60 seconds so refresh preemptively.
1387
- try {
1388
- this.#tx?.commit();
1389
- } catch (e) {
1390
- // Explicit commit can fail but this can be ignored if it will
1391
- // auto-commit anyway.
1392
- if (e.name !== 'InvalidStateError') throw e;
1554
+ async jOpen(zName, fileId, flags, pOutFlags) {
1555
+ try {
1556
+ const url = new URL(zName || Math.random().toString(36).slice(2), 'file://');
1557
+ const path = url.pathname;
1558
+
1559
+ let meta = await this.#idb.q(({ metadata }) => metadata.get(path));
1560
+ if (!meta && (flags & _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OPEN_CREATE)) {
1561
+ meta = {
1562
+ name: path,
1563
+ fileSize: 0,
1564
+ version: 0
1565
+ };
1566
+ await this.#idb.q(({ metadata }) => metadata.put(meta), 'rw');
1393
1567
  }
1394
-
1395
- // Skip to the next task to allow processing.
1396
- await new Promise(resolve => setTimeout(resolve));
1397
- this.#tx = null;
1398
- }
1399
-
1400
- // Run the user function with a retry in case the transaction is invalid.
1401
- for (let i = 0; i < 2; ++i) {
1402
- if (!this.#tx) {
1403
- // @ts-ignore
1404
- this.#tx = db.transaction(db.objectStoreNames, mode, this.#txOptions);
1405
- const timestamp = this.#txTimestamp = performance.now();
1406
-
1407
- // Chain the result of every transaction. If any transaction is
1408
- // aborted then the next sync() call will throw.
1409
- this.#putChain = this.#putChain.then(() => {
1410
- return new Promise((resolve, reject) => {
1411
- this.#tx.addEventListener('complete', event => {
1412
- resolve();
1413
- if (this.#tx === event.target) {
1414
- this.#tx = null;
1415
- }
1416
- log(`transaction ${mapTxToId.get(event.target)} complete`);
1417
- });
1418
- this.#tx.addEventListener('abort', event => {
1419
- console.warn('tx abort', (performance.now() - timestamp)/1000);
1420
- // @ts-ignore
1421
- const e = event.target.error;
1422
- reject(e);
1423
- if (this.#tx === event.target) {
1424
- this.#tx = null;
1425
- }
1426
- log(`transaction ${mapTxToId.get(event.target)} aborted`, e);
1427
- });
1428
- });
1429
- });
1430
-
1431
- log(`new transaction ${nextTxId} ${mode}`);
1432
- mapTxToId.set(this.#tx, nextTxId++);
1568
+
1569
+ if (!meta) {
1570
+ throw new Error(`File ${path} not found`);
1433
1571
  }
1434
1572
 
1435
- try {
1436
- const stores = Object.fromEntries(Array.from(db.objectStoreNames, name => {
1437
- return [name, new ObjectStore(this.#tx.objectStore(name))];
1438
- }));
1439
- return await f(stores);
1440
- } catch (e) {
1441
- this.#tx = null;
1442
- if (i) throw e;
1443
- // console.warn('retrying with new transaction');
1444
- }
1573
+ const file = new File(path, flags, meta);
1574
+ this.mapIdToFile.set(fileId, file);
1575
+ pOutFlags.setInt32(0, flags, true);
1576
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK;
1577
+ } catch (e) {
1578
+ this.lastError = e;
1579
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_CANTOPEN;
1445
1580
  }
1446
1581
  }
1447
1582
 
1448
- async sync() {
1449
- // Wait until all transactions since the previous sync have committed.
1450
- // Throw if any transaction failed.
1451
- await this.#runChain;
1452
- await this.#putChain;
1453
- this.#putChain = Promise.resolve();
1454
- }
1455
- }
1456
-
1457
- /**
1458
- * Helper to convert IDBRequest to Promise.
1459
- * @param {IDBRequest} request
1460
- * @returns {Promise}
1461
- */
1462
- function wrapRequest(request) {
1463
- return new Promise((resolve, reject) => {
1464
- request.addEventListener('success', () => resolve(request.result));
1465
- request.addEventListener('error', () => reject(request.error));
1466
- });
1467
- }
1468
-
1469
- // IDBObjectStore wrapper passed to IDBContext run functions.
1470
- class ObjectStore {
1471
- #objectStore;
1472
-
1473
1583
  /**
1474
- * @param {IDBObjectStore} objectStore
1475
- */
1476
- constructor(objectStore) {
1477
- this.#objectStore = objectStore;
1478
- }
1479
-
1480
- /**
1481
- * @param {IDBValidKey|IDBKeyRange} query
1482
- * @returns {Promise}
1584
+ * @param {string} zName
1585
+ * @param {number} syncDir
1586
+ * @returns {Promise<number>}
1483
1587
  */
1484
- get(query) {
1485
- log(`get ${this.#objectStore.name}`, query);
1486
- const request = this.#objectStore.get(query);
1487
- return wrapRequest(request);
1488
- }
1588
+ async jDelete(zName, syncDir) {
1589
+ try {
1590
+ const url = new URL(zName, 'file://');
1591
+ const path = url.pathname;
1489
1592
 
1490
- /**
1491
- * @param {IDBValidKey|IDBKeyRange} query
1492
- * @param {number} [count]
1493
- * @returns {Promise}
1494
- */
1495
- getAll(query, count) {
1496
- log(`getAll ${this.#objectStore.name}`, query, count);
1497
- const request = this.#objectStore.getAll(query, count);
1498
- return wrapRequest(request);
1499
- }
1593
+ this.#idb.q(({ metadata, blocks }) => {
1594
+ const range = IDBKeyRange.bound([path, -Infinity], [path, Infinity]);
1595
+ blocks.delete(range);
1596
+ metadata.delete(path);
1597
+ }, 'rw');
1500
1598
 
1501
- /**
1502
- * @param {IDBValidKey|IDBKeyRange} query
1503
- * @returns {Promise<IDBValidKey>}
1504
- */
1505
- getKey(query) {
1506
- log(`getKey ${this.#objectStore.name}`, query);
1507
- const request = this.#objectStore.getKey(query);
1508
- return wrapRequest(request);
1599
+ if (syncDir) {
1600
+ await this.#idb.sync(false);
1601
+ }
1602
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK;
1603
+ } catch (e) {
1604
+ this.lastError = e;
1605
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_IOERR_DELETE;
1606
+ }
1509
1607
  }
1510
1608
 
1511
1609
  /**
1512
- * @param {IDBValidKey|IDBKeyRange} query
1513
- * @param {number} [count]
1514
- * @returns {Promise}
1610
+ * @param {string} zName
1611
+ * @param {number} flags
1612
+ * @param {DataView} pResOut
1613
+ * @returns {Promise<number>}
1515
1614
  */
1516
- getAllKeys(query, count) {
1517
- log(`getAllKeys ${this.#objectStore.name}`, query, count);
1518
- const request = this.#objectStore.getAllKeys(query, count);
1519
- return wrapRequest(request);
1520
- }
1615
+ async jAccess(zName, flags, pResOut) {
1616
+ try {
1617
+ const url = new URL(zName, 'file://');
1618
+ const path = url.pathname;
1521
1619
 
1522
- /**
1523
- * @param {any} value
1524
- * @param {IDBValidKey} [key]
1525
- * @returns {Promise}
1526
- */
1527
- put(value, key) {
1528
- log(`put ${this.#objectStore.name}`, value, key);
1529
- const request = this.#objectStore.put(value, key);
1530
- return wrapRequest(request);
1620
+ const meta = await this.#idb.q(({ metadata }) => metadata.get(path));
1621
+ pResOut.setInt32(0, meta ? 1 : 0, true);
1622
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK;
1623
+ } catch (e) {
1624
+ this.lastError = e;
1625
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_IOERR_ACCESS;
1626
+ }
1531
1627
  }
1532
1628
 
1533
1629
  /**
1534
- * @param {IDBValidKey|IDBKeyRange} query
1535
- * @returns {Promise}
1630
+ * @param {number} fileId
1631
+ * @returns {Promise<number>}
1536
1632
  */
1537
- delete(query) {
1538
- log(`delete ${this.#objectStore.name}`, query);
1539
- const request = this.#objectStore.delete(query);
1540
- return wrapRequest(request);
1541
- }
1542
-
1543
- clear() {
1544
- log(`clear ${this.#objectStore.name}`);
1545
- const request = this.#objectStore.clear();
1546
- return wrapRequest(request);
1547
- }
1633
+ async jClose(fileId) {
1634
+ try {
1635
+ const file = this.mapIdToFile.get(fileId);
1636
+ this.mapIdToFile.delete(fileId);
1637
+
1638
+ if (file.flags & _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OPEN_DELETEONCLOSE) {
1639
+ await this.#idb.q(({ metadata, blocks }) => {
1640
+ metadata.delete(file.path);
1641
+ blocks.delete(IDBKeyRange.bound([file.path, 0], [file.path, Infinity]));
1642
+ }, 'rw');
1643
+ }
1548
1644
 
1549
- index(name) {
1550
- return new Index(this.#objectStore.index(name));
1645
+ if (file.needsMetadataSync) {
1646
+ this.#idb.q(({ metadata }) => metadata.put(file.metadata), 'rw');
1647
+ }
1648
+ await this.#idb.sync(file.synchronous === 'full');
1649
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK;
1650
+ } catch (e) {
1651
+ this.lastError = e;
1652
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_IOERR_CLOSE;
1653
+ }
1551
1654
  }
1552
- }
1553
-
1554
- class Index {
1555
- /** @type {IDBIndex} */ #index;
1556
1655
 
1557
1656
  /**
1558
- * @param {IDBIndex} index
1657
+ * @param {number} fileId
1658
+ * @param {Uint8Array} pData
1659
+ * @param {number} iOffset
1660
+ * @returns {Promise<number>}
1559
1661
  */
1560
- constructor(index) {
1561
- this.#index = index;
1662
+ async jRead(fileId, pData, iOffset) {
1663
+ try {
1664
+ const file = this.mapIdToFile.get(fileId);
1665
+
1666
+ let pDataOffset = 0;
1667
+ while (pDataOffset < pData.byteLength) {
1668
+ // Fetch the IndexedDB block for this file location.
1669
+ const fileOffset = iOffset + pDataOffset;
1670
+ const block = await this.#idb.q(({ blocks }) => {
1671
+ const range = IDBKeyRange.bound([file.path, -fileOffset], [file.path, Infinity]);
1672
+ return blocks.get(range);
1673
+ });
1674
+
1675
+ if (!block || block.data.byteLength - block.offset <= fileOffset) {
1676
+ pData.fill(0, pDataOffset);
1677
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_IOERR_SHORT_READ;
1678
+ }
1679
+
1680
+ // Copy block data.
1681
+ const dst = pData.subarray(pDataOffset);
1682
+ const srcOffset = fileOffset + block.offset;
1683
+ const nBytesToCopy = Math.min(
1684
+ Math.max(block.data.byteLength - srcOffset, 0),
1685
+ dst.byteLength);
1686
+ dst.set(block.data.subarray(srcOffset, srcOffset + nBytesToCopy));
1687
+ pDataOffset += nBytesToCopy;
1688
+ }
1689
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK;
1690
+ } catch (e) {
1691
+ this.lastError = e;
1692
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_IOERR_READ;
1693
+ }
1562
1694
  }
1563
1695
 
1564
1696
  /**
1565
- * @param {IDBValidKey|IDBKeyRange} query
1566
- * @param {number} [count]
1567
- * @returns {Promise<IDBValidKey[]>}
1697
+ * @param {number} fileId
1698
+ * @param {Uint8Array} pData
1699
+ * @param {number} iOffset
1700
+ * @returns {number}
1568
1701
  */
1569
- getAllKeys(query, count) {
1570
- log(`IDBIndex.getAllKeys ${this.#index.objectStore.name}<${this.#index.name}>`, query, count);
1571
- const request = this.#index.getAllKeys(query, count);
1572
- return wrapRequest(request);
1573
- }
1574
- }
1575
-
1576
- /***/ }),
1577
-
1578
- /***/ "../../node_modules/@journeyapps/wa-sqlite/src/examples/WebLocks.js":
1579
- /*!**************************************************************************!*\
1580
- !*** ../../node_modules/@journeyapps/wa-sqlite/src/examples/WebLocks.js ***!
1581
- \**************************************************************************/
1582
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
1583
-
1584
- __webpack_require__.r(__webpack_exports__);
1585
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
1586
- /* harmony export */ WebLocksBase: () => (/* binding */ WebLocksBase),
1587
- /* harmony export */ WebLocksExclusive: () => (/* binding */ WebLocksExclusive),
1588
- /* harmony export */ WebLocksShared: () => (/* binding */ WebLocksShared)
1589
- /* harmony export */ });
1590
- /* harmony import */ var _VFS_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../VFS.js */ "../../node_modules/@journeyapps/wa-sqlite/src/VFS.js");
1591
- // Copyright 2022 Roy T. Hashimoto. All Rights Reserved.
1702
+ jWrite(fileId, pData, iOffset) {
1703
+ try {
1704
+ const file = this.mapIdToFile.get(fileId);
1705
+ if (file.flags & _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OPEN_MAIN_DB) {
1706
+ if (!file.rollback) {
1707
+ // Begin a new write transaction.
1708
+ // Add pendingVersion to the metadata in IndexedDB. If we crash
1709
+ // during the transaction, this lets subsequent connections
1710
+ // know to remove blocks from the failed transaction.
1711
+ const pending = Object.assign(
1712
+ { pendingVersion: file.metadata.version - 1 },
1713
+ file.metadata);
1714
+ this.#idb.q(({ metadata }) => metadata.put(pending), 'rw', file.txOptions);
1715
+
1716
+ file.rollback = Object.assign({}, file.metadata);
1717
+ file.metadata.version--;
1718
+ }
1719
+ }
1592
1720
 
1721
+ if (file.flags & _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OPEN_MAIN_DB) {
1722
+ file.changedPages.add(iOffset);
1723
+ }
1593
1724
 
1594
- const LOCK_TYPE_MASK =
1595
- _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_NONE |
1596
- _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_SHARED |
1597
- _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_RESERVED |
1598
- _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_PENDING |
1599
- _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_EXCLUSIVE;
1725
+ const data = pData.slice();
1726
+ const version = file.metadata.version;
1727
+ const isOverwrite = iOffset < file.metadata.fileSize;
1728
+ if (!isOverwrite ||
1729
+ file.flags & _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OPEN_MAIN_DB ||
1730
+ file.flags & _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OPEN_TEMP_DB) {
1731
+ const block = {
1732
+ path: file.path,
1733
+ offset: -iOffset,
1734
+ version: version,
1735
+ data: pData.slice()
1736
+ };
1737
+ this.#idb.q(({ blocks }) => {
1738
+ blocks.put(block);
1739
+ file.changedPages.add(iOffset);
1740
+ }, 'rw', file.txOptions);
1741
+ } else {
1742
+ this.#idb.q(async ({ blocks }) => {
1743
+ // Read the existing block.
1744
+ const range = IDBKeyRange.bound(
1745
+ [file.path, -iOffset],
1746
+ [file.path, Infinity]);
1747
+ const block = await blocks.get(range);
1748
+
1749
+ // Modify the block data.
1750
+ // @ts-ignore
1751
+ block.data.subarray(iOffset + block.offset).set(data);
1600
1752
 
1601
- class WebLocksBase {
1602
- get state() { return this.#state; }
1603
- #state = _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_NONE;
1753
+ // Write back.
1754
+ blocks.put(block);
1755
+ }, 'rw', file.txOptions);
1604
1756
 
1605
- timeoutMillis = 0;
1757
+ }
1606
1758
 
1607
- /** @type {Map<string, (value: any) => void>} */ #releasers = new Map();
1608
- /** @type {Promise<0|5|3850>} */ #pending = Promise.resolve(0);
1759
+ if (file.metadata.fileSize < iOffset + pData.length) {
1760
+ file.metadata.fileSize = iOffset + pData.length;
1761
+ file.needsMetadataSync = true;
1762
+ }
1763
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK;
1764
+ } catch (e) {
1765
+ this.lastError = e;
1766
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_IOERR_WRITE;
1767
+ }
1768
+ }
1609
1769
 
1610
1770
  /**
1611
- * @param {number} flags
1612
- * @returns {Promise<0|5|3850>} SQLITE_OK, SQLITE_BUSY, SQLITE_IOERR_LOCK
1771
+ * @param {number} fileId
1772
+ * @param {number} iSize
1773
+ * @returns {number}
1613
1774
  */
1614
- async lock(flags) {
1615
- return this.#apply(this.#lock, flags);
1775
+ jTruncate(fileId, iSize) {
1776
+ try {
1777
+ const file = this.mapIdToFile.get(fileId);
1778
+ if (iSize < file.metadata.fileSize) {
1779
+ this.#idb.q(({ blocks }) => {
1780
+ const range = IDBKeyRange.bound(
1781
+ [file.path, -Infinity],
1782
+ [file.path, -iSize, Infinity]);
1783
+ blocks.delete(range);
1784
+ }, 'rw', file.txOptions);
1785
+ file.metadata.fileSize = iSize;
1786
+ file.needsMetadataSync = true;
1787
+ }
1788
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK;
1789
+ } catch (e) {
1790
+ this.lastError = e;
1791
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_IOERR_TRUNCATE;
1792
+ }
1616
1793
  }
1617
1794
 
1618
1795
  /**
1796
+ * @param {number} fileId
1619
1797
  * @param {number} flags
1620
- * @returns {Promise<0|5|3850>} SQLITE_OK, SQLITE_IOERR_LOCK
1798
+ * @returns {Promise<number>}
1621
1799
  */
1622
- async unlock(flags) {
1623
- return this.#apply(this.#unlock, flags);
1624
- }
1800
+ async jSync(fileId, flags) {
1801
+ try {
1802
+ const file = this.mapIdToFile.get(fileId);
1803
+ if (file.needsMetadataSync) {
1804
+ this.#idb.q(({ metadata }) => metadata.put(file.metadata), 'rw', file.txOptions);
1805
+ file.needsMetadataSync = false;
1806
+ }
1625
1807
 
1626
- /**
1627
- * @returns {Promise<boolean>}
1628
- */
1629
- async isSomewhereReserved() {
1630
- throw new Error('unimplemented');
1808
+ if (file.flags & _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OPEN_MAIN_DB) {
1809
+ // Sync is only needed here for durability. Visibility for other
1810
+ // connections is ensured in jUnlock().
1811
+ if (file.synchronous === 'full') {
1812
+ await this.#idb.sync(true);
1813
+ }
1814
+ } else {
1815
+ await this.#idb.sync(file.synchronous === 'full');
1816
+ }
1817
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK;
1818
+ } catch (e) {
1819
+ this.lastError = e;
1820
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_IOERR_FSYNC;
1821
+ }
1631
1822
  }
1632
1823
 
1633
1824
  /**
1634
- *
1635
- * @param {(targetState: number) => void} method
1636
- * @param {number} flags
1825
+ * @param {number} fileId
1826
+ * @param {DataView} pSize64
1827
+ * @returns {number}
1637
1828
  */
1638
- async #apply(method, flags) {
1639
- const targetState = flags & LOCK_TYPE_MASK;
1829
+ jFileSize(fileId, pSize64) {
1640
1830
  try {
1641
- // Force locks and unlocks to run sequentially. This allows not
1642
- // waiting for unlocks to complete.
1643
- const call = () => method.call(this, targetState);
1644
- await (this.#pending = this.#pending.then(call, call));
1645
- this.#state = targetState;
1646
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
1831
+ const file = this.mapIdToFile.get(fileId);
1832
+ pSize64.setBigInt64(0, BigInt(file.metadata.fileSize), true);
1833
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK;
1647
1834
  } catch (e) {
1648
- if (e.name === 'AbortError') {
1649
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_BUSY;
1650
- }
1651
- console.error(e);
1652
- return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_IOERR_LOCK;
1835
+ this.lastError = e;
1836
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_IOERR_FSTAT;
1653
1837
  }
1654
1838
  }
1655
1839
 
1656
- async #lock(targetState) {
1657
- if (targetState === this.#state) return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
1658
- switch (this.#state) {
1659
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_NONE:
1660
- switch (targetState) {
1661
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_SHARED:
1662
- return this._NONEtoSHARED();
1663
- default:
1664
- throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);
1665
- }
1840
+ /**
1841
+ * @param {number} fileId
1842
+ * @param {number} lockType
1843
+ * @returns {Promise<number>}
1844
+ */
1845
+ async jLock(fileId, lockType) {
1846
+ // Call the actual lock implementation.
1847
+ const file = this.mapIdToFile.get(fileId);
1848
+ const result = await super.jLock(fileId, lockType);
1849
+
1850
+ if (lockType === _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_LOCK_SHARED) {
1851
+ // Update metadata.
1852
+ file.metadata = await this.#idb.q(async ({ metadata, blocks }) => {
1853
+ // @ts-ignore
1854
+ /** @type {Metadata} */ const m = await metadata.get(file.path);
1855
+ if (m.pendingVersion) {
1856
+ console.warn(`removing failed transaction ${m.pendingVersion}`);
1857
+ await new Promise((resolve, reject) => {
1858
+ const range = IDBKeyRange.bound([m.name, -Infinity], [m.name, Infinity]);
1859
+ const request = blocks.openCursor(range);
1860
+ request.onsuccess = () => {
1861
+ const cursor = request.result;
1862
+ if (cursor) {
1863
+ const block = cursor.value;
1864
+ if (block.version < m.version) {
1865
+ cursor.delete();
1866
+ }
1867
+ cursor.continue();
1868
+ } else {
1869
+ resolve();
1870
+ }
1871
+ };
1872
+ request.onerror = () => reject(request.error);
1873
+ })
1666
1874
 
1667
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_SHARED:
1668
- switch (targetState) {
1669
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_RESERVED:
1670
- return this._SHAREDtoRESERVED();
1671
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_EXCLUSIVE:
1672
- return this._SHAREDtoEXCLUSIVE();
1673
- default:
1674
- throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);
1675
- }
1676
-
1677
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_RESERVED:
1678
- switch (targetState) {
1679
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_EXCLUSIVE:
1680
- return this._RESERVEDtoEXCLUSIVE();
1681
- default:
1682
- throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);
1875
+ delete m.pendingVersion;
1876
+ metadata.put(m);
1683
1877
  }
1684
-
1685
- default:
1686
- throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);
1878
+ return m;
1879
+ }, 'rw', file.txOptions);
1687
1880
  }
1881
+ return result;
1688
1882
  }
1689
1883
 
1690
- async #unlock(targetState) {
1691
- if (targetState === this.#state) return _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_OK;
1692
- switch (this.#state) {
1693
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_EXCLUSIVE:
1694
- switch (targetState) {
1695
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_SHARED:
1696
- return this._EXCLUSIVEtoSHARED();
1697
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_NONE:
1698
- return this._EXCLUSIVEtoNONE();
1699
- default:
1700
- throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);
1701
- }
1702
-
1703
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_RESERVED:
1704
- switch (targetState) {
1705
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_SHARED:
1706
- return this._RESERVEDtoSHARED();
1707
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_NONE:
1708
- return this._RESERVEDtoNONE();
1709
- default:
1710
- throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);
1711
- }
1712
-
1713
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_SHARED:
1714
- switch (targetState) {
1715
- case _VFS_js__WEBPACK_IMPORTED_MODULE_0__.SQLITE_LOCK_NONE:
1716
- return this._SHAREDtoNONE();
1717
- default:
1718
- throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);
1719
- }
1720
-
1721
- default:
1722
- throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);
1884
+ /**
1885
+ * @param {number} fileId
1886
+ * @param {number} lockType
1887
+ * @returns {Promise<number>}
1888
+ */
1889
+ async jUnlock(fileId, lockType) {
1890
+ if (lockType === _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_LOCK_NONE) {
1891
+ const file = this.mapIdToFile.get(fileId);
1892
+ await this.#idb.sync(file.synchronous === 'full');
1723
1893
  }
1724
- }
1725
1894
 
1726
- async _NONEtoSHARED() {
1727
- }
1728
-
1729
- async _SHAREDtoEXCLUSIVE() {
1730
- await this._SHAREDtoRESERVED();
1731
- await this._RESERVEDtoEXCLUSIVE();
1732
- }
1733
-
1734
- async _SHAREDtoRESERVED() {
1735
- }
1736
-
1737
- async _RESERVEDtoEXCLUSIVE() {
1738
- }
1739
-
1740
- async _EXCLUSIVEtoRESERVED() {
1741
- }
1742
-
1743
- async _EXCLUSIVEtoSHARED() {
1744
- await this._EXCLUSIVEtoRESERVED();
1745
- await this._RESERVEDtoSHARED();
1746
- }
1747
-
1748
- async _EXCLUSIVEtoNONE() {
1749
- await this._EXCLUSIVEtoRESERVED();
1750
- await this._RESERVEDtoSHARED();
1751
- await this._SHAREDtoNONE();
1752
- }
1753
-
1754
- async _RESERVEDtoSHARED() {
1755
- }
1756
-
1757
- async _RESERVEDtoNONE() {
1758
- await this._RESERVEDtoSHARED();
1759
- await this._SHAREDtoNONE();
1760
- }
1761
-
1762
- async _SHAREDtoNONE() {
1895
+ // Call the actual unlock implementation.
1896
+ return super.jUnlock(fileId, lockType);
1763
1897
  }
1764
1898
 
1765
1899
  /**
1766
- * @param {string} lockName
1767
- * @param {LockOptions} options
1768
- * @returns {Promise<?Lock>}
1900
+ * @param {number} fileId
1901
+ * @param {number} op
1902
+ * @param {DataView} pArg
1903
+ * @returns {number|Promise<number>}
1769
1904
  */
1770
- _acquireWebLock(lockName, options) {
1771
- return new Promise(async (resolve, reject) => {
1772
- try {
1773
- await navigator.locks.request(lockName, options, lock => {
1774
- resolve(lock);
1775
- if (lock) {
1776
- return new Promise(release => this.#releasers.set(lockName, release));
1777
- }
1778
- });
1779
- } catch(e) {
1780
- reject(e);
1905
+ jFileControl(fileId, op, pArg) {
1906
+ try {
1907
+ const file = this.mapIdToFile.get(fileId);
1908
+ switch (op) {
1909
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_FCNTL_PRAGMA:
1910
+ const key = extractString(pArg, 4);
1911
+ const value = extractString(pArg, 8);
1912
+ this.log?.('xFileControl', file.path, 'PRAGMA', key, value);
1913
+ const setPragmaResponse = response => {
1914
+ const encoded = new TextEncoder().encode(response);
1915
+ const out = this._module._sqlite3_malloc(encoded.byteLength);
1916
+ const outArray = this._module.HEAPU8.subarray(out, out + encoded.byteLength);
1917
+ outArray.set(encoded);
1918
+ pArg.setUint32(0, out, true);
1919
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_ERROR;
1920
+ };
1921
+ switch (key.toLowerCase()) {
1922
+ case 'page_size':
1923
+ if (file.flags & _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OPEN_MAIN_DB) {
1924
+ // Don't allow changing the page size.
1925
+ if (value && file.metadata.fileSize) {
1926
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_ERROR;
1927
+ }
1928
+ }
1929
+ break;
1930
+ case 'synchronous':
1931
+ if (value) {
1932
+ switch (value.toLowerCase()) {
1933
+ case '0':
1934
+ case 'off':
1935
+ file.synchronous = 'off';
1936
+ file.txOptions = { durability: 'relaxed' };
1937
+ break;
1938
+ case '1':
1939
+ case 'normal':
1940
+ file.synchronous = 'normal';
1941
+ file.txOptions = { durability: 'relaxed' };
1942
+ break;
1943
+ case '2':
1944
+ case '3':
1945
+ case 'full':
1946
+ case 'extra':
1947
+ file.synchronous = 'full';
1948
+ file.txOptions = { durability: 'strict' };
1949
+ break;
1950
+ }
1951
+ }
1952
+ break;
1953
+ case 'write_hint':
1954
+ return super.jFileControl(fileId, _WebLocksMixin_js__WEBPACK_IMPORTED_MODULE_2__.WebLocksMixin.WRITE_HINT_OP_CODE, null);
1955
+ }
1956
+ break;
1957
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_FCNTL_SYNC:
1958
+ this.log?.('xFileControl', file.path, 'SYNC');
1959
+ const commitMetadata = Object.assign({}, file.metadata);
1960
+ const prevFileSize = file.rollback.fileSize
1961
+ this.#idb.q(({ metadata, blocks }) => {
1962
+ metadata.put(commitMetadata);
1963
+
1964
+ // Remove old page versions.
1965
+ for (const offset of file.changedPages) {
1966
+ if (offset < prevFileSize) {
1967
+ const range = IDBKeyRange.bound(
1968
+ [file.path, -offset, commitMetadata.version],
1969
+ [file.path, -offset, Infinity],
1970
+ true);
1971
+ blocks.delete(range);
1972
+ }
1973
+ }
1974
+ file.changedPages.clear();
1975
+ }, 'rw', file.txOptions);
1976
+ file.needsMetadataSync = false;
1977
+ file.rollback = null;
1978
+ break;
1979
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_FCNTL_BEGIN_ATOMIC_WRITE:
1980
+ // Every write transaction is atomic, so this is a no-op.
1981
+ this.log?.('xFileControl', file.path, 'BEGIN_ATOMIC_WRITE');
1982
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK;
1983
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_FCNTL_COMMIT_ATOMIC_WRITE:
1984
+ // Every write transaction is atomic, so this is a no-op.
1985
+ this.log?.('xFileControl', file.path, 'COMMIT_ATOMIC_WRITE');
1986
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK;
1987
+ case _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE:
1988
+ this.log?.('xFileControl', file.path, 'ROLLBACK_ATOMIC_WRITE');
1989
+ file.metadata = file.rollback;
1990
+ const rollbackMetadata = Object.assign({}, file.metadata);
1991
+ this.#idb.q(({ metadata, blocks }) => {
1992
+ metadata.put(rollbackMetadata);
1993
+
1994
+ // Remove pages.
1995
+ for (const offset of file.changedPages) {
1996
+ blocks.delete([file.path, -offset, rollbackMetadata.version - 1]);
1997
+ }
1998
+ file.changedPages.clear();
1999
+ }, 'rw', file.txOptions);
2000
+ file.needsMetadataSync = false;
2001
+ file.rollback = null;
2002
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK;
1781
2003
  }
1782
- });
1783
- }
1784
-
1785
- /**
1786
- * @param {string} lockName
1787
- */
1788
- _releaseWebLock(lockName) {
1789
- this.#releasers.get(lockName)?.();
1790
- this.#releasers.delete(lockName);
2004
+ } catch (e) {
2005
+ this.lastError = e;
2006
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_IOERR;
2007
+ }
2008
+ return super.jFileControl(fileId, op, pArg);
1791
2009
  }
1792
-
2010
+
1793
2011
  /**
1794
- * @param {string} lockName
2012
+ * @param {number} pFile
2013
+ * @returns {number|Promise<number>}
1795
2014
  */
1796
- async _pollWebLock(lockName) {
1797
- const query = await navigator.locks.query();
1798
- return query.held.find(({name}) => name === lockName)?.mode;
2015
+ jDeviceCharacteristics(pFile) {
2016
+ return 0
2017
+ | _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_IOCAP_BATCH_ATOMIC
2018
+ | _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
1799
2019
  }
1800
2020
 
1801
2021
  /**
1802
- * @returns {?AbortSignal}
2022
+ * @param {Uint8Array} zBuf
2023
+ * @returns {number|Promise<number>}
1803
2024
  */
1804
- _getTimeoutSignal() {
1805
- if (this.timeoutMillis) {
1806
- const abortController = new AbortController();
1807
- setTimeout(() => abortController.abort(), this.timeoutMillis);
1808
- return abortController.signal;
2025
+ jGetLastError(zBuf) {
2026
+ if (this.lastError) {
2027
+ console.error(this.lastError);
2028
+ const outputArray = zBuf.subarray(0, zBuf.byteLength - 1);
2029
+ const { written } = new TextEncoder().encodeInto(this.lastError.message, outputArray);
2030
+ zBuf[written] = 0;
1809
2031
  }
1810
- return undefined;
2032
+ return _VFS_js__WEBPACK_IMPORTED_MODULE_1__.SQLITE_OK
1811
2033
  }
1812
2034
  }
1813
2035
 
1814
- class WebLocksExclusive extends WebLocksBase {
1815
- /**
1816
- * @param {string} name
1817
- */
1818
- constructor(name) {
1819
- super();
1820
- this._lockName = name + '-outer';
1821
- this._reservedName = name + '-reserved';
2036
+ function extractString(dataView, offset) {
2037
+ const p = dataView.getUint32(offset, true);
2038
+ if (p) {
2039
+ const chars = new Uint8Array(dataView.buffer, p);
2040
+ return new TextDecoder().decode(chars.subarray(0, chars.indexOf(0)));
1822
2041
  }
2042
+ return null;
2043
+ }
1823
2044
 
1824
- async isSomewhereReserved() {
1825
- const mode = await this._pollWebLock(this._reservedName);
1826
- return mode === 'exclusive';
1827
- }
2045
+ class IDBContext {
2046
+ /** @type {IDBDatabase} */ #database;
1828
2047
 
1829
- async _NONEtoSHARED() {
1830
- await this._acquireWebLock(this._lockName, {
1831
- mode: 'exclusive',
1832
- signal: this._getTimeoutSignal()
2048
+ /** @type {Promise} */ #chain = null;
2049
+ /** @type {Promise<any>} */ #txComplete = Promise.resolve();
2050
+ /** @type {IDBRequest?} */ #request = null;
2051
+ /** @type {WeakSet<IDBTransaction>} */ #txPending = new WeakSet();
2052
+
2053
+ log = null;
2054
+
2055
+ static async create(name) {
2056
+ const database = await new Promise((resolve, reject) => {
2057
+ const request = indexedDB.open(name, 6);
2058
+ request.onupgradeneeded = async event => {
2059
+ const db = request.result;
2060
+ if (event.oldVersion) {
2061
+ console.log(`Upgrading IndexedDB from version ${event.oldVersion}`);
2062
+ }
2063
+ switch (event.oldVersion) {
2064
+ case 0:
2065
+ // Start with the original schema.
2066
+ db.createObjectStore('blocks', { keyPath: ['path', 'offset', 'version']})
2067
+ .createIndex('version', ['path', 'version']);
2068
+ // fall through intentionally
2069
+ case 5:
2070
+ const tx = request.transaction;
2071
+ const blocks = tx.objectStore('blocks');
2072
+ blocks.deleteIndex('version');
2073
+ const metadata = db.createObjectStore('metadata', { keyPath: 'name' });
2074
+
2075
+ await new Promise((resolve, reject) => {
2076
+ // Iterate over all the blocks.
2077
+ let lastBlock = {};
2078
+ const request = tx.objectStore('blocks').openCursor();
2079
+ request.onsuccess = () => {
2080
+ const cursor = request.result;
2081
+ if (cursor) {
2082
+ const block = cursor.value;
2083
+ if (typeof block.offset !== 'number' ||
2084
+ (block.path === lastBlock.path && block.offset === lastBlock.offset)) {
2085
+ // Remove superceded block (or the "purge" info).
2086
+ cursor.delete();
2087
+ } else if (block.offset === 0) {
2088
+ // Move metadata to its own store.
2089
+ metadata.put({
2090
+ name: block.path,
2091
+ fileSize: block.fileSize,
2092
+ version: block.version
2093
+ });
2094
+
2095
+ delete block.fileSize;
2096
+ cursor.update(block);
2097
+ }
2098
+ lastBlock = block;
2099
+ cursor.continue();
2100
+ } else {
2101
+ resolve();
2102
+ }
2103
+ };
2104
+ request.onerror = () => reject(request.error);
2105
+ });
2106
+ break;
2107
+ }
2108
+ };
2109
+ request.onsuccess = () => resolve(request.result);
2110
+ request.onerror = () => reject(request.error);
1833
2111
  });
2112
+ return new IDBContext(database);
1834
2113
  }
1835
2114
 
1836
- async _SHAREDtoRESERVED() {
1837
- await this._acquireWebLock(this._reservedName, {
1838
- mode: 'exclusive',
1839
- signal: this._getTimeoutSignal()
1840
- });
2115
+ constructor(database) {
2116
+ this.#database = database;
1841
2117
  }
1842
2118
 
1843
- async _RESERVEDtoSHARED() {
1844
- this._releaseWebLock(this._reservedName);
2119
+ close() {
2120
+ this.#database.close();
1845
2121
  }
1846
2122
 
1847
- async _SHAREDtoNONE() {
1848
- this._releaseWebLock(this._lockName);
1849
- }
1850
- }
2123
+ /**
2124
+ * @param {(stores: Object.<string, IDBObjectStore>) => any} f
2125
+ * @param {'ro'|'rw'} mode
2126
+ * @returns {Promise<any>}
2127
+ */
2128
+ q(f, mode = 'ro', options = {}) {
2129
+ /** @type {IDBTransactionMode} */
2130
+ const txMode = mode === 'ro' ? 'readonly' : 'readwrite';
2131
+ const txOptions = Object.assign({
2132
+ /** @type {IDBTransactionDurability} */ durability: 'default'
2133
+ }, options);
1851
2134
 
1852
- class WebLocksShared extends WebLocksBase {
1853
- maxRetryMillis = 1000;
2135
+ // Ensure that queries run sequentially. If any function rejects,
2136
+ // or any request has an error, or the transaction does not commit,
2137
+ // then no subsequent functions will run until sync() or reset().
2138
+ this.#chain = (this.#chain || Promise.resolve())
2139
+ .then(() => this.#q(f, txMode, txOptions));
2140
+ return this.#chain;
2141
+ }
1854
2142
 
1855
2143
  /**
1856
- * @param {string} name
2144
+ * @param {(stores: Object.<string, IDBObjectStore>) => any} f
2145
+ * @param {IDBTransactionMode} mode
2146
+ * @param {IDBTransactionOptions} options
2147
+ * @returns {Promise<any>}
1857
2148
  */
1858
- constructor(name) {
1859
- super();
1860
- this._outerName = name + '-outer';
1861
- this._innerName = name + '-inner';
1862
- }
2149
+ async #q(f, mode, options) {
2150
+ /** @type {IDBTransaction} */ let tx;
2151
+ if (this.#request &&
2152
+ this.#txPending.has(this.#request.transaction) &&
2153
+ this.#request.transaction.mode >= mode &&
2154
+ this.#request.transaction.durability === options.durability) {
2155
+ // The previous request transaction is compatible and has
2156
+ // not yet completed.
2157
+ tx = this.#request.transaction;
2158
+
2159
+ // If the previous request is pending, wait for it to complete.
2160
+ // This ensures that the transaction will be active.
2161
+ if (this.#request.readyState === 'pending') {
2162
+ await new Promise(resolve => {
2163
+ this.#request.addEventListener('success', resolve, { once: true });
2164
+ this.#request.addEventListener('error', resolve, { once: true });
2165
+ });
2166
+ }
2167
+ }
1863
2168
 
1864
- async isSomewhereReserved() {
1865
- const mode = await this._pollWebLock(this._outerName);
1866
- return mode === 'exclusive';
1867
- }
2169
+ for (let i = 0; i < 2; ++i) {
2170
+ if (!tx) {
2171
+ // The current transaction is missing or doesn't match so
2172
+ // replace it with a new one. wait for the previous
2173
+ // transaction to complete so the lifetimes do not overlap.
2174
+ await this.#txComplete;
1868
2175
 
1869
- async _NONEtoSHARED() {
1870
- await this._acquireWebLock(this._outerName, {
1871
- mode: 'shared',
1872
- signal: this._getTimeoutSignal()
1873
- });
1874
- await this._acquireWebLock(this._innerName, {
1875
- mode: 'shared',
1876
- signal: this._getTimeoutSignal()
1877
- });
1878
- this._releaseWebLock(this._outerName);
1879
- }
2176
+ // Create the new transaction.
2177
+ // @ts-ignore
2178
+ tx = this.#database.transaction(this.#database.objectStoreNames, mode, options);
2179
+ this.log?.('IDBTransaction open', mode);
2180
+ this.#txPending.add(tx);
2181
+ this.#txComplete = new Promise((resolve, reject) => {
2182
+ tx.addEventListener('complete', () => {
2183
+ this.log?.('IDBTransaction complete');
2184
+ this.#txPending.delete(tx);
2185
+ resolve();
2186
+ });
2187
+ tx.addEventListener('abort', () => {
2188
+ this.#txPending.delete(tx);
2189
+ reject(new Error('transaction aborted'));
2190
+ });
2191
+ });
2192
+ }
1880
2193
 
1881
- async _SHAREDtoRESERVED() {
1882
- let timeoutMillis = 1;
1883
- while (true) {
1884
- // Attempt to get the outer lock without blocking.
1885
- const isLocked = await this._acquireWebLock(this._outerName, {
1886
- mode: 'exclusive',
1887
- ifAvailable: true
2194
+ // @ts-ignore
2195
+ // Create object store proxies.
2196
+ const objectStores = [...tx.objectStoreNames].map(name => {
2197
+ return [name, this.proxyStoreOrIndex(tx.objectStore(name))];
1888
2198
  });
1889
- if (isLocked) break;
1890
2199
 
1891
- if (await this.isSomewhereReserved()) {
1892
- // Someone else has a reserved lock so retry cannot succeed.
1893
- throw new DOMException('', 'AbortError');
2200
+ try {
2201
+ // Execute the function.
2202
+ return await f(Object.fromEntries(objectStores));
2203
+ } catch (e) {
2204
+ // Use a new transaction if this one was inactive. This will
2205
+ // happen if the last request in the transaction completed
2206
+ // in a previous task but the transaction has not yet committed.
2207
+ if (!i && e.name === 'TransactionInactiveError') {
2208
+ this.log?.('TransactionInactiveError, retrying');
2209
+ tx = null;
2210
+ continue;
2211
+ }
2212
+ throw e;
1894
2213
  }
1895
-
1896
- await new Promise(resolve => setTimeout(resolve, timeoutMillis));
1897
- timeoutMillis = Math.min(2 * timeoutMillis, this.maxRetryMillis);
1898
2214
  }
1899
- this._releaseWebLock(this._innerName);
1900
2215
  }
1901
2216
 
1902
- async _RESERVEDtoEXCLUSIVE() {
1903
- await this._acquireWebLock(this._innerName, {
1904
- mode: 'exclusive',
1905
- signal: this._getTimeoutSignal()
2217
+ /**
2218
+ * Object store methods that return an IDBRequest, except for cursor
2219
+ * creation, are wrapped to return a Promise. In addition, the
2220
+ * request is used internally for chaining.
2221
+ * @param {IDBObjectStore} objectStore
2222
+ * @returns
2223
+ */
2224
+ proxyStoreOrIndex(objectStore) {
2225
+ return new Proxy(objectStore, {
2226
+ get: (target, property, receiver) => {
2227
+ const result = Reflect.get(target, property, receiver);
2228
+ if (typeof result === 'function') {
2229
+ return (...args) => {
2230
+ const maybeRequest = Reflect.apply(result, target, args);
2231
+ // @ts-ignore
2232
+ if (maybeRequest instanceof IDBRequest && !property.endsWith('Cursor')) {
2233
+ // // Debug logging.
2234
+ // this.log?.(`${target.name}.${String(property)}`, args);
2235
+ // maybeRequest.addEventListener('success', () => {
2236
+ // this.log?.(`${target.name}.${String(property)} success`, maybeRequest.result);
2237
+ // });
2238
+ // maybeRequest.addEventListener('error', () => {
2239
+ // this.log?.(`${target.name}.${String(property)} error`, maybeRequest.error);
2240
+ // });
2241
+
2242
+ // Save the request.
2243
+ this.#request = maybeRequest;
2244
+
2245
+ // Abort the transaction on error.
2246
+ maybeRequest.addEventListener('error', () => {
2247
+ console.error(maybeRequest.error);
2248
+ maybeRequest.transaction.abort();
2249
+ }, { once: true });
2250
+
2251
+ // Return a Promise.
2252
+ return wrap(maybeRequest);
2253
+ }
2254
+ return maybeRequest;
2255
+ }
2256
+ }
2257
+ return result;
2258
+ }
1906
2259
  });
1907
2260
  }
1908
2261
 
1909
- async _EXCLUSIVEtoRESERVED() {
1910
- this._releaseWebLock(this._innerName);
2262
+ /**
2263
+ * @param {boolean} durable
2264
+ */
2265
+ async sync(durable) {
2266
+ if (this.#chain) {
2267
+ // This waits for all IndexedDB calls to be made.
2268
+ await this.#chain;
2269
+ if (durable) {
2270
+ // This waits for the final transaction to commit.
2271
+ await this.#txComplete;
2272
+ }
2273
+ this.reset();
2274
+ }
1911
2275
  }
1912
2276
 
1913
- async _RESERVEDtoSHARED() {
1914
- await this._acquireWebLock(this._innerName, { mode: 'shared' });
1915
- this._releaseWebLock(this._outerName);
2277
+ reset() {
2278
+ this.#chain = null;
2279
+ this.#txComplete = Promise.resolve();
2280
+ this.#request = null;
1916
2281
  }
2282
+ }
1917
2283
 
1918
- async _SHAREDtoNONE() {
1919
- this._releaseWebLock(this._innerName);
1920
- }
2284
+ /**
2285
+ * @param {IDBRequest} request
2286
+ * @returns {Promise}
2287
+ */
2288
+ function wrap(request) {
2289
+ return new Promise((resolve, reject) => {
2290
+ request.onsuccess = () => resolve(request.result);
2291
+ request.onerror = () => reject(request.error);
2292
+ });
1921
2293
  }
1922
2294
 
2295
+
2296
+
1923
2297
  /***/ })
1924
2298
 
1925
2299
  }]);