@livestore/wa-sqlite 0.4.0-dev.21 → 0.4.0-dev.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +46 -36
  2. package/dist/README.md +13 -13
  3. package/dist/fts5/wa-sqlite.mjs +1 -1
  4. package/dist/fts5/wa-sqlite.node.mjs +1 -1
  5. package/dist/fts5/wa-sqlite.node.wasm +0 -0
  6. package/dist/fts5/wa-sqlite.wasm +0 -0
  7. package/dist/wa-sqlite-async.mjs +1 -1
  8. package/dist/wa-sqlite-async.wasm +0 -0
  9. package/dist/wa-sqlite-jspi.mjs +1 -1
  10. package/dist/wa-sqlite-jspi.wasm +0 -0
  11. package/dist/wa-sqlite.mjs +1 -1
  12. package/dist/wa-sqlite.node.mjs +1 -1
  13. package/dist/wa-sqlite.node.wasm +0 -0
  14. package/dist/wa-sqlite.wasm +0 -0
  15. package/package.json +40 -29
  16. package/src/FacadeVFS.js +252 -261
  17. package/src/VFS.js +84 -85
  18. package/src/WebLocksMixin.js +357 -351
  19. package/src/examples/AccessHandlePoolVFS.js +185 -194
  20. package/src/examples/IDBBatchAtomicVFS.js +429 -409
  21. package/src/examples/IDBMirrorVFS.js +402 -409
  22. package/src/examples/MemoryAsyncVFS.js +32 -37
  23. package/src/examples/MemoryVFS.js +71 -75
  24. package/src/examples/OPFSAdaptiveVFS.js +206 -206
  25. package/src/examples/OPFSAnyContextVFS.js +141 -140
  26. package/src/examples/OPFSCoopSyncVFS.js +297 -299
  27. package/src/examples/OPFSPermutedVFS.js +529 -540
  28. package/src/examples/README.md +27 -15
  29. package/src/examples/tag.js +27 -27
  30. package/src/sqlite-api.js +910 -941
  31. package/src/sqlite-constants.js +246 -232
  32. package/src/types/globals.d.ts +52 -52
  33. package/src/types/index.d.ts +586 -576
  34. package/test/AccessHandlePoolVFS.test.js +21 -21
  35. package/test/IDBBatchAtomicVFS.test.js +69 -69
  36. package/test/IDBMirrorVFS.test.js +21 -21
  37. package/test/MemoryAsyncVFS.test.js +21 -21
  38. package/test/MemoryVFS.test.js +21 -21
  39. package/test/OPFSAdaptiveVFS.test.js +21 -21
  40. package/test/OPFSAnyContextVFS.test.js +21 -21
  41. package/test/OPFSCoopSyncVFS.test.js +21 -21
  42. package/test/OPFSPermutedVFS.test.js +21 -21
  43. package/test/TestContext.js +44 -41
  44. package/test/WebLocksMixin.test.js +369 -360
  45. package/test/api.test.js +23 -23
  46. package/test/api_exec.js +72 -61
  47. package/test/api_misc.js +53 -54
  48. package/test/api_statements.js +271 -279
  49. package/test/callbacks.test.js +492 -478
  50. package/test/data/idbv5.json +1135 -1
  51. package/test/sql.test.js +30 -30
  52. package/test/sql_0001.js +49 -33
  53. package/test/sql_0002.js +55 -34
  54. package/test/sql_0003.js +85 -49
  55. package/test/sql_0004.js +76 -47
  56. package/test/sql_0005.js +60 -44
  57. package/test/test-worker.js +171 -163
  58. package/test/vfs_xAccess.js +1 -2
  59. package/test/vfs_xClose.js +50 -49
  60. package/test/vfs_xOpen.js +73 -72
  61. package/test/vfs_xRead.js +31 -31
  62. package/test/vfs_xWrite.js +30 -29
@@ -1,13 +1,13 @@
1
- import { FacadeVFS } from '../FacadeVFS.js';
2
- import * as VFS from '../VFS.js';
1
+ import { FacadeVFS } from '../FacadeVFS.js'
2
+ import * as VFS from '../VFS.js'
3
3
 
4
4
  // Options for navigator.locks.request().
5
- /** @type {LockOptions} */ const SHARED = { mode: 'shared' };
6
- /** @type {LockOptions} */ const POLL_SHARED = { ifAvailable: true, mode: 'shared' };
7
- /** @type {LockOptions} */ const POLL_EXCLUSIVE = { ifAvailable: true, mode: 'exclusive' };
5
+ /** @type {LockOptions} */ const SHARED = { mode: 'shared' }
6
+ /** @type {LockOptions} */ const POLL_SHARED = { ifAvailable: true, mode: 'shared' }
7
+ /** @type {LockOptions} */ const POLL_EXCLUSIVE = { ifAvailable: true, mode: 'exclusive' }
8
8
 
9
9
  // Used only for debug logging.
10
- const contextId = Math.random().toString(36).slice(2);
10
+ const contextId = Math.random().toString(36).slice(2)
11
11
 
12
12
  /**
13
13
  * @typedef {Object} Transaction
@@ -18,434 +18,428 @@ const contextId = Math.random().toString(36).slice(2);
18
18
  */
19
19
 
20
20
  class File {
21
- /** @type {string} */ path;
22
- /** @type {number} */ flags;
21
+ /** @type {string} */ path
22
+ /** @type {number} */ flags
23
23
 
24
- /** @type {number} */ blockSize;
25
- /** @type {Map<number, Uint8Array>} */ blocks;
24
+ /** @type {number} */ blockSize
25
+ /** @type {Map<number, Uint8Array>} */ blocks
26
26
 
27
27
  // Members below are only used for SQLITE_OPEN_MAIN_DB.
28
28
 
29
- /** @type {Transaction} */ viewTx; // last transaction incorporated
30
- /** @type {function?} */ viewReleaser;
29
+ /** @type {Transaction} */ viewTx // last transaction incorporated
30
+ /** @type {function?} */ viewReleaser
31
31
 
32
- /** @type {BroadcastChannel} */ broadcastChannel;
33
- /** @type {Transaction[]} */ broadcastReceived;
32
+ /** @type {BroadcastChannel} */ broadcastChannel
33
+ /** @type {Transaction[]} */ broadcastReceived
34
34
 
35
- /** @type {number} */ lockState;
36
- /** @type {{write?: function, reserved?: function, hint?: function}} */ locks;
35
+ /** @type {number} */ lockState
36
+ /** @type {{write?: function, reserved?: function, hint?: function}} */ locks
37
37
 
38
- /** @type {AbortController} */ abortController;
38
+ /** @type {AbortController} */ abortController
39
39
 
40
- /** @type {Transaction?} */ txActive;
41
- /** @type {boolean} */ txWriteHint;
42
- /** @type {boolean} */ txOverwrite;
40
+ /** @type {Transaction?} */ txActive
41
+ /** @type {boolean} */ txWriteHint
42
+ /** @type {boolean} */ txOverwrite
43
43
 
44
- /** @type {string} */ synchronous;
44
+ /** @type {string} */ synchronous
45
45
 
46
46
  constructor(pathname, flags) {
47
- this.path = pathname;
48
- this.flags = flags;
47
+ this.path = pathname
48
+ this.flags = flags
49
49
 
50
- this.blockSize = 0;
51
- this.blocks = new Map();
50
+ this.blockSize = 0
51
+ this.blocks = new Map()
52
52
  if (flags & VFS.SQLITE_OPEN_MAIN_DB) {
53
- this.viewTx = null;
54
- this.viewReleaser = null;
55
- this.broadcastChannel = new BroadcastChannel('mirror:' + pathname);
56
- this.broadcastReceived = [];
57
- this.lockState = VFS.SQLITE_LOCK_NONE;
58
- this.locks = {};
59
- this.txActive = null;
60
- this.txWriteHint = false;
61
- this.txOverwrite = false;
62
- this.synchronous = 'full';
53
+ this.viewTx = null
54
+ this.viewReleaser = null
55
+ this.broadcastChannel = new BroadcastChannel('mirror:' + pathname)
56
+ this.broadcastReceived = []
57
+ this.lockState = VFS.SQLITE_LOCK_NONE
58
+ this.locks = {}
59
+ this.txActive = null
60
+ this.txWriteHint = false
61
+ this.txOverwrite = false
62
+ this.synchronous = 'full'
63
63
  }
64
64
  }
65
65
  }
