@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 +1 @@
1
- {"version":3,"file":"worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js","mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;AC3KA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;AC52BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;AC3PA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://sdk_web/../../node_modules/@journeyapps/wa-sqlite/src/VFS.js","webpack://sdk_web/../../node_modules/@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js","webpack://sdk_web/../../node_modules/@journeyapps/wa-sqlite/src/examples/IDBContext.js","webpack://sdk_web/../../node_modules/@journeyapps/wa-sqlite/src/examples/WebLocks.js"],"sourcesContent":["// Copyright 2022 Roy T. Hashimoto. All Rights Reserved.\nimport * as VFS from './sqlite-constants.js';\nexport * from './sqlite-constants.js';\n\n// Base class for a VFS.\nexport class Base {\n mxPathName = 64;\n\n /**\n * @param {number} fileId \n * @returns {number}\n */\n xClose(fileId) {\n return VFS.SQLITE_IOERR;\n }\n\n /**\n * @param {number} fileId \n * @param {Uint8Array} pData \n * @param {number} iOffset\n * @returns {number}\n */\n xRead(fileId, pData, iOffset) {\n return VFS.SQLITE_IOERR;\n }\n\n /**\n * @param {number} fileId \n * @param {Uint8Array} pData \n * @param {number} iOffset\n * @returns {number}\n */\n xWrite(fileId, pData, iOffset) {\n return VFS.SQLITE_IOERR;\n }\n\n /**\n * @param {number} fileId \n * @param {number} iSize \n * @returns {number}\n */\n xTruncate(fileId, iSize) {\n return VFS.SQLITE_IOERR;\n }\n\n /**\n * @param {number} fileId \n * @param {*} flags \n * @returns {number}\n */\n xSync(fileId, flags) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} fileId \n * @param {DataView} pSize64 \n * @returns {number}\n */\n xFileSize(fileId, pSize64) {\n return VFS.SQLITE_IOERR;\n }\n\n /**\n * @param {number} fileId \n * @param {number} flags \n * @returns {number}\n */\n xLock(fileId, flags) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} fileId \n * @param {number} flags \n * @returns {number}\n */\n xUnlock(fileId, flags) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} fileId \n * @param {DataView} pResOut \n * @returns {number}\n */\n xCheckReservedLock(fileId, pResOut) {\n pResOut.setInt32(0, 0, true);\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} fileId \n * @param {number} op \n * @param {DataView} pArg \n * @returns {number}\n */\n xFileControl(fileId, op, pArg) {\n return VFS.SQLITE_NOTFOUND;\n }\n\n /**\n * @param {number} fileId \n * @returns {number}\n */\n xSectorSize(fileId) {\n return 512;\n }\n\n /**\n * @param {number} fileId \n * @returns {number}\n */\n xDeviceCharacteristics(fileId) {\n return 0;\n }\n\n /**\n * @param {string?} name \n * @param {number} fileId \n * @param {number} flags \n * @param {DataView} pOutFlags \n * @returns {number}\n */\n xOpen(name, fileId, flags, pOutFlags) {\n return VFS.SQLITE_CANTOPEN;\n }\n\n /**\n * @param {string} name \n * @param {number} syncDir \n * @returns {number}\n */\n xDelete(name, syncDir) {\n return VFS.SQLITE_IOERR;\n }\n\n /**\n * @param {string} name \n * @param {number} flags \n * @param {DataView} pResOut \n * @returns {number}\n */\n xAccess(name, flags, pResOut) {\n return VFS.SQLITE_IOERR;\n }\n\n /**\n * Handle asynchronous operation. This implementation will be overriden on\n * registration by an Asyncify build.\n * @param {function(): Promise<number>} f \n * @returns {number}\n */\n handleAsync(f) {\n // This default implementation deliberately does not match the\n // declared signature. It will be used in testing VFS classes\n // separately from SQLite. This will work acceptably for methods\n // that simply return the handleAsync() result without using it.\n // @ts-ignore\n return f();\n }\n}\n\nexport const FILE_TYPE_MASK = [\n VFS.SQLITE_OPEN_MAIN_DB,\n VFS.SQLITE_OPEN_MAIN_JOURNAL,\n VFS.SQLITE_OPEN_TEMP_DB,\n VFS.SQLITE_OPEN_TEMP_JOURNAL,\n VFS.SQLITE_OPEN_TRANSIENT_DB,\n VFS.SQLITE_OPEN_SUBJOURNAL,\n VFS.SQLITE_OPEN_SUPER_JOURNAL\n].reduce((mask, element) => mask | element);","// Copyright 2022 Roy T. Hashimoto. All Rights Reserved.\nimport * as VFS from '../VFS.js';\nimport { WebLocksExclusive as WebLocks } from './WebLocks.js';\nimport { IDBContext } from './IDBContext.js';\n\nconst SECTOR_SIZE = 512;\nconst MAX_TASK_MILLIS = 3000;\n\n/**\n * @typedef VFSOptions\n * @property {\"default\"|\"strict\"|\"relaxed\"} [durability]\n * @property {\"deferred\"|\"manual\"} [purge]\n * @property {number} [purgeAtLeast]\n */\n\n/** @type {VFSOptions} */\nconst DEFAULT_OPTIONS = {\n durability: \"default\",\n purge: \"deferred\",\n purgeAtLeast: 16\n};\n\nfunction log(...args) {\n // console.debug(...args);\n}\n\n/**\n * @typedef FileBlock IndexedDB object with key [path, offset, version]\n * @property {string} path\n * @property {number} offset negative of position in file\n * @property {number} version\n * @property {Uint8Array} data\n *\n * @property {number} [fileSize] Only present on block 0\n*/\n\n/**\n * @typedef OpenedFileEntry\n * @property {string} path\n * @property {number} flags\n * @property {FileBlock} block0\n * @property {boolean} isMetadataChanged\n * @property {WebLocks} locks\n * \n * @property {Set<number>} [changedPages]\n * @property {boolean} [overwrite]\n */\n\n// This sample VFS stores optionally versioned writes to IndexedDB, which\n// it uses with the SQLite xFileControl() batch atomic write feature.\nexport class IDBBatchAtomicVFS extends VFS.Base {\n #options;\n /** @type {Map<number, OpenedFileEntry>} */ #mapIdToFile = new Map();\n\n /** @type {IDBContext} */ #idb;\n /** @type {Set<string>} */ #pendingPurges = new Set();\n\n #taskTimestamp = performance.now();\n #pendingAsync = new Set();\n\n // Asyncify can grow WebAssembly memory during an asynchronous call.\n // If this happens, then any array buffer arguments will be detached.\n // The workaround is when finding a detached buffer, set this handler\n // function to process the new buffer outside handlerAsync().\n #growthHandler = null;\n\n constructor(idbDatabaseName = 'wa-sqlite', options = DEFAULT_OPTIONS) {\n super();\n this.name = idbDatabaseName;\n this.#options = Object.assign({}, DEFAULT_OPTIONS, options);\n this.#idb = new IDBContext(openDatabase(idbDatabaseName), {\n durability: this.#options.durability\n });\n }\n\n async close() {\n for (const fileId of this.#mapIdToFile.keys()) {\n await this.xClose(fileId);\n }\n\n await this.#idb?.close();\n this.#idb = null;\n }\n\n /**\n * @param {string?} name \n * @param {number} fileId \n * @param {number} flags \n * @param {DataView} pOutFlags \n * @returns {number}\n */\n xOpen(name, fileId, flags, pOutFlags) {\n const result = this.handleAsync(async () => {\n if (name === null) name = `null_${fileId}`;\n log(`xOpen ${name} 0x${fileId.toString(16)} 0x${flags.toString(16)}`);\n\n try {\n // Filenames can be URLs, possibly with query parameters.\n const url = new URL(name, 'http://localhost/');\n /** @type {OpenedFileEntry} */ const file = {\n path: url.pathname,\n flags,\n block0: null,\n isMetadataChanged: true,\n locks: new WebLocks(url.pathname)\n };\n this.#mapIdToFile.set(fileId, file);\n\n // Read the first block, which also contains the file metadata.\n await this.#idb.run('readwrite', async ({blocks}) => {\n file.block0 = await blocks.get(this.#bound(file, 0));\n if (!file.block0) {\n if (flags & VFS.SQLITE_OPEN_CREATE) {\n file.block0 = {\n path: file.path,\n offset: 0,\n version: 0,\n data: new Uint8Array(0),\n fileSize: 0\n };\n blocks.put(file.block0);\n } else {\n throw new Error(`file not found: ${file.path}`);\n }\n }\n });\n\n // @ts-ignore\n if (pOutFlags.buffer.detached || !pOutFlags.buffer.byteLength) {\n pOutFlags = new DataView(new ArrayBuffer(4));\n this.#growthHandler = (pOutFlagsNew) => {\n pOutFlagsNew.setInt32(0, pOutFlags.getInt32(0, true), true);\n };\n }\n pOutFlags.setInt32(0, flags & VFS.SQLITE_OPEN_READONLY, true);\n return VFS.SQLITE_OK;\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_CANTOPEN;\n }\n });\n\n this.#growthHandler?.(pOutFlags);\n this.#growthHandler = null;\n return result;\n }\n\n /**\n * @param {number} fileId \n * @returns {number}\n */\n xClose(fileId) {\n return this.handleAsync(async () => {\n try {\n const file = this.#mapIdToFile.get(fileId);\n if (file) {\n log(`xClose ${file.path}`);\n\n this.#mapIdToFile.delete(fileId);\n if (file.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {\n this.#idb.run('readwrite', ({blocks}) => {\n blocks.delete(IDBKeyRange.bound([file.path], [file.path, []]));\n });\n }\n }\n return VFS.SQLITE_OK;\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n });\n }\n\n /**\n * @param {number} fileId \n * @param {Uint8Array} pData \n * @param {number} iOffset\n * @returns {number}\n */\n xRead(fileId, pData, iOffset) {\n const byteLength = pData.byteLength;\n const result = this.handleAsync(async () => {\n const file = this.#mapIdToFile.get(fileId);\n log(`xRead ${file.path} ${pData.byteLength} ${iOffset}`);\n\n try {\n // Read as many blocks as necessary to satisfy the read request.\n // Usually a read fits within a single write but there is at least\n // one case - rollback after journal spill - where reads cross\n // write boundaries so we have to allow for that.\n const result = await this.#idb.run('readonly', async ({blocks}) => {\n // @ts-ignore\n if (pData.buffer.detached || !pData.buffer.byteLength) {\n // WebAssembly memory has grown, invalidating our buffer. Use\n // a temporary buffer and copy after this asynchronous call\n // completes.\n pData = new Uint8Array(byteLength);\n this.#growthHandler = (pDataNew) => pDataNew.set(pData);\n }\n\n let pDataOffset = 0;\n while (pDataOffset < pData.byteLength) {\n // Fetch the IndexedDB block for this file location.\n const fileOffset = iOffset + pDataOffset;\n /** @type {FileBlock} */\n const block = fileOffset < file.block0.data.byteLength ?\n file.block0 :\n await blocks.get(this.#bound(file, -fileOffset));\n\n if (!block || block.data.byteLength - block.offset <= fileOffset) {\n pData.fill(0, pDataOffset);\n return VFS.SQLITE_IOERR_SHORT_READ;\n }\n\n const buffer = pData.subarray(pDataOffset);\n const blockOffset = fileOffset + block.offset;\n const nBytesToCopy = Math.min(\n Math.max(block.data.byteLength - blockOffset, 0), // source bytes\n buffer.byteLength); // destination bytes\n buffer.set(block.data.subarray(blockOffset, blockOffset + nBytesToCopy));\n pDataOffset += nBytesToCopy;\n }\n return VFS.SQLITE_OK;\n });\n return result;\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n });\n\n this.#growthHandler?.(pData);\n this.#growthHandler = null;\n return result;\n }\n\n /**\n * @param {number} fileId \n * @param {Uint8Array} pData \n * @param {number} iOffset\n * @returns {number}\n */\n xWrite(fileId, pData, iOffset) {\n // Handle asynchronously every MAX_TASK_MILLIS milliseconds. This is\n // tricky because Asyncify calls asynchronous methods twice: once\n // to initiate the call and unwinds the stack, then rewinds the\n // stack and calls again to retrieve the completed result.\n const rewound = this.#pendingAsync.has(fileId);\n if (rewound || performance.now() - this.#taskTimestamp > MAX_TASK_MILLIS) {\n const result = this.handleAsync(async () => {\n if (this.handleAsync !== super.handleAsync) {\n this.#pendingAsync.add(fileId);\n }\n await new Promise(resolve => setTimeout(resolve));\n\n const result = this.#xWriteHelper(fileId, pData.slice(), iOffset);\n this.#taskTimestamp = performance.now();\n return result;\n });\n\n if (rewound) this.#pendingAsync.delete(fileId);\n return result;\n }\n return this.#xWriteHelper(fileId, pData, iOffset);\n }\n\n /**\n * @param {number} fileId \n * @param {Uint8Array} pData \n * @param {number} iOffset\n * @returns {number}\n */\n #xWriteHelper(fileId, pData, iOffset) {\n const file = this.#mapIdToFile.get(fileId);\n log(`xWrite ${file.path} ${pData.byteLength} ${iOffset}`);\n\n try {\n // Update file size if appending.\n const prevFileSize = file.block0.fileSize;\n if (file.block0.fileSize < iOffset + pData.byteLength) {\n file.block0.fileSize = iOffset + pData.byteLength;\n file.isMetadataChanged = true;\n }\n\n // Convert the write directly into an IndexedDB object. Our assumption\n // is that SQLite will only overwrite data with an xWrite of the same\n // offset and size unless the database page size changes, except when\n // changing database page size which is handled by #reblockIfNeeded().\n const block = iOffset === 0 ? file.block0 : {\n path: file.path,\n offset: -iOffset,\n version: file.block0.version,\n data: null\n };\n block.data = pData.slice();\n\n if (file.changedPages) {\n // This write is part of a batch atomic write. All writes in the\n // batch have a new version, so update the changed list to allow\n // old versions to be eventually deleted.\n if (prevFileSize === file.block0.fileSize) {\n file.changedPages.add(-iOffset);\n }\n\n // Defer writing block 0 to IndexedDB until batch commit.\n if (iOffset !== 0) {\n this.#idb.run('readwrite', ({blocks}) => blocks.put(block));\n }\n } else {\n // Not a batch atomic write so write through.\n this.#idb.run('readwrite', ({blocks}) => blocks.put(block));\n }\n\n // Clear dirty flag if page 0 was written.\n file.isMetadataChanged = iOffset === 0 ? false : file.isMetadataChanged;\n return VFS.SQLITE_OK;\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n }\n\n /**\n * @param {number} fileId \n * @param {number} iSize \n * @returns {number}\n */\n xTruncate(fileId, iSize) {\n const file = this.#mapIdToFile.get(fileId);\n log(`xTruncate ${file.path} ${iSize}`);\n\n try {\n Object.assign(file.block0, {\n fileSize: iSize,\n data: file.block0.data.slice(0, iSize)\n });\n\n // Delete all blocks beyond the file size and update metadata.\n // This is never called within a transaction.\n const block0 = Object.assign({}, file.block0);\n this.#idb.run('readwrite', ({blocks})=> {\n blocks.delete(this.#bound(file, -Infinity, -iSize));\n blocks.put(block0);\n });\n return VFS.SQLITE_OK;\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n }\n\n /**\n * @param {number} fileId \n * @param {number} flags \n * @returns {number}\n */\n xSync(fileId, flags) {\n // Skip IndexedDB sync if durability is relaxed and the last\n // sync was recent enough.\n const rewound = this.#pendingAsync.has(fileId);\n if (rewound || this.#options.durability !== 'relaxed' ||\n performance.now() - this.#taskTimestamp > MAX_TASK_MILLIS) {\n const result = this.handleAsync(async () => {\n if (this.handleAsync !== super.handleAsync) {\n this.#pendingAsync.add(fileId);\n }\n\n const result = await this.#xSyncHelper(fileId, flags);\n this.#taskTimestamp = performance.now();\n return result;\n });\n\n if (rewound) this.#pendingAsync.delete(fileId);\n return result;\n }\n\n const file = this.#mapIdToFile.get(fileId);\n log(`xSync ${file.path} ${flags}`);\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} fileId \n * @param {number} flags \n * @returns {Promise<number>}\n */\n async #xSyncHelper(fileId, flags) {\n const file = this.#mapIdToFile.get(fileId);\n log(`xSync ${file.path} ${flags}`);\n try {\n if (file.isMetadataChanged) {\n // Metadata has changed so write block 0 to IndexedDB.\n this.#idb.run('readwrite', async ({blocks}) => {\n await blocks.put(file.block0);\n }); \n file.isMetadataChanged = false;\n }\n await this.#idb.sync();\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} fileId \n * @param {DataView} pSize64 \n * @returns {number}\n */\n xFileSize(fileId, pSize64) {\n const file = this.#mapIdToFile.get(fileId);\n log(`xFileSize ${file.path}`);\n\n pSize64.setBigInt64(0, BigInt(file.block0.fileSize), true)\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} fileId \n * @param {number} flags \n * @returns {number}\n */\n xLock(fileId, flags) {\n return this.handleAsync(async () => {\n const file = this.#mapIdToFile.get(fileId);\n log(`xLock ${file.path} ${flags}`);\n\n try {\n // Acquire the lock.\n const result = await file.locks.lock(flags);\n if (result === VFS.SQLITE_OK && file.locks.state === VFS.SQLITE_LOCK_SHARED) {\n // Update block 0 in case another connection changed it.\n file.block0 = await this.#idb.run('readonly', ({blocks}) => {\n return blocks.get(this.#bound(file, 0));\n });\n }\n return result;\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n });\n }\n\n /**\n * @param {number} fileId \n * @param {number} flags \n * @returns {number}\n */\n xUnlock(fileId, flags) {\n return this.handleAsync(async () => {\n const file = this.#mapIdToFile.get(fileId);\n log(`xUnlock ${file.path} ${flags}`);\n \n try {\n return file.locks.unlock(flags);\n } catch(e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n });\n }\n\n /**\n * @param {number} fileId \n * @param {DataView} pResOut \n * @returns {number}\n */\n xCheckReservedLock(fileId, pResOut) {\n const result = this.handleAsync(async () => {\n const file = this.#mapIdToFile.get(fileId);\n log(`xCheckReservedLock ${file.path}`);\n\n const isReserved = await file.locks.isSomewhereReserved();\n function setOutput(pResOut) {\n };\n\n // @ts-ignore\n if (pResOut.buffer.detached || !pResOut.buffer.byteLength) {\n pResOut = new DataView(new ArrayBuffer(4));\n this.#growthHandler = (pResOutNew) => {\n pResOutNew.setInt32(0, pResOut.getInt32(0, true), true);\n };\n }\n pResOut.setInt32(0, isReserved ? 1 : 0, true);\n return VFS.SQLITE_OK;\n });\n\n this.#growthHandler?.(pResOut);\n this.#growthHandler = null;\n return result;\n }\n\n /**\n * @param {number} fileId \n * @returns {number}\n */\n xSectorSize(fileId) {\n log('xSectorSize');\n return SECTOR_SIZE;\n }\n\n /**\n * @param {number} fileId \n * @returns {number}\n */\n xDeviceCharacteristics(fileId) {\n log('xDeviceCharacteristics');\n return VFS.SQLITE_IOCAP_BATCH_ATOMIC |\n VFS.SQLITE_IOCAP_SAFE_APPEND |\n VFS.SQLITE_IOCAP_SEQUENTIAL |\n VFS.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;\n }\n\n /**\n * @param {number} fileId \n * @param {number} op \n * @param {DataView} pArg \n * @returns {number}\n */\n xFileControl(fileId, op, pArg) {\n const file = this.#mapIdToFile.get(fileId);\n log(`xFileControl ${file.path} ${op}`);\n\n switch (op) {\n case 11: //SQLITE_FCNTL_OVERWRITE\n // This called on VACUUM. Set a flag so we know whether to check\n // later if the page size changed.\n file.overwrite = true;\n return VFS.SQLITE_OK;\n\n case 21: // SQLITE_FCNTL_SYNC\n // This is called at the end of each database transaction, whether\n // it is batch atomic or not. Handle page size changes here.\n if (file.overwrite) {\n // As an optimization we only check for and handle a page file\n // changes if we know a VACUUM has been done because handleAsync()\n // has to unwind and rewind the stack. We must be sure to follow\n // the same conditional path in both calls.\n try {\n return this.handleAsync(async () => {\n await this.#reblockIfNeeded(file);\n return VFS.SQLITE_OK;\n });\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n }\n\n if (file.isMetadataChanged) {\n // Metadata has changed so write block 0 to IndexedDB.\n try {\n this.#idb.run('readwrite', async ({blocks}) => {\n await blocks.put(file.block0);\n });\n file.isMetadataChanged = false;\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n }\n return VFS.SQLITE_OK;\n\n case 22: // SQLITE_FCNTL_COMMIT_PHASETWO\n // This is called after a commit is completed.\n file.overwrite = false;\n return VFS.SQLITE_OK;\n\n case 31: // SQLITE_FCNTL_BEGIN_ATOMIC_WRITE\n return this.handleAsync(async () => {\n try {\n // Prepare a new version for IndexedDB blocks.\n file.block0.version--;\n file.changedPages = new Set();\n\n // Clear blocks from abandoned transactions that would conflict\n // with the new transaction.\n this.#idb.run('readwrite', async ({blocks}) => {\n const keys = await blocks.index('version').getAllKeys(IDBKeyRange.bound(\n [file.path],\n [file.path, file.block0.version]));\n for (const key of keys) {\n blocks.delete(key);\n }\n });\n return VFS.SQLITE_OK;\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n });\n\n case 32: // SQLITE_FCNTL_COMMIT_ATOMIC_WRITE\n try {\n const block0 = Object.assign({}, file.block0);\n block0.data = block0.data.slice();\n const changedPages = file.changedPages;\n file.changedPages = null;\n file.isMetadataChanged = false;\n this.#idb.run('readwrite', async ({blocks})=> {\n // Write block 0 to commit the new version.\n blocks.put(block0);\n\n // Blocks to purge are saved in a special IndexedDB object with\n // an \"index\" of \"purge\". Add pages changed by this transaction.\n const purgeBlock = await blocks.get([file.path, 'purge', 0]) ?? {\n path: file.path,\n offset: 'purge',\n version: 0,\n data: new Map(),\n count: 0\n };\n\n purgeBlock.count += changedPages.size;\n for (const pageIndex of changedPages) {\n purgeBlock.data.set(pageIndex, block0.version);\n }\n\n blocks.put(purgeBlock);\n this.#maybePurge(file.path, purgeBlock.count);\n });\n return VFS.SQLITE_OK;\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n\n case 33: // SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE\n return this.handleAsync(async () => {\n try {\n // Restore original state. Objects for the abandoned version will\n // be left in IndexedDB to be removed by the next atomic write\n // transaction.\n file.changedPages = null;\n file.isMetadataChanged = false;\n file.block0 = await this.#idb.run('readonly', ({blocks}) => {\n return blocks.get([file.path, 0, file.block0.version + 1]);\n });\n return VFS.SQLITE_OK;\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n });\n\n default:\n return VFS.SQLITE_NOTFOUND;\n }\n }\n\n /**\n * @param {string} name \n * @param {number} flags \n * @param {DataView} pResOut \n * @returns {number}\n */\n xAccess(name, flags, pResOut) {\n const result = this.handleAsync(async () => {\n try {\n const path = new URL(name, 'file://localhost/').pathname;\n log(`xAccess ${path} ${flags}`);\n\n // Check if block 0 exists.\n const key = await this.#idb.run('readonly', ({blocks}) => {\n return blocks.getKey(this.#bound({path}, 0));\n });\n\n // @ts-ignore\n if (pResOut.buffer.detached || !pResOut.buffer.byteLength) {\n pResOut = new DataView(new ArrayBuffer(4));\n this.#growthHandler = (pResOutNew) => {\n pResOutNew.setInt32(0, pResOut.getInt32(0, true), true);\n }\n }\n pResOut.setInt32(0, key ? 1 : 0, true);\n return VFS.SQLITE_OK;\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n });\n\n this.#growthHandler?.(pResOut);\n this.#growthHandler = null;\n return result;\n }\n\n /**\n * @param {string} name \n * @param {number} syncDir \n * @returns {number}\n */\n xDelete(name, syncDir) {\n return this.handleAsync(async () => {\n const path = new URL(name, 'file://localhost/').pathname;\n log(`xDelete ${path} ${syncDir}`);\n\n try {\n this.#idb.run('readwrite', ({blocks}) => {\n return blocks.delete(IDBKeyRange.bound([path], [path, []]));\n });\n if (syncDir) {\n await this.#idb.sync();\n }\n return VFS.SQLITE_OK;\n } catch (e) {\n console.error(e);\n return VFS.SQLITE_IOERR;\n }\n });\n }\n\n /**\n * Purge obsolete blocks from a database file.\n * @param {string} path \n */\n async purge(path) {\n const start = Date.now();\n await this.#idb.run('readwrite', async ({blocks}) => {\n const purgeBlock = await blocks.get([path, 'purge', 0]);\n if (purgeBlock) {\n for (const [pageOffset, version] of purgeBlock.data) {\n blocks.delete(IDBKeyRange.bound(\n [path, pageOffset, version],\n [path, pageOffset, Infinity],\n true, false));\n }\n await blocks.delete([path, 'purge', 0]);\n }\n log(`purge ${path} ${purgeBlock?.data.size ?? 0} pages in ${Date.now() - start} ms`);\n });\n }\n\n /**\n * Conditionally schedule a purge task.\n * @param {string} path \n * @param {number} nPages \n */\n #maybePurge(path, nPages) {\n if (this.#options.purge === 'manual' ||\n this.#pendingPurges.has(path) ||\n nPages < this.#options.purgeAtLeast) {\n // No purge needed.\n return;\n }\n \n if (globalThis.requestIdleCallback) {\n globalThis.requestIdleCallback(() => {\n this.purge(path);\n this.#pendingPurges.delete(path)\n });\n } else {\n setTimeout(() => {\n this.purge(path);\n this.#pendingPurges.delete(path)\n });\n }\n this.#pendingPurges.add(path);\n }\n\n #bound(file, begin, end = 0) {\n // Fetch newest block 0. For other blocks, use block 0 version.\n const version = !begin || -begin < file.block0.data.length ?\n -Infinity :\n file.block0.version;\n return IDBKeyRange.bound(\n [file.path, begin, version],\n [file.path, end, Infinity]);\n }\n\n // The database page size can be changed with PRAGMA page_size and VACUUM.\n // The updated file will be overwritten with a regular transaction using\n // the old page size. After that it will be read and written using the\n // new page size, so the IndexedDB objects must be combined or split\n // appropriately.\n async #reblockIfNeeded(file) {\n const oldPageSize = file.block0.data.length;\n if (oldPageSize < 18) return; // no page size defined\n\n const view = new DataView(file.block0.data.buffer, file.block0.data.byteOffset);\n let newPageSize = view.getUint16(16);\n if (newPageSize === 1) newPageSize = 65536;\n if (newPageSize === oldPageSize) return; // no page size change\n\n const maxPageSize = Math.max(oldPageSize, newPageSize);\n const nOldPages = maxPageSize / oldPageSize;\n const nNewPages = maxPageSize / newPageSize;\n\n const newPageCount = view.getUint32(28);\n const fileSize = newPageCount * newPageSize;\n\n const version = file.block0.version;\n await this.#idb.run('readwrite', async ({blocks}) => {\n // When the block size changes, the entire file is rewritten. Delete\n // all blocks older than block 0 to leave a single version at every\n // offset.\n const keys = await blocks.index('version').getAllKeys(IDBKeyRange.bound(\n [file.path, version + 1],\n [file.path, Infinity]\n ));\n for (const key of keys) {\n blocks.delete(key);\n }\n blocks.delete([file.path, 'purge', 0]);\n\n // Do the conversion in chunks of the larger of the page sizes.\n for (let iOffset = 0; iOffset < fileSize; iOffset += maxPageSize) {\n // Fetch nOldPages. They can be fetched in one request because\n // there is now a single version in the file.\n const oldPages = await blocks.getAll(\n IDBKeyRange.lowerBound([file.path, -(iOffset + maxPageSize), Infinity]),\n nOldPages);\n for (const oldPage of oldPages) {\n blocks.delete([oldPage.path, oldPage.offset, oldPage.version]);\n }\n\n // Convert to new pages.\n if (nNewPages === 1) {\n // Combine nOldPages old pages into a new page.\n const buffer = new Uint8Array(newPageSize);\n for (const oldPage of oldPages) {\n buffer.set(oldPage.data, -(iOffset + oldPage.offset));\n }\n const newPage = {\n path: file.path,\n offset: -iOffset,\n version,\n data: buffer\n };\n if (newPage.offset === 0) {\n newPage.fileSize = fileSize;\n file.block0 = newPage;\n }\n blocks.put(newPage);\n } else {\n // Split an old page into nNewPages new pages.\n const oldPage = oldPages[0];\n for (let i = 0; i < nNewPages; ++i) {\n const offset = -(iOffset + i * newPageSize);\n if (-offset >= fileSize) break;\n const newPage = {\n path: oldPage.path,\n offset,\n version,\n data: oldPage.data.subarray(i * newPageSize, (i + 1) * newPageSize)\n }\n if (newPage.offset === 0) {\n newPage.fileSize = fileSize;\n file.block0 = newPage;\n }\n blocks.put(newPage);\n }\n }\n }\n });\n }\n}\n\nfunction openDatabase(idbDatabaseName) {\n return new Promise((resolve, reject) => {\n const request = globalThis.indexedDB.open(idbDatabaseName, 5);\n request.addEventListener('upgradeneeded', function() {\n const blocks = request.result.createObjectStore('blocks', {\n keyPath: ['path', 'offset', 'version']\n });\n blocks.createIndex('version', ['path', 'version']);\n });\n request.addEventListener('success', () => {\n resolve(request.result);\n });\n request.addEventListener('error', () => {\n reject(request.error);\n });\n });\n}","// Copyright 2022 Roy T. Hashimoto. All Rights Reserved.\n\n// IndexedDB transactions older than this will be replaced.\nconst MAX_TRANSACTION_LIFETIME_MILLIS = 5_000;\n\n// For debugging.\nlet nextTxId = 0;\nconst mapTxToId = new WeakMap();\nfunction log(...args) {\n // console.debug(...args);\n}\n\n// This class manages IDBTransaction and IDBRequest instances. It tries\n// to reuse transactions to minimize transaction overhead.\nexport class IDBContext {\n /** @type {IDBDatabase} */ #db;\n /** @type {Promise<IDBDatabase>} */ #dbReady;\n #txOptions;\n\n /** @type {IDBTransaction} */ #tx = null;\n #txTimestamp = 0;\n #runChain = Promise.resolve();\n #putChain = Promise.resolve();\n\n /**\n * @param {IDBDatabase|Promise<IDBDatabase>} idbDatabase\n */\n constructor(idbDatabase, txOptions = { durability: 'default' }) {\n this.#dbReady = Promise.resolve(idbDatabase).then(db => this.#db = db);\n this.#txOptions = txOptions;\n }\n\n async close() {\n const db = this.#db ?? await this.#dbReady;\n await this.#runChain;\n await this.sync();\n db.close();\n }\n \n /**\n * Run a function with the provided object stores. The function\n * should be idempotent in case it is passed an expired transaction.\n * @param {IDBTransactionMode} mode\n * @param {(stores: Object.<string, ObjectStore>) => any} f \n */\n async run(mode, f) {\n // Ensure that functions run sequentially.\n const result = this.#runChain.then(() => this.#run(mode, f));\n this.#runChain = result.catch(() => {});\n return result;\n }\n\n /**\n * @param {IDBTransactionMode} mode\n * @param {(stores: Object.<string, ObjectStore>) => any} f \n * @returns \n */\n async #run(mode, f) {\n const db = this.#db ?? await this.#dbReady;\n if (mode === 'readwrite' && this.#tx?.mode === 'readonly') {\n // Mode requires a new transaction.\n this.#tx = null;\n } else if (performance.now() - this.#txTimestamp > MAX_TRANSACTION_LIFETIME_MILLIS) {\n // Chrome times out transactions after 60 seconds so refresh preemptively.\n try {\n this.#tx?.commit();\n } catch (e) {\n // Explicit commit can fail but this can be ignored if it will\n // auto-commit anyway.\n if (e.name !== 'InvalidStateError') throw e;\n }\n\n // Skip to the next task to allow processing.\n await new Promise(resolve => setTimeout(resolve));\n this.#tx = null;\n }\n\n // Run the user function with a retry in case the transaction is invalid.\n for (let i = 0; i < 2; ++i) {\n if (!this.#tx) {\n // @ts-ignore\n this.#tx = db.transaction(db.objectStoreNames, mode, this.#txOptions);\n const timestamp = this.#txTimestamp = performance.now();\n\n // Chain the result of every transaction. If any transaction is\n // aborted then the next sync() call will throw.\n this.#putChain = this.#putChain.then(() => {\n return new Promise((resolve, reject) => {\n this.#tx.addEventListener('complete', event => {\n resolve();\n if (this.#tx === event.target) {\n this.#tx = null;\n }\n log(`transaction ${mapTxToId.get(event.target)} complete`);\n });\n this.#tx.addEventListener('abort', event => {\n console.warn('tx abort', (performance.now() - timestamp)/1000);\n // @ts-ignore\n const e = event.target.error;\n reject(e);\n if (this.#tx === event.target) {\n this.#tx = null;\n }\n log(`transaction ${mapTxToId.get(event.target)} aborted`, e);\n });\n });\n });\n\n log(`new transaction ${nextTxId} ${mode}`);\n mapTxToId.set(this.#tx, nextTxId++);\n }\n\n try {\n const stores = Object.fromEntries(Array.from(db.objectStoreNames, name => {\n return [name, new ObjectStore(this.#tx.objectStore(name))];\n }));\n return await f(stores);\n } catch (e) {\n this.#tx = null;\n if (i) throw e;\n // console.warn('retrying with new transaction');\n }\n }\n }\n\n async sync() {\n // Wait until all transactions since the previous sync have committed.\n // Throw if any transaction failed.\n await this.#runChain;\n await this.#putChain;\n this.#putChain = Promise.resolve();\n }\n}\n\n/**\n * Helper to convert IDBRequest to Promise.\n * @param {IDBRequest} request \n * @returns {Promise}\n */\nfunction wrapRequest(request) {\n return new Promise((resolve, reject) => {\n request.addEventListener('success', () => resolve(request.result));\n request.addEventListener('error', () => reject(request.error));\n });\n}\n\n// IDBObjectStore wrapper passed to IDBContext run functions.\nclass ObjectStore {\n #objectStore;\n\n /**\n * @param {IDBObjectStore} objectStore \n */\n constructor(objectStore) {\n this.#objectStore = objectStore;\n }\n\n /**\n * @param {IDBValidKey|IDBKeyRange} query \n * @returns {Promise}\n */\n get(query) {\n log(`get ${this.#objectStore.name}`, query);\n const request = this.#objectStore.get(query);\n return wrapRequest(request);\n }\n\n /**\n * @param {IDBValidKey|IDBKeyRange} query \n * @param {number} [count]\n * @returns {Promise}\n */\n getAll(query, count) {\n log(`getAll ${this.#objectStore.name}`, query, count);\n const request = this.#objectStore.getAll(query, count);\n return wrapRequest(request);\n }\n\n /**\n * @param {IDBValidKey|IDBKeyRange} query \n * @returns {Promise<IDBValidKey>}\n */\n getKey(query) {\n log(`getKey ${this.#objectStore.name}`, query);\n const request = this.#objectStore.getKey(query);\n return wrapRequest(request);\n }\n\n /**\n * @param {IDBValidKey|IDBKeyRange} query \n * @param {number} [count]\n * @returns {Promise}\n */\n getAllKeys(query, count) {\n log(`getAllKeys ${this.#objectStore.name}`, query, count);\n const request = this.#objectStore.getAllKeys(query, count);\n return wrapRequest(request);\n }\n\n /**\n * @param {any} value\n * @param {IDBValidKey} [key] \n * @returns {Promise}\n */\n put(value, key) {\n log(`put ${this.#objectStore.name}`, value, key);\n const request = this.#objectStore.put(value, key);\n return wrapRequest(request);\n }\n\n /**\n * @param {IDBValidKey|IDBKeyRange} query \n * @returns {Promise}\n */\n delete(query) {\n log(`delete ${this.#objectStore.name}`, query);\n const request = this.#objectStore.delete(query);\n return wrapRequest(request);\n }\n\n clear() {\n log(`clear ${this.#objectStore.name}`);\n const request = this.#objectStore.clear();\n return wrapRequest(request);\n }\n\n index(name) {\n return new Index(this.#objectStore.index(name));\n }\n}\n\nclass Index {\n /** @type {IDBIndex} */ #index;\n\n /**\n * @param {IDBIndex} index \n */\n constructor(index) {\n this.#index = index;\n }\n\n /**\n * @param {IDBValidKey|IDBKeyRange} query \n * @param {number} [count]\n * @returns {Promise<IDBValidKey[]>}\n */\n getAllKeys(query, count) {\n log(`IDBIndex.getAllKeys ${this.#index.objectStore.name}<${this.#index.name}>`, query, count);\n const request = this.#index.getAllKeys(query, count);\n return wrapRequest(request);\n }\n}","// Copyright 2022 Roy T. Hashimoto. All Rights Reserved.\nimport * as VFS from '../VFS.js';\n\nconst LOCK_TYPE_MASK =\n VFS.SQLITE_LOCK_NONE |\n VFS.SQLITE_LOCK_SHARED |\n VFS.SQLITE_LOCK_RESERVED |\n VFS.SQLITE_LOCK_PENDING |\n VFS.SQLITE_LOCK_EXCLUSIVE;\n\nexport class WebLocksBase {\n get state() { return this.#state; }\n #state = VFS.SQLITE_LOCK_NONE;\n\n timeoutMillis = 0;\n\n /** @type {Map<string, (value: any) => void>} */ #releasers = new Map();\n /** @type {Promise<0|5|3850>} */ #pending = Promise.resolve(0);\n\n /**\n * @param {number} flags \n * @returns {Promise<0|5|3850>} SQLITE_OK, SQLITE_BUSY, SQLITE_IOERR_LOCK\n */\n async lock(flags) {\n return this.#apply(this.#lock, flags);\n }\n\n /**\n * @param {number} flags \n * @returns {Promise<0|5|3850>} SQLITE_OK, SQLITE_IOERR_LOCK\n */\n async unlock(flags) {\n return this.#apply(this.#unlock, flags);\n }\n\n /**\n * @returns {Promise<boolean>}\n */\n async isSomewhereReserved() {\n throw new Error('unimplemented');\n }\n\n /**\n * \n * @param {(targetState: number) => void} method \n * @param {number} flags \n */\n async #apply(method, flags) {\n const targetState = flags & LOCK_TYPE_MASK;\n try {\n // Force locks and unlocks to run sequentially. This allows not\n // waiting for unlocks to complete.\n const call = () => method.call(this, targetState);\n await (this.#pending = this.#pending.then(call, call));\n this.#state = targetState;\n return VFS.SQLITE_OK;\n } catch (e) {\n if (e.name === 'AbortError') {\n return VFS.SQLITE_BUSY;\n }\n console.error(e);\n return VFS.SQLITE_IOERR_LOCK;\n }\n }\n\n async #lock(targetState) {\n if (targetState === this.#state) return VFS.SQLITE_OK;\n switch (this.#state) {\n case VFS.SQLITE_LOCK_NONE:\n switch (targetState) {\n case VFS.SQLITE_LOCK_SHARED:\n return this._NONEtoSHARED();\n default:\n throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);\n }\n\n case VFS.SQLITE_LOCK_SHARED:\n switch (targetState) {\n case VFS.SQLITE_LOCK_RESERVED:\n return this._SHAREDtoRESERVED();\n case VFS.SQLITE_LOCK_EXCLUSIVE:\n return this._SHAREDtoEXCLUSIVE();\n default:\n throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);\n }\n \n case VFS.SQLITE_LOCK_RESERVED:\n switch (targetState) {\n case VFS.SQLITE_LOCK_EXCLUSIVE:\n return this._RESERVEDtoEXCLUSIVE();\n default:\n throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);\n }\n\n default:\n throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);\n }\n }\n\n async #unlock(targetState) {\n if (targetState === this.#state) return VFS.SQLITE_OK;\n switch (this.#state) {\n case VFS.SQLITE_LOCK_EXCLUSIVE:\n switch (targetState) {\n case VFS.SQLITE_LOCK_SHARED:\n return this._EXCLUSIVEtoSHARED();\n case VFS.SQLITE_LOCK_NONE:\n return this._EXCLUSIVEtoNONE();\n default:\n throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);\n }\n \n case VFS.SQLITE_LOCK_RESERVED:\n switch (targetState) {\n case VFS.SQLITE_LOCK_SHARED:\n return this._RESERVEDtoSHARED();\n case VFS.SQLITE_LOCK_NONE:\n return this._RESERVEDtoNONE();\n default:\n throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);\n }\n\n case VFS.SQLITE_LOCK_SHARED:\n switch (targetState) {\n case VFS.SQLITE_LOCK_NONE:\n return this._SHAREDtoNONE();\n default:\n throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);\n }\n\n default:\n throw new Error(`unexpected transition ${this.#state} -> ${targetState}`);\n }\n }\n\n async _NONEtoSHARED() {\n }\n\n async _SHAREDtoEXCLUSIVE() {\n await this._SHAREDtoRESERVED();\n await this._RESERVEDtoEXCLUSIVE();\n }\n\n async _SHAREDtoRESERVED() {\n }\n\n async _RESERVEDtoEXCLUSIVE() {\n }\n\n async _EXCLUSIVEtoRESERVED() {\n }\n\n async _EXCLUSIVEtoSHARED() {\n await this._EXCLUSIVEtoRESERVED();\n await this._RESERVEDtoSHARED();\n }\n\n async _EXCLUSIVEtoNONE() {\n await this._EXCLUSIVEtoRESERVED();\n await this._RESERVEDtoSHARED();\n await this._SHAREDtoNONE();\n }\n\n async _RESERVEDtoSHARED() {\n }\n\n async _RESERVEDtoNONE() {\n await this._RESERVEDtoSHARED();\n await this._SHAREDtoNONE();\n }\n\n async _SHAREDtoNONE() {\n }\n\n /**\n * @param {string} lockName \n * @param {LockOptions} options \n * @returns {Promise<?Lock>}\n */\n _acquireWebLock(lockName, options) {\n return new Promise(async (resolve, reject) => {\n try {\n await navigator.locks.request(lockName, options, lock => {\n resolve(lock);\n if (lock) {\n return new Promise(release => this.#releasers.set(lockName, release));\n }\n });\n } catch(e) {\n reject(e);\n }\n });\n }\n\n /**\n * @param {string} lockName \n */\n _releaseWebLock(lockName) {\n this.#releasers.get(lockName)?.();\n this.#releasers.delete(lockName);\n }\n\n /**\n * @param {string} lockName \n */\n async _pollWebLock(lockName) {\n const query = await navigator.locks.query();\n return query.held.find(({name}) => name === lockName)?.mode;\n }\n\n /**\n * @returns {?AbortSignal}\n */\n _getTimeoutSignal() {\n if (this.timeoutMillis) {\n const abortController = new AbortController();\n setTimeout(() => abortController.abort(), this.timeoutMillis);\n return abortController.signal;\n }\n return undefined;\n }\n}\n\nexport class WebLocksExclusive extends WebLocksBase {\n /**\n * @param {string} name \n */\n constructor(name) {\n super();\n this._lockName = name + '-outer';\n this._reservedName = name + '-reserved';\n }\n\n async isSomewhereReserved() {\n const mode = await this._pollWebLock(this._reservedName);\n return mode === 'exclusive';\n }\n\n async _NONEtoSHARED() {\n await this._acquireWebLock(this._lockName, {\n mode: 'exclusive',\n signal: this._getTimeoutSignal()\n });\n }\n\n async _SHAREDtoRESERVED() {\n await this._acquireWebLock(this._reservedName, {\n mode: 'exclusive',\n signal: this._getTimeoutSignal()\n });\n }\n\n async _RESERVEDtoSHARED() {\n this._releaseWebLock(this._reservedName);\n }\n\n async _SHAREDtoNONE() {\n this._releaseWebLock(this._lockName);\n }\n}\n\nexport class WebLocksShared extends WebLocksBase {\n maxRetryMillis = 1000;\n\n /**\n * @param {string} name \n */\n constructor(name) {\n super();\n this._outerName = name + '-outer';\n this._innerName = name + '-inner';\n }\n\n async isSomewhereReserved() {\n const mode = await this._pollWebLock(this._outerName);\n return mode === 'exclusive';\n }\n\n async _NONEtoSHARED() {\n await this._acquireWebLock(this._outerName, {\n mode: 'shared',\n signal: this._getTimeoutSignal()\n });\n await this._acquireWebLock(this._innerName, {\n mode: 'shared',\n signal: this._getTimeoutSignal()\n });\n this._releaseWebLock(this._outerName);\n }\n\n async _SHAREDtoRESERVED() {\n let timeoutMillis = 1;\n while (true) {\n // Attempt to get the outer lock without blocking.\n const isLocked = await this._acquireWebLock(this._outerName, {\n mode: 'exclusive',\n ifAvailable: true\n });\n if (isLocked) break;\n\n if (await this.isSomewhereReserved()) {\n // Someone else has a reserved lock so retry cannot succeed.\n throw new DOMException('', 'AbortError');\n }\n\n await new Promise(resolve => setTimeout(resolve, timeoutMillis));\n timeoutMillis = Math.min(2 * timeoutMillis, this.maxRetryMillis);\n }\n this._releaseWebLock(this._innerName);\n }\n\n async _RESERVEDtoEXCLUSIVE() {\n await this._acquireWebLock(this._innerName, {\n mode: 'exclusive',\n signal: this._getTimeoutSignal()\n });\n }\n\n async _EXCLUSIVEtoRESERVED() {\n this._releaseWebLock(this._innerName);\n }\n\n async _RESERVEDtoSHARED() {\n await this._acquireWebLock(this._innerName, { mode: 'shared' });\n this._releaseWebLock(this._outerName);\n }\n\n async _SHAREDtoNONE() {\n this._releaseWebLock(this._innerName);\n }\n}"],"names":[],"sourceRoot":""}
1
+ {"version":3,"file":"worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js","mappings":";;;;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3fA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;AC7NA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;AC3ZA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://sdk_web/../../node_modules/@journeyapps/wa-sqlite/src/FacadeVFS.js","webpack://sdk_web/../../node_modules/@journeyapps/wa-sqlite/src/VFS.js","webpack://sdk_web/../../node_modules/@journeyapps/wa-sqlite/src/WebLocksMixin.js","webpack://sdk_web/../../node_modules/@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js"],"sourcesContent":["// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.\nimport * as VFS from './VFS.js';\n\nconst AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;\n\n// Convenience base class for a JavaScript VFS.\n// The raw xOpen, xRead, etc. function signatures receive only C primitives\n// which aren't easy to work with. This class provides corresponding calls\n// like jOpen, jRead, etc., which receive JavaScript-friendlier arguments\n// such as string, Uint8Array, and DataView.\nexport class FacadeVFS extends VFS.Base {\n /**\n * @param {string} name \n * @param {object} module \n */\n constructor(name, module) {\n super(name, module);\n }\n\n /**\n * Override to indicate which methods are asynchronous.\n * @param {string} methodName \n * @returns {boolean}\n */\n hasAsyncMethod(methodName) {\n // The input argument is a string like \"xOpen\", so convert to \"jOpen\".\n // Then check if the method exists and is async.\n const jMethodName = `j${methodName.slice(1)}`;\n return this[jMethodName] instanceof AsyncFunction;\n }\n \n /**\n * Return the filename for a file id for use by mixins.\n * @param {number} pFile \n * @returns {string}\n */\n getFilename(pFile) {\n throw new Error('unimplemented');\n }\n\n /**\n * @param {string?} filename \n * @param {number} pFile \n * @param {number} flags \n * @param {DataView} pOutFlags \n * @returns {number|Promise<number>}\n */\n jOpen(filename, pFile, flags, pOutFlags) {\n return VFS.SQLITE_CANTOPEN;\n }\n\n /**\n * @param {string} filename \n * @param {number} syncDir \n * @returns {number|Promise<number>}\n */\n jDelete(filename, syncDir) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {string} filename \n * @param {number} flags \n * @param {DataView} pResOut \n * @returns {number|Promise<number>}\n */\n jAccess(filename, flags, pResOut) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {string} filename \n * @param {Uint8Array} zOut \n * @returns {number|Promise<number>}\n */\n jFullPathname(filename, zOut) {\n // Copy the filename to the output buffer.\n const { read, written } = new TextEncoder().encodeInto(filename, zOut);\n if (read < filename.length) return VFS.SQLITE_IOERR;\n if (written >= zOut.length) return VFS.SQLITE_IOERR;\n zOut[written] = 0;\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {Uint8Array} zBuf \n * @returns {number|Promise<number>}\n */\n jGetLastError(zBuf) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @returns {number|Promise<number>}\n */\n jClose(pFile) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {Uint8Array} pData \n * @param {number} iOffset \n * @returns {number|Promise<number>}\n */\n jRead(pFile, pData, iOffset) {\n pData.fill(0);\n return VFS.SQLITE_IOERR_SHORT_READ;\n }\n\n /**\n * @param {number} pFile \n * @param {Uint8Array} pData \n * @param {number} iOffset \n * @returns {number|Promise<number>}\n */\n jWrite(pFile, pData, iOffset) {\n return VFS.SQLITE_IOERR_WRITE;\n }\n\n /**\n * @param {number} pFile \n * @param {number} size \n * @returns {number|Promise<number>}\n */\n jTruncate(pFile, size) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {number} flags \n * @returns {number|Promise<number>}\n */\n jSync(pFile, flags) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {DataView} pSize\n * @returns {number|Promise<number>}\n */\n jFileSize(pFile, pSize) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {number} lockType \n * @returns {number|Promise<number>}\n */\n jLock(pFile, lockType) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {number} lockType \n * @returns {number|Promise<number>}\n */\n jUnlock(pFile, lockType) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {DataView} pResOut \n * @returns {number|Promise<number>}\n */\n jCheckReservedLock(pFile, pResOut) {\n pResOut.setInt32(0, 0, true);\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile\n * @param {number} op\n * @param {DataView} pArg\n * @returns {number|Promise<number>}\n */\n jFileControl(pFile, op, pArg) {\n return VFS.SQLITE_NOTFOUND;\n }\n\n /**\n * @param {number} pFile\n * @returns {number|Promise<number>}\n */\n jSectorSize(pFile) {\n return super.xSectorSize(pFile);\n }\n\n /**\n * @param {number} pFile\n * @returns {number|Promise<number>}\n */\n jDeviceCharacteristics(pFile) {\n return 0;\n }\n\n /**\n * @param {number} pVfs \n * @param {number} zName \n * @param {number} pFile \n * @param {number} flags \n * @param {number} pOutFlags \n * @returns {number|Promise<number>}\n */\n xOpen(pVfs, zName, pFile, flags, pOutFlags) {\n const filename = this.#decodeFilename(zName, flags);\n const pOutFlagsView = this.#makeTypedDataView('Int32', pOutFlags);\n this['log']?.('jOpen', filename, pFile, '0x' + flags.toString(16));\n return this.jOpen(filename, pFile, flags, pOutFlagsView);\n }\n\n /**\n * @param {number} pVfs \n * @param {number} zName \n * @param {number} syncDir \n * @returns {number|Promise<number>}\n */\n xDelete(pVfs, zName, syncDir) {\n const filename = this._module.UTF8ToString(zName);\n this['log']?.('jDelete', filename, syncDir);\n return this.jDelete(filename, syncDir);\n }\n\n /**\n * @param {number} pVfs \n * @param {number} zName \n * @param {number} flags \n * @param {number} pResOut \n * @returns {number|Promise<number>}\n */\n xAccess(pVfs, zName, flags, pResOut) {\n const filename = this._module.UTF8ToString(zName);\n const pResOutView = this.#makeTypedDataView('Int32', pResOut);\n this['log']?.('jAccess', filename, flags);\n return this.jAccess(filename, flags, pResOutView);\n }\n\n /**\n * @param {number} pVfs \n * @param {number} zName \n * @param {number} nOut \n * @param {number} zOut \n * @returns {number|Promise<number>}\n */\n xFullPathname(pVfs, zName, nOut, zOut) {\n const filename = this._module.UTF8ToString(zName);\n const zOutArray = this._module.HEAPU8.subarray(zOut, zOut + nOut);\n this['log']?.('jFullPathname', filename, nOut);\n return this.jFullPathname(filename, zOutArray);\n }\n\n /**\n * @param {number} pVfs \n * @param {number} nBuf \n * @param {number} zBuf \n * @returns {number|Promise<number>}\n */\n xGetLastError(pVfs, nBuf, zBuf) {\n const zBufArray = this._module.HEAPU8.subarray(zBuf, zBuf + nBuf);\n this['log']?.('jGetLastError', nBuf);\n return this.jGetLastError(zBufArray);\n }\n\n /**\n * @param {number} pFile \n * @returns {number|Promise<number>}\n */\n xClose(pFile) {\n this['log']?.('jClose', pFile);\n return this.jClose(pFile);\n }\n\n /**\n * @param {number} pFile \n * @param {number} pData \n * @param {number} iAmt \n * @param {number} iOffsetLo \n * @param {number} iOffsetHi \n * @returns {number|Promise<number>}\n */\n xRead(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {\n const pDataArray = this.#makeDataArray(pData, iAmt);\n const iOffset = delegalize(iOffsetLo, iOffsetHi);\n this['log']?.('jRead', pFile, iAmt, iOffset);\n return this.jRead(pFile, pDataArray, iOffset);\n }\n\n /**\n * @param {number} pFile \n * @param {number} pData \n * @param {number} iAmt \n * @param {number} iOffsetLo \n * @param {number} iOffsetHi \n * @returns {number|Promise<number>}\n */\n xWrite(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {\n const pDataArray = this.#makeDataArray(pData, iAmt);\n const iOffset = delegalize(iOffsetLo, iOffsetHi);\n this['log']?.('jWrite', pFile, pDataArray, iOffset);\n return this.jWrite(pFile, pDataArray, iOffset);\n }\n\n /**\n * @param {number} pFile \n * @param {number} sizeLo \n * @param {number} sizeHi \n * @returns {number|Promise<number>}\n */\n xTruncate(pFile, sizeLo, sizeHi) {\n const size = delegalize(sizeLo, sizeHi);\n this['log']?.('jTruncate', pFile, size);\n return this.jTruncate(pFile, size);\n }\n\n /**\n * @param {number} pFile \n * @param {number} flags \n * @returns {number|Promise<number>}\n */\n xSync(pFile, flags) {\n this['log']?.('jSync', pFile, flags);\n return this.jSync(pFile, flags);\n }\n\n /**\n * \n * @param {number} pFile \n * @param {number} pSize \n * @returns {number|Promise<number>}\n */\n xFileSize(pFile, pSize) {\n const pSizeView = this.#makeTypedDataView('BigInt64', pSize);\n this['log']?.('jFileSize', pFile);\n return this.jFileSize(pFile, pSizeView);\n }\n\n /**\n * @param {number} pFile \n * @param {number} lockType \n * @returns {number|Promise<number>}\n */\n xLock(pFile, lockType) {\n this['log']?.('jLock', pFile, lockType);\n return this.jLock(pFile, lockType);\n }\n\n /**\n * @param {number} pFile \n * @param {number} lockType \n * @returns {number|Promise<number>}\n */\n xUnlock(pFile, lockType) {\n this['log']?.('jUnlock', pFile, lockType);\n return this.jUnlock(pFile, lockType);\n } \n\n /**\n * @param {number} pFile \n * @param {number} pResOut \n * @returns {number|Promise<number>}\n */\n xCheckReservedLock(pFile, pResOut) {\n const pResOutView = this.#makeTypedDataView('Int32', pResOut);\n this['log']?.('jCheckReservedLock', pFile);\n return this.jCheckReservedLock(pFile, pResOutView);\n }\n\n /**\n * @param {number} pFile \n * @param {number} op \n * @param {number} pArg \n * @returns {number|Promise<number>}\n */\n xFileControl(pFile, op, pArg) {\n const pArgView = new DataView(\n this._module.HEAPU8.buffer,\n this._module.HEAPU8.byteOffset + pArg);\n this['log']?.('jFileControl', pFile, op, pArgView);\n return this.jFileControl(pFile, op, pArgView);\n }\n\n /**\n * @param {number} pFile \n * @returns {number|Promise<number>}\n */\n xSectorSize(pFile) {\n this['log']?.('jSectorSize', pFile);\n return this.jSectorSize(pFile);\n }\n\n /**\n * @param {number} pFile \n * @returns {number|Promise<number>}\n */\n xDeviceCharacteristics(pFile) {\n this['log']?.('jDeviceCharacteristics', pFile);\n return this.jDeviceCharacteristics(pFile);\n }\n\n /**\n * Wrapped DataView for pointer arguments.\n * Pointers to a single value are passed using DataView. A Proxy\n * wrapper prevents use of incorrect type or endianness.\n * @param {'Int32'|'BigInt64'} type \n * @param {number} byteOffset \n * @returns {DataView}\n */\n #makeTypedDataView(type, byteOffset) {\n const byteLength = type === 'Int32' ? 4 : 8;\n const getter = `get${type}`;\n const setter = `set${type}`;\n const makeDataView = () => new DataView(\n this._module.HEAPU8.buffer,\n this._module.HEAPU8.byteOffset + byteOffset,\n byteLength);\n let dataView = makeDataView();\n return new Proxy(dataView, {\n get(_, prop) {\n if (dataView.buffer.byteLength === 0) {\n // WebAssembly memory resize detached the buffer.\n dataView = makeDataView();\n }\n if (prop === getter) {\n return function(byteOffset, littleEndian) {\n if (!littleEndian) throw new Error('must be little endian');\n return dataView[prop](byteOffset, littleEndian);\n }\n }\n if (prop === setter) {\n return function(byteOffset, value, littleEndian) {\n if (!littleEndian) throw new Error('must be little endian');\n return dataView[prop](byteOffset, value, littleEndian);\n }\n }\n if (typeof prop === 'string' && (prop.match(/^(get)|(set)/))) {\n throw new Error('invalid type');\n }\n const result = dataView[prop];\n return typeof result === 'function' ? result.bind(dataView) : result;\n }\n });\n }\n\n /**\n * @param {number} byteOffset \n * @param {number} byteLength \n */\n #makeDataArray(byteOffset, byteLength) {\n let target = this._module.HEAPU8.subarray(byteOffset, byteOffset + byteLength);\n return new Proxy(target, {\n get: (_, prop, receiver) => {\n if (target.buffer.byteLength === 0) {\n // WebAssembly memory resize detached the buffer.\n target = this._module.HEAPU8.subarray(byteOffset, byteOffset + byteLength);\n }\n const result = target[prop];\n return typeof result === 'function' ? result.bind(target) : result;\n }\n });\n }\n\n #decodeFilename(zName, flags) {\n if (flags & VFS.SQLITE_OPEN_URI) {\n // The first null-terminated string is the URI path. Subsequent\n // strings are query parameter keys and values.\n // https://www.sqlite.org/c3ref/open.html#urifilenamesinsqlite3open\n let pName = zName;\n let state = 1;\n const charCodes = [];\n while (state) {\n const charCode = this._module.HEAPU8[pName++];\n if (charCode) {\n charCodes.push(charCode);\n } else {\n if (!this._module.HEAPU8[pName]) state = null;\n switch (state) {\n case 1: // path\n charCodes.push('?'.charCodeAt(0));\n state = 2;\n break;\n case 2: // key\n charCodes.push('='.charCodeAt(0));\n state = 3;\n break;\n case 3: // value\n charCodes.push('&'.charCodeAt(0));\n state = 2;\n break;\n }\n }\n }\n return new TextDecoder().decode(new Uint8Array(charCodes));\n }\n return zName ? this._module.UTF8ToString(zName) : null;\n }\n}\n\n// Emscripten \"legalizes\" 64-bit integer arguments by passing them as\n// two 32-bit signed integers.\nfunction delegalize(lo32, hi32) {\n return (hi32 * 0x100000000) + lo32 + (lo32 < 0 ? 2**32 : 0);\n}\n","// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.\nimport * as VFS from './sqlite-constants.js';\nexport * from './sqlite-constants.js';\n\nconst DEFAULT_SECTOR_SIZE = 512;\n\n// Base class for a VFS.\nexport class Base {\n name;\n mxPathname = 64;\n _module;\n\n /**\n * @param {string} name \n * @param {object} module \n */\n constructor(name, module) {\n this.name = name;\n this._module = module;\n }\n\n /**\n * @returns {void|Promise<void>} \n */\n close() {\n }\n\n /**\n * @returns {boolean|Promise<boolean>}\n */\n isReady() {\n return true;\n }\n\n /**\n * Overload in subclasses to indicate which methods are asynchronous.\n * @param {string} methodName \n * @returns {boolean}\n */\n hasAsyncMethod(methodName) {\n return false;\n }\n\n /**\n * @param {number} pVfs \n * @param {number} zName \n * @param {number} pFile \n * @param {number} flags \n * @param {number} pOutFlags \n * @returns {number|Promise<number>}\n */\n xOpen(pVfs, zName, pFile, flags, pOutFlags) {\n return VFS.SQLITE_CANTOPEN;\n }\n\n /**\n * @param {number} pVfs \n * @param {number} zName \n * @param {number} syncDir \n * @returns {number|Promise<number>}\n */\n xDelete(pVfs, zName, syncDir) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pVfs \n * @param {number} zName \n * @param {number} flags \n * @param {number} pResOut \n * @returns {number|Promise<number>}\n */\n xAccess(pVfs, zName, flags, pResOut) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pVfs \n * @param {number} zName \n * @param {number} nOut \n * @param {number} zOut \n * @returns {number|Promise<number>}\n */\n xFullPathname(pVfs, zName, nOut, zOut) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pVfs \n * @param {number} nBuf \n * @param {number} zBuf \n * @returns {number|Promise<number>}\n */\n xGetLastError(pVfs, nBuf, zBuf) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @returns {number|Promise<number>}\n */\n xClose(pFile) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {number} pData \n * @param {number} iAmt \n * @param {number} iOffsetLo \n * @param {number} iOffsetHi \n * @returns {number|Promise<number>}\n */\n xRead(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {number} pData \n * @param {number} iAmt \n * @param {number} iOffsetLo \n * @param {number} iOffsetHi \n * @returns {number|Promise<number>}\n */\n xWrite(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {number} sizeLo \n * @param {number} sizeHi \n * @returns {number|Promise<number>}\n */\n xTruncate(pFile, sizeLo, sizeHi) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {number} flags \n * @returns {number|Promise<number>}\n */\n xSync(pFile, flags) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * \n * @param {number} pFile \n * @param {number} pSize \n * @returns {number|Promise<number>}\n */\n xFileSize(pFile, pSize) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {number} lockType \n * @returns {number|Promise<number>}\n */\n xLock(pFile, lockType) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {number} lockType \n * @returns {number|Promise<number>}\n */\n xUnlock(pFile, lockType) {\n return VFS.SQLITE_OK;\n } \n\n /**\n * @param {number} pFile \n * @param {number} pResOut \n * @returns {number|Promise<number>}\n */\n xCheckReservedLock(pFile, pResOut) {\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile \n * @param {number} op \n * @param {number} pArg \n * @returns {number|Promise<number>}\n */\n xFileControl(pFile, op, pArg) {\n return VFS.SQLITE_NOTFOUND;\n }\n\n /**\n * @param {number} pFile \n * @returns {number|Promise<number>}\n */\n xSectorSize(pFile) {\n return DEFAULT_SECTOR_SIZE;\n }\n\n /**\n * @param {number} pFile \n * @returns {number|Promise<number>}\n */\n xDeviceCharacteristics(pFile) {\n return 0;\n }\n}\n\nexport const FILE_TYPE_MASK = [\n VFS.SQLITE_OPEN_MAIN_DB,\n VFS.SQLITE_OPEN_MAIN_JOURNAL,\n VFS.SQLITE_OPEN_TEMP_DB,\n VFS.SQLITE_OPEN_TEMP_JOURNAL,\n VFS.SQLITE_OPEN_TRANSIENT_DB,\n VFS.SQLITE_OPEN_SUBJOURNAL,\n VFS.SQLITE_OPEN_SUPER_JOURNAL,\n VFS.SQLITE_OPEN_WAL\n].reduce((mask, element) => mask | element);","import * as VFS from './VFS.js';\n\n// Options for navigator.locks.request().\n/** @type {LockOptions} */ const SHARED = { mode: 'shared' };\n/** @type {LockOptions} */ const POLL_SHARED = { ifAvailable: true, mode: 'shared' };\n/** @type {LockOptions} */ const POLL_EXCLUSIVE = { ifAvailable: true, mode: 'exclusive' };\n\nconst POLICIES = ['exclusive', 'shared', 'shared+hint'];\n\n/**\n * @typedef LockState\n * @property {string} baseName\n * @property {number} type\n * @property {boolean} writeHint\n * \n * These properties are functions that release a specific lock.\n * @property {(() => void)?} [gate]\n * @property {(() => void)?} [access]\n * @property {(() => void)?} [reserved]\n * @property {(() => void)?} [hint]\n */\n\n/**\n * Mix-in for FacadeVFS that implements the SQLite VFS locking protocol.\n * @param {*} superclass FacadeVFS (or subclass)\n * @returns \n */\nexport const WebLocksMixin = superclass => class extends superclass {\n #options = {\n lockPolicy: 'exclusive',\n lockTimeout: Infinity\n };\n\n /** @type {Map<number, LockState>} */ #mapIdToState = new Map();\n\n constructor(name, module, options) {\n super(name, module, options);\n Object.assign(this.#options, options);\n if (POLICIES.indexOf(this.#options.lockPolicy) === -1) {\n throw new Error(`WebLocksMixin: invalid lock mode: ${options.lockPolicy}`);\n }\n }\n\n /**\n * @param {number} fileId \n * @param {number} lockType \n * @returns {Promise<number>}\n */\n async jLock(fileId, lockType) {\n try {\n // Create state on first lock.\n if (!this.#mapIdToState.has(fileId)) {\n const name = this.getFilename(fileId);\n const state = {\n baseName: name,\n type: VFS.SQLITE_LOCK_NONE,\n writeHint: false\n };\n this.#mapIdToState.set(fileId, state);\n }\n\n const lockState = this.#mapIdToState.get(fileId);\n if (lockType <= lockState.type) return VFS.SQLITE_OK;\n \n switch (this.#options.lockPolicy) {\n case 'exclusive':\n return await this.#lockExclusive(lockState, lockType);\n case 'shared':\n case 'shared+hint':\n return await this.#lockShared(lockState, lockType);\n }\n } catch (e) {\n console.error('WebLocksMixin: lock error', e);\n return VFS.SQLITE_IOERR_LOCK;\n }\n }\n \n /**\n * @param {number} fileId \n * @param {number} lockType \n * @returns {Promise<number>}\n */\n async jUnlock(fileId, lockType) {\n try {\n const lockState = this.#mapIdToState.get(fileId);\n if (lockType >= lockState.type) return VFS.SQLITE_OK;\n \n switch (this.#options.lockPolicy) {\n case 'exclusive':\n return await this.#unlockExclusive(lockState, lockType);\n case 'shared':\n case 'shared+hint':\n return await this.#unlockShared(lockState, lockType);\n }\n } catch (e) {\n console.error('WebLocksMixin: unlock error', e);\n return VFS.SQLITE_IOERR_UNLOCK;\n }\n }\n\n /**\n * @param {number} fileId \n * @param {DataView} pResOut \n * @returns {Promise<number>}\n */\n async jCheckReservedLock(fileId, pResOut) {\n try {\n const lockState = this.#mapIdToState.get(fileId);\n switch (this.#options.lockPolicy) {\n case 'exclusive':\n return this.#checkReservedExclusive(lockState, pResOut);\n case 'shared':\n case 'shared+hint':\n return await this.#checkReservedShared(lockState, pResOut);\n }\n } catch (e) {\n console.error('WebLocksMixin: check reserved lock error', e);\n return VFS.SQLITE_IOERR_CHECKRESERVEDLOCK;\n }\n pResOut.setInt32(0, 0, true);\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {number} pFile\n * @param {number} op\n * @param {DataView} pArg\n * @returns {number|Promise<number>}\n */\n jFileControl(pFile, op, pArg) {\n const lockState = this.#mapIdToState.get(pFile) ??\n (() => {\n // Call jLock() to create the lock state.\n this.jLock(pFile, VFS.SQLITE_LOCK_NONE);\n return this.#mapIdToState.get(pFile);\n })();\n if (op === WebLocksMixin.WRITE_HINT_OP_CODE &&\n this.#options.lockPolicy === 'shared+hint'){\n lockState.writeHint = true;\n }\n return VFS.SQLITE_NOTFOUND;\n }\n\n /**\n * @param {LockState} lockState \n * @param {number} lockType \n * @returns \n */\n async #lockExclusive(lockState, lockType) {\n if (!lockState.access) {\n if (!await this.#acquire(lockState, 'access')) {\n return VFS.SQLITE_BUSY;\n }\n console.assert(!!lockState.access);\n }\n lockState.type = lockType;\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {LockState} lockState \n * @param {number} lockType \n * @returns {number}\n */\n #unlockExclusive(lockState, lockType) {\n if (lockType === VFS.SQLITE_LOCK_NONE) {\n lockState.access?.();\n console.assert(!lockState.access);\n }\n lockState.type = lockType;\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {LockState} lockState \n * @param {DataView} pResOut \n * @returns {number}\n */\n #checkReservedExclusive(lockState, pResOut) {\n pResOut.setInt32(0, 0, true);\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {LockState} lockState \n * @param {number} lockType \n * @returns \n */\n async #lockShared(lockState, lockType) {\n switch (lockState.type) {\n case VFS.SQLITE_LOCK_NONE:\n switch (lockType) {\n case VFS.SQLITE_LOCK_SHARED:\n if (lockState.writeHint) {\n // xFileControl() has hinted that this transaction will\n // write. Acquire the hint lock, which is required to reach\n // the RESERVED state.\n if (!await this.#acquire(lockState, 'hint')) {\n // Timeout before lock acquired.\n return VFS.SQLITE_BUSY;\n }\n }\n\n // Must have the gate lock to request the access lock.\n if (!await this.#acquire(lockState, 'gate', SHARED)) {\n // Timeout before lock acquired.\n lockState.hint?.();\n return VFS.SQLITE_BUSY;\n }\n await this.#acquire(lockState, 'access', SHARED);\n lockState.gate();\n console.assert(!lockState.gate);\n console.assert(!!lockState.access);\n console.assert(!lockState.reserved);\n break;\n\n default:\n throw new Error('unsupported lock transition');\n }\n break;\n case VFS.SQLITE_LOCK_SHARED:\n switch (lockType) {\n case VFS.SQLITE_LOCK_RESERVED:\n if (this.#options.lockPolicy === 'shared+hint') {\n // Ideally we should already have the hint lock, but if not\n // poll for it here.\n if (!lockState.hint &&\n !await this.#acquire(lockState, 'hint', POLL_EXCLUSIVE)) {\n // Another connection has the hint lock so this is a\n // deadlock. This connection must retry.\n return VFS.SQLITE_BUSY;\n }\n }\n\n // Poll for the reserved lock. This should always succeed\n // if all clients use the 'shared+hint' policy.\n if (!await this.#acquire(lockState, 'reserved', POLL_EXCLUSIVE)) {\n // This is a deadlock. The connection holding the reserved\n // lock blocks us, and it can't acquire an exclusive access\n // lock because we hold a shared access lock. This connection\n // must retry.\n lockState.hint?.();\n return VFS.SQLITE_BUSY;\n }\n lockState.access();\n console.assert(!lockState.gate);\n console.assert(!lockState.access);\n console.assert(!!lockState.reserved);\n break;\n\n case VFS.SQLITE_LOCK_EXCLUSIVE:\n // Jumping directly from SHARED to EXCLUSIVE without passing\n // through RESERVED is only done with a hot journal.\n if (!await this.#acquire(lockState, 'gate')) {\n // Timeout before lock acquired.\n return VFS.SQLITE_BUSY;\n }\n lockState.access();\n if (!await this.#acquire(lockState, 'access')) {\n // Timeout before lock acquired.\n lockState.gate();\n return VFS.SQLITE_BUSY;\n }\n console.assert(!!lockState.gate);\n console.assert(!!lockState.access);\n console.assert(!lockState.reserved);\n break;\n\n default:\n throw new Error('unsupported lock transition');\n }\n break;\n case VFS.SQLITE_LOCK_RESERVED:\n switch (lockType) {\n case VFS.SQLITE_LOCK_EXCLUSIVE:\n // Prevent other connections from entering the SHARED state.\n if (!await this.#acquire(lockState, 'gate')) {\n // Timeout before lock acquired.\n return VFS.SQLITE_BUSY;\n }\n\n // Block until all other connections exit the SHARED state.\n if (!await this.#acquire(lockState, 'access')) {\n // Timeout before lock acquired.\n lockState.gate();\n return VFS.SQLITE_BUSY;\n }\n console.assert(!!lockState.gate);\n console.assert(!!lockState.access);\n console.assert(!!lockState.reserved);\n break;\n\n default:\n throw new Error('unsupported lock transition');\n }\n break;\n }\n lockState.type = lockType;\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {LockState} lockState \n * @param {number} lockType \n * @returns \n */\n async #unlockShared(lockState, lockType) {\n // lockType can only be SQLITE_LOCK_SHARED or SQLITE_LOCK_NONE.\n if (lockType === VFS.SQLITE_LOCK_NONE) {\n lockState.access?.();\n lockState.gate?.();\n lockState.reserved?.();\n lockState.hint?.();\n lockState.writeHint = false;\n console.assert(!lockState.access);\n console.assert(!lockState.gate);\n console.assert(!lockState.reserved);\n console.assert(!lockState.hint);\n } else { // lockType === VFS.SQLITE_LOCK_SHARED\n switch (lockState.type) {\n case VFS.SQLITE_LOCK_EXCLUSIVE:\n // Release our exclusive access lock and reacquire it with a\n // shared lock. This should always succeed because we hold\n // the gate lock.\n lockState.access();\n await this.#acquire(lockState, 'access', SHARED);\n\n // Release our gate and reserved locks. We might not have a\n // reserved lock if we were handling a hot journal.\n lockState.gate();\n lockState.reserved?.();\n lockState.hint?.();\n console.assert(!!lockState.access);\n console.assert(!lockState.gate);\n console.assert(!lockState.reserved);\n break;\n\n case VFS.SQLITE_LOCK_RESERVED:\n // This transition is rare, probably only on an I/O error\n // while writing to a journal file.\n await this.#acquire(lockState, 'access', SHARED);\n lockState.reserved();\n lockState.hint?.();\n console.assert(!!lockState.access);\n console.assert(!lockState.gate);\n console.assert(!lockState.reserved);\n break;\n }\n }\n lockState.type = lockType;\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {LockState} lockState \n * @param {DataView} pResOut \n * @returns {Promise<number>}\n */\n async #checkReservedShared(lockState, pResOut) {\n if (await this.#acquire(lockState, 'reserved', POLL_SHARED)) {\n // We were able to get the lock so it was not reserved.\n lockState.reserved();\n pResOut.setInt32(0, 0, true);\n } else {\n pResOut.setInt32(0, 1, true);\n }\n return VFS.SQLITE_OK;\n }\n\n /**\n * @param {LockState} lockState \n * @param {'gate'|'access'|'reserved'|'hint'} name\n * @param {LockOptions} options \n * @returns {Promise<boolean>}\n */\n #acquire(lockState, name, options = {}) {\n console.assert(!lockState[name]);\n return new Promise(resolve => {\n if (!options.ifAvailable && this.#options.lockTimeout < Infinity) {\n // Add a timeout to the lock request.\n const controller = new AbortController();\n options = Object.assign({}, options, { signal: controller.signal });\n setTimeout(() => {\n controller.abort();\n resolve?.(false);\n }, this.#options.lockTimeout);\n }\n\n const lockName = `lock##${lockState.baseName}##${name}`;\n navigator.locks.request(lockName, options, lock => {\n if (lock) {\n return new Promise(release => {\n lockState[name] = () => {\n release();\n lockState[name] = null;\n };\n resolve(true);\n resolve = null;\n });\n } else {\n lockState[name] = null;\n resolve(false);\n resolve = null;\n }\n }).catch(e => {\n if (e.name !== 'AbortError') throw e;\n });\n });\n }\n}\n\nWebLocksMixin.WRITE_HINT_OP_CODE = -9999;","// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.\nimport { FacadeVFS } from '../FacadeVFS.js';\nimport * as VFS from '../VFS.js';\nimport { WebLocksMixin } from '../WebLocksMixin.js';\n\n/**\n * @typedef Metadata\n * @property {string} name\n * @property {number} fileSize\n * @property {number} version\n * @property {number} [pendingVersion]\n */\n\nclass File {\n /** @type {string} */ path;\n /** @type {number} */ flags;\n\n /** @type {Metadata} */ metadata;\n /** @type {number} */ fileSize = 0;\n\n /** @type {boolean} */ needsMetadataSync = false;\n /** @type {Metadata} */ rollback = null;\n /** @type {Set<number>} */ changedPages = new Set();\n\n /** @type {string} */ synchronous = 'full';\n /** @type {IDBTransactionOptions} */ txOptions = { durability: 'strict' };\n\n constructor(path, flags, metadata) {\n this.path = path;\n this.flags = flags;\n this.metadata = metadata;\n }\n}\n\nexport class IDBBatchAtomicVFS extends WebLocksMixin(FacadeVFS) {\n /** @type {Map<number, File>} */ mapIdToFile = new Map();\n lastError = null;\n\n log = null; // console.log\n\n /** @type {Promise} */ #isReady;\n /** @type {IDBContext} */ #idb;\n\n static async create(name, module, options) {\n const vfs = new IDBBatchAtomicVFS(name, module, options);\n await vfs.isReady();\n return vfs;\n }\n\n constructor(name, module, options = {}) {\n super(name, module, options);\n this.#isReady = this.#initialize(options.idbName ?? name);\n }\n\n async #initialize(name) {\n this.#idb = await IDBContext.create(name);\n }\n\n close() {\n this.#idb.close();\n }\n \n async isReady() {\n await super.isReady();\n await this.#isReady;\n }\n\n getFilename(fileId) {\n const pathname = this.mapIdToFile.get(fileId).path;\n return `IDB(${this.name}):${pathname}`\n }\n \n /**\n * @param {string?} zName \n * @param {number} fileId \n * @param {number} flags \n * @param {DataView} pOutFlags \n * @returns {Promise<number>}\n */\n async jOpen(zName, fileId, flags, pOutFlags) {\n try {\n const url = new URL(zName || Math.random().toString(36).slice(2), 'file://');\n const path = url.pathname;\n\n let meta = await this.#idb.q(({ metadata }) => metadata.get(path));\n if (!meta && (flags & VFS.SQLITE_OPEN_CREATE)) {\n meta = {\n name: path,\n fileSize: 0,\n version: 0\n };\n await this.#idb.q(({ metadata }) => metadata.put(meta), 'rw');\n }\n \n if (!meta) {\n throw new Error(`File ${path} not found`);\n }\n\n const file = new File(path, flags, meta);\n this.mapIdToFile.set(fileId, file);\n pOutFlags.setInt32(0, flags, true);\n return VFS.SQLITE_OK;\n } catch (e) {\n this.lastError = e;\n return VFS.SQLITE_CANTOPEN;\n }\n }\n\n /**\n * @param {string} zName \n * @param {number} syncDir \n * @returns {Promise<number>}\n */\n async jDelete(zName, syncDir) {\n try {\n const url = new URL(zName, 'file://');\n const path = url.pathname;\n\n this.#idb.q(({ metadata, blocks }) => {\n const range = IDBKeyRange.bound([path, -Infinity], [path, Infinity]);\n blocks.delete(range);\n metadata.delete(path);\n }, 'rw');\n\n if (syncDir) {\n await this.#idb.sync(false);\n }\n return VFS.SQLITE_OK;\n } catch (e) {\n this.lastError = e;\n return VFS.SQLITE_IOERR_DELETE;\n }\n }\n\n /**\n * @param {string} zName \n * @param {number} flags \n * @param {DataView} pResOut \n * @returns {Promise<number>}\n */\n async jAccess(zName, flags, pResOut) {\n try {\n const url = new URL(zName, 'file://');\n const path = url.pathname;\n\n const meta = await this.#idb.q(({ metadata }) => metadata.get(path));\n pResOut.setInt32(0, meta ? 1 : 0, true);\n return VFS.SQLITE_OK;\n } catch (e) {\n this.lastError = e;\n return VFS.SQLITE_IOERR_ACCESS;\n } \n }\n\n /**\n * @param {number} fileId \n * @returns {Promise<number>}\n */\n async jClose(fileId) {\n try {\n const file = this.mapIdToFile.get(fileId);\n this.mapIdToFile.delete(fileId);\n\n if (file.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {\n await this.#idb.q(({ metadata, blocks }) => {\n metadata.delete(file.path);\n blocks.delete(IDBKeyRange.bound([file.path, 0], [file.path, Infinity]));\n }, 'rw'); \n }\n\n if (file.needsMetadataSync) {\n this.#idb.q(({ metadata }) => metadata.put(file.metadata), 'rw');\n }\n await this.#idb.sync(file.synchronous === 'full');\n return VFS.SQLITE_OK;\n } catch (e) {\n this.lastError = e;\n return VFS.SQLITE_IOERR_CLOSE;\n }\n }\n\n /**\n * @param {number} fileId \n * @param {Uint8Array} pData \n * @param {number} iOffset\n * @returns {Promise<number>}\n */\n async jRead(fileId, pData, iOffset) {\n try {\n const file = this.mapIdToFile.get(fileId);\n\n let pDataOffset = 0;\n while (pDataOffset < pData.byteLength) {\n // Fetch the IndexedDB block for this file location.\n const fileOffset = iOffset + pDataOffset;\n const block = await this.#idb.q(({ blocks }) => {\n const range = IDBKeyRange.bound([file.path, -fileOffset], [file.path, Infinity]);\n return blocks.get(range);\n }); \n \n if (!block || block.data.byteLength - block.offset <= fileOffset) {\n pData.fill(0, pDataOffset);\n return VFS.SQLITE_IOERR_SHORT_READ;\n }\n\n // Copy block data.\n const dst = pData.subarray(pDataOffset);\n const srcOffset = fileOffset + block.offset;\n const nBytesToCopy = Math.min(\n Math.max(block.data.byteLength - srcOffset, 0),\n dst.byteLength);\n dst.set(block.data.subarray(srcOffset, srcOffset + nBytesToCopy));\n pDataOffset += nBytesToCopy;\n }\n return VFS.SQLITE_OK;\n } catch (e) {\n this.lastError = e;\n return VFS.SQLITE_IOERR_READ;\n }\n }\n\n /**\n * @param {number} fileId \n * @param {Uint8Array} pData \n * @param {number} iOffset\n * @returns {number}\n */\n jWrite(fileId, pData, iOffset) {\n try {\n const file = this.mapIdToFile.get(fileId);\n if (file.flags & VFS.SQLITE_OPEN_MAIN_DB) {\n if (!file.rollback) {\n // Begin a new write transaction.\n // Add pendingVersion to the metadata in IndexedDB. If we crash\n // during the transaction, this lets subsequent connections\n // know to remove blocks from the failed transaction.\n const pending = Object.assign(\n { pendingVersion: file.metadata.version - 1 },\n file.metadata);\n this.#idb.q(({ metadata }) => metadata.put(pending), 'rw', file.txOptions);\n\n file.rollback = Object.assign({}, file.metadata);\n file.metadata.version--;\n }\n }\n\n if (file.flags & VFS.SQLITE_OPEN_MAIN_DB) {\n file.changedPages.add(iOffset);\n }\n\n const data = pData.slice();\n const version = file.metadata.version;\n const isOverwrite = iOffset < file.metadata.fileSize;\n if (!isOverwrite ||\n file.flags & VFS.SQLITE_OPEN_MAIN_DB ||\n file.flags & VFS.SQLITE_OPEN_TEMP_DB) {\n const block = {\n path: file.path,\n offset: -iOffset,\n version: version,\n data: pData.slice()\n };\n this.#idb.q(({ blocks }) => {\n blocks.put(block);\n file.changedPages.add(iOffset);\n }, 'rw', file.txOptions);\n } else {\n this.#idb.q(async ({ blocks }) => {\n // Read the existing block.\n const range = IDBKeyRange.bound(\n [file.path, -iOffset],\n [file.path, Infinity]);\n const block = await blocks.get(range);\n\n // Modify the block data.\n // @ts-ignore\n block.data.subarray(iOffset + block.offset).set(data);\n\n // Write back.\n blocks.put(block);\n }, 'rw', file.txOptions);\n\n }\n\n if (file.metadata.fileSize < iOffset + pData.length) {\n file.metadata.fileSize = iOffset + pData.length;\n file.needsMetadataSync = true;\n }\n return VFS.SQLITE_OK;\n } catch (e) {\n this.lastError = e;\n return VFS.SQLITE_IOERR_WRITE;\n }\n }\n\n /**\n * @param {number} fileId \n * @param {number} iSize \n * @returns {number}\n */\n jTruncate(fileId, iSize) {\n try {\n const file = this.mapIdToFile.get(fileId);\n if (iSize < file.metadata.fileSize) {\n this.#idb.q(({ blocks }) => {\n const range = IDBKeyRange.bound(\n [file.path, -Infinity],\n [file.path, -iSize, Infinity]);\n blocks.delete(range);\n }, 'rw', file.txOptions);\n file.metadata.fileSize = iSize;\n file.needsMetadataSync = true;\n }\n return VFS.SQLITE_OK;\n } catch (e) {\n this.lastError = e;\n return VFS.SQLITE_IOERR_TRUNCATE;\n }\n }\n\n /**\n * @param {number} fileId \n * @param {number} flags \n * @returns {Promise<number>}\n */\n async jSync(fileId, flags) {\n try {\n const file = this.mapIdToFile.get(fileId);\n if (file.needsMetadataSync) {\n this.#idb.q(({ metadata }) => metadata.put(file.metadata), 'rw', file.txOptions);\n file.needsMetadataSync = false;\n }\n\n if (file.flags & VFS.SQLITE_OPEN_MAIN_DB) {\n // Sync is only needed here for durability. Visibility for other\n // connections is ensured in jUnlock().\n if (file.synchronous === 'full') {\n await this.#idb.sync(true);\n }\n } else {\n await this.#idb.sync(file.synchronous === 'full');\n }\n return VFS.SQLITE_OK;\n } catch (e) {\n this.lastError = e;\n return VFS.SQLITE_IOERR_FSYNC;\n }\n }\n\n /**\n * @param {number} fileId \n * @param {DataView} pSize64 \n * @returns {number}\n */\n jFileSize(fileId, pSize64) {\n try {\n const file = this.mapIdToFile.get(fileId);\n pSize64.setBigInt64(0, BigInt(file.metadata.fileSize), true);\n return VFS.SQLITE_OK;\n } catch (e) {\n this.lastError = e;\n return VFS.SQLITE_IOERR_FSTAT;\n }\n }\n\n /**\n * @param {number} fileId \n * @param {number} lockType \n * @returns {Promise<number>}\n */\n async jLock(fileId, lockType) {\n // Call the actual lock implementation.\n const file = this.mapIdToFile.get(fileId);\n const result = await super.jLock(fileId, lockType);\n\n if (lockType === VFS.SQLITE_LOCK_SHARED) {\n // Update metadata.\n file.metadata = await this.#idb.q(async ({ metadata, blocks }) => {\n // @ts-ignore\n /** @type {Metadata} */ const m = await metadata.get(file.path);\n if (m.pendingVersion) {\n console.warn(`removing failed transaction ${m.pendingVersion}`);\n await new Promise((resolve, reject) => {\n const range = IDBKeyRange.bound([m.name, -Infinity], [m.name, Infinity]);\n const request = blocks.openCursor(range);\n request.onsuccess = () => {\n const cursor = request.result;\n if (cursor) {\n const block = cursor.value;\n if (block.version < m.version) {\n cursor.delete();\n }\n cursor.continue();\n } else {\n resolve();\n }\n };\n request.onerror = () => reject(request.error);\n })\n\n delete m.pendingVersion;\n metadata.put(m);\n }\n return m;\n }, 'rw', file.txOptions);\n }\n return result;\n }\n\n /**\n * @param {number} fileId \n * @param {number} lockType \n * @returns {Promise<number>}\n */\n async jUnlock(fileId, lockType) {\n if (lockType === VFS.SQLITE_LOCK_NONE) {\n const file = this.mapIdToFile.get(fileId);\n await this.#idb.sync(file.synchronous === 'full');\n }\n\n // Call the actual unlock implementation.\n return super.jUnlock(fileId, lockType);\n }\n\n /**\n * @param {number} fileId\n * @param {number} op\n * @param {DataView} pArg\n * @returns {number|Promise<number>}\n */\n jFileControl(fileId, op, pArg) {\n try {\n const file = this.mapIdToFile.get(fileId);\n switch (op) {\n case VFS.SQLITE_FCNTL_PRAGMA:\n const key = extractString(pArg, 4);\n const value = extractString(pArg, 8);\n this.log?.('xFileControl', file.path, 'PRAGMA', key, value);\n const setPragmaResponse = response => {\n const encoded = new TextEncoder().encode(response);\n const out = this._module._sqlite3_malloc(encoded.byteLength);\n const outArray = this._module.HEAPU8.subarray(out, out + encoded.byteLength);\n outArray.set(encoded);\n pArg.setUint32(0, out, true);\n return VFS.SQLITE_ERROR;\n };\n switch (key.toLowerCase()) {\n case 'page_size':\n if (file.flags & VFS.SQLITE_OPEN_MAIN_DB) {\n // Don't allow changing the page size.\n if (value && file.metadata.fileSize) {\n return VFS.SQLITE_ERROR;\n }\n }\n break;\n case 'synchronous':\n if (value) {\n switch (value.toLowerCase()) {\n case '0':\n case 'off':\n file.synchronous = 'off';\n file.txOptions = { durability: 'relaxed' };\n break;\n case '1':\n case 'normal':\n file.synchronous = 'normal';\n file.txOptions = { durability: 'relaxed' };\n break;\n case '2':\n case '3':\n case 'full':\n case 'extra':\n file.synchronous = 'full';\n file.txOptions = { durability: 'strict' };\n break;\n }\n }\n break;\n case 'write_hint':\n return super.jFileControl(fileId, WebLocksMixin.WRITE_HINT_OP_CODE, null);\n }\n break;\n case VFS.SQLITE_FCNTL_SYNC:\n this.log?.('xFileControl', file.path, 'SYNC');\n const commitMetadata = Object.assign({}, file.metadata);\n const prevFileSize = file.rollback.fileSize\n this.#idb.q(({ metadata, blocks }) => {\n metadata.put(commitMetadata);\n\n // Remove old page versions.\n for (const offset of file.changedPages) {\n if (offset < prevFileSize) {\n const range = IDBKeyRange.bound(\n [file.path, -offset, commitMetadata.version],\n [file.path, -offset, Infinity],\n true);\n blocks.delete(range);\n }\n }\n file.changedPages.clear();\n }, 'rw', file.txOptions);\n file.needsMetadataSync = false;\n file.rollback = null;\n break;\n case VFS.SQLITE_FCNTL_BEGIN_ATOMIC_WRITE:\n // Every write transaction is atomic, so this is a no-op.\n this.log?.('xFileControl', file.path, 'BEGIN_ATOMIC_WRITE');\n return VFS.SQLITE_OK;\n case VFS.SQLITE_FCNTL_COMMIT_ATOMIC_WRITE:\n // Every write transaction is atomic, so this is a no-op.\n this.log?.('xFileControl', file.path, 'COMMIT_ATOMIC_WRITE');\n return VFS.SQLITE_OK;\n case VFS.SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE:\n this.log?.('xFileControl', file.path, 'ROLLBACK_ATOMIC_WRITE');\n file.metadata = file.rollback;\n const rollbackMetadata = Object.assign({}, file.metadata);\n this.#idb.q(({ metadata, blocks }) => {\n metadata.put(rollbackMetadata);\n\n // Remove pages.\n for (const offset of file.changedPages) {\n blocks.delete([file.path, -offset, rollbackMetadata.version - 1]);\n }\n file.changedPages.clear();\n }, 'rw', file.txOptions);\n file.needsMetadataSync = false;\n file.rollback = null;\n return VFS.SQLITE_OK;\n }\n } catch (e) {\n this.lastError = e;\n return VFS.SQLITE_IOERR;\n }\n return super.jFileControl(fileId, op, pArg);\n }\n \n /**\n * @param {number} pFile\n * @returns {number|Promise<number>}\n */\n jDeviceCharacteristics(pFile) {\n return 0\n | VFS.SQLITE_IOCAP_BATCH_ATOMIC\n | VFS.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;\n }\n\n /**\n * @param {Uint8Array} zBuf \n * @returns {number|Promise<number>}\n */\n jGetLastError(zBuf) {\n if (this.lastError) {\n console.error(this.lastError);\n const outputArray = zBuf.subarray(0, zBuf.byteLength - 1);\n const { written } = new TextEncoder().encodeInto(this.lastError.message, outputArray);\n zBuf[written] = 0;\n }\n return VFS.SQLITE_OK\n }\n}\n\nfunction extractString(dataView, offset) {\n const p = dataView.getUint32(offset, true);\n if (p) {\n const chars = new Uint8Array(dataView.buffer, p);\n return new TextDecoder().decode(chars.subarray(0, chars.indexOf(0)));\n }\n return null;\n}\n\nexport class IDBContext {\n /** @type {IDBDatabase} */ #database;\n\n /** @type {Promise} */ #chain = null;\n /** @type {Promise<any>} */ #txComplete = Promise.resolve();\n /** @type {IDBRequest?} */ #request = null;\n /** @type {WeakSet<IDBTransaction>} */ #txPending = new WeakSet();\n \n log = null;\n\n static async create(name) {\n const database = await new Promise((resolve, reject) => {\n const request = indexedDB.open(name, 6);\n request.onupgradeneeded = async event => {\n const db = request.result;\n if (event.oldVersion) {\n console.log(`Upgrading IndexedDB from version ${event.oldVersion}`);\n }\n switch (event.oldVersion) {\n case 0:\n // Start with the original schema.\n db.createObjectStore('blocks', { keyPath: ['path', 'offset', 'version']})\n .createIndex('version', ['path', 'version']);\n // fall through intentionally\n case 5:\n const tx = request.transaction;\n const blocks = tx.objectStore('blocks');\n blocks.deleteIndex('version');\n const metadata = db.createObjectStore('metadata', { keyPath: 'name' });\n\n await new Promise((resolve, reject) => {\n // Iterate over all the blocks.\n let lastBlock = {};\n const request = tx.objectStore('blocks').openCursor();\n request.onsuccess = () => {\n const cursor = request.result;\n if (cursor) {\n const block = cursor.value;\n if (typeof block.offset !== 'number' ||\n (block.path === lastBlock.path && block.offset === lastBlock.offset)) {\n // Remove superceded block (or the \"purge\" info).\n cursor.delete();\n } else if (block.offset === 0) {\n // Move metadata to its own store.\n metadata.put({\n name: block.path,\n fileSize: block.fileSize,\n version: block.version\n });\n\n delete block.fileSize;\n cursor.update(block);\n }\n lastBlock = block;\n cursor.continue();\n } else {\n resolve();\n }\n };\n request.onerror = () => reject(request.error);\n });\n break;\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n return new IDBContext(database);\n }\n\n constructor(database) {\n this.#database = database;\n }\n\n close() {\n this.#database.close();\n }\n\n /**\n * @param {(stores: Object.<string, IDBObjectStore>) => any} f \n * @param {'ro'|'rw'} mode \n * @returns {Promise<any>}\n */\n q(f, mode = 'ro', options = {}) {\n /** @type {IDBTransactionMode} */\n const txMode = mode === 'ro' ? 'readonly' : 'readwrite';\n const txOptions = Object.assign({\n /** @type {IDBTransactionDurability} */ durability: 'default'\n }, options);\n\n // Ensure that queries run sequentially. If any function rejects,\n // or any request has an error, or the transaction does not commit,\n // then no subsequent functions will run until sync() or reset().\n this.#chain = (this.#chain || Promise.resolve())\n .then(() => this.#q(f, txMode, txOptions));\n return this.#chain;\n }\n\n /**\n * @param {(stores: Object.<string, IDBObjectStore>) => any} f \n * @param {IDBTransactionMode} mode \n * @param {IDBTransactionOptions} options\n * @returns {Promise<any>}\n */\n async #q(f, mode, options) {\n /** @type {IDBTransaction} */ let tx;\n if (this.#request &&\n this.#txPending.has(this.#request.transaction) &&\n this.#request.transaction.mode >= mode &&\n this.#request.transaction.durability === options.durability) {\n // The previous request transaction is compatible and has\n // not yet completed.\n tx = this.#request.transaction;\n\n // If the previous request is pending, wait for it to complete.\n // This ensures that the transaction will be active.\n if (this.#request.readyState === 'pending') {\n await new Promise(resolve => {\n this.#request.addEventListener('success', resolve, { once: true });\n this.#request.addEventListener('error', resolve, { once: true });\n });\n }\n }\n\n for (let i = 0; i < 2; ++i) {\n if (!tx) {\n // The current transaction is missing or doesn't match so\n // replace it with a new one. wait for the previous\n // transaction to complete so the lifetimes do not overlap.\n await this.#txComplete;\n\n // Create the new transaction.\n // @ts-ignore\n tx = this.#database.transaction(this.#database.objectStoreNames, mode, options);\n this.log?.('IDBTransaction open', mode);\n this.#txPending.add(tx);\n this.#txComplete = new Promise((resolve, reject) => {\n tx.addEventListener('complete', () => {\n this.log?.('IDBTransaction complete');\n this.#txPending.delete(tx);\n resolve();\n });\n tx.addEventListener('abort', () => {\n this.#txPending.delete(tx);\n reject(new Error('transaction aborted'));\n });\n });\n }\n\n // @ts-ignore\n // Create object store proxies.\n const objectStores = [...tx.objectStoreNames].map(name => {\n return [name, this.proxyStoreOrIndex(tx.objectStore(name))];\n });\n\n try {\n // Execute the function.\n return await f(Object.fromEntries(objectStores));\n } catch (e) {\n // Use a new transaction if this one was inactive. This will\n // happen if the last request in the transaction completed\n // in a previous task but the transaction has not yet committed.\n if (!i && e.name === 'TransactionInactiveError') {\n this.log?.('TransactionInactiveError, retrying');\n tx = null;\n continue;\n }\n throw e;\n }\n }\n }\n\n /**\n * Object store methods that return an IDBRequest, except for cursor\n * creation, are wrapped to return a Promise. In addition, the\n * request is used internally for chaining.\n * @param {IDBObjectStore} objectStore \n * @returns \n */\n proxyStoreOrIndex(objectStore) {\n return new Proxy(objectStore, {\n get: (target, property, receiver) => {\n const result = Reflect.get(target, property, receiver);\n if (typeof result === 'function') {\n return (...args) => {\n const maybeRequest = Reflect.apply(result, target, args);\n // @ts-ignore\n if (maybeRequest instanceof IDBRequest && !property.endsWith('Cursor')) {\n // // Debug logging.\n // this.log?.(`${target.name}.${String(property)}`, args);\n // maybeRequest.addEventListener('success', () => {\n // this.log?.(`${target.name}.${String(property)} success`, maybeRequest.result);\n // });\n // maybeRequest.addEventListener('error', () => {\n // this.log?.(`${target.name}.${String(property)} error`, maybeRequest.error);\n // });\n \n // Save the request.\n this.#request = maybeRequest;\n\n // Abort the transaction on error.\n maybeRequest.addEventListener('error', () => {\n console.error(maybeRequest.error);\n maybeRequest.transaction.abort();\n }, { once: true }); \n\n // Return a Promise.\n return wrap(maybeRequest);\n }\n return maybeRequest;\n }\n }\n return result;\n }\n });\n }\n\n /**\n * @param {boolean} durable \n */\n async sync(durable) {\n if (this.#chain) {\n // This waits for all IndexedDB calls to be made.\n await this.#chain;\n if (durable) {\n // This waits for the final transaction to commit.\n await this.#txComplete;\n }\n this.reset();\n }\n }\n\n reset() {\n this.#chain = null;\n this.#txComplete = Promise.resolve();\n this.#request = null;\n }\n}\n\n/**\n * @param {IDBRequest} request \n * @returns {Promise}\n */\nfunction wrap(request) {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\n"],"names":[],"sourceRoot":""}
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/web",
3
- "version": "1.10.1",
3
+ "version": "1.11.0",
4
4
  "description": "A Web SDK for JourneyApps PowerSync",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",
