@sqlite.org/sqlite-wasm
Advanced tools
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| /* @preserve | ||
| 2022-09-16 | ||
| The author disclaims copyright to this source code. In place of a | ||
| legal notice, here is a blessing: | ||
| * May you do good and not evil. | ||
| * May you find forgiveness for yourself and forgive others. | ||
| * May you share freely, never taking more than you give. | ||
| *********************************************************************** | ||
| A Worker which manages asynchronous OPFS handles on behalf of a | ||
| synchronous API which controls it via a combination of Worker | ||
| messages, SharedArrayBuffer, and Atomics. It is the asynchronous | ||
| counterpart of the API defined in sqlite3-vfs-opfs.js. | ||
| Highly indebted to: | ||
| https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/OriginPrivateFileSystemVFS.js | ||
| for demonstrating how to use the OPFS APIs. | ||
| This file is to be loaded as a Worker. It does not have any direct | ||
| access to the sqlite3 JS/WASM bits, so any bits which it needs (most | ||
| notably SQLITE_xxx integer codes) have to be imported into it via an | ||
| initialization process. | ||
| This file represents an implementation detail of a larger piece of | ||
| code, and not a public interface. Its details may change at any time | ||
| and are not intended to be used by any client-level code. | ||
| 2022-11-27: Chrome v108 changes some async methods to synchronous, as | ||
| documented at: | ||
| https://developer.chrome.com/blog/sync-methods-for-accesshandles/ | ||
| Firefox v111 and Safari 16.4, both released in March 2023, also | ||
| include this. | ||
| We cannot change to the sync forms at this point without breaking | ||
| clients who use Chrome v104-ish or higher. truncate(), getSize(), | ||
| flush(), and close() are now (as of v108) synchronous. Calling them | ||
| with an "await", as we have to for the async forms, is still legal | ||
| with the sync forms but is superfluous. Calling the async forms with | ||
| theFunc().then(...) is not compatible with the change to | ||
| synchronous, but we do do not use those APIs that way. i.e. we don't | ||
| _need_ to change anything for this, but at some point (after Chrome | ||
| versions (approximately) 104-107 are extinct) should change our | ||
| usage of those methods to remove the "await". | ||
| */ | ||
| const wPost = (type, ...args) => postMessage({ | ||
| type, | ||
| payload: args | ||
| }); | ||
| const installAsyncProxy = function() { | ||
| const toss = function(...args) { | ||
| throw new Error(args.join(" ")); | ||
| }; | ||
| if (globalThis.window === globalThis) toss("This code cannot run from the main thread.", "Load it as a Worker from a separate Worker."); | ||
| else if (!navigator?.storage?.getDirectory) toss("This API requires navigator.storage.getDirectory."); | ||
| const state = Object.create(null); | ||
| state.verbose = 1; | ||
| const loggers = { | ||
| 0: console.error.bind(console), | ||
| 1: console.warn.bind(console), | ||
| 2: console.log.bind(console) | ||
| }; | ||
| const logImpl = (level, ...args) => { | ||
| if (state.verbose > level) loggers[level]("OPFS asyncer:", ...args); | ||
| }; | ||
| const log = (...args) => logImpl(2, ...args); | ||
| const warn = (...args) => logImpl(1, ...args); | ||
| const error = (...args) => logImpl(0, ...args); | ||
| const __openFiles = Object.create(null); | ||
| const __implicitLocks = /* @__PURE__ */ new Set(); | ||
| const getResolvedPath = function(filename, splitIt) { | ||
| const p = new URL(filename, "file://irrelevant").pathname; | ||
| return splitIt ? p.split("/").filter((v) => !!v) : p; | ||
| }; | ||
| const getDirForFilename = async function f(absFilename, createDirs = false) { | ||
| const path = getResolvedPath(absFilename, true); | ||
| const filename = path.pop(); | ||
| let dh = state.rootDir; | ||
| for (const dirName of path) if (dirName) dh = await dh.getDirectoryHandle(dirName, { create: !!createDirs }); | ||
| return [dh, filename]; | ||
| }; | ||
| const closeSyncHandle = async (fh) => { | ||
| if (fh.syncHandle) { | ||
| log("Closing sync handle for", fh.filenameAbs); | ||
| const h = fh.syncHandle; | ||
| delete fh.syncHandle; | ||
| delete fh.xLock; | ||
| __implicitLocks.delete(fh.fid); | ||
| return h.close(); | ||
| } | ||
| }; | ||
| const closeSyncHandleNoThrow = async (fh) => { | ||
| try { | ||
| await closeSyncHandle(fh); | ||
| } catch (e) { | ||
| warn("closeSyncHandleNoThrow() ignoring:", e, fh); | ||
| } | ||
| }; | ||
| const releaseImplicitLocks = async () => { | ||
| if (__implicitLocks.size) for (const fid of __implicitLocks) { | ||
| const fh = __openFiles[fid]; | ||
| await closeSyncHandleNoThrow(fh); | ||
| log("Auto-unlocked", fid, fh.filenameAbs); | ||
| } | ||
| }; | ||
| const releaseImplicitLock = async (fh) => { | ||
| if (fh.releaseImplicitLocks && __implicitLocks.has(fh.fid)) return closeSyncHandleNoThrow(fh); | ||
| }; | ||
| class GetSyncHandleError extends Error { | ||
| constructor(errorObject, ...msg) { | ||
| super([ | ||
| ...msg, | ||
| ": " + errorObject.name + ":", | ||
| errorObject.message | ||
| ].join(" "), { cause: errorObject }); | ||
| this.name = "GetSyncHandleError"; | ||
| } | ||
| } | ||
| GetSyncHandleError.convertRc = (e, rc) => { | ||
| if (e instanceof GetSyncHandleError) { | ||
| if (e.cause.name === "NoModificationAllowedError" || e.cause.name === "DOMException" && 0 === e.cause.message.indexOf("Access Handles cannot")) return state.sq3Codes.SQLITE_BUSY; | ||
| else if ("NotFoundError" === e.cause.name) return state.sq3Codes.SQLITE_CANTOPEN; | ||
| } else if ("NotFoundError" === e?.name) return state.sq3Codes.SQLITE_CANTOPEN; | ||
| return rc; | ||
| }; | ||
| const getSyncHandle = async (fh, opName) => { | ||
| if (!fh.syncHandle) { | ||
| const t = performance.now(); | ||
| log("Acquiring sync handle for", fh.filenameAbs); | ||
| const maxTries = 6, msBase = state.asyncIdleWaitTime * 2; | ||
| let i = 1, ms = msBase; | ||
| for (;; ms = msBase * ++i) try { | ||
| fh.syncHandle = await fh.fileHandle.createSyncAccessHandle(); | ||
| break; | ||
| } catch (e) { | ||
| if (i === maxTries) throw new GetSyncHandleError(e, "Error getting sync handle for", opName + "().", maxTries, "attempts failed.", fh.filenameAbs); | ||
| warn("Error getting sync handle for", opName + "(). Waiting", ms, "ms and trying again.", fh.filenameAbs, e); | ||
| Atomics.wait(state.sabOPView, state.opIds.retry, 0, ms); | ||
| } | ||
| log("Got", opName + "() sync handle for", fh.filenameAbs, "in", performance.now() - t, "ms"); | ||
| if (!fh.xLock) { | ||
| __implicitLocks.add(fh.fid); | ||
| log("Acquired implicit lock for", opName + "()", fh.fid, fh.filenameAbs); | ||
| } | ||
| } | ||
| return fh.syncHandle; | ||
| }; | ||
| const storeAndNotify = (opName, value) => { | ||
| log(opName + "() => notify(", value, ")"); | ||
| Atomics.store(state.sabOPView, state.opIds.rc, value); | ||
| Atomics.notify(state.sabOPView, state.opIds.rc); | ||
| }; | ||
| const affirmNotRO = function(opName, fh) { | ||
| if (fh.readOnly) toss(opName + "(): File is read-only: " + fh.filenameAbs); | ||
| }; | ||
| let flagAsyncShutdown = false; | ||
| const vfsAsyncImpls = { | ||
| "opfs-async-shutdown": async () => { | ||
| flagAsyncShutdown = true; | ||
| storeAndNotify("opfs-async-shutdown", 0); | ||
| }, | ||
| mkdir: async (dirname) => { | ||
| let rc = 0; | ||
| try { | ||
| await getDirForFilename(dirname + "/filepart", true); | ||
| } catch (e) { | ||
| state.s11n.storeException(2, e); | ||
| rc = state.sq3Codes.SQLITE_IOERR; | ||
| } | ||
| storeAndNotify("mkdir", rc); | ||
| }, | ||
| xAccess: async (filename) => { | ||
| let rc = 0; | ||
| try { | ||
| const [dh, fn] = await getDirForFilename(filename); | ||
| await dh.getFileHandle(fn); | ||
| } catch (e) { | ||
| state.s11n.storeException(2, e); | ||
| rc = state.sq3Codes.SQLITE_IOERR; | ||
| } | ||
| storeAndNotify("xAccess", rc); | ||
| }, | ||
| xClose: async function(fid) { | ||
| const opName = "xClose"; | ||
| __implicitLocks.delete(fid); | ||
| const fh = __openFiles[fid]; | ||
| let rc = 0; | ||
| if (fh) { | ||
| delete __openFiles[fid]; | ||
| await closeSyncHandle(fh); | ||
| if (fh.deleteOnClose) try { | ||
| await fh.dirHandle.removeEntry(fh.filenamePart); | ||
| } catch (e) { | ||
| warn("Ignoring dirHandle.removeEntry() failure of", fh, e); | ||
| } | ||
| } else { | ||
| state.s11n.serialize(); | ||
| rc = state.sq3Codes.SQLITE_NOTFOUND; | ||
| } | ||
| storeAndNotify(opName, rc); | ||
| }, | ||
| xDelete: async function(...args) { | ||
| storeAndNotify("xDelete", await vfsAsyncImpls.xDeleteNoWait(...args)); | ||
| }, | ||
| xDeleteNoWait: async function(filename, syncDir = 0, recursive = false) { | ||
| let rc = 0; | ||
| try { | ||
| while (filename) { | ||
| const [hDir, filenamePart] = await getDirForFilename(filename, false); | ||
| if (!filenamePart) break; | ||
| await hDir.removeEntry(filenamePart, { recursive }); | ||
| if (4660 !== syncDir) break; | ||
| recursive = false; | ||
| filename = getResolvedPath(filename, true); | ||
| filename.pop(); | ||
| filename = filename.join("/"); | ||
| } | ||
| } catch (e) { | ||
| state.s11n.storeException(2, e); | ||
| rc = state.sq3Codes.SQLITE_IOERR_DELETE; | ||
| } | ||
| return rc; | ||
| }, | ||
| xFileSize: async function(fid) { | ||
| const fh = __openFiles[fid]; | ||
| let rc = 0; | ||
| try { | ||
| const sz = await (await getSyncHandle(fh, "xFileSize")).getSize(); | ||
| state.s11n.serialize(Number(sz)); | ||
| } catch (e) { | ||
| state.s11n.storeException(1, e); | ||
| rc = GetSyncHandleError.convertRc(e, state.sq3Codes.SQLITE_IOERR); | ||
| } | ||
| await releaseImplicitLock(fh); | ||
| storeAndNotify("xFileSize", rc); | ||
| }, | ||
| xLock: async function(fid, lockType) { | ||
| const fh = __openFiles[fid]; | ||
| let rc = 0; | ||
| const oldLockType = fh.xLock; | ||
| fh.xLock = lockType; | ||
| if (!fh.syncHandle) try { | ||
| await getSyncHandle(fh, "xLock"); | ||
| __implicitLocks.delete(fid); | ||
| } catch (e) { | ||
| state.s11n.storeException(1, e); | ||
| rc = GetSyncHandleError.convertRc(e, state.sq3Codes.SQLITE_IOERR_LOCK); | ||
| fh.xLock = oldLockType; | ||
| } | ||
| storeAndNotify("xLock", rc); | ||
| }, | ||
| xOpen: async function(fid, filename, flags, opfsFlags) { | ||
| const opName = "xOpen"; | ||
| const create = state.sq3Codes.SQLITE_OPEN_CREATE & flags; | ||
| try { | ||
| let hDir, filenamePart; | ||
| try { | ||
| [hDir, filenamePart] = await getDirForFilename(filename, !!create); | ||
| } catch (e) { | ||
| state.s11n.storeException(1, e); | ||
| storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND); | ||
| return; | ||
| } | ||
| if (state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN & opfsFlags) try { | ||
| await hDir.removeEntry(filenamePart); | ||
| } catch (e) {} | ||
| const hFile = await hDir.getFileHandle(filenamePart, { create }); | ||
| const fh = Object.assign(Object.create(null), { | ||
| fid, | ||
| filenameAbs: filename, | ||
| filenamePart, | ||
| dirHandle: hDir, | ||
| fileHandle: hFile, | ||
| sabView: state.sabFileBufView, | ||
| readOnly: !create && !!(state.sq3Codes.SQLITE_OPEN_READONLY & flags), | ||
| deleteOnClose: !!(state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags) | ||
| }); | ||
| fh.releaseImplicitLocks = opfsFlags & state.opfsFlags.OPFS_UNLOCK_ASAP || state.opfsFlags.defaultUnlockAsap; | ||
| __openFiles[fid] = fh; | ||
| storeAndNotify(opName, 0); | ||
| } catch (e) { | ||
| error(opName, e); | ||
| state.s11n.storeException(1, e); | ||
| storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR); | ||
| } | ||
| }, | ||
| xRead: async function(fid, n, offset64) { | ||
| let rc = 0, nRead; | ||
| const fh = __openFiles[fid]; | ||
| try { | ||
| nRead = (await getSyncHandle(fh, "xRead")).read(fh.sabView.subarray(0, n), { at: Number(offset64) }); | ||
| if (nRead < n) { | ||
| fh.sabView.fill(0, nRead, n); | ||
| rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ; | ||
| } | ||
| } catch (e) { | ||
| error("xRead() failed", e, fh); | ||
| state.s11n.storeException(1, e); | ||
| rc = GetSyncHandleError.convertRc(e, state.sq3Codes.SQLITE_IOERR_READ); | ||
| } | ||
| await releaseImplicitLock(fh); | ||
| storeAndNotify("xRead", rc); | ||
| }, | ||
| xSync: async function(fid, flags) { | ||
| const fh = __openFiles[fid]; | ||
| let rc = 0; | ||
| if (!fh.readOnly && fh.syncHandle) try { | ||
| await fh.syncHandle.flush(); | ||
| } catch (e) { | ||
| state.s11n.storeException(2, e); | ||
| rc = state.sq3Codes.SQLITE_IOERR_FSYNC; | ||
| } | ||
| storeAndNotify("xSync", rc); | ||
| }, | ||
| xTruncate: async function(fid, size) { | ||
| let rc = 0; | ||
| const fh = __openFiles[fid]; | ||
| try { | ||
| affirmNotRO("xTruncate", fh); | ||
| await (await getSyncHandle(fh, "xTruncate")).truncate(size); | ||
| } catch (e) { | ||
| error("xTruncate():", e, fh); | ||
| state.s11n.storeException(2, e); | ||
| rc = GetSyncHandleError.convertRc(e, state.sq3Codes.SQLITE_IOERR_TRUNCATE); | ||
| } | ||
| await releaseImplicitLock(fh); | ||
| storeAndNotify("xTruncate", rc); | ||
| }, | ||
| xUnlock: async function(fid, lockType) { | ||
| let rc = 0; | ||
| const fh = __openFiles[fid]; | ||
| if (fh.syncHandle && state.sq3Codes.SQLITE_LOCK_NONE === lockType) try { | ||
| await closeSyncHandle(fh); | ||
| } catch (e) { | ||
| state.s11n.storeException(1, e); | ||
| rc = state.sq3Codes.SQLITE_IOERR_UNLOCK; | ||
| } | ||
| storeAndNotify("xUnlock", rc); | ||
| }, | ||
| xWrite: async function(fid, n, offset64) { | ||
| let rc; | ||
| const fh = __openFiles[fid]; | ||
| try { | ||
| affirmNotRO("xWrite", fh); | ||
| rc = n === (await getSyncHandle(fh, "xWrite")).write(fh.sabView.subarray(0, n), { at: Number(offset64) }) ? 0 : state.sq3Codes.SQLITE_IOERR_WRITE; | ||
| } catch (e) { | ||
| error("xWrite():", e, fh); | ||
| state.s11n.storeException(1, e); | ||
| rc = GetSyncHandleError.convertRc(e, state.sq3Codes.SQLITE_IOERR_WRITE); | ||
| } | ||
| await releaseImplicitLock(fh); | ||
| storeAndNotify("xWrite", rc); | ||
| } | ||
| }; | ||
| const initS11n = () => { | ||
| if (state.s11n) return state.s11n; | ||
| const textDecoder = new TextDecoder(), textEncoder = new TextEncoder("utf-8"), viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize), viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize); | ||
| state.s11n = Object.create(null); | ||
| const TypeIds = Object.create(null); | ||
| TypeIds.number = { | ||
| id: 1, | ||
| size: 8, | ||
| getter: "getFloat64", | ||
| setter: "setFloat64" | ||
| }; | ||
| TypeIds.bigint = { | ||
| id: 2, | ||
| size: 8, | ||
| getter: "getBigInt64", | ||
| setter: "setBigInt64" | ||
| }; | ||
| TypeIds.boolean = { | ||
| id: 3, | ||
| size: 4, | ||
| getter: "getInt32", | ||
| setter: "setInt32" | ||
| }; | ||
| TypeIds.string = { id: 4 }; | ||
| const getTypeId = (v) => TypeIds[typeof v] || toss("Maintenance required: this value type cannot be serialized.", v); | ||
| const getTypeIdById = (tid) => { | ||
| switch (tid) { | ||
| case TypeIds.number.id: return TypeIds.number; | ||
| case TypeIds.bigint.id: return TypeIds.bigint; | ||
| case TypeIds.boolean.id: return TypeIds.boolean; | ||
| case TypeIds.string.id: return TypeIds.string; | ||
| default: toss("Invalid type ID:", tid); | ||
| } | ||
| }; | ||
| state.s11n.deserialize = function(clear = false) { | ||
| const argc = viewU8[0]; | ||
| const rc = argc ? [] : null; | ||
| if (argc) { | ||
| const typeIds = []; | ||
| let offset = 1, i, n, v; | ||
| for (i = 0; i < argc; ++i, ++offset) typeIds.push(getTypeIdById(viewU8[offset])); | ||
| for (i = 0; i < argc; ++i) { | ||
| const t = typeIds[i]; | ||
| if (t.getter) { | ||
| v = viewDV[t.getter](offset, state.littleEndian); | ||
| offset += t.size; | ||
| } else { | ||
| n = viewDV.getInt32(offset, state.littleEndian); | ||
| offset += 4; | ||
| v = textDecoder.decode(viewU8.slice(offset, offset + n)); | ||
| offset += n; | ||
| } | ||
| rc.push(v); | ||
| } | ||
| } | ||
| if (clear) viewU8[0] = 0; | ||
| return rc; | ||
| }; | ||
| state.s11n.serialize = function(...args) { | ||
| if (args.length) { | ||
| const typeIds = []; | ||
| let i = 0, offset = 1; | ||
| viewU8[0] = args.length & 255; | ||
| for (; i < args.length; ++i, ++offset) { | ||
| typeIds.push(getTypeId(args[i])); | ||
| viewU8[offset] = typeIds[i].id; | ||
| } | ||
| for (i = 0; i < args.length; ++i) { | ||
| const t = typeIds[i]; | ||
| if (t.setter) { | ||
| viewDV[t.setter](offset, args[i], state.littleEndian); | ||
| offset += t.size; | ||
| } else { | ||
| const s = textEncoder.encode(args[i]); | ||
| viewDV.setInt32(offset, s.byteLength, state.littleEndian); | ||
| offset += 4; | ||
| viewU8.set(s, offset); | ||
| offset += s.byteLength; | ||
| } | ||
| } | ||
| } else viewU8[0] = 0; | ||
| }; | ||
| state.s11n.storeException = state.asyncS11nExceptions ? ((priority, e) => { | ||
| if (priority <= state.asyncS11nExceptions) state.s11n.serialize([ | ||
| e.name, | ||
| ": ", | ||
| e.message | ||
| ].join("")); | ||
| }) : () => {}; | ||
| return state.s11n; | ||
| }; | ||
| const waitLoop = async function f() { | ||
| const opHandlers = Object.create(null); | ||
| for (let k of Object.keys(state.opIds)) { | ||
| const vi = vfsAsyncImpls[k]; | ||
| if (!vi) continue; | ||
| const o = Object.create(null); | ||
| opHandlers[state.opIds[k]] = o; | ||
| o.key = k; | ||
| o.f = vi; | ||
| } | ||
| while (!flagAsyncShutdown) try { | ||
| if ("not-equal" !== Atomics.wait(state.sabOPView, state.opIds.whichOp, 0, state.asyncIdleWaitTime)) { | ||
| await releaseImplicitLocks(); | ||
| continue; | ||
| } | ||
| const opId = Atomics.load(state.sabOPView, state.opIds.whichOp); | ||
| Atomics.store(state.sabOPView, state.opIds.whichOp, 0); | ||
| const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #", opId); | ||
| const args = state.s11n.deserialize(true) || []; | ||
| if (hnd.f) await hnd.f(...args); | ||
| else error("Missing callback for opId", opId); | ||
| } catch (e) { | ||
| error("in waitLoop():", e); | ||
| } | ||
| }; | ||
| navigator.storage.getDirectory().then(function(d) { | ||
| state.rootDir = d; | ||
| globalThis.onmessage = function({ data }) { | ||
| switch (data.type) { | ||
| case "opfs-async-init": { | ||
| const opt = data.args; | ||
| for (const k in opt) state[k] = opt[k]; | ||
| state.verbose = opt.verbose ?? 1; | ||
| state.sabOPView = new Int32Array(state.sabOP); | ||
| state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize); | ||
| state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize); | ||
| Object.keys(vfsAsyncImpls).forEach((k) => { | ||
| if (!Number.isFinite(state.opIds[k])) toss("Maintenance required: missing state.opIds[", k, "]"); | ||
| }); | ||
| initS11n(); | ||
| log("init state", state); | ||
| wPost("opfs-async-inited"); | ||
| waitLoop(); | ||
| break; | ||
| } | ||
| case "opfs-async-restart": | ||
| if (flagAsyncShutdown) { | ||
| warn("Restarting after opfs-async-shutdown. Might or might not work."); | ||
| flagAsyncShutdown = false; | ||
| waitLoop(); | ||
| } | ||
| break; | ||
| } | ||
| }; | ||
| wPost("opfs-async-loaded"); | ||
| }).catch((e) => error("error initializing OPFS asyncer:", e)); | ||
| }; | ||
| if (!globalThis.SharedArrayBuffer) wPost("opfs-unavailable", "Missing SharedArrayBuffer API.", "The server must emit the COOP/COEP response headers to enable that."); | ||
| else if (!globalThis.Atomics) wPost("opfs-unavailable", "Missing Atomics API.", "The server must emit the COOP/COEP response headers to enable that."); | ||
| else if (!globalThis.FileSystemHandle || !globalThis.FileSystemDirectoryHandle || !globalThis.FileSystemFileHandle || !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle || !navigator?.storage?.getDirectory) wPost("opfs-unavailable", "Missing required OPFS APIs."); | ||
| else installAsyncProxy(); | ||
| export {}; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
+48
-40
| { | ||
| "name": "@sqlite.org/sqlite-wasm", | ||
| "version": "3.51.1-build2", | ||
| "version": "3.51.2-build2", | ||
| "description": "SQLite Wasm conveniently wrapped as an ES Module.", | ||
| "type": "module", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/sqlite/sqlite-wasm.git" | ||
| }, | ||
| "author": "Thomas Steiner (tomac@google.com)", | ||
| "license": "Apache-2.0", | ||
| "bugs": { | ||
| "url": "https://github.com/sqlite/sqlite-wasm/issues" | ||
| }, | ||
| "homepage": "https://github.com/sqlite/sqlite-wasm#readme", | ||
| "keywords": [ | ||
@@ -16,58 +27,55 @@ "sqlite", | ||
| ], | ||
| "main": "index.mjs", | ||
| "node": "node.mjs", | ||
| "type": "module", | ||
| "files": [ | ||
| "index.d.ts", | ||
| "index.mjs", | ||
| "node.mjs", | ||
| "sqlite-wasm/" | ||
| ], | ||
| "types": "index.d.ts", | ||
| "publishConfig": { | ||
| "access": "public" | ||
| }, | ||
| "main": "./dist/index.mjs", | ||
| "node": "./dist/node.mjs", | ||
| "types": "./dist/index.d.mts", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./index.d.ts", | ||
| "node": "./node.mjs", | ||
| "import": "./index.mjs", | ||
| "main": "./index.mjs", | ||
| "browser": "./index.mjs" | ||
| "types": "./dist/index.d.mts", | ||
| "node": "./dist/node.mjs", | ||
| "import": "./dist/index.mjs", | ||
| "main": "./dist/index.mjs", | ||
| "browser": "./dist/index.mjs" | ||
| }, | ||
| "./package.json": "./package.json", | ||
| "./sqlite3.wasm": "./sqlite-wasm/jswasm/sqlite3.wasm" | ||
| "./sqlite3.wasm": "./dist/sqlite3.wasm" | ||
| }, | ||
| "bin": { | ||
| "sqlite-wasm": "bin/index.js" | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "README.md" | ||
| ], | ||
| "scripts": { | ||
| "test": "vitest", | ||
| "test:node": "vitest --project node", | ||
| "test:browser": "vitest --project browser", | ||
| "publint": "npx publint", | ||
| "check-types": "tsc", | ||
| "clean": "shx rm -rf sqlite-wasm", | ||
| "build": "npm run clean && node bin/index.js", | ||
| "build": "tsdown", | ||
| "start": "npx http-server --coop", | ||
| "start:node": "cd demo && node node.mjs", | ||
| "fix": "npx prettier . --write", | ||
| "prepare": "lefthook install", | ||
| "prepublishOnly": "npm run build && npm run fix && npm run publint && npm run check-types", | ||
| "deploy": "npm run prepublishOnly && git add . && git commit -am 'New release' && git push && npm publish --access public" | ||
| "deploy": "npm run prepublishOnly && git add . && git commit -am 'New release' && git push && npm publish" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/sqlite/sqlite-wasm.git" | ||
| "engines": { | ||
| "node": ">=22" | ||
| }, | ||
| "author": "Thomas Steiner (tomac@google.com)", | ||
| "license": "Apache-2.0", | ||
| "bugs": { | ||
| "url": "https://github.com/sqlite/sqlite-wasm/issues" | ||
| }, | ||
| "homepage": "https://github.com/sqlite/sqlite-wasm#readme", | ||
| "devDependencies": { | ||
| "decompress": "^4.2.1", | ||
| "@types/node": "^25.0.9", | ||
| "@vitest/browser": "^4.0.17", | ||
| "@vitest/browser-playwright": "^4.0.17", | ||
| "happy-dom": "^20.3.4", | ||
| "http-server": "github:vapier/http-server", | ||
| "module-workers-polyfill": "^0.3.2", | ||
| "node-fetch": "^3.3.2", | ||
| "prettier": "^3.7.3", | ||
| "prettier-plugin-jsdoc": "^1.7.0", | ||
| "publint": "^0.3.15", | ||
| "shx": "^0.4.0", | ||
| "typescript": "^5.9.3" | ||
| "lefthook": "2.0.15", | ||
| "playwright": "^1.57.0", | ||
| "prettier": "^3.8.0", | ||
| "prettier-plugin-jsdoc": "^1.8.0", | ||
| "publint": "^0.3.16", | ||
| "tsdown": "^0.20.0-beta.4", | ||
| "typescript": "^5.9.3", | ||
| "vitest": "^4.0.17" | ||
| } | ||
| } |
+71
-5
@@ -226,11 +226,77 @@ # SQLite Wasm | ||
| 1. Update the version number in `package.json` reflecting the current | ||
| 1. Manually trigger the | ||
| [GitHub Actions workflow](../../actions/workflows/build-wasm.yml). By | ||
| default, it uses the latest SQLite tag. This pull request will contain the | ||
| latest `sqlite3.wasm` and related bindings. | ||
| 2. Once the above pull request is validated and merged, update the version | ||
| number in `package.json`, reflecting the current | ||
| [SQLite version number](https://sqlite.org/download.html) and add a build | ||
| identifier suffix like `-build1`. The complete version number should read | ||
| something like `3.41.2-build1`. | ||
| 1. Run `npm run build` to build the ES Module. This downloads the latest SQLite | ||
| Wasm binary and builds the ES Module. | ||
| 1. Run `npm run deploy` to commit the changes, push to GitHub, and publish the | ||
| new version to npm. | ||
| ## Building the SQLite Wasm locally | ||
| 1. Build the Docker image: | ||
| ```bash | ||
| docker build -t sqlite-wasm-builder:env . | ||
| ``` | ||
| 2. Run the build: | ||
| **Unix (Linux/macOS):** | ||
| ```bash | ||
| docker run --rm \ | ||
| -e SQLITE_REF="master" \ | ||
| -v "$(pwd)/out":/out \ | ||
| -v "$(pwd)/src/bin":/src/bin \ | ||
| sqlite-wasm-builder:env build | ||
| ``` | ||
| **Windows (PowerShell):** | ||
| ```powershell | ||
| docker run --rm ` | ||
| -e SQLITE_REF="master" ` | ||
| -v "${PWD}/out:/out" ` | ||
| -v "${PWD}/src/bin:/src/bin" ` | ||
| sqlite-wasm-builder:env build | ||
| ``` | ||
| **Windows (Command Prompt):** | ||
| ```cmd | ||
| docker run --rm ^ | ||
| -e SQLITE_REF="master" ^ | ||
| -v "%cd%/out:/out" ^ | ||
| -v "%cd%/src/bin:/src/bin" ^ | ||
| sqlite-wasm-builder:env build | ||
| ``` | ||
| ## Running tests | ||
| The test suite consists of Node.js tests and browser-based tests (using Vitest | ||
| Browser Mode). Tests aim to sanity-check the exported scripts. We test for | ||
| correct exports and **very** basic functionality. | ||
| 1. Install dependencies: | ||
| ```bash | ||
| npm install | ||
| ``` | ||
| 2. Install Playwright browsers (required for browser tests): | ||
| ```bash | ||
| npx playwright install chromium --with-deps --no-shell | ||
| ``` | ||
| 3. Run all tests: | ||
| ```bash | ||
| npm test | ||
| ``` | ||
| ## License | ||
@@ -237,0 +303,0 @@ |
-62
| #!/usr/bin/env node | ||
| import fs from 'fs'; | ||
| import fetch from 'node-fetch'; | ||
| import decompress from 'decompress'; | ||
| async function getSqliteWasmDownloadLink() { | ||
| const response = await fetch('https://sqlite.org/download.html'); | ||
| const html = await response.text(); | ||
| const sqliteWasmLink = | ||
| 'https://sqlite.org/' + | ||
| html | ||
| .replace( | ||
| /^.*?<!-- Download product data for scripts to read(.*?)-->.*?$/gms, | ||
| '$1', | ||
| ) | ||
| .split(/\n/) | ||
| .filter((row) => /sqlite-wasm/.test(row))[0] | ||
| .split(/,/)[2]; | ||
| console.log(`Found SQLite Wasm download link: ${sqliteWasmLink}`); | ||
| return sqliteWasmLink; | ||
| } | ||
| async function downloadAndUnzipSqliteWasm(sqliteWasmDownloadLink) { | ||
| if (!sqliteWasmDownloadLink) { | ||
| throw new Error('Unable to find SQLite Wasm download link'); | ||
| } | ||
| console.log('Downloading and unzipping SQLite Wasm...'); | ||
| const response = await fetch(sqliteWasmDownloadLink); | ||
| if (!response.ok || response.status !== 200) { | ||
| throw new Error( | ||
| `Unable to download SQLite Wasm from ${sqliteWasmDownloadLink}`, | ||
| ); | ||
| } | ||
| const buffer = await response.arrayBuffer(); | ||
| fs.writeFileSync('sqlite-wasm.zip', Buffer.from(buffer)); | ||
| const files = await decompress('sqlite-wasm.zip', 'sqlite-wasm', { | ||
| strip: 1, | ||
| filter: (file) => | ||
| /jswasm/.test(file.path) && /(\.mjs|\.wasm|\.js)$/.test(file.path), | ||
| }); | ||
| console.log( | ||
| `Downloaded and unzipped:\n${files | ||
| .map((file) => (/\//.test(file.path) ? '‣ ' + file.path + '\n' : '')) | ||
| .join('')}`, | ||
| ); | ||
| fs.rmSync('sqlite-wasm.zip'); | ||
| } | ||
| async function main() { | ||
| try { | ||
| const sqliteWasmLink = await getSqliteWasmDownloadLink(); | ||
| await downloadAndUnzipSqliteWasm(sqliteWasmLink); | ||
| fs.copyFileSync( | ||
| './node_modules/module-workers-polyfill/module-workers-polyfill.min.js', | ||
| './demo/module-workers-polyfill.min.js', | ||
| ); | ||
| } catch (err) { | ||
| console.error(err.name, err.message); | ||
| } | ||
| } | ||
| main(); |
Sorry, the diff of this file is too big to display
| import { default as sqlite3InitModule } from './sqlite-wasm/jswasm/sqlite3.mjs'; | ||
| import './sqlite-wasm/jswasm/sqlite3-worker1-promiser.mjs'; | ||
| const sqlite3Worker1Promiser = globalThis.sqlite3Worker1Promiser; | ||
| export default sqlite3InitModule; | ||
| export { sqlite3Worker1Promiser }; |
-3
| import { default as sqlite3InitModule } from './sqlite-wasm/jswasm/sqlite3-node.mjs'; | ||
| export default sqlite3InitModule; |
| /* | ||
| 2022-09-16 | ||
| The author disclaims copyright to this source code. In place of a | ||
| legal notice, here is a blessing: | ||
| * May you do good and not evil. | ||
| * May you find forgiveness for yourself and forgive others. | ||
| * May you share freely, never taking more than you give. | ||
| *********************************************************************** | ||
| A Worker which manages asynchronous OPFS handles on behalf of a | ||
| synchronous API which controls it via a combination of Worker | ||
| messages, SharedArrayBuffer, and Atomics. It is the asynchronous | ||
| counterpart of the API defined in sqlite3-vfs-opfs.js. | ||
| Highly indebted to: | ||
| https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/OriginPrivateFileSystemVFS.js | ||
| for demonstrating how to use the OPFS APIs. | ||
| This file is to be loaded as a Worker. It does not have any direct | ||
| access to the sqlite3 JS/WASM bits, so any bits which it needs (most | ||
| notably SQLITE_xxx integer codes) have to be imported into it via an | ||
| initialization process. | ||
| This file represents an implementation detail of a larger piece of | ||
| code, and not a public interface. Its details may change at any time | ||
| and are not intended to be used by any client-level code. | ||
| 2022-11-27: Chrome v108 changes some async methods to synchronous, as | ||
| documented at: | ||
| https://developer.chrome.com/blog/sync-methods-for-accesshandles/ | ||
| Firefox v111 and Safari 16.4, both released in March 2023, also | ||
| include this. | ||
| We cannot change to the sync forms at this point without breaking | ||
| clients who use Chrome v104-ish or higher. truncate(), getSize(), | ||
| flush(), and close() are now (as of v108) synchronous. Calling them | ||
| with an "await", as we have to for the async forms, is still legal | ||
| with the sync forms but is superfluous. Calling the async forms with | ||
| theFunc().then(...) is not compatible with the change to | ||
| synchronous, but we do do not use those APIs that way. i.e. we don't | ||
| _need_ to change anything for this, but at some point (after Chrome | ||
| versions (approximately) 104-107 are extinct) should change our | ||
| usage of those methods to remove the "await". | ||
| */ | ||
| 'use strict'; | ||
| const wPost = (type, ...args) => postMessage({ type, payload: args }); | ||
| const installAsyncProxy = function () { | ||
| const toss = function (...args) { | ||
| throw new Error(args.join(' ')); | ||
| }; | ||
| if (globalThis.window === globalThis) { | ||
| toss( | ||
| 'This code cannot run from the main thread.', | ||
| 'Load it as a Worker from a separate Worker.', | ||
| ); | ||
| } else if (!navigator?.storage?.getDirectory) { | ||
| toss('This API requires navigator.storage.getDirectory.'); | ||
| } | ||
| const state = Object.create(null); | ||
| state.verbose = 1; | ||
| const loggers = { | ||
| 0: console.error.bind(console), | ||
| 1: console.warn.bind(console), | ||
| 2: console.log.bind(console), | ||
| }; | ||
| const logImpl = (level, ...args) => { | ||
| if (state.verbose > level) loggers[level]('OPFS asyncer:', ...args); | ||
| }; | ||
| const log = (...args) => logImpl(2, ...args); | ||
| const warn = (...args) => logImpl(1, ...args); | ||
| const error = (...args) => logImpl(0, ...args); | ||
| const __openFiles = Object.create(null); | ||
| const __implicitLocks = new Set(); | ||
| const getResolvedPath = function (filename, splitIt) { | ||
| const p = new URL(filename, 'file://irrelevant').pathname; | ||
| return splitIt ? p.split('/').filter((v) => !!v) : p; | ||
| }; | ||
| const getDirForFilename = async function f(absFilename, createDirs = false) { | ||
| const path = getResolvedPath(absFilename, true); | ||
| const filename = path.pop(); | ||
| let dh = state.rootDir; | ||
| for (const dirName of path) { | ||
| if (dirName) { | ||
| dh = await dh.getDirectoryHandle(dirName, { create: !!createDirs }); | ||
| } | ||
| } | ||
| return [dh, filename]; | ||
| }; | ||
| const closeSyncHandle = async (fh) => { | ||
| if (fh.syncHandle) { | ||
| log('Closing sync handle for', fh.filenameAbs); | ||
| const h = fh.syncHandle; | ||
| delete fh.syncHandle; | ||
| delete fh.xLock; | ||
| __implicitLocks.delete(fh.fid); | ||
| return h.close(); | ||
| } | ||
| }; | ||
| const closeSyncHandleNoThrow = async (fh) => { | ||
| try { | ||
| await closeSyncHandle(fh); | ||
| } catch (e) { | ||
| warn('closeSyncHandleNoThrow() ignoring:', e, fh); | ||
| } | ||
| }; | ||
| const releaseImplicitLocks = async () => { | ||
| if (__implicitLocks.size) { | ||
| for (const fid of __implicitLocks) { | ||
| const fh = __openFiles[fid]; | ||
| await closeSyncHandleNoThrow(fh); | ||
| log('Auto-unlocked', fid, fh.filenameAbs); | ||
| } | ||
| } | ||
| }; | ||
| const releaseImplicitLock = async (fh) => { | ||
| if (fh.releaseImplicitLocks && __implicitLocks.has(fh.fid)) { | ||
| return closeSyncHandleNoThrow(fh); | ||
| } | ||
| }; | ||
| class GetSyncHandleError extends Error { | ||
| constructor(errorObject, ...msg) { | ||
| super( | ||
| [...msg, ': ' + errorObject.name + ':', errorObject.message].join(' '), | ||
| { | ||
| cause: errorObject, | ||
| }, | ||
| ); | ||
| this.name = 'GetSyncHandleError'; | ||
| } | ||
| } | ||
| GetSyncHandleError.convertRc = (e, rc) => { | ||
| if (e instanceof GetSyncHandleError) { | ||
| if ( | ||
| e.cause.name === 'NoModificationAllowedError' || | ||
| (e.cause.name === 'DOMException' && | ||
| 0 === e.cause.message.indexOf('Access Handles cannot')) | ||
| ) { | ||
| return state.sq3Codes.SQLITE_BUSY; | ||
| } else if ('NotFoundError' === e.cause.name) { | ||
| return state.sq3Codes.SQLITE_CANTOPEN; | ||
| } | ||
| } else if ('NotFoundError' === e?.name) { | ||
| return state.sq3Codes.SQLITE_CANTOPEN; | ||
| } | ||
| return rc; | ||
| }; | ||
| const getSyncHandle = async (fh, opName) => { | ||
| if (!fh.syncHandle) { | ||
| const t = performance.now(); | ||
| log('Acquiring sync handle for', fh.filenameAbs); | ||
| const maxTries = 6, | ||
| msBase = state.asyncIdleWaitTime * 2; | ||
| let i = 1, | ||
| ms = msBase; | ||
| for (; true; ms = msBase * ++i) { | ||
| try { | ||
| fh.syncHandle = await fh.fileHandle.createSyncAccessHandle(); | ||
| break; | ||
| } catch (e) { | ||
| if (i === maxTries) { | ||
| throw new GetSyncHandleError( | ||
| e, | ||
| 'Error getting sync handle for', | ||
| opName + '().', | ||
| maxTries, | ||
| 'attempts failed.', | ||
| fh.filenameAbs, | ||
| ); | ||
| } | ||
| warn( | ||
| 'Error getting sync handle for', | ||
| opName + '(). Waiting', | ||
| ms, | ||
| 'ms and trying again.', | ||
| fh.filenameAbs, | ||
| e, | ||
| ); | ||
| Atomics.wait(state.sabOPView, state.opIds.retry, 0, ms); | ||
| } | ||
| } | ||
| log( | ||
| 'Got', | ||
| opName + '() sync handle for', | ||
| fh.filenameAbs, | ||
| 'in', | ||
| performance.now() - t, | ||
| 'ms', | ||
| ); | ||
| if (!fh.xLock) { | ||
| __implicitLocks.add(fh.fid); | ||
| log( | ||
| 'Acquired implicit lock for', | ||
| opName + '()', | ||
| fh.fid, | ||
| fh.filenameAbs, | ||
| ); | ||
| } | ||
| } | ||
| return fh.syncHandle; | ||
| }; | ||
| const storeAndNotify = (opName, value) => { | ||
| log(opName + '() => notify(', value, ')'); | ||
| Atomics.store(state.sabOPView, state.opIds.rc, value); | ||
| Atomics.notify(state.sabOPView, state.opIds.rc); | ||
| }; | ||
| const affirmNotRO = function (opName, fh) { | ||
| if (fh.readOnly) toss(opName + '(): File is read-only: ' + fh.filenameAbs); | ||
| }; | ||
| let flagAsyncShutdown = false; | ||
| const vfsAsyncImpls = { | ||
| 'opfs-async-shutdown': async () => { | ||
| flagAsyncShutdown = true; | ||
| storeAndNotify('opfs-async-shutdown', 0); | ||
| }, | ||
| mkdir: async (dirname) => { | ||
| let rc = 0; | ||
| try { | ||
| await getDirForFilename(dirname + '/filepart', true); | ||
| } catch (e) { | ||
| state.s11n.storeException(2, e); | ||
| rc = state.sq3Codes.SQLITE_IOERR; | ||
| } | ||
| storeAndNotify('mkdir', rc); | ||
| }, | ||
| xAccess: async (filename) => { | ||
| let rc = 0; | ||
| try { | ||
| const [dh, fn] = await getDirForFilename(filename); | ||
| await dh.getFileHandle(fn); | ||
| } catch (e) { | ||
| state.s11n.storeException(2, e); | ||
| rc = state.sq3Codes.SQLITE_IOERR; | ||
| } | ||
| storeAndNotify('xAccess', rc); | ||
| }, | ||
| xClose: async function (fid) { | ||
| const opName = 'xClose'; | ||
| __implicitLocks.delete(fid); | ||
| const fh = __openFiles[fid]; | ||
| let rc = 0; | ||
| if (fh) { | ||
| delete __openFiles[fid]; | ||
| await closeSyncHandle(fh); | ||
| if (fh.deleteOnClose) { | ||
| try { | ||
| await fh.dirHandle.removeEntry(fh.filenamePart); | ||
| } catch (e) { | ||
| warn('Ignoring dirHandle.removeEntry() failure of', fh, e); | ||
| } | ||
| } | ||
| } else { | ||
| state.s11n.serialize(); | ||
| rc = state.sq3Codes.SQLITE_NOTFOUND; | ||
| } | ||
| storeAndNotify(opName, rc); | ||
| }, | ||
| xDelete: async function (...args) { | ||
| const rc = await vfsAsyncImpls.xDeleteNoWait(...args); | ||
| storeAndNotify('xDelete', rc); | ||
| }, | ||
| xDeleteNoWait: async function (filename, syncDir = 0, recursive = false) { | ||
| let rc = 0; | ||
| try { | ||
| while (filename) { | ||
| const [hDir, filenamePart] = await getDirForFilename(filename, false); | ||
| if (!filenamePart) break; | ||
| await hDir.removeEntry(filenamePart, { recursive }); | ||
| if (0x1234 !== syncDir) break; | ||
| recursive = false; | ||
| filename = getResolvedPath(filename, true); | ||
| filename.pop(); | ||
| filename = filename.join('/'); | ||
| } | ||
| } catch (e) { | ||
| state.s11n.storeException(2, e); | ||
| rc = state.sq3Codes.SQLITE_IOERR_DELETE; | ||
| } | ||
| return rc; | ||
| }, | ||
| xFileSize: async function (fid) { | ||
| const fh = __openFiles[fid]; | ||
| let rc = 0; | ||
| try { | ||
| const sz = await (await getSyncHandle(fh, 'xFileSize')).getSize(); | ||
| state.s11n.serialize(Number(sz)); | ||
| } catch (e) { | ||
| state.s11n.storeException(1, e); | ||
| rc = GetSyncHandleError.convertRc(e, state.sq3Codes.SQLITE_IOERR); | ||
| } | ||
| await releaseImplicitLock(fh); | ||
| storeAndNotify('xFileSize', rc); | ||
| }, | ||
| xLock: async function (fid, lockType) { | ||
| const fh = __openFiles[fid]; | ||
| let rc = 0; | ||
| const oldLockType = fh.xLock; | ||
| fh.xLock = lockType; | ||
| if (!fh.syncHandle) { | ||
| try { | ||
| await getSyncHandle(fh, 'xLock'); | ||
| __implicitLocks.delete(fid); | ||
| } catch (e) { | ||
| state.s11n.storeException(1, e); | ||
| rc = GetSyncHandleError.convertRc( | ||
| e, | ||
| state.sq3Codes.SQLITE_IOERR_LOCK, | ||
| ); | ||
| fh.xLock = oldLockType; | ||
| } | ||
| } | ||
| storeAndNotify('xLock', rc); | ||
| }, | ||
| xOpen: async function (fid, filename, flags, opfsFlags) { | ||
| const opName = 'xOpen'; | ||
| const create = state.sq3Codes.SQLITE_OPEN_CREATE & flags; | ||
| try { | ||
| let hDir, filenamePart; | ||
| try { | ||
| [hDir, filenamePart] = await getDirForFilename(filename, !!create); | ||
| } catch (e) { | ||
| state.s11n.storeException(1, e); | ||
| storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND); | ||
| return; | ||
| } | ||
| if (state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN & opfsFlags) { | ||
| try { | ||
| await hDir.removeEntry(filenamePart); | ||
| } catch (e) {} | ||
| } | ||
| const hFile = await hDir.getFileHandle(filenamePart, { create }); | ||
| const fh = Object.assign(Object.create(null), { | ||
| fid: fid, | ||
| filenameAbs: filename, | ||
| filenamePart: filenamePart, | ||
| dirHandle: hDir, | ||
| fileHandle: hFile, | ||
| sabView: state.sabFileBufView, | ||
| readOnly: !create && !!(state.sq3Codes.SQLITE_OPEN_READONLY & flags), | ||
| deleteOnClose: !!(state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags), | ||
| }); | ||
| fh.releaseImplicitLocks = | ||
| opfsFlags & state.opfsFlags.OPFS_UNLOCK_ASAP || | ||
| state.opfsFlags.defaultUnlockAsap; | ||
| __openFiles[fid] = fh; | ||
| storeAndNotify(opName, 0); | ||
| } catch (e) { | ||
| error(opName, e); | ||
| state.s11n.storeException(1, e); | ||
| storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR); | ||
| } | ||
| }, | ||
| xRead: async function (fid, n, offset64) { | ||
| let rc = 0, | ||
| nRead; | ||
| const fh = __openFiles[fid]; | ||
| try { | ||
| nRead = (await getSyncHandle(fh, 'xRead')).read( | ||
| fh.sabView.subarray(0, n), | ||
| { at: Number(offset64) }, | ||
| ); | ||
| if (nRead < n) { | ||
| fh.sabView.fill(0, nRead, n); | ||
| rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ; | ||
| } | ||
| } catch (e) { | ||
| error('xRead() failed', e, fh); | ||
| state.s11n.storeException(1, e); | ||
| rc = GetSyncHandleError.convertRc(e, state.sq3Codes.SQLITE_IOERR_READ); | ||
| } | ||
| await releaseImplicitLock(fh); | ||
| storeAndNotify('xRead', rc); | ||
| }, | ||
| xSync: async function (fid, flags) { | ||
| const fh = __openFiles[fid]; | ||
| let rc = 0; | ||
| if (!fh.readOnly && fh.syncHandle) { | ||
| try { | ||
| await fh.syncHandle.flush(); | ||
| } catch (e) { | ||
| state.s11n.storeException(2, e); | ||
| rc = state.sq3Codes.SQLITE_IOERR_FSYNC; | ||
| } | ||
| } | ||
| storeAndNotify('xSync', rc); | ||
| }, | ||
| xTruncate: async function (fid, size) { | ||
| let rc = 0; | ||
| const fh = __openFiles[fid]; | ||
| try { | ||
| affirmNotRO('xTruncate', fh); | ||
| await (await getSyncHandle(fh, 'xTruncate')).truncate(size); | ||
| } catch (e) { | ||
| error('xTruncate():', e, fh); | ||
| state.s11n.storeException(2, e); | ||
| rc = GetSyncHandleError.convertRc( | ||
| e, | ||
| state.sq3Codes.SQLITE_IOERR_TRUNCATE, | ||
| ); | ||
| } | ||
| await releaseImplicitLock(fh); | ||
| storeAndNotify('xTruncate', rc); | ||
| }, | ||
| xUnlock: async function (fid, lockType) { | ||
| let rc = 0; | ||
| const fh = __openFiles[fid]; | ||
| if (fh.syncHandle && state.sq3Codes.SQLITE_LOCK_NONE === lockType) { | ||
| try { | ||
| await closeSyncHandle(fh); | ||
| } catch (e) { | ||
| state.s11n.storeException(1, e); | ||
| rc = state.sq3Codes.SQLITE_IOERR_UNLOCK; | ||
| } | ||
| } | ||
| storeAndNotify('xUnlock', rc); | ||
| }, | ||
| xWrite: async function (fid, n, offset64) { | ||
| let rc; | ||
| const fh = __openFiles[fid]; | ||
| try { | ||
| affirmNotRO('xWrite', fh); | ||
| rc = | ||
| n === | ||
| (await getSyncHandle(fh, 'xWrite')).write(fh.sabView.subarray(0, n), { | ||
| at: Number(offset64), | ||
| }) | ||
| ? 0 | ||
| : state.sq3Codes.SQLITE_IOERR_WRITE; | ||
| } catch (e) { | ||
| error('xWrite():', e, fh); | ||
| state.s11n.storeException(1, e); | ||
| rc = GetSyncHandleError.convertRc(e, state.sq3Codes.SQLITE_IOERR_WRITE); | ||
| } | ||
| await releaseImplicitLock(fh); | ||
| storeAndNotify('xWrite', rc); | ||
| }, | ||
| }; | ||
| const initS11n = () => { | ||
| if (state.s11n) return state.s11n; | ||
| const textDecoder = new TextDecoder(), | ||
| textEncoder = new TextEncoder('utf-8'), | ||
| viewU8 = new Uint8Array( | ||
| state.sabIO, | ||
| state.sabS11nOffset, | ||
| state.sabS11nSize, | ||
| ), | ||
| viewDV = new DataView( | ||
| state.sabIO, | ||
| state.sabS11nOffset, | ||
| state.sabS11nSize, | ||
| ); | ||
| state.s11n = Object.create(null); | ||
| const TypeIds = Object.create(null); | ||
| TypeIds.number = { | ||
| id: 1, | ||
| size: 8, | ||
| getter: 'getFloat64', | ||
| setter: 'setFloat64', | ||
| }; | ||
| TypeIds.bigint = { | ||
| id: 2, | ||
| size: 8, | ||
| getter: 'getBigInt64', | ||
| setter: 'setBigInt64', | ||
| }; | ||
| TypeIds.boolean = { | ||
| id: 3, | ||
| size: 4, | ||
| getter: 'getInt32', | ||
| setter: 'setInt32', | ||
| }; | ||
| TypeIds.string = { id: 4 }; | ||
| const getTypeId = (v) => | ||
| TypeIds[typeof v] || | ||
| toss('Maintenance required: this value type cannot be serialized.', v); | ||
| const getTypeIdById = (tid) => { | ||
| switch (tid) { | ||
| case TypeIds.number.id: | ||
| return TypeIds.number; | ||
| case TypeIds.bigint.id: | ||
| return TypeIds.bigint; | ||
| case TypeIds.boolean.id: | ||
| return TypeIds.boolean; | ||
| case TypeIds.string.id: | ||
| return TypeIds.string; | ||
| default: | ||
| toss('Invalid type ID:', tid); | ||
| } | ||
| }; | ||
| state.s11n.deserialize = function (clear = false) { | ||
| const argc = viewU8[0]; | ||
| const rc = argc ? [] : null; | ||
| if (argc) { | ||
| const typeIds = []; | ||
| let offset = 1, | ||
| i, | ||
| n, | ||
| v; | ||
| for (i = 0; i < argc; ++i, ++offset) { | ||
| typeIds.push(getTypeIdById(viewU8[offset])); | ||
| } | ||
| for (i = 0; i < argc; ++i) { | ||
| const t = typeIds[i]; | ||
| if (t.getter) { | ||
| v = viewDV[t.getter](offset, state.littleEndian); | ||
| offset += t.size; | ||
| } else { | ||
| n = viewDV.getInt32(offset, state.littleEndian); | ||
| offset += 4; | ||
| v = textDecoder.decode(viewU8.slice(offset, offset + n)); | ||
| offset += n; | ||
| } | ||
| rc.push(v); | ||
| } | ||
| } | ||
| if (clear) viewU8[0] = 0; | ||
| return rc; | ||
| }; | ||
| state.s11n.serialize = function (...args) { | ||
| if (args.length) { | ||
| const typeIds = []; | ||
| let i = 0, | ||
| offset = 1; | ||
| viewU8[0] = args.length & 0xff; | ||
| for (; i < args.length; ++i, ++offset) { | ||
| typeIds.push(getTypeId(args[i])); | ||
| viewU8[offset] = typeIds[i].id; | ||
| } | ||
| for (i = 0; i < args.length; ++i) { | ||
| const t = typeIds[i]; | ||
| if (t.setter) { | ||
| viewDV[t.setter](offset, args[i], state.littleEndian); | ||
| offset += t.size; | ||
| } else { | ||
| const s = textEncoder.encode(args[i]); | ||
| viewDV.setInt32(offset, s.byteLength, state.littleEndian); | ||
| offset += 4; | ||
| viewU8.set(s, offset); | ||
| offset += s.byteLength; | ||
| } | ||
| } | ||
| } else { | ||
| viewU8[0] = 0; | ||
| } | ||
| }; | ||
| state.s11n.storeException = state.asyncS11nExceptions | ||
| ? (priority, e) => { | ||
| if (priority <= state.asyncS11nExceptions) { | ||
| state.s11n.serialize([e.name, ': ', e.message].join('')); | ||
| } | ||
| } | ||
| : () => {}; | ||
| return state.s11n; | ||
| }; | ||
| const waitLoop = async function f() { | ||
| const opHandlers = Object.create(null); | ||
| for (let k of Object.keys(state.opIds)) { | ||
| const vi = vfsAsyncImpls[k]; | ||
| if (!vi) continue; | ||
| const o = Object.create(null); | ||
| opHandlers[state.opIds[k]] = o; | ||
| o.key = k; | ||
| o.f = vi; | ||
| } | ||
| while (!flagAsyncShutdown) { | ||
| try { | ||
| if ( | ||
| 'not-equal' !== | ||
| Atomics.wait( | ||
| state.sabOPView, | ||
| state.opIds.whichOp, | ||
| 0, | ||
| state.asyncIdleWaitTime, | ||
| ) | ||
| ) { | ||
| await releaseImplicitLocks(); | ||
| continue; | ||
| } | ||
| const opId = Atomics.load(state.sabOPView, state.opIds.whichOp); | ||
| Atomics.store(state.sabOPView, state.opIds.whichOp, 0); | ||
| const hnd = | ||
| opHandlers[opId] ?? toss('No waitLoop handler for whichOp #', opId); | ||
| const args = state.s11n.deserialize(true) || []; | ||
| if (hnd.f) await hnd.f(...args); | ||
| else error('Missing callback for opId', opId); | ||
| } catch (e) { | ||
| error('in waitLoop():', e); | ||
| } | ||
| } | ||
| }; | ||
| navigator.storage | ||
| .getDirectory() | ||
| .then(function (d) { | ||
| state.rootDir = d; | ||
| globalThis.onmessage = function ({ data }) { | ||
| switch (data.type) { | ||
| case 'opfs-async-init': { | ||
| const opt = data.args; | ||
| for (const k in opt) state[k] = opt[k]; | ||
| state.verbose = opt.verbose ?? 1; | ||
| state.sabOPView = new Int32Array(state.sabOP); | ||
| state.sabFileBufView = new Uint8Array( | ||
| state.sabIO, | ||
| 0, | ||
| state.fileBufferSize, | ||
| ); | ||
| state.sabS11nView = new Uint8Array( | ||
| state.sabIO, | ||
| state.sabS11nOffset, | ||
| state.sabS11nSize, | ||
| ); | ||
| Object.keys(vfsAsyncImpls).forEach((k) => { | ||
| if (!Number.isFinite(state.opIds[k])) { | ||
| toss('Maintenance required: missing state.opIds[', k, ']'); | ||
| } | ||
| }); | ||
| initS11n(); | ||
| log('init state', state); | ||
| wPost('opfs-async-inited'); | ||
| waitLoop(); | ||
| break; | ||
| } | ||
| case 'opfs-async-restart': | ||
| if (flagAsyncShutdown) { | ||
| warn( | ||
| 'Restarting after opfs-async-shutdown. Might or might not work.', | ||
| ); | ||
| flagAsyncShutdown = false; | ||
| waitLoop(); | ||
| } | ||
| break; | ||
| } | ||
| }; | ||
| wPost('opfs-async-loaded'); | ||
| }) | ||
| .catch((e) => error('error initializing OPFS asyncer:', e)); | ||
| }; | ||
| if (!globalThis.SharedArrayBuffer) { | ||
| wPost( | ||
| 'opfs-unavailable', | ||
| 'Missing SharedArrayBuffer API.', | ||
| 'The server must emit the COOP/COEP response headers to enable that.', | ||
| ); | ||
| } else if (!globalThis.Atomics) { | ||
| wPost( | ||
| 'opfs-unavailable', | ||
| 'Missing Atomics API.', | ||
| 'The server must emit the COOP/COEP response headers to enable that.', | ||
| ); | ||
| } else if ( | ||
| !globalThis.FileSystemHandle || | ||
| !globalThis.FileSystemDirectoryHandle || | ||
| !globalThis.FileSystemFileHandle || | ||
| !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle || | ||
| !navigator?.storage?.getDirectory | ||
| ) { | ||
| wPost('opfs-unavailable', 'Missing required OPFS APIs.'); | ||
| } else { | ||
| installAsyncProxy(); | ||
| } |
| /* | ||
| 2022-05-23 | ||
| The author disclaims copyright to this source code. In place of a | ||
| legal notice, here is a blessing: | ||
| * May you do good and not evil. | ||
| * May you find forgiveness for yourself and forgive others. | ||
| * May you share freely, never taking more than you give. | ||
| *********************************************************************** | ||
| This is a JS Worker file for the main sqlite3 api. It loads | ||
| sqlite3.js, initializes the module, and postMessage()'s a message | ||
| after the module is initialized: | ||
| {type: 'sqlite3-api', result: 'worker1-ready'} | ||
| This seemingly superfluous level of indirection is necessary when | ||
| loading sqlite3.js via a Worker. Instantiating a worker with new | ||
| Worker("sqlite.js") will not (cannot) call sqlite3InitModule() to | ||
| initialize the module due to a timing/order-of-operations conflict | ||
| (and that symbol is not exported in a way that a Worker loading it | ||
| that way can see it). Thus JS code wanting to load the sqlite3 | ||
| Worker-specific API needs to pass _this_ file (or equivalent) to the | ||
| Worker constructor and then listen for an event in the form shown | ||
| above in order to know when the module has completed initialization. | ||
| This file accepts a URL arguments to adjust how it loads sqlite3.js: | ||
| - `sqlite3.dir`, if set, treats the given directory name as the | ||
| directory from which `sqlite3.js` will be loaded. | ||
| */ | ||
| import { default as sqlite3InitModule } from './sqlite3-bundler-friendly.mjs'; | ||
| sqlite3InitModule().then((sqlite3) => sqlite3.initWorker1API()); |
| /* | ||
| 2022-08-24 | ||
| The author disclaims copyright to this source code. In place of a | ||
| legal notice, here is a blessing: | ||
| * May you do good and not evil. | ||
| * May you find forgiveness for yourself and forgive others. | ||
| * May you share freely, never taking more than you give. | ||
| *********************************************************************** | ||
| This file implements a Promise-based proxy for the sqlite3 Worker | ||
| API #1. It is intended to be included either from the main thread or | ||
| a Worker, but only if (A) the environment supports nested Workers | ||
| and (B) it's _not_ a Worker which loads the sqlite3 WASM/JS | ||
| module. This file's features will load that module and provide a | ||
| slightly simpler client-side interface than the slightly-lower-level | ||
| Worker API does. | ||
| This script necessarily exposes one global symbol, but clients may | ||
| freely `delete` that symbol after calling it. | ||
| */ | ||
| 'use strict'; | ||
| globalThis.sqlite3Worker1Promiser = function callee( | ||
| config = callee.defaultConfig, | ||
| ) { | ||
| if (1 === arguments.length && 'function' === typeof arguments[0]) { | ||
| const f = config; | ||
| config = Object.assign(Object.create(null), callee.defaultConfig); | ||
| config.onready = f; | ||
| } else { | ||
| config = Object.assign(Object.create(null), callee.defaultConfig, config); | ||
| } | ||
| const handlerMap = Object.create(null); | ||
| const noop = function () {}; | ||
| const err = config.onerror || noop; | ||
| const debug = config.debug || noop; | ||
| const idTypeMap = config.generateMessageId ? undefined : Object.create(null); | ||
| const genMsgId = | ||
| config.generateMessageId || | ||
| function (msg) { | ||
| return ( | ||
| msg.type + '#' + (idTypeMap[msg.type] = (idTypeMap[msg.type] || 0) + 1) | ||
| ); | ||
| }; | ||
| const toss = (...args) => { | ||
| throw new Error(args.join(' ')); | ||
| }; | ||
| if (!config.worker) config.worker = callee.defaultConfig.worker; | ||
| if ('function' === typeof config.worker) config.worker = config.worker(); | ||
| let dbId; | ||
| let promiserFunc; | ||
| config.worker.onmessage = function (ev) { | ||
| ev = ev.data; | ||
| debug('worker1.onmessage', ev); | ||
| let msgHandler = handlerMap[ev.messageId]; | ||
| if (!msgHandler) { | ||
| if (ev && 'sqlite3-api' === ev.type && 'worker1-ready' === ev.result) { | ||
| if (config.onready) config.onready(promiserFunc); | ||
| return; | ||
| } | ||
| msgHandler = handlerMap[ev.type]; | ||
| if (msgHandler && msgHandler.onrow) { | ||
| msgHandler.onrow(ev); | ||
| return; | ||
| } | ||
| if (config.onunhandled) config.onunhandled(arguments[0]); | ||
| else err('sqlite3Worker1Promiser() unhandled worker message:', ev); | ||
| return; | ||
| } | ||
| delete handlerMap[ev.messageId]; | ||
| switch (ev.type) { | ||
| case 'error': | ||
| msgHandler.reject(ev); | ||
| return; | ||
| case 'open': | ||
| if (!dbId) dbId = ev.dbId; | ||
| break; | ||
| case 'close': | ||
| if (ev.dbId === dbId) dbId = undefined; | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
| try { | ||
| msgHandler.resolve(ev); | ||
| } catch (e) { | ||
| msgHandler.reject(e); | ||
| } | ||
| }; | ||
| return (promiserFunc = function () { | ||
| let msg; | ||
| if (1 === arguments.length) { | ||
| msg = arguments[0]; | ||
| } else if (2 === arguments.length) { | ||
| msg = Object.create(null); | ||
| msg.type = arguments[0]; | ||
| msg.args = arguments[1]; | ||
| msg.dbId = msg.args.dbId; | ||
| } else { | ||
| toss('Invalid arguments for sqlite3Worker1Promiser()-created factory.'); | ||
| } | ||
| if (!msg.dbId && msg.type !== 'open') msg.dbId = dbId; | ||
| msg.messageId = genMsgId(msg); | ||
| msg.departureTime = performance.now(); | ||
| const proxy = Object.create(null); | ||
| proxy.message = msg; | ||
| let rowCallbackId; | ||
| if ('exec' === msg.type && msg.args) { | ||
| if ('function' === typeof msg.args.callback) { | ||
| rowCallbackId = msg.messageId + ':row'; | ||
| proxy.onrow = msg.args.callback; | ||
| msg.args.callback = rowCallbackId; | ||
| handlerMap[rowCallbackId] = proxy; | ||
| } else if ('string' === typeof msg.args.callback) { | ||
| toss( | ||
| 'exec callback may not be a string when using the Promise interface.', | ||
| ); | ||
| } | ||
| } | ||
| let p = new Promise(function (resolve, reject) { | ||
| proxy.resolve = resolve; | ||
| proxy.reject = reject; | ||
| handlerMap[msg.messageId] = proxy; | ||
| debug( | ||
| 'Posting', | ||
| msg.type, | ||
| 'message to Worker dbId=' + (dbId || 'default') + ':', | ||
| msg, | ||
| ); | ||
| config.worker.postMessage(msg); | ||
| }); | ||
| if (rowCallbackId) p = p.finally(() => delete handlerMap[rowCallbackId]); | ||
| return p; | ||
| }); | ||
| }; | ||
| globalThis.sqlite3Worker1Promiser.defaultConfig = { | ||
| worker: function () { | ||
| return new Worker( | ||
| new URL('sqlite3-worker1-bundler-friendly.mjs', import.meta.url), | ||
| { | ||
| type: 'module', | ||
| }, | ||
| ); | ||
| }, | ||
| onerror: (...args) => console.error('worker1 promiser error', ...args), | ||
| }; | ||
| globalThis.sqlite3Worker1Promiser.v2 = function callee( | ||
| config = callee.defaultConfig, | ||
| ) { | ||
| let oldFunc; | ||
| if ('function' == typeof config) { | ||
| oldFunc = config; | ||
| config = {}; | ||
| } else if ('function' === typeof config?.onready) { | ||
| oldFunc = config.onready; | ||
| delete config.onready; | ||
| } | ||
| const promiseProxy = Object.create(null); | ||
| config = Object.assign(config || Object.create(null), { | ||
| onready: async function (func) { | ||
| try { | ||
| if (oldFunc) await oldFunc(func); | ||
| promiseProxy.resolve(func); | ||
| } catch (e) { | ||
| promiseProxy.reject(e); | ||
| } | ||
| }, | ||
| }); | ||
| const p = new Promise(function (resolve, reject) { | ||
| promiseProxy.resolve = resolve; | ||
| promiseProxy.reject = reject; | ||
| }); | ||
| try { | ||
| this.original(config); | ||
| } catch (e) { | ||
| promiseProxy.reject(e); | ||
| } | ||
| return p; | ||
| }.bind({ | ||
| original: sqlite3Worker1Promiser, | ||
| }); | ||
| globalThis.sqlite3Worker1Promiser.v2.defaultConfig = | ||
| globalThis.sqlite3Worker1Promiser.defaultConfig; | ||
| export default sqlite3Worker1Promiser.v2; |
| /* | ||
| 2022-08-24 | ||
| The author disclaims copyright to this source code. In place of a | ||
| legal notice, here is a blessing: | ||
| * May you do good and not evil. | ||
| * May you find forgiveness for yourself and forgive others. | ||
| * May you share freely, never taking more than you give. | ||
| *********************************************************************** | ||
| This file implements a Promise-based proxy for the sqlite3 Worker | ||
| API #1. It is intended to be included either from the main thread or | ||
| a Worker, but only if (A) the environment supports nested Workers | ||
| and (B) it's _not_ a Worker which loads the sqlite3 WASM/JS | ||
| module. This file's features will load that module and provide a | ||
| slightly simpler client-side interface than the slightly-lower-level | ||
| Worker API does. | ||
| This script necessarily exposes one global symbol, but clients may | ||
| freely `delete` that symbol after calling it. | ||
| */ | ||
| 'use strict'; | ||
| globalThis.sqlite3Worker1Promiser = function callee( | ||
| config = callee.defaultConfig, | ||
| ) { | ||
| if (1 === arguments.length && 'function' === typeof arguments[0]) { | ||
| const f = config; | ||
| config = Object.assign(Object.create(null), callee.defaultConfig); | ||
| config.onready = f; | ||
| } else { | ||
| config = Object.assign(Object.create(null), callee.defaultConfig, config); | ||
| } | ||
| const handlerMap = Object.create(null); | ||
| const noop = function () {}; | ||
| const err = config.onerror || noop; | ||
| const debug = config.debug || noop; | ||
| const idTypeMap = config.generateMessageId ? undefined : Object.create(null); | ||
| const genMsgId = | ||
| config.generateMessageId || | ||
| function (msg) { | ||
| return ( | ||
| msg.type + '#' + (idTypeMap[msg.type] = (idTypeMap[msg.type] || 0) + 1) | ||
| ); | ||
| }; | ||
| const toss = (...args) => { | ||
| throw new Error(args.join(' ')); | ||
| }; | ||
| if (!config.worker) config.worker = callee.defaultConfig.worker; | ||
| if ('function' === typeof config.worker) config.worker = config.worker(); | ||
| let dbId; | ||
| let promiserFunc; | ||
| config.worker.onmessage = function (ev) { | ||
| ev = ev.data; | ||
| debug('worker1.onmessage', ev); | ||
| let msgHandler = handlerMap[ev.messageId]; | ||
| if (!msgHandler) { | ||
| if (ev && 'sqlite3-api' === ev.type && 'worker1-ready' === ev.result) { | ||
| if (config.onready) config.onready(promiserFunc); | ||
| return; | ||
| } | ||
| msgHandler = handlerMap[ev.type]; | ||
| if (msgHandler && msgHandler.onrow) { | ||
| msgHandler.onrow(ev); | ||
| return; | ||
| } | ||
| if (config.onunhandled) config.onunhandled(arguments[0]); | ||
| else err('sqlite3Worker1Promiser() unhandled worker message:', ev); | ||
| return; | ||
| } | ||
| delete handlerMap[ev.messageId]; | ||
| switch (ev.type) { | ||
| case 'error': | ||
| msgHandler.reject(ev); | ||
| return; | ||
| case 'open': | ||
| if (!dbId) dbId = ev.dbId; | ||
| break; | ||
| case 'close': | ||
| if (ev.dbId === dbId) dbId = undefined; | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
| try { | ||
| msgHandler.resolve(ev); | ||
| } catch (e) { | ||
| msgHandler.reject(e); | ||
| } | ||
| }; | ||
| return (promiserFunc = function () { | ||
| let msg; | ||
| if (1 === arguments.length) { | ||
| msg = arguments[0]; | ||
| } else if (2 === arguments.length) { | ||
| msg = Object.create(null); | ||
| msg.type = arguments[0]; | ||
| msg.args = arguments[1]; | ||
| msg.dbId = msg.args.dbId; | ||
| } else { | ||
| toss('Invalid arguments for sqlite3Worker1Promiser()-created factory.'); | ||
| } | ||
| if (!msg.dbId && msg.type !== 'open') msg.dbId = dbId; | ||
| msg.messageId = genMsgId(msg); | ||
| msg.departureTime = performance.now(); | ||
| const proxy = Object.create(null); | ||
| proxy.message = msg; | ||
| let rowCallbackId; | ||
| if ('exec' === msg.type && msg.args) { | ||
| if ('function' === typeof msg.args.callback) { | ||
| rowCallbackId = msg.messageId + ':row'; | ||
| proxy.onrow = msg.args.callback; | ||
| msg.args.callback = rowCallbackId; | ||
| handlerMap[rowCallbackId] = proxy; | ||
| } else if ('string' === typeof msg.args.callback) { | ||
| toss( | ||
| 'exec callback may not be a string when using the Promise interface.', | ||
| ); | ||
| } | ||
| } | ||
| let p = new Promise(function (resolve, reject) { | ||
| proxy.resolve = resolve; | ||
| proxy.reject = reject; | ||
| handlerMap[msg.messageId] = proxy; | ||
| debug( | ||
| 'Posting', | ||
| msg.type, | ||
| 'message to Worker dbId=' + (dbId || 'default') + ':', | ||
| msg, | ||
| ); | ||
| config.worker.postMessage(msg); | ||
| }); | ||
| if (rowCallbackId) p = p.finally(() => delete handlerMap[rowCallbackId]); | ||
| return p; | ||
| }); | ||
| }; | ||
| globalThis.sqlite3Worker1Promiser.defaultConfig = { | ||
| worker: function () { | ||
| let theJs = 'sqlite3-worker1.js'; | ||
| if (this.currentScript) { | ||
| const src = this.currentScript.src.split('/'); | ||
| src.pop(); | ||
| theJs = src.join('/') + '/' + theJs; | ||
| } else if (globalThis.location) { | ||
| const urlParams = new URL(globalThis.location.href).searchParams; | ||
| if (urlParams.has('sqlite3.dir')) { | ||
| theJs = urlParams.get('sqlite3.dir') + '/' + theJs; | ||
| } | ||
| } | ||
| return new Worker(theJs + globalThis.location.search); | ||
| }.bind({ | ||
| currentScript: globalThis?.document?.currentScript, | ||
| }), | ||
| onerror: (...args) => console.error('worker1 promiser error', ...args), | ||
| }; | ||
| globalThis.sqlite3Worker1Promiser.v2 = function callee( | ||
| config = callee.defaultConfig, | ||
| ) { | ||
| let oldFunc; | ||
| if ('function' == typeof config) { | ||
| oldFunc = config; | ||
| config = {}; | ||
| } else if ('function' === typeof config?.onready) { | ||
| oldFunc = config.onready; | ||
| delete config.onready; | ||
| } | ||
| const promiseProxy = Object.create(null); | ||
| config = Object.assign(config || Object.create(null), { | ||
| onready: async function (func) { | ||
| try { | ||
| if (oldFunc) await oldFunc(func); | ||
| promiseProxy.resolve(func); | ||
| } catch (e) { | ||
| promiseProxy.reject(e); | ||
| } | ||
| }, | ||
| }); | ||
| const p = new Promise(function (resolve, reject) { | ||
| promiseProxy.resolve = resolve; | ||
| promiseProxy.reject = reject; | ||
| }); | ||
| try { | ||
| this.original(config); | ||
| } catch (e) { | ||
| promiseProxy.reject(e); | ||
| } | ||
| return p; | ||
| }.bind({ | ||
| original: sqlite3Worker1Promiser, | ||
| }); | ||
| globalThis.sqlite3Worker1Promiser.v2.defaultConfig = | ||
| globalThis.sqlite3Worker1Promiser.defaultConfig; |
| /* | ||
| 2022-08-24 | ||
| The author disclaims copyright to this source code. In place of a | ||
| legal notice, here is a blessing: | ||
| * May you do good and not evil. | ||
| * May you find forgiveness for yourself and forgive others. | ||
| * May you share freely, never taking more than you give. | ||
| *********************************************************************** | ||
| This file implements a Promise-based proxy for the sqlite3 Worker | ||
| API #1. It is intended to be included either from the main thread or | ||
| a Worker, but only if (A) the environment supports nested Workers | ||
| and (B) it's _not_ a Worker which loads the sqlite3 WASM/JS | ||
| module. This file's features will load that module and provide a | ||
| slightly simpler client-side interface than the slightly-lower-level | ||
| Worker API does. | ||
| This script necessarily exposes one global symbol, but clients may | ||
| freely `delete` that symbol after calling it. | ||
| */ | ||
| 'use strict'; | ||
| globalThis.sqlite3Worker1Promiser = function callee( | ||
| config = callee.defaultConfig, | ||
| ) { | ||
| if (1 === arguments.length && 'function' === typeof arguments[0]) { | ||
| const f = config; | ||
| config = Object.assign(Object.create(null), callee.defaultConfig); | ||
| config.onready = f; | ||
| } else { | ||
| config = Object.assign(Object.create(null), callee.defaultConfig, config); | ||
| } | ||
| const handlerMap = Object.create(null); | ||
| const noop = function () {}; | ||
| const err = config.onerror || noop; | ||
| const debug = config.debug || noop; | ||
| const idTypeMap = config.generateMessageId ? undefined : Object.create(null); | ||
| const genMsgId = | ||
| config.generateMessageId || | ||
| function (msg) { | ||
| return ( | ||
| msg.type + '#' + (idTypeMap[msg.type] = (idTypeMap[msg.type] || 0) + 1) | ||
| ); | ||
| }; | ||
| const toss = (...args) => { | ||
| throw new Error(args.join(' ')); | ||
| }; | ||
| if (!config.worker) config.worker = callee.defaultConfig.worker; | ||
| if ('function' === typeof config.worker) config.worker = config.worker(); | ||
| let dbId; | ||
| let promiserFunc; | ||
| config.worker.onmessage = function (ev) { | ||
| ev = ev.data; | ||
| debug('worker1.onmessage', ev); | ||
| let msgHandler = handlerMap[ev.messageId]; | ||
| if (!msgHandler) { | ||
| if (ev && 'sqlite3-api' === ev.type && 'worker1-ready' === ev.result) { | ||
| if (config.onready) config.onready(promiserFunc); | ||
| return; | ||
| } | ||
| msgHandler = handlerMap[ev.type]; | ||
| if (msgHandler && msgHandler.onrow) { | ||
| msgHandler.onrow(ev); | ||
| return; | ||
| } | ||
| if (config.onunhandled) config.onunhandled(arguments[0]); | ||
| else err('sqlite3Worker1Promiser() unhandled worker message:', ev); | ||
| return; | ||
| } | ||
| delete handlerMap[ev.messageId]; | ||
| switch (ev.type) { | ||
| case 'error': | ||
| msgHandler.reject(ev); | ||
| return; | ||
| case 'open': | ||
| if (!dbId) dbId = ev.dbId; | ||
| break; | ||
| case 'close': | ||
| if (ev.dbId === dbId) dbId = undefined; | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
| try { | ||
| msgHandler.resolve(ev); | ||
| } catch (e) { | ||
| msgHandler.reject(e); | ||
| } | ||
| }; | ||
| return (promiserFunc = function () { | ||
| let msg; | ||
| if (1 === arguments.length) { | ||
| msg = arguments[0]; | ||
| } else if (2 === arguments.length) { | ||
| msg = Object.create(null); | ||
| msg.type = arguments[0]; | ||
| msg.args = arguments[1]; | ||
| msg.dbId = msg.args.dbId; | ||
| } else { | ||
| toss('Invalid arguments for sqlite3Worker1Promiser()-created factory.'); | ||
| } | ||
| if (!msg.dbId && msg.type !== 'open') msg.dbId = dbId; | ||
| msg.messageId = genMsgId(msg); | ||
| msg.departureTime = performance.now(); | ||
| const proxy = Object.create(null); | ||
| proxy.message = msg; | ||
| let rowCallbackId; | ||
| if ('exec' === msg.type && msg.args) { | ||
| if ('function' === typeof msg.args.callback) { | ||
| rowCallbackId = msg.messageId + ':row'; | ||
| proxy.onrow = msg.args.callback; | ||
| msg.args.callback = rowCallbackId; | ||
| handlerMap[rowCallbackId] = proxy; | ||
| } else if ('string' === typeof msg.args.callback) { | ||
| toss( | ||
| 'exec callback may not be a string when using the Promise interface.', | ||
| ); | ||
| } | ||
| } | ||
| let p = new Promise(function (resolve, reject) { | ||
| proxy.resolve = resolve; | ||
| proxy.reject = reject; | ||
| handlerMap[msg.messageId] = proxy; | ||
| debug( | ||
| 'Posting', | ||
| msg.type, | ||
| 'message to Worker dbId=' + (dbId || 'default') + ':', | ||
| msg, | ||
| ); | ||
| config.worker.postMessage(msg); | ||
| }); | ||
| if (rowCallbackId) p = p.finally(() => delete handlerMap[rowCallbackId]); | ||
| return p; | ||
| }); | ||
| }; | ||
| globalThis.sqlite3Worker1Promiser.defaultConfig = { | ||
| worker: function () { | ||
| return new Worker(new URL('sqlite3-worker1.js', import.meta.url)); | ||
| }, | ||
| onerror: (...args) => console.error('worker1 promiser error', ...args), | ||
| }; | ||
| globalThis.sqlite3Worker1Promiser.v2 = function callee( | ||
| config = callee.defaultConfig, | ||
| ) { | ||
| let oldFunc; | ||
| if ('function' == typeof config) { | ||
| oldFunc = config; | ||
| config = {}; | ||
| } else if ('function' === typeof config?.onready) { | ||
| oldFunc = config.onready; | ||
| delete config.onready; | ||
| } | ||
| const promiseProxy = Object.create(null); | ||
| config = Object.assign(config || Object.create(null), { | ||
| onready: async function (func) { | ||
| try { | ||
| if (oldFunc) await oldFunc(func); | ||
| promiseProxy.resolve(func); | ||
| } catch (e) { | ||
| promiseProxy.reject(e); | ||
| } | ||
| }, | ||
| }); | ||
| const p = new Promise(function (resolve, reject) { | ||
| promiseProxy.resolve = resolve; | ||
| promiseProxy.reject = reject; | ||
| }); | ||
| try { | ||
| this.original(config); | ||
| } catch (e) { | ||
| promiseProxy.reject(e); | ||
| } | ||
| return p; | ||
| }.bind({ | ||
| original: sqlite3Worker1Promiser, | ||
| }); | ||
| globalThis.sqlite3Worker1Promiser.v2.defaultConfig = | ||
| globalThis.sqlite3Worker1Promiser.defaultConfig; | ||
| export default sqlite3Worker1Promiser.v2; |
| /* | ||
| 2022-05-23 | ||
| The author disclaims copyright to this source code. In place of a | ||
| legal notice, here is a blessing: | ||
| * May you do good and not evil. | ||
| * May you find forgiveness for yourself and forgive others. | ||
| * May you share freely, never taking more than you give. | ||
| *********************************************************************** | ||
| This is a JS Worker file for the main sqlite3 api. It loads | ||
| sqlite3.js, initializes the module, and postMessage()'s a message | ||
| after the module is initialized: | ||
| {type: 'sqlite3-api', result: 'worker1-ready'} | ||
| This seemingly superfluous level of indirection is necessary when | ||
| loading sqlite3.js via a Worker. Instantiating a worker with new | ||
| Worker("sqlite.js") will not (cannot) call sqlite3InitModule() to | ||
| initialize the module due to a timing/order-of-operations conflict | ||
| (and that symbol is not exported in a way that a Worker loading it | ||
| that way can see it). Thus JS code wanting to load the sqlite3 | ||
| Worker-specific API needs to pass _this_ file (or equivalent) to the | ||
| Worker constructor and then listen for an event in the form shown | ||
| above in order to know when the module has completed initialization. | ||
| This file accepts a URL arguments to adjust how it loads sqlite3.js: | ||
| - `sqlite3.dir`, if set, treats the given directory name as the | ||
| directory from which `sqlite3.js` will be loaded. | ||
| */ | ||
| 'use strict'; | ||
| { | ||
| const urlParams = globalThis.location | ||
| ? new URL(globalThis.location.href).searchParams | ||
| : new URLSearchParams(); | ||
| let theJs = 'sqlite3.js'; | ||
| if (urlParams.has('sqlite3.dir')) { | ||
| theJs = urlParams.get('sqlite3.dir') + '/' + theJs; | ||
| } | ||
| importScripts(theJs); | ||
| } | ||
| sqlite3InitModule().then((sqlite3) => sqlite3.initWorker1API()); |
| /* | ||
| 2022-05-23 | ||
| The author disclaims copyright to this source code. In place of a | ||
| legal notice, here is a blessing: | ||
| * May you do good and not evil. | ||
| * May you find forgiveness for yourself and forgive others. | ||
| * May you share freely, never taking more than you give. | ||
| *********************************************************************** | ||
| This is a JS Worker file for the main sqlite3 api. It loads | ||
| sqlite3.js, initializes the module, and postMessage()'s a message | ||
| after the module is initialized: | ||
| {type: 'sqlite3-api', result: 'worker1-ready'} | ||
| This seemingly superfluous level of indirection is necessary when | ||
| loading sqlite3.js via a Worker. Instantiating a worker with new | ||
| Worker("sqlite.js") will not (cannot) call sqlite3InitModule() to | ||
| initialize the module due to a timing/order-of-operations conflict | ||
| (and that symbol is not exported in a way that a Worker loading it | ||
| that way can see it). Thus JS code wanting to load the sqlite3 | ||
| Worker-specific API needs to pass _this_ file (or equivalent) to the | ||
| Worker constructor and then listen for an event in the form shown | ||
| above in order to know when the module has completed initialization. | ||
| This file accepts a URL arguments to adjust how it loads sqlite3.js: | ||
| - `sqlite3.dir`, if set, treats the given directory name as the | ||
| directory from which `sqlite3.js` will be loaded. | ||
| */ | ||
| return new Worker(new URL('sqlite3.js', import.meta.url)); | ||
| sqlite3InitModule().then((sqlite3) => sqlite3.initWorker1API()); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
2242132
7.59%310
27.05%13
44.44%8
-50%30843
-10.62%