@powersync/web 1.10.1 → 1.11.0

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