66
66
 
67
67
  export class IDBMirrorVFS extends FacadeVFS {
68
- /** @type {Map<number, File>} */ #mapIdToFile = new Map();
69
- /** @type {Map<string, File>} */ #mapPathToFile = new Map();
70
- #lastError = null;
68
+ /** @type {Map<number, File>} */ #mapIdToFile = new Map()
69
+ /** @type {Map<string, File>} */ #mapPathToFile = new Map()
70
+ #lastError = null
71
71
 
72
- /** @type {IDBDatabase} */ #idb;
72
+ /** @type {IDBDatabase} */ #idb
73
73
 
74
- log = null; // console.log;
74
+ log = null // console.log;
75
75
 
76
- /** @type {Promise} */ #isReady;
76
+ /** @type {Promise} */ #isReady
77
77
 
78
78
  static async create(name, module, options) {
79
- const instance = new IDBMirrorVFS(name, module, options);
80
- await instance.isReady();
81
- return instance;
79
+ const instance = new IDBMirrorVFS(name, module, options)
80
+ await instance.isReady()
81
+ return instance
82
82
  }
83
83
 
84
84
  constructor(name, module, options = {}) {
85
- super(name, module);
86
- this.#isReady = this.#initialize(name);
85
+ super(name, module)
86
+ this.#isReady = this.#initialize(name)
87
87
  }
88
88
 
89
89
  async #initialize(name) {
90
90
  // Open IndexedDB database, creating it if necessary.
91
91
  this.#idb = await new Promise((resolve, reject) => {
92
- const request = indexedDB.open(name, 1);
92
+ const request = indexedDB.open(name, 1)
93
93
  request.onupgradeneeded = (event) => {
94
- const db = request.result;
94
+ const db = request.result
95
95
  switch (event.oldVersion) {
96
96
  case 0:
97
- db.createObjectStore('blocks', { keyPath: ['path', 'offset'] });
98
- db.createObjectStore('tx', { keyPath: ['path', 'txId'] });
99
- break;
97
+ db.createObjectStore('blocks', { keyPath: ['path', 'offset'] })
98
+ db.createObjectStore('tx', { keyPath: ['path', 'txId'] })
99
+ break
100
100
  }
101
- };
102
- request.onsuccess = () => resolve(request.result);
103
- request.onerror = () => reject(request.error);
104
- });
101
+ }
102
+ request.onsuccess = () => resolve(request.result)
103
+ request.onerror = () => reject(request.error)
104
+ })
105
105
  }
106
106
 
107
107
  close() {
108
- return this.#idb.close();
108
+ return this.#idb.close()
109
109
  }
110
110
 
111
111
  async isReady() {
112
- await super.isReady();
113
- return this.#isReady;
112
+ await super.isReady()
113
+ return this.#isReady
114
114
  }
115
115
 
116
116
  /**
117
- * @param {string?} zName
118
- * @param {number} fileId
119
- * @param {number} flags
120
- * @param {DataView} pOutFlags
117
+ * @param {string?} zName
118
+ * @param {number} fileId
119
+ * @param {number} flags
120
+ * @param {DataView} pOutFlags
121
121
  * @returns {Promise<number>}
122
122
  */
123
123
  async jOpen(zName, fileId, flags, pOutFlags) {
124
124
  try {
125
- const url = new URL(zName || Math.random().toString(36).slice(2), 'file://');
126
- const path = url.pathname;
125
+ const url = new URL(zName || Math.random().toString(36).slice(2), 'file://')
126
+ const path = url.pathname
127
127
 
128
- let file;
128
+ let file
129
129
  if (flags & VFS.SQLITE_OPEN_MAIN_DB) {
130
130
  // TODO
131
- file = new File(path, flags);
131
+ file = new File(path, flags)
132
132
 
133
- const idbTx = this.#idb.transaction(['blocks', 'tx'], 'readwrite');
134
- const blocks = idbTx.objectStore('blocks');
135
- if (await idbX(blocks.count([path, 0])) === 0) {
133
+ const idbTx = this.#idb.transaction(['blocks', 'tx'], 'readwrite')
134
+ const blocks = idbTx.objectStore('blocks')
135
+ if ((await idbX(blocks.count([path, 0]))) === 0) {
136
136
  // File does not yet exist.
137
137
  if (flags & VFS.SQLITE_OPEN_CREATE) {
138
- await idbX(blocks.put({ path, offset: 0, data: new Uint8Array(0) }));
138
+ await idbX(blocks.put({ path, offset: 0, data: new Uint8Array(0) }))
139
139
  } else {
140
- throw new Error('File not found');
140
+ throw new Error('File not found')
141
141
  }
142
142
  }
143
-
143
+
144
144
  // Load pages into memory from IndexedDB.
145
145
  await new Promise((resolve, reject) => {
146
- const range = IDBKeyRange.bound([path, 0], [path, Infinity]);
147
- const request = blocks.openCursor(range);
146
+ const range = IDBKeyRange.bound([path, 0], [path, Infinity])
147
+ const request = blocks.openCursor(range)
148
148
  request.onsuccess = () => {
149
- const cursor = request.result;
149
+ const cursor = request.result
150
150
  if (cursor) {
151
- const { offset, data } = cursor.value;
152
- file.blocks.set(offset, data);
153
- cursor.continue();
151
+ const { offset, data } = cursor.value
152
+ file.blocks.set(offset, data)
153
+ cursor.continue()
154
154
  } else {
155
- resolve();
155
+ resolve()
156
156
  }
157
- };
158
- request.onerror = () => reject(request.error);
159
- });
160
- file.blockSize = file.blocks.get(0)?.byteLength ?? 0;
157
+ }
158
+ request.onerror = () => reject(request.error)
159
+ })
160
+ file.blockSize = file.blocks.get(0)?.byteLength ?? 0
161
161
 
162
162
  // Get the last transaction id.
163
- const transactions = idbTx.objectStore('tx');
163
+ const transactions = idbTx.objectStore('tx')
164
164
  file.viewTx = await new Promise((resolve, reject) => {
165
- const range = IDBKeyRange.bound([path, 0], [path, Infinity]);
166
- const request = transactions.openCursor(range, 'prev');
165
+ const range = IDBKeyRange.bound([path, 0], [path, Infinity])
166
+ const request = transactions.openCursor(range, 'prev')
167
167
  request.onsuccess = () => {
168
- const cursor = request.result;
168
+ const cursor = request.result
169
169
  if (cursor) {
170
- resolve(cursor.value);
170
+ resolve(cursor.value)
171
171
  } else {
172
- resolve({ txId: 0 });
172
+ resolve({ txId: 0 })
173
173
  }
174
- };
175
- request.onerror = () => reject(request.error);
176
- });
174
+ }
175
+ request.onerror = () => reject(request.error)
176
+ })
177
177
 