@@ -56,7 +56,7 @@
56
56
  "author": "JOURNEYAPPS",
57
57
  "license": "Apache-2.0",
58
58
  "peerDependencies": {
59
- "@journeyapps/wa-sqlite": "^0.4.1",
59
+ "@journeyapps/wa-sqlite": "^1.0.0",
60
60
  "@powersync/common": "workspace:^1.21.0"
61
61
  },
62
62
  "dependencies": {
@@ -67,7 +67,7 @@
67
67
  "js-logger": "^1.6.1"
68
68
  },
69
69
  "devDependencies": {
70
- "@journeyapps/wa-sqlite": "^0.4.1",
70
+ "@journeyapps/wa-sqlite": "^1.0.0",
71
71
  "@types/uuid": "^9.0.6",
72
72
  "@vitest/browser": "^2.1.4",
73
73
  "crypto-browserify": "^3.12.0",
@@ -1,14 +1,19 @@
1
- import * as SQLite from '@journeyapps/wa-sqlite';
2
1
  import '@journeyapps/wa-sqlite';
3
- import * as Comlink from 'comlink';
2
+ import * as SQLite from '@journeyapps/wa-sqlite';
4
3
  import { Mutex } from 'async-mutex';
4
+ import * as Comlink from 'comlink';
5
5
  let nextId = 1;
6
6
  export async function _openDB(dbFileName, options = { useWebWorker: true }) {
7
7
  const { default: moduleFactory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite-async.mjs');
8
8
  const module = await moduleFactory();
9
9
  const sqlite3 = SQLite.Factory(module);
10
+ /**
11
+ * Register the PowerSync core SQLite extension
12
+ */
13
+ module.ccall('powersync_init_static', 'int', []);
10
14
  const { IDBBatchAtomicVFS } = await import('@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js');
11
- const vfs = new IDBBatchAtomicVFS(dbFileName);
15
+ // @ts-expect-error The types for this static method are missing upstream
16
+ const vfs = await IDBBatchAtomicVFS.create(dbFileName, module, { lockPolicy: 'exclusive' });
12
17
  sqlite3.vfs_register(vfs, true);
13
18
  const db = await sqlite3.open_v2(dbFileName);
14
19
  const statementMutex = new Mutex();
@@ -24,7 +29,10 @@ export async function _openDB(dbFileName, options = { useWebWorker: true }) {
24
29
  updatedTables.clear();
25
30
  Array.from(listeners.values()).forEach((l) => l(event));
26
31
  }
27
- sqlite3.register_table_onchange_hook(db, (opType, tableName, rowId) => {
32
+ sqlite3.update_hook(db, (updateType, dbName, tableName) => {
33
+ if (!tableName) {
34
+ return;
35
+ }
28
36
  updatedTables.add(tableName);
29
37
  if (updateTimer == null) {
30
38
  updateTimer = setTimeout(fireUpdates, 0);
@@ -106,40 +114,36 @@ export async function _openDB(dbFileName, options = { useWebWorker: true }) {
106
114
  const executeBatch = async (sql, bindings) => {
107
115
  return _acquireExecuteLock(async () => {
108
116
  let affectedRows = 0;
109
- const str = sqlite3.str_new(db, sql);
110
- const query = sqlite3.str_value(str);
111
117
  try {
112
118
  await executeSingleStatement('BEGIN TRANSACTION');
113
- //Prepare statement once
114
- const prepared = await sqlite3.prepare_v2(db, query);
115
- if (prepared === null) {
116
- return {
117
- rowsAffected: 0,
118
- rows: { _array: [], length: 0 }
119
- };
120
- }
121
119
  const wrappedBindings = bindings ? bindings : [];
122
- for (const binding of wrappedBindings) {
123
- // TODO not sure why this is needed currently, but booleans break
124
- for (let i = 0; i < binding.length; i++) {
125
- const b = binding[i];
126
- if (typeof b == 'boolean') {
127
- binding[i] = b ? 1 : 0;
128
- }
120
+ for await (const stmt of sqlite3.statements(db, sql)) {
121
+ if (stmt === null) {
122
+ return {
123
+ rowsAffected: 0,
124
+ rows: { _array: [], length: 0 }
125
+ };
129
126
  }
130
- //Reset bindings
131
- sqlite3.reset(prepared.stmt);
132
- if (bindings) {
133
- sqlite3.bind_collection(prepared.stmt, binding);
134
- }
135
- const result = await sqlite3.step(prepared.stmt);
136
- if (result === SQLite.SQLITE_DONE) {
137
- //The value returned by sqlite3_changes() immediately after an INSERT, UPDATE or DELETE statement run on a view is always zero.
138
- affectedRows += sqlite3.changes(db);
127
+ //Prepare statement once
128
+ for (const binding of wrappedBindings) {
129
+ // TODO not sure why this is needed currently, but booleans break
130
+ for (let i = 0; i < binding.length; i++) {
131
+ const b = binding[i];
132
+ if (typeof b == 'boolean') {
133
+ binding[i] = b ? 1 : 0;
134
+ }
135
+ }
136
+ if (bindings) {
137
+ sqlite3.bind_collection(stmt, binding);
138
+ }
139
+ const result = await sqlite3.step(stmt);
140
+ if (result === SQLite.SQLITE_DONE) {
141
+ //The value returned by sqlite3_changes() immediately after an INSERT, UPDATE or DELETE statement run on a view is always zero.
142
+ affectedRows += sqlite3.changes(db);
143
+ }
144
+ sqlite3.reset(stmt);
139
145
  }
140
146
  }
141
- //Finalize prepared statement
142
- await sqlite3.finalize(prepared.stmt);
143
147
  await executeSingleStatement('COMMIT');
144
148
  }
145
149
  catch (err) {
@@ -149,9 +153,6 @@ export async function _openDB(dbFileName, options = { useWebWorker: true }) {
149
153
  rows: { _array: [], length: 0 }
150
154
  };
151
155
  }
152
- finally {
153
- sqlite3.str_finish(str);
154
- }
155
156
  const result = {
156
157
  rowsAffected: affectedRows,
157
158
  rows: { _array: [], length: 0 }