UNPKG

@livestore/sqlite-wasm

Version:

474 lines • 15.3 kB
// Based on https://github.com/rhashimoto/wa-sqlite/blob/master/src/FacadeVFS.js /* eslint-disable unicorn/prefer-code-point */ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable prefer-arrow/prefer-arrow-functions */ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck import * as VFS from '@livestore/wa-sqlite/src/VFS.js'; const AsyncFunction = Object.getPrototypeOf(async () => { }).constructor; export class FacadeVFS extends VFS.Base { /** * @param {string} name * @param {object} module */ constructor(name, module) { super(name, module); } /** * Override to indicate which methods are asynchronous. * @param {string} methodName * @returns {boolean} */ hasAsyncMethod(methodName) { // The input argument is a string like "xOpen", so convert to "jOpen". // Then check if the method exists and is async. const jMethodName = `j${methodName.slice(1)}`; return this[jMethodName] instanceof AsyncFunction; } /** * Return the filename for a file id for use by mixins. * @param {number} pFile * @returns {string} */ getFilename(pFile) { throw new Error('unimplemented'); } /** * @param {string?} filename * @param {number} pFile * @param {number} flags * @param {DataView} pOutFlags * @returns {number|Promise<number>} */ jOpen(filename, pFile, flags, pOutFlags) { return VFS.SQLITE_CANTOPEN; } /** * @param {string} filename * @param {number} syncDir * @returns {number|Promise<number>} */ jDelete(filename, syncDir) { return VFS.SQLITE_OK; } /** * @param {string} filename * @param {number} flags * @param {DataView} pResOut * @returns {number|Promise<number>} */ jAccess(filename, flags, pResOut) { return VFS.SQLITE_OK; } /** * @param {string} filename * @param {Uint8Array} zOut * @returns {number|Promise<number>} */ jFullPathname(filename, zOut) { // Copy the filename to the output buffer. const { read, written } = new TextEncoder().encodeInto(filename, zOut); if (read < filename.length) return VFS.SQLITE_IOERR; if (written >= zOut.length) return VFS.SQLITE_IOERR; zOut[written] = 0; return VFS.SQLITE_OK; } /** * @param {Uint8Array} zBuf * @returns {number|Promise<number>} */ jGetLastError(zBuf) { return VFS.SQLITE_OK; } /** * @param {number} pFile * @returns {number|Promise<number>} */ jClose(pFile) { return VFS.SQLITE_OK; } /** * @param {number} pFile * @param {Uint8Array} pData * @param {number} iOffset * @returns {number|Promise<number>} */ jRead(pFile, pData, iOffset) { pData.fill(0); return VFS.SQLITE_IOERR_SHORT_READ; } /** * @param {number} pFile * @param {Uint8Array} pData * @param {number} iOffset * @returns {number|Promise<number>} */ jWrite(pFile, pData, iOffset) { return VFS.SQLITE_IOERR_WRITE; } /** * @param {number} pFile * @param {number} size * @returns {number|Promise<number>} */ jTruncate(pFile, size) { return VFS.SQLITE_OK; } /** * @param {number} pFile * @param {number} flags * @returns {number|Promise<number>} */ jSync(pFile, flags) { return VFS.SQLITE_OK; } /** * @param {number} pFile * @param {DataView} pSize * @returns {number|Promise<number>} */ jFileSize(pFile, pSize) { return VFS.SQLITE_OK; } /** * @param {number} pFile * @param {number} lockType * @returns {number|Promise<number>} */ jLock(pFile, lockType) { return VFS.SQLITE_OK; } /** * @param {number} pFile * @param {number} lockType * @returns {number|Promise<number>} */ jUnlock(pFile, lockType) { return VFS.SQLITE_OK; } /** * @param {number} pFile * @param {DataView} pResOut * @returns {number|Promise<number>} */ jCheckReservedLock(pFile, pResOut) { pResOut.setInt32(0, 0, true); return VFS.SQLITE_OK; } /** * @param {number} pFile * @param {number} op * @param {DataView} pArg * @returns {number|Promise<number>} */ jFileControl(pFile, op, pArg) { return VFS.SQLITE_NOTFOUND; } /** * @param {number} pFile * @returns {number|Promise<number>} */ jSectorSize(pFile) { return super.xSectorSize(pFile); } /** * @param {number} pFile * @returns {number|Promise<number>} */ jDeviceCharacteristics(pFile) { return 0; } /** * @param {number} pVfs * @param {number} zName * @param {number} pFile * @param {number} flags * @param {number} pOutFlags * @returns {number|Promise<number>} */ xOpen(pVfs, zName, pFile, flags, pOutFlags) { const filename = this.#decodeFilename(zName, flags); const pOutFlagsView = this.#makeTypedDataView('Int32', pOutFlags); this['log']?.('jOpen', filename, pFile, '0x' + flags.toString(16)); return this.jOpen(filename, pFile, flags, pOutFlagsView); } /** * @param {number} pVfs * @param {number} zName * @param {number} syncDir * @returns {number|Promise<number>} */ xDelete(pVfs, zName, syncDir) { const filename = this._module.UTF8ToString(zName); this['log']?.('jDelete', filename, syncDir); return this.jDelete(filename, syncDir); } /** * @param {number} pVfs * @param {number} zName * @param {number} flags * @param {number} pResOut * @returns {number|Promise<number>} */ xAccess(pVfs, zName, flags, pResOut) { const filename = this._module.UTF8ToString(zName); const pResOutView = this.#makeTypedDataView('Int32', pResOut); this['log']?.('jAccess', filename, flags); return this.jAccess(filename, flags, pResOutView); } /** * @param {number} pVfs * @param {number} zName * @param {number} nOut * @param {number} zOut * @returns {number|Promise<number>} */ xFullPathname(pVfs, zName, nOut, zOut) { const filename = this._module.UTF8ToString(zName); const zOutArray = this._module.HEAPU8.subarray(zOut, zOut + nOut); this['log']?.('jFullPathname', filename, nOut); return this.jFullPathname(filename, zOutArray); } /** * @param {number} pVfs * @param {number} nBuf * @param {number} zBuf * @returns {number|Promise<number>} */ xGetLastError(pVfs, nBuf, zBuf) { const zBufArray = this._module.HEAPU8.subarray(zBuf, zBuf + nBuf); this['log']?.('jGetLastError', nBuf); return this.jGetLastError(zBufArray); } /** * @param {number} pFile * @returns {number|Promise<number>} */ xClose(pFile) { this['log']?.('jClose', pFile); return this.jClose(pFile); } /** * @param {number} pFile * @param {number} pData * @param {number} iAmt * @param {number} iOffsetLo * @param {number} iOffsetHi * @returns {number|Promise<number>} */ xRead(pFile, pData, iAmt, iOffsetLo, iOffsetHi) { const pDataArray = this.#makeDataArray(pData, iAmt); const iOffset = delegalize(iOffsetLo, iOffsetHi); this['log']?.('jRead', pFile, iAmt, iOffset); return this.jRead(pFile, pDataArray, iOffset); } /** * @param {number} pFile * @param {number} pData * @param {number} iAmt * @param {number} iOffsetLo * @param {number} iOffsetHi * @returns {number|Promise<number>} */ xWrite(pFile, pData, iAmt, iOffsetLo, iOffsetHi) { const pDataArray = this.#makeDataArray(pData, iAmt); const iOffset = delegalize(iOffsetLo, iOffsetHi); this['log']?.('jWrite', pFile, pDataArray, iOffset); return this.jWrite(pFile, pDataArray, iOffset); } /** * @param {number} pFile * @param {number} sizeLo * @param {number} sizeHi * @returns {number|Promise<number>} */ xTruncate(pFile, sizeLo, sizeHi) { const size = delegalize(sizeLo, sizeHi); this['log']?.('jTruncate', pFile, size); return this.jTruncate(pFile, size); } /** * @param {number} pFile * @param {number} flags * @returns {number|Promise<number>} */ xSync(pFile, flags) { this['log']?.('jSync', pFile, flags); return this.jSync(pFile, flags); } /** * * @param {number} pFile * @param {number} pSize * @returns {number|Promise<number>} */ xFileSize(pFile, pSize) { const pSizeView = this.#makeTypedDataView('BigInt64', pSize); this['log']?.('jFileSize', pFile); return this.jFileSize(pFile, pSizeView); } /** * @param {number} pFile * @param {number} lockType * @returns {number|Promise<number>} */ xLock(pFile, lockType) { this['log']?.('jLock', pFile, lockType); return this.jLock(pFile, lockType); } /** * @param {number} pFile * @param {number} lockType * @returns {number|Promise<number>} */ xUnlock(pFile, lockType) { this['log']?.('jUnlock', pFile, lockType); return this.jUnlock(pFile, lockType); } /** * @param {number} pFile * @param {number} pResOut * @returns {number|Promise<number>} */ xCheckReservedLock(pFile, pResOut) { const pResOutView = this.#makeTypedDataView('Int32', pResOut); this['log']?.('jCheckReservedLock', pFile); return this.jCheckReservedLock(pFile, pResOutView); } /** * @param {number} pFile * @param {number} op * @param {number} pArg * @returns {number|Promise<number>} */ xFileControl(pFile, op, pArg) { const pArgView = new DataView(this._module.HEAPU8.buffer, this._module.HEAPU8.byteOffset + pArg); this['log']?.('jFileControl', pFile, op, pArgView); return this.jFileControl(pFile, op, pArgView); } /** * @param {number} pFile * @returns {number|Promise<number>} */ xSectorSize(pFile) { this['log']?.('jSectorSize', pFile); return this.jSectorSize(pFile); } /** * @param {number} pFile * @returns {number|Promise<number>} */ xDeviceCharacteristics(pFile) { this['log']?.('jDeviceCharacteristics', pFile); return this.jDeviceCharacteristics(pFile); } /** * Wrapped DataView for pointer arguments. * Pointers to a single value are passed using DataView. A Proxy * wrapper prevents use of incorrect type or endianness. * @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' && /^(get)|(set)/.test(prop)) { throw new Error('invalid type'); } const result = dataView[prop]; return typeof result === 'function' ? result.bind(dataView) : result; }, }); } /** * @param {number} byteOffset * @param {number} byteLength */ #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; }, }); } #decodeFilename(zName, flags) { if (flags & VFS.SQLITE_OPEN_URI) { // The first null-terminated string is the URI path. Subsequent // strings are query parameter keys and values. // https://www.sqlite.org/c3ref/open.html#urifilenamesinsqlite3open let pName = zName; let state = 1; const charCodes = []; while (state) { const charCode = this._module.HEAPU8[pName++]; if (charCode) { charCodes.push(charCode); } else { if (!this._module.HEAPU8[pName]) state = null; switch (state) { case 1: { // path charCodes.push('?'.charCodeAt(0)); state = 2; break; } case 2: { // key charCodes.push('='.charCodeAt(0)); state = 3; break; } case 3: { // value charCodes.push('&'.charCodeAt(0)); state = 2; break; } } } } return new TextDecoder().decode(new Uint8Array(charCodes)); } return zName ? this._module.UTF8ToString(zName) : null; } } // Emscripten "legalizes" 64-bit integer arguments by passing them as // two 32-bit signed integers. function delegalize(lo32, hi32) { return hi32 * 0x1_00_00_00_00 + lo32 + (lo32 < 0 ? 2 ** 32 : 0); } //# sourceMappingURL=FacadeVFS.js.map