178
178
  // Publish our view of the database. This prevents other connections
179
179
  // from overwriting file data we still need.
180
- await this.#setView(file, file.viewTx);
180
+ await this.#setView(file, file.viewTx)
181
181
 
182
182
  // Listen for broadcasts. Messages are cached until the database
183
183
  // is unlocked.
184
- file.broadcastChannel.addEventListener('message', event => {
185
- file.broadcastReceived.push(event.data);
184
+ file.broadcastChannel.addEventListener('message', (event) => {
185
+ file.broadcastReceived.push(event.data)
186
186
  if (file.lockState === VFS.SQLITE_LOCK_NONE) {
187
- this.#processBroadcasts(file);
187
+ this.#processBroadcasts(file)
188
188
  }
189
- });
189
+ })
190
190
  } else {
191
191
  // Not a main database so not stored in IndexedDB.
192
- file = this.#mapPathToFile.get(path);
192
+ file = this.#mapPathToFile.get(path)
193
193
  if (!file) {
194
194
  if (flags & VFS.SQLITE_OPEN_CREATE) {
195
- file = new File(path, flags);
196
- file.blocks.set(0, new Uint8Array(0));
195
+ file = new File(path, flags)
196
+ file.blocks.set(0, new Uint8Array(0))
197
197
  } else {
198
- throw new Error('File not found');
198
+ throw new Error('File not found')
199
199
  }
200
200
  }
201
201
  }
202
202
 
203
- pOutFlags.setInt32(0, flags, true);
204
- this.#mapIdToFile.set(fileId, file);
205
- this.#mapPathToFile.set(path, file);
206
- return VFS.SQLITE_OK;
203
+ pOutFlags.setInt32(0, flags, true)
204
+ this.#mapIdToFile.set(fileId, file)
205
+ this.#mapPathToFile.set(path, file)
206
+ return VFS.SQLITE_OK
207
207
  } catch (e) {
208
- this.#lastError = e;
209
- return VFS.SQLITE_CANTOPEN;
208
+ this.#lastError = e
209
+ return VFS.SQLITE_CANTOPEN
210
210
  }
211
211
  }
212
212
 
213
213
  /**
214
- * @param {string} zName
215
- * @param {number} syncDir
214
+ * @param {string} zName
215
+ * @param {number} syncDir
216
216
  * @returns {Promise<number>}
217
217
  */
218
218
  async jDelete(zName, syncDir) {
219
219
  try {
220
- const url = new URL(zName, 'file://');
221
- const pathname = url.pathname;
220
+ const url = new URL(zName, 'file://')
221
+ const pathname = url.pathname
222
222
 
223
- const result = await this.#deleteFile(pathname);
223
+ const result = await this.#deleteFile(pathname)
224
224
  if (syncDir) {
225
- await result;
225
+ await result
226
226
  }
227
- return VFS.SQLITE_OK;
227
+ return VFS.SQLITE_OK
228
228
  } catch (e) {
229
- return VFS.SQLITE_IOERR_DELETE;
229
+ return VFS.SQLITE_IOERR_DELETE
230
230
  }
231
231
  }
232
232
 
233
233
  /**
234
- * @param {string} zName
235
- * @param {number} flags
236
- * @param {DataView} pResOut
234
+ * @param {string} zName
235
+ * @param {number} flags
236
+ * @param {DataView} pResOut
237
237
  * @returns {Promise<number>}
238
238
  */
239
239
  async jAccess(zName, flags, pResOut) {
240
240
  try {
241
- const url = new URL(zName, 'file://');
242
- const pathname = url.pathname;
241
+ const url = new URL(zName, 'file://')
242
+ const pathname = url.pathname
243
243
 
244
244
  // This test ignores main database files that have not been opened
245
245
  // with this connection. SQLite does not call jAccess() on main
246
246
  // database files, so avoiding an IndexedDB test saves time.
247
- const exists = this.#mapPathToFile.has(pathname);
248
- pResOut.setInt32(0, exists ? 1 : 0, true);
249
- return VFS.SQLITE_OK;
247
+ const exists = this.#mapPathToFile.has(pathname)
248
+ pResOut.setInt32(0, exists ? 1 : 0, true)
249
+ return VFS.SQLITE_OK
250
250
  } catch (e) {
251
- this.#lastError = e;
252
- return VFS.SQLITE_IOERR_ACCESS;
251
+ this.#lastError = e
252
+ return VFS.SQLITE_IOERR_ACCESS
253
253
  }
254
254
  }
255
255
 
256
256
  /**
257
- * @param {number} fileId
257
+ * @param {number} fileId
258
258
  * @returns {Promise<number>}
259
259
  */
260
260
  async jClose(fileId) {
261
261
  try {
262
- const file = this.#mapIdToFile.get(fileId);
263
- this.#mapIdToFile.delete(fileId);
262
+ const file = this.#mapIdToFile.get(fileId)
263
+ this.#mapIdToFile.delete(fileId)
264
264
 
265
265
  if (file?.flags & VFS.SQLITE_OPEN_MAIN_DB) {
266
- file.broadcastChannel.close();
267
- file.viewReleaser?.();
266
+ file.broadcastChannel.close()
267
+ file.viewReleaser?.()
268
268
  }
269
269
 
270
270
  if (file?.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
271
- this.#deleteFile(file.path);
271
+ this.#deleteFile(file.path)
272
272
  }
273
- return VFS.SQLITE_OK;
273
+ return VFS.SQLITE_OK
274
274
  } catch (e) {
275
- return VFS.SQLITE_IOERR_CLOSE;
275
+ return VFS.SQLITE_IOERR_CLOSE
276
276
  }
277
277
  }
278
278
 
279
279
  /**
280
- * @param {number} fileId
281
- * @param {Uint8Array} pData
280
+ * @param {number} fileId
281
+ * @param {Uint8Array} pData
282
282
  * @param {number} iOffset
283
283
  * @returns {number}
284
284
  */
