From 8623ef601f55924dbe1cc65cc726e69629acf19e Mon Sep 17 00:00:00 2001 From: Roy Hashimoto <156154+rhashimoto@users.noreply.github.com> Date: Wed, 18 Jun 2025 11:07:01 -0700 Subject: [PATCH 1/9] Update issue templates --- .../-do-not-post-anything-other-than-a-bug-report.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/-do-not-post-anything-other-than-a-bug-report.md diff --git a/.github/ISSUE_TEMPLATE/-do-not-post-anything-other-than-a-bug-report.md b/.github/ISSUE_TEMPLATE/-do-not-post-anything-other-than-a-bug-report.md new file mode 100644 index 00000000..a4590df9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/-do-not-post-anything-other-than-a-bug-report.md @@ -0,0 +1,10 @@ +--- +name: " Do not post anything other than a bug report" +about: Issues are only for possible bugs in project code. +title: '' +labels: '' +assignees: '' + +--- + + From 4f2b7e8f87acef4e8d9902e6a131f48f656d023e Mon Sep 17 00:00:00 2001 From: Roy Hashimoto <156154+rhashimoto@users.noreply.github.com> Date: Sat, 21 Jun 2025 09:31:30 -0700 Subject: [PATCH 2/9] Replace Facade Proxy with handwritten proxy. (#285) * Replace Proxy with handwritten proxy for jRead/jWrite buffers. * Replace Proxy with handwritten proxy for VFS return data. --------- Co-authored-by: Roy Hashimoto --- src/FacadeVFS.js | 265 +++++++++++++++++++++++++++++++------- src/examples/MemoryVFS.js | 2 +- 2 files changed, 220 insertions(+), 47 deletions(-) diff --git a/src/FacadeVFS.js b/src/FacadeVFS.js index c975fdc9..dbc4947a 100644 --- a/src/FacadeVFS.js +++ b/src/FacadeVFS.js @@ -405,64 +405,30 @@ export class FacadeVFS extends VFS.Base { /** * Wrapped DataView for pointer arguments. - * Pointers to a single value are passed using DataView. A Proxy - * wrapper prevents use of incorrect type or endianness. + * Pointers to a single value are passed using a DataView-like class. + * This wrapper class prevents use of incorrect type or endianness, and + * reacquires the underlying buffer when the WebAssembly memory is resized. * @param {'Int32'|'BigInt64'} type * @param {number} byteOffset * @returns {DataView} */ #makeTypedDataView(type, byteOffset) { - const byteLength = type === 'Int32' ? 4 : 8; - const getter = `get${type}`; - const setter = `set${type}`; - const makeDataView = () => new DataView( - this._module.HEAPU8.buffer, - this._module.HEAPU8.byteOffset + byteOffset, - byteLength); - let dataView = makeDataView(); - return new Proxy(dataView, { - get(_, prop) { - if (dataView.buffer.byteLength === 0) { - // WebAssembly memory resize detached the buffer. - dataView = makeDataView(); - } - if (prop === getter) { - return function(byteOffset, littleEndian) { - if (!littleEndian) throw new Error('must be little endian'); - return dataView[prop](byteOffset, littleEndian); - } - } - if (prop === setter) { - return function(byteOffset, value, littleEndian) { - if (!littleEndian) throw new Error('must be little endian'); - return dataView[prop](byteOffset, value, littleEndian); - } - } - if (typeof prop === 'string' && (prop.match(/^(get)|(set)/))) { - throw new Error('invalid type'); - } - const result = dataView[prop]; - return typeof result === 'function' ? result.bind(dataView) : result; - } - }); + // @ts-ignore + return new DataViewProxy(this._module, byteOffset, type); } /** + * Wrapped Uint8Array for buffer arguments. + * Memory blocks are passed as a Uint8Array-like class. This wrapper + * class reacquires the underlying buffer when the WebAssembly memory + * is resized. * @param {number} byteOffset * @param {number} byteLength + * @returns {Uint8Array} */ #makeDataArray(byteOffset, byteLength) { - let target = this._module.HEAPU8.subarray(byteOffset, byteOffset + byteLength); - return new Proxy(target, { - get: (_, prop, receiver) => { - if (target.buffer.byteLength === 0) { - // WebAssembly memory resize detached the buffer. - target = this._module.HEAPU8.subarray(byteOffset, byteOffset + byteLength); - } - const result = target[prop]; - return typeof result === 'function' ? result.bind(target) : result; - } - }); + // @ts-ignore + return new Uint8ArrayProxy(this._module, byteOffset, byteLength); } #decodeFilename(zName, flags) { @@ -506,3 +472,210 @@ export class FacadeVFS extends VFS.Base { function delegalize(lo32, hi32) { return (hi32 * 0x100000000) + lo32 + (lo32 < 0 ? 2**32 : 0); } + +// This class provides a Uint8Array-like interface for a WebAssembly memory +// buffer. It is used to access memory blocks passed as arguments to +// xRead, xWrite, etc. The class reacquires the underlying buffer when the +// WebAssembly memory is resized, which can happen when the memory is +// detached and resized by the WebAssembly module. +// +// Note that although this class implements the same methods as Uint8Array, +// it is not a real Uint8Array and passing it to functions that expect +// a Uint8Array may not work. Use subarray() to get a real Uint8Array +// if needed. +class Uint8ArrayProxy { + #module; + + #_array = new Uint8Array() + get #array() { + if (this.#_array.buffer.byteLength === 0) { + // WebAssembly memory resize detached the buffer so re-create the + // array with the new buffer. + this.#_array = this.#module.HEAPU8.subarray( + this.byteOffset, + this.byteOffset + this.byteLength); + } + return this.#_array; + } + + /** + * @param {*} module + * @param {number} byteOffset + * @param {number} byteLength + */ + constructor(module, byteOffset, byteLength) { + this.#module = module; + this.byteOffset = byteOffset; + this.length = this.byteLength = byteLength; + } + + get buffer() { + return this.#array.buffer; + } + + at(index) { + return this.#array.at(index); + } + copyWithin(target, start, end) { + this.#array.copyWithin(target, start, end); + } + entries() { + return this.#array.entries(); + } + every(predicate) { + return this.#array.every(predicate); + } + fill(value, start, end) { + this.#array.fill(value, start, end); + } + filter(predicate) { + return this.#array.filter(predicate); + } + find(predicate) { + return this.#array.find(predicate); + } + findIndex(predicate) { + return this.#array.findIndex(predicate); + } + findLast(predicate) { + return this.#array.findLast(predicate); + } + findLastIndex(predicate) { + return this.#array.findLastIndex(predicate); + } + forEach(callback) { + this.#array.forEach(callback); + } + includes(value, start) { + return this.#array.includes(value, start); + } + indexOf(value, start) { + return this.#array.indexOf(value, start); + } + join(separator) { + return this.#array.join(separator); + } + keys() { + return this.#array.keys(); + } + lastIndexOf(value, start) { + return this.#array.lastIndexOf(value, start); + } + map(callback) { + return this.#array.map(callback); + } + reduce(callback, initialValue) { + return this.#array.reduce(callback, initialValue); + } + reduceRight(callback, initialValue) { + return this.#array.reduceRight(callback, initialValue); + } + reverse() { + this.#array.reverse(); + } + set(array, offset) { + this.#array.set(array, offset); + } + slice(start, end) { + return this.#array.slice(start, end); + } + some(predicate) { + return this.#array.some(predicate); + } + sort(compareFn) { + this.#array.sort(compareFn); + } + subarray(begin, end) { + return this.#array.subarray(begin, end); + } + toLocaleString(locales, options) { + // @ts-ignore + return this.#array.toLocaleString(locales, options); + } + toReversed() { + return this.#array.toReversed(); + } + toSorted(compareFn) { + return this.#array.toSorted(compareFn); + } + toString() { + return this.#array.toString(); + } + values() { + return this.#array.values(); + } + with(index, value) { + return this.#array.with(index, value); + } + [Symbol.iterator]() { + return this.#array[Symbol.iterator](); + } +} + +// This class provides a DataView-like interface for a WebAssembly memory +// buffer, restricted to either Int32 or BigInt64 types. It also reacquires +// the underlying buffer when the WebAssembly memory is resized, which can +// happen when the memory is detached and resized by the WebAssembly module. +class DataViewProxy { + #module; + #type; + + #_view = new DataView(new ArrayBuffer(0)); + get #view() { + if (this.#_view.buffer.byteLength === 0) { + // WebAssembly memory resize detached the buffer so re-create the + // view with the new buffer. + this.#_view = new DataView( + this.#module.HEAPU8.buffer, + this.#module.HEAPU8.byteOffset + this.byteOffset); + } + return this.#_view; + } + + /** + * @param {*} module + * @param {number} byteOffset + * @param {'Int32'|'BigInt64'} type + */ + constructor(module, byteOffset, type) { + this.#module = module; + this.byteOffset = byteOffset; + this.#type = type; + } + + get buffer() { + return this.#view.buffer; + } + get byteLength() { + return this.#type === 'Int32' ? 4 : 8; + } + + getInt32(byteOffset, littleEndian) { + if (this.#type !== 'Int32') { + throw new Error('invalid type'); + } + if (!littleEndian) throw new Error('must be little endian'); + return this.#view.getInt32(byteOffset, littleEndian); + } + setInt32(byteOffset, value, littleEndian) { + if (this.#type !== 'Int32') { + throw new Error('invalid type'); + } + if (!littleEndian) throw new Error('must be little endian'); + this.#view.setInt32(byteOffset, value, littleEndian); + } + getBigInt64(byteOffset, littleEndian) { + if (this.#type !== 'BigInt64') { + throw new Error('invalid type'); + } + if (!littleEndian) throw new Error('must be little endian'); + return this.#view.getBigInt64(byteOffset, littleEndian); + } + setBigInt64(byteOffset, value, littleEndian) { + if (this.#type !== 'BigInt64') { + throw new Error('invalid type'); + } + if (!littleEndian) throw new Error('must be little endian'); + this.#view.setBigInt64(byteOffset, value, littleEndian); + } +} \ No newline at end of file diff --git a/src/examples/MemoryVFS.js b/src/examples/MemoryVFS.js index 9da7b71f..fd1b72c6 100644 --- a/src/examples/MemoryVFS.js +++ b/src/examples/MemoryVFS.js @@ -116,7 +116,7 @@ export class MemoryVFS extends FacadeVFS { } // Copy data. - new Uint8Array(file.data, iOffset, pData.byteLength).set(pData); + new Uint8Array(file.data, iOffset, pData.byteLength).set(pData.subarray()); file.size = Math.max(file.size, iOffset + pData.byteLength); return VFS.SQLITE_OK; } From 21cc38a00f54a21eceef0dfc1e31f3c4dae75701 Mon Sep 17 00:00:00 2001 From: Roy Hashimoto <156154+rhashimoto@users.noreply.github.com> Date: Fri, 22 Aug 2025 06:19:52 -0700 Subject: [PATCH 3/9] Use non-CAPTCHA SQLite download URL. (#289) * Use non-CAPTCHA SQLite download URL. * Use consistent Makefile variable bracing. --------- Co-authored-by: Roy Hashimoto --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 409e366a..053b3538 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # dependencies SQLITE_VERSION = version-3.50.1 -SQLITE_TARBALL_URL = https://www.sqlite.org/src/tarball/sqlite.tar.gz?r=${SQLITE_VERSION} +SQLITE_TARBALL_URL = https://www.sqlite.org/src/tarball/$(SQLITE_VERSION)/sqlite.tar.gz EXTENSION_FUNCTIONS = extension-functions.c EXTENSION_FUNCTIONS_URL = https://www.sqlite.org/contrib/download/extension-functions.c?get=25 From a47eda0c7e0d53072921cf1369678af0c07b0414 Mon Sep 17 00:00:00 2001 From: Roy Hashimoto <156154+rhashimoto@users.noreply.github.com> Date: Wed, 24 Sep 2025 10:57:14 -0700 Subject: [PATCH 4/9] Fix WebLocksMixin state initialization. (#293) * Fix WebLocksMixin state initialization. * Don't fetch state in WebLocksMixin file control unnecessarily. * Minor fixes. --------- Co-authored-by: Roy Hashimoto --- src/WebLocksMixin.js | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/WebLocksMixin.js b/src/WebLocksMixin.js index 9b1e97b6..e5fd48f9 100644 --- a/src/WebLocksMixin.js +++ b/src/WebLocksMixin.js @@ -48,18 +48,7 @@ export const WebLocksMixin = superclass => class extends superclass { */ async jLock(fileId, lockType) { try { - // Create state on first lock. - if (!this.#mapIdToState.has(fileId)) { - const name = this.getFilename(fileId); - const state = { - baseName: name, - type: VFS.SQLITE_LOCK_NONE, - writeHint: false - }; - this.#mapIdToState.set(fileId, state); - } - - const lockState = this.#mapIdToState.get(fileId); + const lockState = this.#getLockState(fileId); if (lockType <= lockState.type) return VFS.SQLITE_OK; switch (this.#options.lockPolicy) { @@ -82,10 +71,8 @@ export const WebLocksMixin = superclass => class extends superclass { */ async jUnlock(fileId, lockType) { try { - // SQLite can call xUnlock() without ever calling xLock() so - // the state may not exist. - const lockState = this.#mapIdToState.get(fileId); - if (!(lockType < lockState?.type)) return VFS.SQLITE_OK; + const lockState = this.#getLockState(fileId); + if (!(lockType < lockState.type)) return VFS.SQLITE_OK; switch (this.#options.lockPolicy) { case 'exclusive': @@ -107,7 +94,7 @@ export const WebLocksMixin = superclass => class extends superclass { */ async jCheckReservedLock(fileId, pResOut) { try { - const lockState = this.#mapIdToState.get(fileId); + const lockState = this.#getLockState(fileId); switch (this.#options.lockPolicy) { case 'exclusive': return this.#checkReservedExclusive(lockState, pResOut); @@ -130,19 +117,29 @@ export const WebLocksMixin = superclass => class extends superclass { * @returns {number|Promise} */ jFileControl(fileId, op, pArg) { - const lockState = this.#mapIdToState.get(fileId) ?? - (() => { - // Call jLock() to create the lock state. - this.jLock(fileId, VFS.SQLITE_LOCK_NONE); - return this.#mapIdToState.get(fileId); - })(); if (op === WebLocksMixin.WRITE_HINT_OP_CODE && this.#options.lockPolicy === 'shared+hint'){ + const lockState = this.#getLockState(fileId); lockState.writeHint = true; } return VFS.SQLITE_NOTFOUND; } + #getLockState(fileId) { + let lockState = this.#mapIdToState.get(fileId); + if (!lockState) { + // The state doesn't exist yet so create it. + const name = this.getFilename(fileId); + lockState = { + baseName: name, + type: VFS.SQLITE_LOCK_NONE, + writeHint: false + }; + this.#mapIdToState.set(fileId, lockState); + } + return lockState + } + /** * @param {LockState} lockState * @param {number} lockType From a3b1324ed5a57928141b02eb3204421e1164ed53 Mon Sep 17 00:00:00 2001 From: Roy Hashimoto Date: Wed, 24 Sep 2025 11:00:04 -0700 Subject: [PATCH 5/9] Bump package version. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d11a7581..690d10ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wa-sqlite", - "version": "1.0.8", + "version": "1.0.9", "type": "module", "main": "src/sqlite-api.js", "types": "src/types/index.d.ts", From c65ec43872c21d3e9c7c8fac0e668ef6543d0b18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 22:28:47 +0000 Subject: [PATCH 6/9] Bump brace-expansion from 1.1.11 to 1.1.12 Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12. - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12) --- updated-dependencies: - dependency-name: brace-expansion dependency-version: 1.1.12 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e40b41d3..bdbcebea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1161,12 +1161,12 @@ __metadata: linkType: hard "brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" dependencies: balanced-match: "npm:^1.0.0" concat-map: "npm:0.0.1" - checksum: faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 + checksum: 12cb6d6310629e3048cadb003e1aca4d8c9bb5c67c3c321bafdd7e7a50155de081f78ea3e0ed92ecc75a9015e784f301efc8132383132f4f7904ad1ac529c562 languageName: node linkType: hard From 51983d39d36c4a33c5e68b3f164e81dc6560203b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 22:45:16 +0000 Subject: [PATCH 7/9] Bump koa from 2.16.1 to 2.16.3 Bumps [koa](https://github.com/koajs/koa) from 2.16.1 to 2.16.3. - [Release notes](https://github.com/koajs/koa/releases) - [Changelog](https://github.com/koajs/koa/blob/master/History.md) - [Commits](https://github.com/koajs/koa/compare/v2.16.1...v2.16.3) --- updated-dependencies: - dependency-name: koa dependency-version: 2.16.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index bdbcebea..1e6e6b7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2714,8 +2714,8 @@ __metadata: linkType: hard "koa@npm:^2.13.0": - version: 2.16.1 - resolution: "koa@npm:2.16.1" + version: 2.16.3 + resolution: "koa@npm:2.16.3" dependencies: accepts: "npm:^1.3.5" cache-content-type: "npm:^1.0.0" @@ -2740,7 +2740,7 @@ __metadata: statuses: "npm:^1.5.0" type-is: "npm:^1.6.16" vary: "npm:^1.1.2" - checksum: f33b95227e48bffd3a682996e6cf72c4ae2992671529c6c914b76d28172219c9cbd8201b16cc028dc25fafc8f1dc9391a6e7e045740a10ee7d89a5631031a974 + checksum: 62b6bc4939003eab2b77d523207e252f4eed3f75471fce3b50fe46a80fb01b9f425d4094437f25e3579ad90bcf43b652c166ac5b58d277255ed82a0ea7069ac8 languageName: node linkType: hard From 7f9841edf2ceddd9d13c5dfa1b934b808b530b51 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 22:57:23 +0000 Subject: [PATCH 8/9] Bump tar-fs from 3.0.9 to 3.1.1 Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 3.0.9 to 3.1.1. - [Commits](https://github.com/mafintosh/tar-fs/compare/v3.0.9...v3.1.1) --- updated-dependencies: - dependency-name: tar-fs dependency-version: 3.1.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1e6e6b7a..3a49dbc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3983,8 +3983,8 @@ __metadata: linkType: hard "tar-fs@npm:^3.0.6, tar-fs@npm:^3.0.8": - version: 3.0.9 - resolution: "tar-fs@npm:3.0.9" + version: 3.1.1 + resolution: "tar-fs@npm:3.1.1" dependencies: bare-fs: "npm:^4.0.1" bare-path: "npm:^3.0.0" @@ -3995,7 +3995,7 @@ __metadata: optional: true bare-path: optional: true - checksum: 00e194ef36ced339000099c3cb5205fcd9636a531158d73e0fc1ca056fbcf8dcf39a398cbc71f030bbf938d4f477f47e2913c6e37e9c007bad31e0768a120590 + checksum: f7f7540b563e10541dc0b95f710c68fc1fccde0c1177b4d3bab2023c6d18da19d941a8697fdc1abff54914b71b6e5f2dfb0455572b5c8993b2ab76571cbbc923 languageName: node linkType: hard From 5571a7b575b43f5632a3bda2a8be2b35fa2ccb9e Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Mon, 1 Dec 2025 14:03:02 +0200 Subject: [PATCH 9/9] add changeset --- .changeset/spicy-ties-sing.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/spicy-ties-sing.md diff --git a/.changeset/spicy-ties-sing.md b/.changeset/spicy-ties-sing.md new file mode 100644 index 00000000..efcd0765 --- /dev/null +++ b/.changeset/spicy-ties-sing.md @@ -0,0 +1,5 @@ +--- +'@journeyapps/wa-sqlite': patch +--- + +Update upstream to [v1.0.9](https://github.com/rhashimoto/wa-sqlite/releases/tag/v1.0.9)