285
285
  jRead(fileId, pData, iOffset) {
286
286
  try {
287
- const file = this.#mapIdToFile.get(fileId);
287
+ const file = this.#mapIdToFile.get(fileId)
288
288
 
289
- let bytesRead = 0;
290
- let pDataOffset = 0;
289
+ let bytesRead = 0
290
+ let pDataOffset = 0
291
291
  while (pDataOffset < pData.byteLength) {
292
292
  // File data is stored in fixed-size blocks. Get the next block
293
293
  // needed.
294
- const fileOffset = iOffset + pDataOffset;
295
- const blockIndex = Math.floor(fileOffset / file.blockSize);
296
- const blockOffset = fileOffset % file.blockSize;
294
+ const fileOffset = iOffset + pDataOffset
295
+ const blockIndex = Math.floor(fileOffset / file.blockSize)
296
+ const blockOffset = fileOffset % file.blockSize
297
297
  const block =
298
- file.txActive?.blocks.get(blockIndex * file.blockSize) ??
299
- file.blocks.get(blockIndex * file.blockSize);
298
+ file.txActive?.blocks.get(blockIndex * file.blockSize) ?? file.blocks.get(blockIndex * file.blockSize)
300
299
  if (!block) {
301
- break;
300
+ break
302
301
  }
303
302
 
304
303
  // Copy block data to the read buffer.
305
- const blockLength = Math.min(
306
- block.byteLength - blockOffset,
307
- pData.byteLength - pDataOffset);
308
- pData.set(block.subarray(blockOffset, blockOffset + blockLength), pDataOffset);
309
- pDataOffset += blockLength;
310
- bytesRead += blockLength;
304
+ const blockLength = Math.min(block.byteLength - blockOffset, pData.byteLength - pDataOffset)
305
+ pData.set(block.subarray(blockOffset, blockOffset + blockLength), pDataOffset)
306
+ pDataOffset += blockLength
307
+ bytesRead += blockLength
311
308
  }
312
309
 
313
310
  if (bytesRead < pData.byteLength) {
314
- pData.fill(0, bytesRead);
315
- return VFS.SQLITE_IOERR_SHORT_READ;
311
+ pData.fill(0, bytesRead)
312
+ return VFS.SQLITE_IOERR_SHORT_READ
316
313
  }
317
- return VFS.SQLITE_OK;
314
+ return VFS.SQLITE_OK
318
315
  } catch (e) {
319
- this.#lastError = e;
320
- return VFS.SQLITE_IOERR_READ;
316
+ this.#lastError = e
317
+ return VFS.SQLITE_IOERR_READ
321
318
  }
322
319
  }
323
320
 
324
321
  /**
325
- * @param {number} fileId
326
- * @param {Uint8Array} pData
322
+ * @param {number} fileId
323
+ * @param {Uint8Array} pData
327
324
  * @param {number} iOffset
328
325
  * @returns {number}
329
326
  */
330
327
  jWrite(fileId, pData, iOffset) {
331
328
  try {
332
- const file = this.#mapIdToFile.get(fileId);
329
+ const file = this.#mapIdToFile.get(fileId)
333
330
 
334
331
  if (file.flags & VFS.SQLITE_OPEN_MAIN_DB) {
335
- this.#requireTxActive(file);
332
+ this.#requireTxActive(file)
336
333
  // SQLite is not necessarily written sequentially, so fill in the
337
334
  // unwritten blocks here.
338
- for (let fillOffset = file.txActive.fileSize;
339
- fillOffset < iOffset; fillOffset += pData.byteLength) {
340
- file.txActive.blocks.set(fillOffset, new Uint8Array(pData.byteLength));
335
+ for (let fillOffset = file.txActive.fileSize; fillOffset < iOffset; fillOffset += pData.byteLength) {
336
+ file.txActive.blocks.set(fillOffset, new Uint8Array(pData.byteLength))
341
337
  }
342
- file.txActive.blocks.set(iOffset, pData.slice());
343
- file.txActive.fileSize = Math.max(file.txActive.fileSize, iOffset + pData.byteLength);
344
- file.blockSize = pData.byteLength;
338
+ file.txActive.blocks.set(iOffset, pData.slice())
339
+ file.txActive.fileSize = Math.max(file.txActive.fileSize, iOffset + pData.byteLength)
340
+ file.blockSize = pData.byteLength
345
341
  } else {
346
342
  // All files that are not main databases are stored in a single
347
343
  // block.
348
- let block = file.blocks.get(0);
344
+ let block = file.blocks.get(0)
349
345
  if (iOffset + pData.byteLength > block.byteLength) {
350
346
  // Resize the block buffer.
351
- const newSize = Math.max(iOffset + pData.byteLength, 2 * block.byteLength);
352
- const newBlock = new Uint8Array(newSize);
353
- newBlock.set(block);
354
- file.blocks.set(0, newBlock);
355
- block = newBlock;
347
+ const newSize = Math.max(iOffset + pData.byteLength, 2 * block.byteLength)
348
+ const newBlock = new Uint8Array(newSize)
349
+ newBlock.set(block)
350
+ file.blocks.set(0, newBlock)
351
+ block = newBlock
356
352
  }
357
- block.set(pData, iOffset);
358
- file.blockSize = Math.max(file.blockSize, iOffset + pData.byteLength);
353
+ block.set(pData, iOffset)
354
+ file.blockSize = Math.max(file.blockSize, iOffset + pData.byteLength)
359
355
  }
360
- return VFS.SQLITE_OK;
356
+ return VFS.SQLITE_OK
361
357
  } catch (e) {
362
- this.lastError = e;
363
- return VFS.SQLITE_IOERR_WRITE;
358
+ this.lastError = e
359
+ return VFS.SQLITE_IOERR_WRITE
364
360
  }
365
361
  }
366
362
 
367
363
  /**
368
- * @param {number} fileId
369
- * @param {number} iSize
364
+ * @param {number} fileId
365
+ * @param {number} iSize
370
366
  * @returns {number}
371
367
  */
372
368
  jTruncate(fileId, iSize) {
373
369
  try {
374
- const file = this.#mapIdToFile.get(fileId);
370
+ const file = this.#mapIdToFile.get(fileId)
375
371
 
376
372
  if (file.flags & VFS.SQLITE_OPEN_MAIN_DB) {
377
- this.#requireTxActive(file);
378
- file.txActive.fileSize = iSize;
373
+ this.#requireTxActive(file)
374
+ file.txActive.fileSize = iSize
379
375
  } else {
380
376
  // All files that are not main databases are stored in a single
381
377
  // block.
382
378
  if (iSize < file.blockSize) {
383
- const block = file.blocks.get(0);
384
- file.blocks.set(0, block.subarray(0, iSize));
385
- file.blockSize = iSize;
379
+ const block = file.blocks.get(0)
380
+ file.blocks.set(0, block.subarray(0, iSize))
381
+ file.blockSize = iSize
386
382
  }
387
383
  }
388
- return VFS.SQLITE_OK;
384
+ return VFS.SQLITE_OK
389
385
  } catch (e) {
390
- console.error(e);
391
- this.lastError = e;
392
- return VFS.SQLITE_IOERR_TRUNCATE;
386
+ console.error(e)
387
+ this.lastError = e
388
+ return VFS.SQLITE_IOERR_TRUNCATE
393
389
  }
394
390
  }
395
391
 
396
392
  /**
397
- * @param {number} fileId
398
- * @param {DataView} pSize64
393
+ * @param {number} fileId
394
+ * @param {DataView} pSize64
399
395
  * @returns {number|Promise<number>}
400
396
  */
401
397
  jFileSize(fileId, pSize64) {
402
- const file = this.#mapIdToFile.get(fileId);
403
- const size = file.txActive?.fileSize ?? file.blockSize * file.blocks.size;
404
- pSize64.setBigInt64(0, BigInt(size), true);
405
- return VFS.SQLITE_OK;
398
+ const file = this.#mapIdToFile.get(fileId)
399
+ const size = file.txActive?.fileSize ?? file.blockSize * file.blocks.size
400
+ pSize64.setBigInt64(0, BigInt(size), true)
401
+ return VFS.SQLITE_OK
406
402
  }
407
403
 
408
404
  /**
409
- * @param {number} fileId
410
- * @param {number} lockType
405
+ * @param {number} fileId
406
+ * @param {number} lockType
411
407
  * @returns {Promise<number>}
412
408
  */
413
409
  async jLock(fileId, lockType) {
414
- const file = this.#mapIdToFile.get(fileId);
415
- if (lockType <= file.lockState) return VFS.SQLITE_OK;
410
+ const file = this.#mapIdToFile.get(fileId)
411
+ if (lockType <= file.lockState) return VFS.SQLITE_OK
416
412
  switch (lockType) {
417
413
  case VFS.SQLITE_LOCK_SHARED:
418
414
  if (file.txWriteHint) {
419
- // xFileControl() has hinted that this transaction will
420
- // write. Acquire the hint lock, which is required to reach
421
- // the RESERVED state.
422
- if (!await this.#lock(file, 'hint')) {
423
- return VFS.SQLITE_BUSY;
424
- }
415
+ // xFileControl() has hinted that this transaction will
416
+ // write. Acquire the hint lock, which is required to reach
417
+ // the RESERVED state.
418
+ if (!(await this.#lock(file, 'hint'))) {
419
+ return VFS.SQLITE_BUSY
420
+ }
425
421
  }
426
- break;
422
+ break
427
423
  case VFS.SQLITE_LOCK_RESERVED:
428
424
  // Ideally we should already have the hint lock, but if not
429
425
  // poll for it here.
430
- if (!file.locks.hint && !await this.#lock(file, 'hint', POLL_EXCLUSIVE)) {
431
- return VFS.SQLITE_BUSY;
426
+ if (!file.locks.hint && !(await this.#lock(file, 'hint', POLL_EXCLUSIVE))) {
427
+ return VFS.SQLITE_BUSY
432
428
  }
433
429
 
434
- if (!await this.#lock(file, 'reserved', POLL_EXCLUSIVE)) {
435
- file.locks.hint();
436
- return VFS.SQLITE_BUSY;
430
+ if (!(await this.#lock(file, 'reserved', POLL_EXCLUSIVE))) {
431
+ file.locks.hint()
432
+ return VFS.SQLITE_BUSY
437
433
  }
438
434
 
439
435
  // In order to write, our view of the database must be up to date.
440
436
  // To check this, first fetch all transactions in IndexedDB equal to
441
437
  // or greater than our view.
442
- const idbTx = this.#idb.transaction(['blocks', 'tx']);
443
- const range = IDBKeyRange.bound(
444
- [file.path, file.viewTx.txId],
445
- [file.path, Infinity]);
438
+ const idbTx = this.#idb.transaction(['blocks', 'tx'])
439
+ const range = IDBKeyRange.bound([file.path, file.viewTx.txId], [file.path, Infinity])
446
440
 
447
441
  /** @type {Transaction[]} */
448
- const entries = await idbX(idbTx.objectStore('tx').getAll(range));
442
+ const entries = await idbX(idbTx.objectStore('tx').getAll(range))
449
443
 
450
444
  // Ideally the fetched list of transactions should contain one
451
445
  // entry matching our view. If not then our view is out of date.
@@ -453,80 +447,80 @@ export class IDBMirrorVFS extends FacadeVFS {
453
447
  // There are newer transactions in IndexedDB that we haven't
454
448
  // seen via broadcast. Ensure that they are incorporated on unlock,
455
449
  // and force the application to retry.
456
- const blocks = idbTx.objectStore('blocks');
450
+ const blocks = idbTx.objectStore('blocks')
457
451
  for (const entry of entries) {
458
452
  // When transactions are stored to IndexedDB, the page data is
459
453
  // stripped to save time and space. Restore the page data here.
460
454
  for (const offset of Array.from(entry.blocks.keys())) {
461
- const value = await idbX(blocks.get([file.path, offset]));
462
- entry.blocks.set(offset, value.data);
455
+ const value = await idbX(blocks.get([file.path, offset]))
456
+ entry.blocks.set(offset, value.data)
463
457
  }
464
458
  }
465
- file.broadcastReceived.push(...entries);
466
- file.locks.reserved();
459
+ file.broadcastReceived.push(...entries)
460
+ file.locks.reserved()
467
461
  return VFS.SQLITE_BUSY
468
462
  }
469
463
 
470
- console.assert(entries[0]?.txId === file.viewTx.txId || !file.viewTx.txId);
471
- break;
464
+ console.assert(entries[0]?.txId === file.viewTx.txId || !file.viewTx.txId)
465
+ break
472
466
  case VFS.SQLITE_LOCK_EXCLUSIVE:
473
- await this.#lock(file, 'write');
474
- break;
467
+ await this.#lock(file, 'write')
468
+ break
475
469
  }
476
- file.lockState = lockType;
477
- return VFS.SQLITE_OK;
470
+ file.lockState = lockType
471
+ return VFS.SQLITE_OK
478
472
  }
479
473
 
480
474
  /**
481
- * @param {number} fileId
482
- * @param {number} lockType
475
+ * @param {number} fileId
476
+ * @param {number} lockType
483
477
  * @returns {number}
484
478
  */
485
479
  jUnlock(fileId, lockType) {
486
- const file = this.#mapIdToFile.get(fileId);
487
- if (lockType >= file.lockState) return VFS.SQLITE_OK;
480
+ const file = this.#mapIdToFile.get(fileId)
481
+ if (lockType >= file.lockState) return VFS.SQLITE_OK
488
482
  switch (lockType) {
489
483
  case VFS.SQLITE_LOCK_SHARED:
490
- file.locks.write?.();
491
- file.locks.reserved?.();
492
- file.locks.hint?.();
493
- break;
484
+ file.locks.write?.()
485
+ file.locks.reserved?.()
486
+ file.locks.hint?.()
487
+ break
494
488
  case VFS.SQLITE_LOCK_NONE:
495
489
  // Don't release the read lock here. It will be released on demand
496
490
  // when a broadcast notifies us that another connections wants to
497
491
  // VACUUM.
498
- this.#processBroadcasts(file);
499
- file.locks.write?.();
500
- file.locks.reserved?.();
501
- file.locks.hint?.();
502
- break;
492
+ this.#processBroadcasts(file)
493
+ file.locks.write?.()
494
+ file.locks.reserved?.()
495
+ file.locks.hint?.()
496
+ break
503
497
  }
504
- file.lockState = lockType;
505
- return VFS.SQLITE_OK;
498
+ file.lockState = lockType
499
+ return VFS.SQLITE_OK
506
500
  }
507
501
 
508
502
  /**
509
503
  * @param {number} fileId
510
- * @param {DataView} pResOut
504
+ * @param {DataView} pResOut
511
505
  * @returns {Promise<number>}
512
506
  */
513
507
  async jCheckReservedLock(fileId, pResOut) {
514
508
  try {
515
- const file = this.#mapIdToFile.get(fileId);
516
- console.assert(file.flags & VFS.SQLITE_OPEN_MAIN_DB);
509
+ const file = this.#mapIdToFile.get(fileId)
510
+ console.assert(file.flags & VFS.SQLITE_OPEN_MAIN_DB)
517
511
  if (await this.#lock(file, 'reserved', POLL_SHARED)) {
518
512
  // This looks backwards, but if we get the lock then no one
519
513
  // else had it.
520
- pResOut.setInt32(0, 0, true);
521
- file.locks.reserved();
514
+ pResOut.setInt32(0, 0, true)
515
+ file.locks.reserved()
522
516
  } else {
523
- pResOut.setInt32(0, 1, true);
517
+ pResOut.setInt32(0, 1, true)
524
518
  }
525
- return VFS.SQLITE_OK;
519
+ return VFS.SQLITE_OK
526
520
  } catch (e) {
527
- console.error(e);
528
- this.lastError = e;
529
- return VFS.SQLITE_IOERR_LOCK;
521
+ console.error(e)
522
+ this.lastError = e
523
+ return VFS.SQLITE_IOERR_LOCK
530
524
  }
531
525
  }
532
526
 
@@ -538,19 +532,19 @@ export class IDBMirrorVFS extends FacadeVFS {
538
532
  */
539
533
  async jFileControl(fileId, op, pArg) {
540
534
  try {
541
- const file = this.#mapIdToFile.get(fileId);
535
+ const file = this.#mapIdToFile.get(fileId)
542
536
  switch (op) {
543
537
  case VFS.SQLITE_FCNTL_PRAGMA:
544
- const key = cvtString(pArg, 4);
545
- const value = cvtString(pArg, 8);
546
- this.log?.('xFileControl', file.path, 'PRAGMA', key, value);
538
+ const key = cvtString(pArg, 4)
539
+ const value = cvtString(pArg, 8)
540
+ this.log?.('xFileControl', file.path, 'PRAGMA', key, value)
547
541
  switch (key.toLowerCase()) {
548
542
  case 'page_size':
549
543
  // Don't allow changing the page size.
550
544
  if (value && file.blockSize && Number(value) !== file.blockSize) {
551
- return VFS.SQLITE_ERROR;
545
+ return VFS.SQLITE_ERROR
552
546
  }
553
- break;
547
+ break
554
548
  case 'synchronous':
555
549
  // This VFS only recognizes 'full' and not 'full'.
556
550
  if (value) {
@@ -559,29 +553,29 @@ export class IDBMirrorVFS extends FacadeVFS {
559
553
  case '2':
560
554
  case 'extra':
561
555
  case '3':
562
- file.synchronous = 'full';
563
- break;
556
+ file.synchronous = 'full'
557
+ break
564
558
  case 'normal':
565
559
  case '1':
566
- file.synchronous = 'normal';
567
- break;
560
+ file.synchronous = 'normal'
561
+ break
568
562
  default:
569
- console.warn(`unsupported synchronous mode: ${value}`);
570
- return VFS.SQLITE_ERROR;
571
- }
563
+ console.warn(`unsupported synchronous mode: ${value}`)
564
+ return VFS.SQLITE_ERROR
565
+ }
572
566
  }
573
- break;
574
- }
575
- break;
567
+ break
568
+ }
569
+ break
576
570
  case VFS.SQLITE_FCNTL_BEGIN_ATOMIC_WRITE:
577
- this.log?.('xFileControl', 'BEGIN_ATOMIC_WRITE', file.path);
578
- return VFS.SQLITE_OK;
571
+ this.log?.('xFileControl', 'BEGIN_ATOMIC_WRITE', file.path)
572
+ return VFS.SQLITE_OK
579
573
  case VFS.SQLITE_FCNTL_COMMIT_ATOMIC_WRITE:
580
- this.log?.('xFileControl', 'COMMIT_ATOMIC_WRITE', file.path);
581
- return VFS.SQLITE_OK;
574
+ this.log?.('xFileControl', 'COMMIT_ATOMIC_WRITE', file.path)
575
+ return VFS.SQLITE_OK
582
576
  case VFS.SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE:
583
- this.#dropTx(file);
584
- return VFS.SQLITE_OK;
577
+ this.#dropTx(file)
578
+ return VFS.SQLITE_OK
585
579
  case VFS.SQLITE_FCNTL_SYNC:
586
580
  // Propagate database writes to IndexedDB and other clients. Most
587
581
  // often this is a SQLite transaction, but it can also be a
@@ -589,30 +583,30 @@ export class IDBMirrorVFS extends FacadeVFS {
589
583
  //
590
584
  // If SQLITE_FCNTL_OVERWRITE has been received then propagation is
591
585
  // deferred until SQLITE_FCNTL_COMMIT_PHASETWO for file truncation.
592
- this.log?.('xFileControl', 'SYNC', file.path);
586
+ this.log?.('xFileControl', 'SYNC', file.path)
593
587
  if (file.txActive && !file.txOverwrite) {
594
- await this.#commitTx(file);
588
+ await this.#commitTx(file)
595
589
  }
596
- break;
590
+ break
597
591
  case VFS.SQLITE_FCNTL_OVERWRITE:
598
592
  // Marks the beginning of a VACUUM.
599
- file.txOverwrite = true;
600
- break;
593
+ file.txOverwrite = true
594
+ break
601
595
  case VFS.SQLITE_FCNTL_COMMIT_PHASETWO:
602
- // Commit database writes for VACUUM. Other writes will already
603
- // be propagated by SQLITE_FCNTL_SYNC.
604
- this.log?.('xFileControl', 'COMMIT_PHASETWO', file.path);
605
- if (file.txActive) {
606
- await this.#commitTx(file);
607
- }
608
- file.txOverwrite = false;
609
- break;
610
- }
596
+ // Commit database writes for VACUUM. Other writes will already
597
+ // be propagated by SQLITE_FCNTL_SYNC.
598
+ this.log?.('xFileControl', 'COMMIT_PHASETWO', file.path)
599
+ if (file.txActive) {
600
+ await this.#commitTx(file)
601
+ }
602
+ file.txOverwrite = false
603
+ break
604
+ }
611
605
  } catch (e) {
612
- this.#lastError = e;
613
- return VFS.SQLITE_IOERR;
606
+ this.#lastError = e
607
+ return VFS.SQLITE_IOERR
614
608
  }
615
- return VFS.SQLITE_NOTFOUND;
609
+ return VFS.SQLITE_NOTFOUND
616
610
  }
617
611
 
618
612
  /**
@@ -620,105 +614,101 @@ export class IDBMirrorVFS extends FacadeVFS {
620
614
  * @returns {number|Promise<number>}
621
615
  */
622
616
  jDeviceCharacteristics(fileId) {
623
- return 0
624
- | VFS.SQLITE_IOCAP_BATCH_ATOMIC
625
- | VFS.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
617
+ return 0 | VFS.SQLITE_IOCAP_BATCH_ATOMIC | VFS.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
626
618
  }
627
619
 
628
620
  /**
629
- * @param {Uint8Array} zBuf
621
+ * @param {Uint8Array} zBuf
630
622
  * @returns {number}
631
623
  */
632
624
  jGetLastError(zBuf) {
633
625
  if (this.#lastError) {
634
- console.error(this.#lastError);
635
- const outputArray = zBuf.subarray(0, zBuf.byteLength - 1);
636
- const { written } = new TextEncoder().encodeInto(this.#lastError.message, outputArray);
637
- zBuf[written] = 0;
626
+ console.error(this.#lastError)
627
+ const outputArray = zBuf.subarray(0, zBuf.byteLength - 1)
628
+ const { written } = new TextEncoder().encodeInto(this.#lastError.message, outputArray)
629
+ zBuf[written] = 0
638
630
  }
639
631
  return VFS.SQLITE_OK
640
632
  }
641
633
 
642
634
  /**
643
- *
644
- * @param {File} file
645
- * @param {Transaction} tx
635
+ *
636
+ * @param {File} file
637
+ * @param {Transaction} tx
646
638
  */
647
639
  #acceptTx(file, tx) {
648
640
  // Add/update transaction pages.
649
641
  for (const [offset, data] of tx.blocks) {
650
- file.blocks.set(offset, data);
642
+ file.blocks.set(offset, data)
651
643
  if (file.blockSize === 0) {
652
- file.blockSize = data.byteLength;
644
+ file.blockSize = data.byteLength
653
645
  }
654
646
  }
655
647
 
656
- let truncated = tx.fileSize + file.blockSize;
648
+ let truncated = tx.fileSize + file.blockSize
657
649
  while (file.blocks.delete(truncated)) {
658
- truncated += file.blockSize;
650
+ truncated += file.blockSize
659
651
  }
660
652
 
661
- file.viewTx = tx;
653
+ file.viewTx = tx
662
654
  }
663
655
 
664
656
  /**
665
- * @param {File} file
657
+ * @param {File} file
666
658
  */
667
659
  async #commitTx(file) {
668
660
  // Advance our own view. Even if we received our own broadcasts (we
669
661
  // don't), we want our view to be updated synchronously.
670
- this.#acceptTx(file, file.txActive);
671
- this.#setView(file, file.txActive);
662
+ this.#acceptTx(file, file.txActive)
663
+ this.#setView(file, file.txActive)
672
664
 
673
- const oldestTxId = await this.#getOldestTxInUse(file);
665
+ const oldestTxId = await this.#getOldestTxInUse(file)
674
666
 
675
667
  // Update IndexedDB page data.
676
- const idbTx = this.#idb.transaction(['blocks', 'tx'], 'readwrite');
677
- const blocks = idbTx.objectStore('blocks');
668
+ const idbTx = this.#idb.transaction(['blocks', 'tx'], 'readwrite')
669
+ const blocks = idbTx.objectStore('blocks')
678
670
  for (const [offset, data] of file.txActive.blocks) {
679
- blocks.put({ path: file.path, offset, data });
671
+ blocks.put({ path: file.path, offset, data })
680
672
  }
681
673
 
682
674
  // Delete obsolete transactions no longer needed.
683
- const oldRange = IDBKeyRange.bound(
684
- [file.path, -Infinity], [file.path, oldestTxId],
685
- false, true);
686
- idbTx.objectStore('tx').delete(oldRange);
675
+ const oldRange = IDBKeyRange.bound([file.path, -Infinity], [file.path, oldestTxId], false, true)
676
+ idbTx.objectStore('tx').delete(oldRange)
687
677
 
688
678
  // Save transaction object. Omit page data as an optimization.
689
- const txSansData = Object.assign({}, file.txActive);
690
- txSansData.blocks = new Map(Array.from(file.txActive.blocks, ([k]) => [k, null]));
691
- idbTx.objectStore('tx').put(txSansData);
679
+ const txSansData = Object.assign({}, file.txActive)
680
+ txSansData.blocks = new Map(Array.from(file.txActive.blocks, ([k]) => [k, null]))
681
+ idbTx.objectStore('tx').put(txSansData)
692
682
 
693
683
  // Broadcast transaction once it commits.
694
684
  const complete = new Promise((resolve, reject) => {
695
- const message = file.txActive;
685
+ const message = file.txActive
696
686
  idbTx.oncomplete = () => {
697
- file.broadcastChannel.postMessage(message);
698
- resolve();
699
- };
700
- idbTx.onabort = () => reject(idbTx.error);
701
- idbTx.commit();
702
- });
687
+ file.broadcastChannel.postMessage(message)
688
+ resolve()
689
+ }
690
+ idbTx.onabort = () => reject(idbTx.error)
691
+ idbTx.commit()
692
+ })
703
693
 
704
694
  if (file.synchronous === 'full') {
705
- await complete;
695
+ await complete
706
696
  }
707
697
 
708
- file.txActive = null;
709
- file.txWriteHint = false;
698
+ file.txActive = null
699
+ file.txWriteHint = false
710
700
  }
711
701
 
712
702
  /**
713
- * @param {File} file
703
+ * @param {File} file
714
704
  */
715
705
  #dropTx(file) {
716
- file.txActive = null;
717
- file.txWriteHint = false;
706
+ file.txActive = null
707
+ file.txWriteHint = false
718
708
  }
719
709
 
720
710
  /**
721
- * @param {File} file
711
+ * @param {File} file
722
712
  */
723
713
  #requireTxActive(file) {
724
714
  if (!file.txActive) {
@@ -727,32 +717,33 @@ export class IDBMirrorVFS extends FacadeVFS {
727
717
  txId: file.viewTx.txId + 1,
728
718
  blocks: new Map(),
729
719
  fileSize: file.blockSize * file.blocks.size,
730
- };
720
+ }
731
721
  }
732
722
  }
733
723
 
734
724
  /**
735
- * @param {string} path
725
+ * @param {string} path
736
726
  * @returns {Promise}
737
727
  */
738
728
  async #deleteFile(path) {
739
- this.#mapPathToFile.delete(path);
729
+ this.#mapPathToFile.delete(path)
740
730
 
741
731
  // Only main databases are stored in IndexedDB and SQLite never
742
732
  // deletes main databases, but delete blocks here anyway for
743
733
  // standalone use.
744
- const request = this.#idb.transaction(['blocks'], 'readwrite')
734
+ const request = this.#idb
735
+ .transaction(['blocks'], 'readwrite')
745
736
  .objectStore('blocks')
746
- .delete(IDBKeyRange.bound([path, 0], [path, Infinity]));
737
+ .delete(IDBKeyRange.bound([path, 0], [path, Infinity]))
747
738
  await new Promise((resolve, reject) => {
748
- const idbTx = request.transaction;
749
- idbTx.oncomplete = resolve;
750
- idbTx.onerror = () => reject(idbTx.error);
751
- });
739
+ const idbTx = request.transaction
740
+ idbTx.oncomplete = resolve
741
+ idbTx.onerror = () => reject(idbTx.error)
742
+ })
752
743
  }
753
744
 
754
745
  /**
755
- * @param {File} file
746
+ * @param {File} file
756
747
  * @returns {Promise<number>}
757
748
  */
758
749
  async #getOldestTxInUse(file) {
@@ -760,130 +751,132 @@ export class IDBMirrorVFS extends FacadeVFS {
760
751
  // the latest transaction it knows about. We can find the oldest
761
752
  // transaction by listing the those locks and extracting the earliest
762
753
  // transaction id.
763
- const TX_LOCK_REGEX = /^(.*)@@\[(\d+)\]$/;
764
- let oldestTxId = file.viewTx.txId;
765
- const locks = await navigator.locks.query();
754
+ const TX_LOCK_REGEX = /^(.*)@@\[(\d+)\]$/
755
+ let oldestTxId = file.viewTx.txId
756
+ const locks = await navigator.locks.query()
766
757
  for (const { name } of locks.held) {
767
- const m = TX_LOCK_REGEX.exec(name);
758
+ const m = TX_LOCK_REGEX.exec(name)
768
759
  if (m && m[1] === file.path) {
769
- oldestTxId = Math.min(oldestTxId, Number(m[2]));
760
+ oldestTxId = Math.min(oldestTxId, Number(m[2]))
770
761
  }
771
762
  }
772
- return oldestTxId;
763
+ return oldestTxId
773
764
  }
774
765
 
775
766
  /**
776
767
  * Acquire one of the database file internal Web Locks.
777
- * @param {File} file
778
- * @param {'write'|'reserved'|'hint'} name
779
- * @param {LockOptions} options
768
+ * @param {File} file
769
+ * @param {'write'|'reserved'|'hint'} name
770
+ * @param {LockOptions} options
780
771
  * @returns {Promise<boolean>}
781
772
  */
782
773
  #lock(file, name, options = {}) {
783
- return new Promise(resolve => {
784
- const lockName = `${file.path}@@${name}`;
785
- navigator.locks.request(lockName, options, lock => {
786
- if (lock) {
787
- return new Promise(release => {
788
- file.locks[name] = () => {
789
- release();
790
- file.locks[name] = null;
791
- };
792
- resolve(true);
793
- });
794
- } else {
795
- file.locks[name] = null;
796
- resolve(false);
797
- }
798
- }).catch(e => {
799
- if (e.name !== 'AbortError') throw e;
800
- });
801
- });
774
+ return new Promise((resolve) => {
775
+ const lockName = `${file.path}@@${name}`
776
+ navigator.locks
777
+ .request(lockName, options, (lock) => {
778
+ if (lock) {
779
+ return new Promise((release) => {
780
+ file.locks[name] = () => {
781
+ release()
782
+ file.locks[name] = null
783
+ }
784
+ resolve(true)
785
+ })
786
+ } else {
787
+ file.locks[name] = null
788
+ resolve(false)
789
+ }
790
+ })
791
+ .catch((e) => {
792
+ if (e.name !== 'AbortError') throw e
793
+ })
794
+ })
802
795
  }
803
796
 
804
797
  /**
805
798
  * Handle prevously received messages from other connections.
806
- * @param {File} file
799
+ * @param {File} file
807
800
  */
808
801
  #processBroadcasts(file) {
809
802
  // Sort transaction messages by id.
810
- file.broadcastReceived.sort((a, b) => a.txId - b.txId);
803
+ file.broadcastReceived.sort((a, b) => a.txId - b.txId)
811
804
 
812
- let nHandled = 0;
813
- let newTx = file.viewTx;
805
+ let nHandled = 0
806
+ let newTx = file.viewTx
814
807
  for (const message of file.broadcastReceived) {
815
808
  if (message.txId <= newTx.txId) {
816
809
  // This transaction is already incorporated into our view.
817
810
  } else if (message.txId === newTx.txId + 1) {
818
811
  // This is the next expected transaction.
819
- this.log?.(`accept tx ${message.txId}`);
820
- this.#acceptTx(file, message);
821
- newTx = message;
812
+ this.log?.(`accept tx ${message.txId}`)
813
+ this.#acceptTx(file, message)
814
+ newTx = message
822
815
  } else {
823
816
  // There is a gap in the transaction sequence.
824
- console.warn(`missing tx ${newTx.txId + 1} (got ${message.txId})`);
825
- break;
817
+ console.warn(`missing tx ${newTx.txId + 1} (got ${message.txId})`)
818
+ break
826
819
  }
827
- nHandled++;
820
+ nHandled++
828
821
  }
829
822
 
830
823
  // Remove handled messages from the list.
831
- file.broadcastReceived.splice(0, nHandled);
824
+ file.broadcastReceived.splice(0, nHandled)
832
825
 
833
826
  // Tell other connections about a change in our view.
834
827
  if (newTx.txId > file.viewTx.txId) {
835
828
  // No need to await here.
836
- this.#setView(file, newTx);
829
+ this.#setView(file, newTx)
837
830
  }
838
831
  }
839
832
 
840
833
  /**
841
- * @param {File} file
842
- * @param {Transaction} tx
834
+ * @param {File} file
835
+ * @param {Transaction} tx
843
836
  */
844
837
  async #setView(file, tx) {
845
838
  // Publish our view of the database with a lock name that includes
846
839
  // the transaction id. As long as we hold the lock, no other connection
847
840
  // will overwrite data we are using.
848
- file.viewTx = tx;
849
- const lockName = `${file.path}@@[${tx.txId}]`;
850
- const newReleaser = await new Promise(resolve => {
851
- navigator.locks.request(lockName, SHARED, lock => {
852
- return new Promise(release => {
853
- resolve(release);
854
- });
855
- });
856
- });
841
+ file.viewTx = tx
842
+ const lockName = `${file.path}@@[${tx.txId}]`
843
+ const newReleaser = await new Promise((resolve) => {
844
+ navigator.locks.request(lockName, SHARED, (lock) => {
845
+ return new Promise((release) => {
846
+ resolve(release)
847
+ })
848
+ })
849
+ })
857
850
 
858
851
  // The new lock is acquired so release the old one.
859
- file.viewReleaser?.();
860
- file.viewReleaser = newReleaser;
852
+ file.viewReleaser?.()
853
+ file.viewReleaser = newReleaser
861
854
  }
862
855
  }
863
856
 
864
857
  /**
865
858
  * Wrap IndexedDB request with a Promise.
866
- * @param {IDBRequest} request
867
- * @returns
859
+ * @param {IDBRequest} request
860
+ * @returns
868
861
  */
869
862
  function idbX(request) {
870
863
  return new Promise((resolve, reject) => {
871
- request.onsuccess = () => resolve(request.result);
872
- request.onerror = () => reject(request.error);
873
- });
864
+ request.onsuccess = () => resolve(request.result)
865
+ request.onerror = () => reject(request.error)
866
+ })
874
867
  }
875
868
 
876
869
  /**
877
870
  * Extract a C string from WebAssembly memory.
878
- * @param {DataView} dataView
879
- * @param {number} offset
880
- * @returns
871
+ * @param {DataView} dataView
872
+ * @param {number} offset
873
+ * @returns
881
874
  */
882
875
  function cvtString(dataView, offset) {
883
- const p = dataView.getUint32(offset, true);
876
+ const p = dataView.getUint32(offset, true)
884
877
  if (p) {
885
- const chars = new Uint8Array(dataView.buffer, p);
886
- return new TextDecoder().decode(chars.subarray(0, chars.indexOf(0)));
878
+ const chars = new Uint8Array(dataView.buffer, p)
879
+ return new TextDecoder().decode(chars.subarray(0, chars.indexOf(0)))
887
880
  }
888
- return null;
881
+ return null
889
882
  }