Initial commit: Plane
Some checks failed
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Admin Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Sync Repositories / sync_changes (push) Has been cancelled

Synced from upstream: 8853637e981ed7d8a6cff32bd98e7afe20f54362
This commit is contained in:
chuan
2025-11-07 00:00:52 +08:00
commit 8ebde8aa05
4886 changed files with 462270 additions and 0 deletions

View File

@@ -0,0 +1,136 @@
import * as Comlink from "comlink";
import { OPFSCoopSyncVFS as MyVFS } from "./wa-sqlite/src/OPFSCoopSyncVFS";
import * as SQLite from "./wa-sqlite/src/sqlite-api";
import SQLiteESMFactory from "./wa-sqlite/src/wa-sqlite.mjs";
type TQueryProps = {
sql: string;
rowMode: string;
returnValue: string;
bind: any[];
};
const mergeToObject = (columns: string[], row: any[]) => {
const obj: any = {};
columns.forEach((column, index) => {
obj[column] = row[index];
});
return obj;
};
interface SQLiteInstance {
db: unknown;
exec: (sql: string) => Promise<unknown[]>;
}
export class DBClass {
private instance: SQLiteInstance = {} as SQLiteInstance;
private sqlite3: any;
private tp: Promise<any>[] = [];
private tpResolver: any = [];
async init(dbName: string) {
if (!dbName || typeof dbName !== "string") {
throw new Error("Invalid database name");
}
try {
const m = await SQLiteESMFactory();
this.sqlite3 = SQLite.Factory(m);
const vfs = await MyVFS.create("plane", m);
this.sqlite3.vfs_register(vfs, true);
// Fallback in rare cases where the database is not initialized in time
const p = new Promise((resolve) => setTimeout(() => resolve(false), 2000));
const dbPromise = this.sqlite3.open_v2(
`${dbName}.sqlite3`,
this.sqlite3.OPEN_READWRITE | this.sqlite3.OPEN_CREATE,
"plane"
);
const db = await Promise.any([dbPromise, p]);
if (!db) {
throw new Error("Failed to initialize in time");
}
this.instance.db = db;
this.instance.exec = async (sql: string) => {
const rows: any[] = [];
await this.sqlite3.exec(db, sql, (row: any[], columns: string[]) => {
rows.push(mergeToObject(columns, row));
});
return rows;
};
return true;
} catch (error) {
throw new Error(`Failed to initialize database: ${(error as any)?.message}`);
}
}
runQuery(sql: string) {
return this.instance?.exec?.(sql);
}
async exec(props: string | TQueryProps) {
// @todo this will fail if the transaction is started any other way
// eg: BEGIN, OR BEGIN TRANSACTION
if (props === "BEGIN;") {
let promiseToAwait;
if (this.tp.length > 0) {
promiseToAwait = this.tp.shift();
}
const p = new Promise((resolve, reject) => {
this.tpResolver.push({ resolve, reject });
});
this.tp.push(p);
if (promiseToAwait) {
await promiseToAwait;
}
}
let sql: string, bind: any[];
if (typeof props === "string") {
sql = props;
} else {
({ sql, bind } = props);
if (bind) {
for await (const stmt of this.sqlite3.statements(this.instance.db, sql)) {
bind.forEach((b, i) => {
this.sqlite3.bind(stmt, i + 1, b);
});
const rows = [];
do {
const columns = await this.sqlite3.column_names(stmt);
const row = await this.sqlite3.row(stmt);
rows.push(mergeToObject(columns, row));
} while ((await this.sqlite3.step(stmt)) === SQLite.SQLITE_ROW);
return rows;
}
}
}
if (sql === "COMMIT;" && this.tp) {
await this.instance?.exec?.(sql);
if (this.tp.length > 0) {
const { resolve } = this.tpResolver.shift();
resolve();
}
return;
}
return await this.instance?.exec?.(sql);
}
async close() {
try {
if (!this.instance.db) {
return;
}
await this.sqlite3.close(this.instance.db);
// Clear instance to prevent usage after closing
this.instance = {} as SQLiteInstance;
} catch (error) {
throw new Error(`Failed to close database: ${(error as any)?.message}`);
}
}
}
Comlink.expose(DBClass);

View File

@@ -0,0 +1,508 @@
// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
import * as VFS from './VFS.js';
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
// Convenience base class for a JavaScript VFS.
// The raw xOpen, xRead, etc. function signatures receive only C primitives
// which aren't easy to work with. This class provides corresponding calls
// like jOpen, jRead, etc., which receive JavaScript-friendlier arguments
// such as string, Uint8Array, and DataView.
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' && (prop.match(/^(get)|(set)/))) {
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 * 0x100000000) + lo32 + (lo32 < 0 ? 2**32 : 0);
}

View File

@@ -0,0 +1,592 @@
// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
import { FacadeVFS } from "./FacadeVFS.js";
import * as VFS from "./VFS.js";
const DEFAULT_TEMPORARY_FILES = 10;
const LOCK_NOTIFY_INTERVAL = 1000;
const DB_RELATED_FILE_SUFFIXES = ["", "-journal", "-wal"];
const finalizationRegistry = new FinalizationRegistry((releaser) => releaser());
class File {
/** @type {string} */ path;
/** @type {number} */ flags;
/** @type {FileSystemSyncAccessHandle} */ accessHandle;
/** @type {PersistentFile?} */ persistentFile;
constructor(path, flags) {
this.path = path;
this.flags = flags;
}
}
class PersistentFile {
/** @type {FileSystemFileHandle} */ fileHandle;
/** @type {FileSystemSyncAccessHandle} */ accessHandle = null;
// The following properties are for main database files.
/** @type {boolean} */ isLockBusy = false;
/** @type {boolean} */ isFileLocked = false;
/** @type {boolean} */ isRequestInProgress = false;
/** @type {function} */ handleLockReleaser = null;
/** @type {BroadcastChannel} */ handleRequestChannel;
/** @type {boolean} */ isHandleRequested = false;
constructor(fileHandle) {
this.fileHandle = fileHandle;
}
}
export class OPFSCoopSyncVFS extends FacadeVFS {
/** @type {Map<number, File>} */ mapIdToFile = new Map();
lastError = null;
log = null; //function(...args) { console.log(`[${contextName}]`, ...args) };
/** @type {Map<string, PersistentFile>} */ persistentFiles = new Map();
/** @type {Map<string, FileSystemSyncAccessHandle>} */ boundAccessHandles = new Map();
/** @type {Set<FileSystemSyncAccessHandle>} */ unboundAccessHandles = new Set();
/** @type {Set<string>} */ accessiblePaths = new Set();
releaser = null;
static async create(name, module) {
const vfs = new OPFSCoopSyncVFS(name, module);
await Promise.all([vfs.isReady(), vfs.#initialize(DEFAULT_TEMPORARY_FILES)]);
return vfs;
}
constructor(name, module) {
super(name, module);
}
async #initialize(nTemporaryFiles) {
// Delete temporary directories no longer in use.
const root = await navigator.storage.getDirectory();
// @ts-ignore
for await (const entry of root.values()) {
if (entry.kind === "directory" && entry.name.startsWith(".ahp-")) {
// A lock with the same name as the directory protects it from
// being deleted.
await navigator.locks.request(entry.name, { ifAvailable: true }, async (lock) => {
if (lock) {
this.log?.(`Deleting temporary directory ${entry.name}`);
await root.removeEntry(entry.name, { recursive: true });
} else {
this.log?.(`Temporary directory ${entry.name} is in use`);
}
});
}
}
// Create our temporary directory.
const tmpDirName = `.ahp-${Math.random().toString(36).slice(2)}`;
this.releaser = await new Promise((resolve) => {
navigator.locks.request(tmpDirName, () => {
return new Promise((release) => {
resolve(release);
});
});
});
finalizationRegistry.register(this, this.releaser);
const tmpDir = await root.getDirectoryHandle(tmpDirName, { create: true });
// Populate temporary directory.
for (let i = 0; i < nTemporaryFiles; i++) {
const tmpFile = await tmpDir.getFileHandle(`${i}.tmp`, { create: true });
const tmpAccessHandle = await tmpFile.createSyncAccessHandle();
this.unboundAccessHandles.add(tmpAccessHandle);
}
}
/**
* @param {string?} zName
* @param {number} fileId
* @param {number} flags
* @param {DataView} pOutFlags
* @returns {number}
*/
jOpen(zName, fileId, flags, pOutFlags) {
try {
const url = new URL(zName || Math.random().toString(36).slice(2), "file://");
const path = url.pathname;
if (flags & VFS.SQLITE_OPEN_MAIN_DB) {
const persistentFile = this.persistentFiles.get(path);
if (persistentFile?.isRequestInProgress) {
// Should not reach here unless SQLite itself retries an open.
// Otherwise, asynchronous operations started on a previous
// open try should have completed.
return VFS.SQLITE_BUSY;
} else if (!persistentFile) {
// This is the usual starting point for opening a database.
// Register a Promise that resolves when the database and related
// files are ready to be used.
this.log?.(`creating persistent file for ${path}`);
const create = !!(flags & VFS.SQLITE_OPEN_CREATE);
this._module.retryOps.push(
(async () => {
try {
// Get the path directory handle.
let dirHandle = await navigator.storage.getDirectory();
const directories = path.split("/").filter((d) => d);
const filename = directories.pop();
for (const directory of directories) {
dirHandle = await dirHandle.getDirectoryHandle(directory, { create });
}
// Get file handles for the database and related files,
// and create persistent file instances.
for (const suffix of DB_RELATED_FILE_SUFFIXES) {
const fileHandle = await dirHandle.getFileHandle(filename + suffix, { create });
await this.#createPersistentFile(fileHandle);
}
// Get access handles for the files.
const file = new File(path, flags);
file.persistentFile = this.persistentFiles.get(path);
await this.#requestAccessHandle(file);
} catch (e) {
// Use an invalid persistent file to signal this error
// for the retried open.
const persistentFile = new PersistentFile(null);
this.persistentFiles.set(path, persistentFile);
console.error(e);
}
})()
);
return VFS.SQLITE_BUSY;
} else if (!persistentFile.fileHandle) {
// The asynchronous open operation failed.
this.persistentFiles.delete(path);
return VFS.SQLITE_CANTOPEN;
} else if (!persistentFile.accessHandle) {
// This branch is reached if the database was previously opened
// and closed.
this._module.retryOps.push(
(async () => {
const file = new File(path, flags);
file.persistentFile = this.persistentFiles.get(path);
await this.#requestAccessHandle(file);
})()
);
return VFS.SQLITE_BUSY;
}
}
if (!this.accessiblePaths.has(path) && !(flags & VFS.SQLITE_OPEN_CREATE)) {
throw new Error(`File ${path} not found`);
}
const file = new File(path, flags);
this.mapIdToFile.set(fileId, file);
if (this.persistentFiles.has(path)) {
file.persistentFile = this.persistentFiles.get(path);
} else if (this.boundAccessHandles.has(path)) {
// This temporary file was previously created and closed. Reopen
// the same access handle.
file.accessHandle = this.boundAccessHandles.get(path);
} else if (this.unboundAccessHandles.size) {
// Associate an unbound access handle to this file.
file.accessHandle = this.unboundAccessHandles.values().next().value;
file.accessHandle.truncate(0);
this.unboundAccessHandles.delete(file.accessHandle);
this.boundAccessHandles.set(path, file.accessHandle);
}
this.accessiblePaths.add(path);
pOutFlags.setInt32(0, flags, true);
return VFS.SQLITE_OK;
} catch (e) {
this.lastError = e;
return VFS.SQLITE_CANTOPEN;
}
}
/**
* @param {string} zName
* @param {number} syncDir
* @returns {number}
*/
jDelete(zName, syncDir) {
try {
const url = new URL(zName, "file://");
const path = url.pathname;
if (this.persistentFiles.has(path)) {
const persistentFile = this.persistentFiles.get(path);
persistentFile.accessHandle.truncate(0);
} else {
this.boundAccessHandles.get(path)?.truncate(0);
}
this.accessiblePaths.delete(path);
return VFS.SQLITE_OK;
} catch (e) {
this.lastError = e;
return VFS.SQLITE_IOERR_DELETE;
}
}
/**
* @param {string} zName
* @param {number} flags
* @param {DataView} pResOut
* @returns {number}
*/
jAccess(zName, flags, pResOut) {
try {
const url = new URL(zName, "file://");
const path = url.pathname;
pResOut.setInt32(0, this.accessiblePaths.has(path) ? 1 : 0, true);
return VFS.SQLITE_OK;
} catch (e) {
this.lastError = e;
return VFS.SQLITE_IOERR_ACCESS;
}
}
/**
* @param {number} fileId
* @returns {number}
*/
jClose(fileId) {
try {
const file = this.mapIdToFile.get(fileId);
this.mapIdToFile.delete(fileId);
if (file?.flags & VFS.SQLITE_OPEN_MAIN_DB) {
if (file.persistentFile?.handleLockReleaser) {
this.#releaseAccessHandle(file);
}
} else if (file?.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
file.accessHandle.truncate(0);
this.accessiblePaths.delete(file.path);
if (!this.persistentFiles.has(file.path)) {
this.boundAccessHandles.delete(file.path);
this.unboundAccessHandles.add(file.accessHandle);
}
}
return VFS.SQLITE_OK;
} catch (e) {
this.lastError = e;
return VFS.SQLITE_IOERR_CLOSE;
}
}
/**
* @param {number} fileId
* @param {Uint8Array} pData
* @param {number} iOffset
* @returns {number}
*/
jRead(fileId, pData, iOffset) {
try {
const file = this.mapIdToFile.get(fileId);
// On Chrome (at least), passing pData to accessHandle.read() is
// an error because pData is a Proxy of a Uint8Array. Calling
// subarray() produces a real Uint8Array and that works.
const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
const bytesRead = accessHandle.read(pData.subarray(), { at: iOffset });
// Opening a database file performs one read without a xLock call.
if (file.flags & VFS.SQLITE_OPEN_MAIN_DB && !file.persistentFile.isFileLocked) {
this.#releaseAccessHandle(file);
}
if (bytesRead < pData.byteLength) {
pData.fill(0, bytesRead);
return VFS.SQLITE_IOERR_SHORT_READ;
}
return VFS.SQLITE_OK;
} catch (e) {
this.lastError = e;
return VFS.SQLITE_IOERR_READ;
}
}
/**
* @param {number} fileId
* @param {Uint8Array} pData
* @param {number} iOffset
* @returns {number}
*/
jWrite(fileId, pData, iOffset) {
try {
const file = this.mapIdToFile.get(fileId);
// On Chrome (at least), passing pData to accessHandle.write() is
// an error because pData is a Proxy of a Uint8Array. Calling
// subarray() produces a real Uint8Array and that works.
const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
const nBytes = accessHandle.write(pData.subarray(), { at: iOffset });
if (nBytes !== pData.byteLength) throw new Error("short write");
return VFS.SQLITE_OK;
} catch (e) {
this.lastError = e;
return VFS.SQLITE_IOERR_WRITE;
}
}
/**
* @param {number} fileId
* @param {number} iSize
* @returns {number}
*/
jTruncate(fileId, iSize) {
try {
const file = this.mapIdToFile.get(fileId);
const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
accessHandle.truncate(iSize);
return VFS.SQLITE_OK;
} catch (e) {
this.lastError = e;
return VFS.SQLITE_IOERR_TRUNCATE;
}
}
/**
* @param {number} fileId
* @param {number} flags
* @returns {number}
*/
jSync(fileId, flags) {
try {
const file = this.mapIdToFile.get(fileId);
const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
accessHandle.flush();
return VFS.SQLITE_OK;
} catch (e) {
this.lastError = e;
return VFS.SQLITE_IOERR_FSYNC;
}
}
/**
* @param {number} fileId
* @param {DataView} pSize64
* @returns {number}
*/
jFileSize(fileId, pSize64) {
try {
const file = this.mapIdToFile.get(fileId);
const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
const size = accessHandle.getSize();
pSize64.setBigInt64(0, BigInt(size), true);
return VFS.SQLITE_OK;
} catch (e) {
this.lastError = e;
return VFS.SQLITE_IOERR_FSTAT;
}
}
/**
* @param {number} fileId
* @param {number} lockType
* @returns {number}
*/
jLock(fileId, lockType) {
const file = this.mapIdToFile.get(fileId);
if (file.persistentFile.isRequestInProgress) {
file.persistentFile.isLockBusy = true;
return VFS.SQLITE_BUSY;
}
file.persistentFile.isFileLocked = true;
if (!file.persistentFile.handleLockReleaser) {
// Start listening for notifications from other connections.
// This is before we actually get access handles, but waiting to
// listen until then allows a race condition where notifications
// are missed.
file.persistentFile.handleRequestChannel.onmessage = () => {
this.log?.(`received notification for ${file.path}`);
if (file.persistentFile.isFileLocked) {
// We're still using the access handle, so mark it to be
// released when we're done.
file.persistentFile.isHandleRequested = true;
} else {
// Release the access handles immediately.
this.#releaseAccessHandle(file);
}
file.persistentFile.handleRequestChannel.onmessage = null;
};
this.#requestAccessHandle(file);
this.log?.("returning SQLITE_BUSY");
file.persistentFile.isLockBusy = true;
return VFS.SQLITE_BUSY;
}
file.persistentFile.isLockBusy = false;
return VFS.SQLITE_OK;
}
/**
* @param {number} fileId
* @param {number} lockType
* @returns {number}
*/
jUnlock(fileId, lockType) {
const file = this.mapIdToFile.get(fileId);
if (lockType === VFS.SQLITE_LOCK_NONE) {
// Don't change any state if this unlock is because xLock returned
// SQLITE_BUSY.
if (!file.persistentFile.isLockBusy) {
if (file.persistentFile.isHandleRequested) {
// Another connection wants the access handle.
this.#releaseAccessHandle(file);
this.isHandleRequested = false;
}
file.persistentFile.isFileLocked = false;
}
}
return VFS.SQLITE_OK;
}
/**
* @param {number} fileId
* @param {number} op
* @param {DataView} pArg
* @returns {number|Promise<number>}
*/
jFileControl(fileId, op, pArg) {
try {
const file = this.mapIdToFile.get(fileId);
switch (op) {
case VFS.SQLITE_FCNTL_PRAGMA:
const key = extractString(pArg, 4);
const value = extractString(pArg, 8);
this.log?.("xFileControl", file.path, "PRAGMA", key, value);
switch (key.toLowerCase()) {
case "journal_mode":
if (value && !["off", "memory", "delete", "wal"].includes(value.toLowerCase())) {
throw new Error('journal_mode must be "off", "memory", "delete", or "wal"');
}
break;
}
break;
}
} catch (e) {
this.lastError = e;
return VFS.SQLITE_IOERR;
}
return VFS.SQLITE_NOTFOUND;
}
/**
* @param {Uint8Array} zBuf
* @returns
*/
jGetLastError(zBuf) {
if (this.lastError) {
console.error(this.lastError);
const outputArray = zBuf.subarray(0, zBuf.byteLength - 1);
const { written } = new TextEncoder().encodeInto(this.lastError.message, outputArray);
zBuf[written] = 0;
}
return VFS.SQLITE_OK;
}
/**
* @param {FileSystemFileHandle} fileHandle
* @returns {Promise<PersistentFile>}
*/
async #createPersistentFile(fileHandle) {
const persistentFile = new PersistentFile(fileHandle);
const root = await navigator.storage.getDirectory();
const relativePath = await root.resolve(fileHandle);
const path = `/${relativePath.join("/")}`;
persistentFile.handleRequestChannel = new BroadcastChannel(`ahp:${path}`);
this.persistentFiles.set(path, persistentFile);
const f = await fileHandle.getFile();
if (f.size) {
this.accessiblePaths.add(path);
}
return persistentFile;
}
/**
* @param {File} file
*/
#requestAccessHandle(file) {
console.assert(!file.persistentFile.handleLockReleaser);
if (!file.persistentFile.isRequestInProgress) {
file.persistentFile.isRequestInProgress = true;
this._module.retryOps.push(
(async () => {
// Acquire the Web Lock.
file.persistentFile.handleLockReleaser = await this.#acquireLock(file.persistentFile);
// Get access handles for the database and releated files in parallel.
this.log?.(`creating access handles for ${file.path}`);
await Promise.all(
DB_RELATED_FILE_SUFFIXES.map(async (suffix) => {
const persistentFile = this.persistentFiles.get(file.path + suffix);
if (persistentFile) {
persistentFile.accessHandle = await persistentFile.fileHandle.createSyncAccessHandle();
}
})
);
file.persistentFile.isRequestInProgress = false;
})()
);
return this._module.retryOps.at(-1);
}
return Promise.resolve();
}
/**
* @param {File} file
*/
async #releaseAccessHandle(file) {
DB_RELATED_FILE_SUFFIXES.forEach(async (suffix) => {
const persistentFile = this.persistentFiles.get(file.path + suffix);
if (persistentFile) {
persistentFile.accessHandle?.close();
persistentFile.accessHandle = null;
}
});
this.log?.(`access handles closed for ${file.path}`);
file.persistentFile.handleLockReleaser?.();
file.persistentFile.handleLockReleaser = null;
this.log?.(`lock released for ${file.path}`);
}
/**
* @param {PersistentFile} persistentFile
* @returns {Promise<function>} lock releaser
*/
#acquireLock(persistentFile) {
return new Promise((resolve) => {
// Tell other connections we want the access handle.
const lockName = persistentFile.handleRequestChannel.name;
const notify = () => {
this.log?.(`notifying for ${lockName}`);
persistentFile.handleRequestChannel.postMessage(null);
};
const notifyId = setInterval(notify, LOCK_NOTIFY_INTERVAL);
setTimeout(notify);
this.log?.(`lock requested: ${lockName}`);
navigator.locks.request(lockName, (lock) => {
// We have the lock. Stop asking other connections for it.
this.log?.(`lock acquired: ${lockName}`, lock);
clearInterval(notifyId);
return new Promise(resolve);
});
});
}
}
function extractString(dataView, offset) {
const p = dataView.getUint32(offset, true);
if (p) {
const chars = new Uint8Array(dataView.buffer, p);
return new TextDecoder().decode(chars.subarray(0, chars.indexOf(0)));
}
return null;
}

View File

@@ -0,0 +1,222 @@
// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
import * as VFS from './sqlite-constants.js';
export * from './sqlite-constants.js';
const DEFAULT_SECTOR_SIZE = 512;
// Base class for a VFS.
export class Base {
name;
mxPathname = 64;
_module;
/**
* @param {string} name
* @param {object} module
*/
constructor(name, module) {
this.name = name;
this._module = module;
}
/**
* @returns {void|Promise<void>}
*/
close() {
}
/**
* @returns {boolean|Promise<boolean>}
*/
isReady() {
return true;
}
/**
* Overload in subclasses to indicate which methods are asynchronous.
* @param {string} methodName
* @returns {boolean}
*/
hasAsyncMethod(methodName) {
return false;
}
/**
* @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) {
return VFS.SQLITE_CANTOPEN;
}
/**
* @param {number} pVfs
* @param {number} zName
* @param {number} syncDir
* @returns {number|Promise<number>}
*/
xDelete(pVfs, zName, syncDir) {
return VFS.SQLITE_OK;
}
/**
* @param {number} pVfs
* @param {number} zName
* @param {number} flags
* @param {number} pResOut
* @returns {number|Promise<number>}
*/
xAccess(pVfs, zName, flags, pResOut) {
return VFS.SQLITE_OK;
}
/**
* @param {number} pVfs
* @param {number} zName
* @param {number} nOut
* @param {number} zOut
* @returns {number|Promise<number>}
*/
xFullPathname(pVfs, zName, nOut, zOut) {
return VFS.SQLITE_OK;
}
/**
* @param {number} pVfs
* @param {number} nBuf
* @param {number} zBuf
* @returns {number|Promise<number>}
*/
xGetLastError(pVfs, nBuf, zBuf) {
return VFS.SQLITE_OK;
}
/**
* @param {number} pFile
* @returns {number|Promise<number>}
*/
xClose(pFile) {
return VFS.SQLITE_OK;
}
/**
* @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) {
return VFS.SQLITE_OK;
}
/**
* @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) {
return VFS.SQLITE_OK;
}
/**
* @param {number} pFile
* @param {number} sizeLo
* @param {number} sizeHi
* @returns {number|Promise<number>}
*/
xTruncate(pFile, sizeLo, sizeHi) {
return VFS.SQLITE_OK;
}
/**
* @param {number} pFile
* @param {number} flags
* @returns {number|Promise<number>}
*/
xSync(pFile, flags) {
return VFS.SQLITE_OK;
}
/**
*
* @param {number} pFile
* @param {number} pSize
* @returns {number|Promise<number>}
*/
xFileSize(pFile, pSize) {
return VFS.SQLITE_OK;
}
/**
* @param {number} pFile
* @param {number} lockType
* @returns {number|Promise<number>}
*/
xLock(pFile, lockType) {
return VFS.SQLITE_OK;
}
/**
* @param {number} pFile
* @param {number} lockType
* @returns {number|Promise<number>}
*/
xUnlock(pFile, lockType) {
return VFS.SQLITE_OK;
}
/**
* @param {number} pFile
* @param {number} pResOut
* @returns {number|Promise<number>}
*/
xCheckReservedLock(pFile, pResOut) {
return VFS.SQLITE_OK;
}
/**
* @param {number} pFile
* @param {number} op
* @param {number} pArg
* @returns {number|Promise<number>}
*/
xFileControl(pFile, op, pArg) {
return VFS.SQLITE_NOTFOUND;
}
/**
* @param {number} pFile
* @returns {number|Promise<number>}
*/
xSectorSize(pFile) {
return DEFAULT_SECTOR_SIZE;
}
/**
* @param {number} pFile
* @returns {number|Promise<number>}
*/
xDeviceCharacteristics(pFile) {
return 0;
}
}
export const FILE_TYPE_MASK = [
VFS.SQLITE_OPEN_MAIN_DB,
VFS.SQLITE_OPEN_MAIN_JOURNAL,
VFS.SQLITE_OPEN_TEMP_DB,
VFS.SQLITE_OPEN_TEMP_JOURNAL,
VFS.SQLITE_OPEN_TRANSIENT_DB,
VFS.SQLITE_OPEN_SUBJOURNAL,
VFS.SQLITE_OPEN_SUPER_JOURNAL,
VFS.SQLITE_OPEN_WAL
].reduce((mask, element) => mask | element);

View File

@@ -0,0 +1,899 @@
// Copyright 2021 Roy T. Hashimoto. All Rights Reserved.
import * as SQLite from "./sqlite-constants.js";
export * from "./sqlite-constants.js";
const MAX_INT64 = 0x7fffffffffffffffn;
const MIN_INT64 = -0x8000000000000000n;
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
export class SQLiteError extends Error {
constructor(message, code) {
super(message);
this.code = code;
}
}
const async = true;
/**
* Builds a Javascript API from the Emscripten module. This API is still
* low-level and closely corresponds to the C API exported by the module,
* but differs in some specifics like throwing exceptions on errors.
* @param {*} Module SQLite Emscripten module
* @returns {SQLiteAPI}
*/
export function Factory(Module) {
/** @type {SQLiteAPI} */ const sqlite3 = {};
Module.retryOps = [];
const sqliteFreeAddress = Module._getSqliteFree();
// Allocate some space for 32-bit returned values.
const tmp = Module._malloc(8);
const tmpPtr = [tmp, tmp + 4];
// Convert a JS string to a C string. sqlite3_malloc is used to allocate
// memory (use sqlite3_free to deallocate).
function createUTF8(s) {
if (typeof s !== "string") return 0;
const utf8 = new TextEncoder().encode(s);
const zts = Module._sqlite3_malloc(utf8.byteLength + 1);
Module.HEAPU8.set(utf8, zts);
Module.HEAPU8[zts + utf8.byteLength] = 0;
return zts;
}
/**
* Concatenate 32-bit numbers into a 64-bit (signed) BigInt.
* @param {number} lo32
* @param {number} hi32
* @returns {bigint}
*/
function cvt32x2ToBigInt(lo32, hi32) {
return (BigInt(hi32) << 32n) | (BigInt(lo32) & 0xffffffffn);
}
/**
* Concatenate 32-bit numbers and return as number or BigInt, depending
* on the value.
* @param {number} lo32
* @param {number} hi32
* @returns {number|bigint}
*/
const cvt32x2AsSafe = (function () {
const hiMax = BigInt(Number.MAX_SAFE_INTEGER) >> 32n;
const hiMin = BigInt(Number.MIN_SAFE_INTEGER) >> 32n;
return function (lo32, hi32) {
if (hi32 > hiMax || hi32 < hiMin) {
// Can't be expressed as a Number so use BigInt.
return cvt32x2ToBigInt(lo32, hi32);
} else {
// Combine the upper and lower 32-bit numbers. The complication is
// that lo32 is a signed integer which makes manipulating its bits
// a little tricky - the sign bit gets handled separately.
return hi32 * 0x100000000 + (lo32 & 0x7fffffff) - (lo32 & 0x80000000);
}
};
})();
const databases = new Set();
function verifyDatabase(db) {
if (!databases.has(db)) {
throw new SQLiteError("not a database", SQLite.SQLITE_MISUSE);
}
}
const mapStmtToDB = new Map();
function verifyStatement(stmt) {
if (!mapStmtToDB.has(stmt)) {
throw new SQLiteError("not a statement", SQLite.SQLITE_MISUSE);
}
}
sqlite3.bind_collection = function (stmt, bindings) {
verifyStatement(stmt);
const isArray = Array.isArray(bindings);
const nBindings = sqlite3.bind_parameter_count(stmt);
for (let i = 1; i <= nBindings; ++i) {
const key = isArray ? i - 1 : sqlite3.bind_parameter_name(stmt, i);
const value = bindings[key];
if (value !== undefined) {
sqlite3.bind(stmt, i, value);
}
}
return SQLite.SQLITE_OK;
};
sqlite3.bind = function (stmt, i, value) {
verifyStatement(stmt);
switch (typeof value) {
case "number":
if (value === (value | 0)) {
return sqlite3.bind_int(stmt, i, value);
} else {
return sqlite3.bind_double(stmt, i, value);
}
case "string":
return sqlite3.bind_text(stmt, i, value);
default:
if (value instanceof Uint8Array || Array.isArray(value)) {
return sqlite3.bind_blob(stmt, i, value);
} else if (value === null) {
return sqlite3.bind_null(stmt, i);
} else if (typeof value === "bigint") {
return sqlite3.bind_int64(stmt, i, value);
} else if (value === undefined) {
// Existing binding (or NULL) will be used.
return SQLite.SQLITE_NOTICE;
} else {
console.warn("unknown binding converted to null", value);
return sqlite3.bind_null(stmt, i);
}
}
};
sqlite3.bind_blob = (function () {
const fname = "sqlite3_bind_blob";
const f = Module.cwrap(fname, ...decl("nnnnn:n"));
return function (stmt, i, value) {
verifyStatement(stmt);
// @ts-ignore
const byteLength = value.byteLength ?? value.length;
const ptr = Module._sqlite3_malloc(byteLength);
Module.HEAPU8.subarray(ptr).set(value);
const result = f(stmt, i, ptr, byteLength, sqliteFreeAddress);
return check(fname, result, mapStmtToDB.get(stmt));
};
})();
sqlite3.bind_parameter_count = (function () {
const fname = "sqlite3_bind_parameter_count";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (stmt) {
verifyStatement(stmt);
const result = f(stmt);
return result;
};
})();
sqlite3.bind_double = (function () {
const fname = "sqlite3_bind_double";
const f = Module.cwrap(fname, ...decl("nnn:n"));
return function (stmt, i, value) {
verifyStatement(stmt);
const result = f(stmt, i, value);
return check(fname, result, mapStmtToDB.get(stmt));
};
})();
sqlite3.bind_int = (function () {
const fname = "sqlite3_bind_int";
const f = Module.cwrap(fname, ...decl("nnn:n"));
return function (stmt, i, value) {
verifyStatement(stmt);
if (value > 0x7fffffff || value < -0x80000000) return SQLite.SQLITE_RANGE;
const result = f(stmt, i, value);
return check(fname, result, mapStmtToDB.get(stmt));
};
})();
sqlite3.bind_int64 = (function () {
const fname = "sqlite3_bind_int64";
const f = Module.cwrap(fname, ...decl("nnnn:n"));
return function (stmt, i, value) {
verifyStatement(stmt);
if (value > MAX_INT64 || value < MIN_INT64) return SQLite.SQLITE_RANGE;
const lo32 = value & 0xffffffffn;
const hi32 = value >> 32n;
const result = f(stmt, i, Number(lo32), Number(hi32));
return check(fname, result, mapStmtToDB.get(stmt));
};
})();
sqlite3.bind_null = (function () {
const fname = "sqlite3_bind_null";
const f = Module.cwrap(fname, ...decl("nn:n"));
return function (stmt, i) {
verifyStatement(stmt);
const result = f(stmt, i);
return check(fname, result, mapStmtToDB.get(stmt));
};
})();
sqlite3.bind_parameter_name = (function () {
const fname = "sqlite3_bind_parameter_name";
const f = Module.cwrap(fname, ...decl("n:s"));
return function (stmt, i) {
verifyStatement(stmt);
const result = f(stmt, i);
return result;
};
})();
sqlite3.bind_text = (function () {
const fname = "sqlite3_bind_text";
const f = Module.cwrap(fname, ...decl("nnnnn:n"));
return function (stmt, i, value) {
verifyStatement(stmt);
const ptr = createUTF8(value);
const result = f(stmt, i, ptr, -1, sqliteFreeAddress);
return check(fname, result, mapStmtToDB.get(stmt));
};
})();
sqlite3.changes = (function () {
const fname = "sqlite3_changes";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (db) {
verifyDatabase(db);
const result = f(db);
return result;
};
})();
sqlite3.clear_bindings = (function () {
const fname = "sqlite3_clear_bindings";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (stmt) {
verifyStatement(stmt);
const result = f(stmt);
return check(fname, result, mapStmtToDB.get(stmt));
};
})();
sqlite3.close = (function () {
const fname = "sqlite3_close";
const f = Module.cwrap(fname, ...decl("n:n"), { async });
return async function (db) {
verifyDatabase(db);
const result = await f(db);
databases.delete(db);
return check(fname, result, db);
};
})();
sqlite3.column = function (stmt, iCol) {
verifyStatement(stmt);
const type = sqlite3.column_type(stmt, iCol);
switch (type) {
case SQLite.SQLITE_BLOB:
return sqlite3.column_blob(stmt, iCol);
case SQLite.SQLITE_FLOAT:
return sqlite3.column_double(stmt, iCol);
case SQLite.SQLITE_INTEGER:
const lo32 = sqlite3.column_int(stmt, iCol);
const hi32 = Module.getTempRet0();
return cvt32x2AsSafe(lo32, hi32);
case SQLite.SQLITE_NULL:
return null;
case SQLite.SQLITE_TEXT:
return sqlite3.column_text(stmt, iCol);
default:
throw new SQLiteError("unknown type", type);
}
};
sqlite3.column_blob = (function () {
const fname = "sqlite3_column_blob";
const f = Module.cwrap(fname, ...decl("nn:n"));
return function (stmt, iCol) {
verifyStatement(stmt);
const nBytes = sqlite3.column_bytes(stmt, iCol);
const address = f(stmt, iCol);
const result = Module.HEAPU8.subarray(address, address + nBytes);
return result;
};
})();
sqlite3.column_bytes = (function () {
const fname = "sqlite3_column_bytes";
const f = Module.cwrap(fname, ...decl("nn:n"));
return function (stmt, iCol) {
verifyStatement(stmt);
const result = f(stmt, iCol);
return result;
};
})();
sqlite3.column_count = (function () {
const fname = "sqlite3_column_count";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (stmt) {
verifyStatement(stmt);
const result = f(stmt);
return result;
};
})();
sqlite3.column_double = (function () {
const fname = "sqlite3_column_double";
const f = Module.cwrap(fname, ...decl("nn:n"));
return function (stmt, iCol) {
verifyStatement(stmt);
const result = f(stmt, iCol);
return result;
};
})();
sqlite3.column_int = (function () {
// Retrieve int64 but use only the lower 32 bits. The upper 32-bits are
// accessible with Module.getTempRet0().
const fname = "sqlite3_column_int64";
const f = Module.cwrap(fname, ...decl("nn:n"));
return function (stmt, iCol) {
verifyStatement(stmt);
const result = f(stmt, iCol);
return result;
};
})();
sqlite3.column_int64 = (function () {
const fname = "sqlite3_column_int64";
const f = Module.cwrap(fname, ...decl("nn:n"));
return function (stmt, iCol) {
verifyStatement(stmt);
const lo32 = f(stmt, iCol);
const hi32 = Module.getTempRet0();
const result = cvt32x2ToBigInt(lo32, hi32);
return result;
};
})();
sqlite3.column_name = (function () {
const fname = "sqlite3_column_name";
const f = Module.cwrap(fname, ...decl("nn:s"));
return function (stmt, iCol) {
verifyStatement(stmt);
const result = f(stmt, iCol);
return result;
};
})();
sqlite3.column_names = function (stmt) {
const columns = [];
const nColumns = sqlite3.column_count(stmt);
for (let i = 0; i < nColumns; ++i) {
columns.push(sqlite3.column_name(stmt, i));
}
return columns;
};
sqlite3.column_text = (function () {
const fname = "sqlite3_column_text";
const f = Module.cwrap(fname, ...decl("nn:s"));
return function (stmt, iCol) {
verifyStatement(stmt);
const result = f(stmt, iCol);
return result;
};
})();
sqlite3.column_type = (function () {
const fname = "sqlite3_column_type";
const f = Module.cwrap(fname, ...decl("nn:n"));
return function (stmt, iCol) {
verifyStatement(stmt);
const result = f(stmt, iCol);
return result;
};
})();
sqlite3.create_function = function (db, zFunctionName, nArg, eTextRep, pApp, xFunc, xStep, xFinal) {
verifyDatabase(db);
// Convert SQLite callback arguments to JavaScript-friendly arguments.
function adapt(f) {
return f instanceof AsyncFunction
? async (ctx, n, values) => f(ctx, Module.HEAP32.subarray(values / 4, values / 4 + n))
: (ctx, n, values) => f(ctx, Module.HEAP32.subarray(values / 4, values / 4 + n));
}
const result = Module.create_function(
db,
zFunctionName,
nArg,
eTextRep,
pApp,
xFunc && adapt(xFunc),
xStep && adapt(xStep),
xFinal
);
return check("sqlite3_create_function", result, db);
};
sqlite3.data_count = (function () {
const fname = "sqlite3_data_count";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (stmt) {
verifyStatement(stmt);
const result = f(stmt);
return result;
};
})();
sqlite3.exec = async function (db, sql, callback) {
for await (const stmt of sqlite3.statements(db, sql)) {
let columns;
while ((await sqlite3.step(stmt)) === SQLite.SQLITE_ROW) {
if (callback) {
columns = columns ?? sqlite3.column_names(stmt);
const row = sqlite3.row(stmt);
await callback(row, columns);
}
}
}
return SQLite.SQLITE_OK;
};
sqlite3.finalize = (function () {
const fname = "sqlite3_finalize";
const f = Module.cwrap(fname, ...decl("n:n"), { async });
return async function (stmt) {
const result = await f(stmt);
mapStmtToDB.delete(stmt);
// Don't throw on error here. Typically the error has already been
// thrown and finalize() is part of the cleanup.
return result;
};
})();
sqlite3.get_autocommit = (function () {
const fname = "sqlite3_get_autocommit";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (db) {
const result = f(db);
return result;
};
})();
sqlite3.libversion = (function () {
const fname = "sqlite3_libversion";
const f = Module.cwrap(fname, ...decl(":s"));
return function () {
const result = f();
return result;
};
})();
sqlite3.libversion_number = (function () {
const fname = "sqlite3_libversion_number";
const f = Module.cwrap(fname, ...decl(":n"));
return function () {
const result = f();
return result;
};
})();
sqlite3.limit = (function () {
const fname = "sqlite3_limit";
const f = Module.cwrap(fname, ...decl("nnn:n"));
return function (db, id, newVal) {
const result = f(db, id, newVal);
return result;
};
})();
sqlite3.open_v2 = (function () {
const fname = "sqlite3_open_v2";
const f = Module.cwrap(fname, ...decl("snnn:n"), { async });
return async function (zFilename, flags, zVfs) {
flags = flags || SQLite.SQLITE_OPEN_CREATE | SQLite.SQLITE_OPEN_READWRITE;
zVfs = createUTF8(zVfs);
try {
// Allow retry operations.
const rc = await retry(() => f(zFilename, tmpPtr[0], flags, zVfs));
const db = Module.getValue(tmpPtr[0], "*");
databases.add(db);
Module.ccall("RegisterExtensionFunctions", "void", ["number"], [db]);
check(fname, rc);
return db;
} finally {
Module._sqlite3_free(zVfs);
}
};
})();
sqlite3.progress_handler = function (db, nProgressOps, handler, userData) {
verifyDatabase(db);
Module.progress_handler(db, nProgressOps, handler, userData);
};
sqlite3.reset = (function () {
const fname = "sqlite3_reset";
const f = Module.cwrap(fname, ...decl("n:n"), { async });
return async function (stmt) {
verifyStatement(stmt);
const result = await f(stmt);
return check(fname, result, mapStmtToDB.get(stmt));
};
})();
sqlite3.result = function (context, value) {
switch (typeof value) {
case "number":
if (value === (value | 0)) {
sqlite3.result_int(context, value);
} else {
sqlite3.result_double(context, value);
}
break;
case "string":
sqlite3.result_text(context, value);
break;
default:
if (value instanceof Uint8Array || Array.isArray(value)) {
sqlite3.result_blob(context, value);
} else if (value === null) {
sqlite3.result_null(context);
} else if (typeof value === "bigint") {
return sqlite3.result_int64(context, value);
} else {
console.warn("unknown result converted to null", value);
sqlite3.result_null(context);
}
break;
}
};
sqlite3.result_blob = (function () {
const fname = "sqlite3_result_blob";
const f = Module.cwrap(fname, ...decl("nnnn:n"));
return function (context, value) {
// @ts-ignore
const byteLength = value.byteLength ?? value.length;
const ptr = Module._sqlite3_malloc(byteLength);
Module.HEAPU8.subarray(ptr).set(value);
f(context, ptr, byteLength, sqliteFreeAddress); // void return
};
})();
sqlite3.result_double = (function () {
const fname = "sqlite3_result_double";
const f = Module.cwrap(fname, ...decl("nn:n"));
return function (context, value) {
f(context, value); // void return
};
})();
sqlite3.result_int = (function () {
const fname = "sqlite3_result_int";
const f = Module.cwrap(fname, ...decl("nn:n"));
return function (context, value) {
f(context, value); // void return
};
})();
sqlite3.result_int64 = (function () {
const fname = "sqlite3_result_int64";
const f = Module.cwrap(fname, ...decl("nnn:n"));
return function (context, value) {
if (value > MAX_INT64 || value < MIN_INT64) return SQLite.SQLITE_RANGE;
const lo32 = value & 0xffffffffn;
const hi32 = value >> 32n;
f(context, Number(lo32), Number(hi32)); // void return
};
})();
sqlite3.result_null = (function () {
const fname = "sqlite3_result_null";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (context) {
f(context); // void return
};
})();
sqlite3.result_text = (function () {
const fname = "sqlite3_result_text";
const f = Module.cwrap(fname, ...decl("nnnn:n"));
return function (context, value) {
const ptr = createUTF8(value);
f(context, ptr, -1, sqliteFreeAddress); // void return
};
})();
sqlite3.row = function (stmt) {
const row = [];
const nColumns = sqlite3.data_count(stmt);
for (let i = 0; i < nColumns; ++i) {
const value = sqlite3.column(stmt, i);
// Copy blob if aliasing volatile WebAssembly memory. This avoids an
// unnecessary copy if users monkey patch column_blob to copy.
// @ts-ignore
row.push(value?.buffer === Module.HEAPU8.buffer ? value.slice() : value);
}
return row;
};
sqlite3.set_authorizer = function (db, xAuth, pApp) {
verifyDatabase(db);
// Convert SQLite callback arguments to JavaScript-friendly arguments.
function cvtArgs(_, iAction, p3, p4, p5, p6) {
return [
_,
iAction,
Module.UTF8ToString(p3),
Module.UTF8ToString(p4),
Module.UTF8ToString(p5),
Module.UTF8ToString(p6),
];
}
function adapt(f) {
return f instanceof AsyncFunction
? async (_, iAction, p3, p4, p5, p6) => f(...cvtArgs(_, iAction, p3, p4, p5, p6))
: (_, iAction, p3, p4, p5, p6) => f(...cvtArgs(_, iAction, p3, p4, p5, p6));
}
const result = Module.set_authorizer(db, adapt(xAuth), pApp);
return check("sqlite3_set_authorizer", result, db);
};
sqlite3.sql = (function () {
const fname = "sqlite3_sql";
const f = Module.cwrap(fname, ...decl("n:s"));
return function (stmt) {
verifyStatement(stmt);
const result = f(stmt);
return result;
};
})();
sqlite3.statements = function (db, sql, options = {}) {
const prepare = Module.cwrap(
"sqlite3_prepare_v3",
"number",
["number", "number", "number", "number", "number", "number"],
{ async: true }
);
return (async function* () {
const onFinally = [];
try {
// Encode SQL string to UTF-8.
const utf8 = new TextEncoder().encode(sql);
// Copy encoded string to WebAssembly memory. The SQLite docs say
// zero-termination is a minor optimization so add room for that.
// Also add space for the statement handle and SQL tail pointer.
const allocSize = utf8.byteLength - (utf8.byteLength % 4) + 12;
const pzHead = Module._sqlite3_malloc(allocSize);
const pzEnd = pzHead + utf8.byteLength + 1;
onFinally.push(() => Module._sqlite3_free(pzHead));
Module.HEAPU8.set(utf8, pzHead);
Module.HEAPU8[pzEnd - 1] = 0;
// Use extra space for the statement handle and SQL tail pointer.
const pStmt = pzHead + allocSize - 8;
const pzTail = pzHead + allocSize - 4;
// Ensure that statement handles are not leaked.
let stmt;
function maybeFinalize() {
if (stmt && !options.unscoped) {
sqlite3.finalize(stmt);
}
stmt = 0;
}
onFinally.push(maybeFinalize);
// Loop over statements.
Module.setValue(pzTail, pzHead, "*");
do {
// Reclaim resources for the previous iteration.
maybeFinalize();
// Call sqlite3_prepare_v3() for the next statement.
// Allow retry operations.
const zTail = Module.getValue(pzTail, "*");
const rc = await retry(() => {
return prepare(db, zTail, pzEnd - pzTail, options.flags || 0, pStmt, pzTail);
});
if (rc !== SQLite.SQLITE_OK) {
check("sqlite3_prepare_v3", rc, db);
}
stmt = Module.getValue(pStmt, "*");
if (stmt) {
mapStmtToDB.set(stmt, db);
yield stmt;
}
} while (stmt);
} finally {
while (onFinally.length) {
onFinally.pop()();
}
}
})();
};
sqlite3.step = (function () {
const fname = "sqlite3_step";
const f = Module.cwrap(fname, ...decl("n:n"), { async });
return async function (stmt) {
verifyStatement(stmt);
// Allow retry operations.
const rc = await retry(() => f(stmt));
return check(fname, rc, mapStmtToDB.get(stmt), [SQLite.SQLITE_ROW, SQLite.SQLITE_DONE]);
};
})();
sqlite3.update_hook = function (db, xUpdateHook) {
verifyDatabase(db);
// Convert SQLite callback arguments to JavaScript-friendly arguments.
function cvtArgs(iUpdateType, dbName, tblName, lo32, hi32) {
return [iUpdateType, Module.UTF8ToString(dbName), Module.UTF8ToString(tblName), cvt32x2ToBigInt(lo32, hi32)];
}
function adapt(f) {
return f instanceof AsyncFunction
? async (iUpdateType, dbName, tblName, lo32, hi32) => f(...cvtArgs(iUpdateType, dbName, tblName, lo32, hi32))
: (iUpdateType, dbName, tblName, lo32, hi32) => f(...cvtArgs(iUpdateType, dbName, tblName, lo32, hi32));
}
Module.update_hook(db, adapt(xUpdateHook));
};
sqlite3.value = function (pValue) {
const type = sqlite3.value_type(pValue);
switch (type) {
case SQLite.SQLITE_BLOB:
return sqlite3.value_blob(pValue);
case SQLite.SQLITE_FLOAT:
return sqlite3.value_double(pValue);
case SQLite.SQLITE_INTEGER:
const lo32 = sqlite3.value_int(pValue);
const hi32 = Module.getTempRet0();
return cvt32x2AsSafe(lo32, hi32);
case SQLite.SQLITE_NULL:
return null;
case SQLite.SQLITE_TEXT:
return sqlite3.value_text(pValue);
default:
throw new SQLiteError("unknown type", type);
}
};
sqlite3.value_blob = (function () {
const fname = "sqlite3_value_blob";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (pValue) {
const nBytes = sqlite3.value_bytes(pValue);
const address = f(pValue);
const result = Module.HEAPU8.subarray(address, address + nBytes);
return result;
};
})();
sqlite3.value_bytes = (function () {
const fname = "sqlite3_value_bytes";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (pValue) {
const result = f(pValue);
return result;
};
})();
sqlite3.value_double = (function () {
const fname = "sqlite3_value_double";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (pValue) {
const result = f(pValue);
return result;
};
})();
sqlite3.value_int = (function () {
const fname = "sqlite3_value_int64";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (pValue) {
const result = f(pValue);
return result;
};
})();
sqlite3.value_int64 = (function () {
const fname = "sqlite3_value_int64";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (pValue) {
const lo32 = f(pValue);
const hi32 = Module.getTempRet0();
const result = cvt32x2ToBigInt(lo32, hi32);
return result;
};
})();
sqlite3.value_text = (function () {
const fname = "sqlite3_value_text";
const f = Module.cwrap(fname, ...decl("n:s"));
return function (pValue) {
const result = f(pValue);
return result;
};
})();
sqlite3.value_type = (function () {
const fname = "sqlite3_value_type";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (pValue) {
const result = f(pValue);
return result;
};
})();
sqlite3.vfs_register = function (vfs, makeDefault) {
const result = Module.vfs_register(vfs, makeDefault);
return check("sqlite3_vfs_register", result);
};
function check(fname, result, db = null, allowed = [SQLite.SQLITE_OK]) {
if (allowed.includes(result)) return result;
const message = db ? Module.ccall("sqlite3_errmsg", "string", ["number"], [db]) : fname;
throw new SQLiteError(message, result);
}
// This function is used to automatically retry failed calls that
// have pending retry operations that should allow the retry to
// succeed.
async function retry(f) {
let rc;
do {
// Wait for all pending retry operations to complete. This is
// normally empty on the first loop iteration.
if (Module.retryOps.length) {
await Promise.all(Module.retryOps);
Module.retryOps = [];
}
rc = await f();
// Retry on failure with new pending retry operations.
} while (rc && Module.retryOps.length);
return rc;
}
return sqlite3;
}
// Helper function to use a more compact signature specification.
function decl(s) {
const result = [];
const m = s.match(/([ns@]*):([nsv@])/);
switch (m[2]) {
case "n":
result.push("number");
break;
case "s":
result.push("string");
break;
case "v":
result.push(null);
break;
}
const args = [];
for (let c of m[1]) {
switch (c) {
case "n":
args.push("number");
break;
case "s":
args.push("string");
break;
}
}
result.push(args);
return result;
}

View File

@@ -0,0 +1,275 @@
// Primary result codes.
// https://www.sqlite.org/rescode.html
export const SQLITE_OK = 0;
export const SQLITE_ERROR = 1;
export const SQLITE_INTERNAL = 2;
export const SQLITE_PERM = 3;
export const SQLITE_ABORT = 4;
export const SQLITE_BUSY = 5;
export const SQLITE_LOCKED = 6;
export const SQLITE_NOMEM = 7;
export const SQLITE_READONLY = 8;
export const SQLITE_INTERRUPT = 9;
export const SQLITE_IOERR = 10;
export const SQLITE_CORRUPT = 11;
export const SQLITE_NOTFOUND = 12;
export const SQLITE_FULL = 13;
export const SQLITE_CANTOPEN = 14;
export const SQLITE_PROTOCOL = 15;
export const SQLITE_EMPTY = 16;
export const SQLITE_SCHEMA = 17;
export const SQLITE_TOOBIG = 18;
export const SQLITE_CONSTRAINT = 19;
export const SQLITE_MISMATCH = 20;
export const SQLITE_MISUSE = 21;
export const SQLITE_NOLFS = 22;
export const SQLITE_AUTH = 23;
export const SQLITE_FORMAT = 24;
export const SQLITE_RANGE = 25;
export const SQLITE_NOTADB = 26;
export const SQLITE_NOTICE = 27;
export const SQLITE_WARNING = 28;
export const SQLITE_ROW = 100;
export const SQLITE_DONE = 101;
// Extended error codes.
export const SQLITE_IOERR_ACCESS = 3338;
export const SQLITE_IOERR_CHECKRESERVEDLOCK = 3594;
export const SQLITE_IOERR_CLOSE = 4106;
export const SQLITE_IOERR_DATA = 8202;
export const SQLITE_IOERR_DELETE = 2570;
export const SQLITE_IOERR_DELETE_NOENT = 5898;
export const SQLITE_IOERR_DIR_FSYNC = 1290;
export const SQLITE_IOERR_FSTAT = 1802;
export const SQLITE_IOERR_FSYNC = 1034;
export const SQLITE_IOERR_GETTEMPPATH = 6410;
export const SQLITE_IOERR_LOCK = 3850;
export const SQLITE_IOERR_NOMEM = 3082;
export const SQLITE_IOERR_READ = 266;
export const SQLITE_IOERR_RDLOCK = 2314;
export const SQLITE_IOERR_SEEK = 5642;
export const SQLITE_IOERR_SHORT_READ = 522;
export const SQLITE_IOERR_TRUNCATE = 1546;
export const SQLITE_IOERR_UNLOCK = 2058;
export const SQLITE_IOERR_VNODE = 6922;
export const SQLITE_IOERR_WRITE = 778;
export const SQLITE_IOERR_BEGIN_ATOMIC = 7434;
export const SQLITE_IOERR_COMMIT_ATOMIC = 7690;
export const SQLITE_IOERR_ROLLBACK_ATOMIC = 7946;
// Other extended result codes.
export const SQLITE_CONSTRAINT_CHECK = 275;
export const SQLITE_CONSTRAINT_COMMITHOOK = 531;
export const SQLITE_CONSTRAINT_FOREIGNKEY = 787;
export const SQLITE_CONSTRAINT_FUNCTION = 1043;
export const SQLITE_CONSTRAINT_NOTNULL = 1299;
export const SQLITE_CONSTRAINT_PINNED = 2835;
export const SQLITE_CONSTRAINT_PRIMARYKEY = 1555;
export const SQLITE_CONSTRAINT_ROWID = 2579;
export const SQLITE_CONSTRAINT_TRIGGER = 1811;
export const SQLITE_CONSTRAINT_UNIQUE = 2067;
export const SQLITE_CONSTRAINT_VTAB = 2323;
// Open flags.
// https://www.sqlite.org/c3ref/c_open_autoproxy.html
export const SQLITE_OPEN_READONLY = 0x00000001;
export const SQLITE_OPEN_READWRITE = 0x00000002;
export const SQLITE_OPEN_CREATE = 0x00000004;
export const SQLITE_OPEN_DELETEONCLOSE = 0x00000008;
export const SQLITE_OPEN_EXCLUSIVE = 0x00000010;
export const SQLITE_OPEN_AUTOPROXY = 0x00000020;
export const SQLITE_OPEN_URI = 0x00000040;
export const SQLITE_OPEN_MEMORY = 0x00000080;
export const SQLITE_OPEN_MAIN_DB = 0x00000100;
export const SQLITE_OPEN_TEMP_DB = 0x00000200;
export const SQLITE_OPEN_TRANSIENT_DB = 0x00000400;
export const SQLITE_OPEN_MAIN_JOURNAL = 0x00000800;
export const SQLITE_OPEN_TEMP_JOURNAL = 0x00001000;
export const SQLITE_OPEN_SUBJOURNAL = 0x00002000;
export const SQLITE_OPEN_SUPER_JOURNAL = 0x00004000;
export const SQLITE_OPEN_NOMUTEX = 0x00008000;
export const SQLITE_OPEN_FULLMUTEX = 0x00010000;
export const SQLITE_OPEN_SHAREDCACHE = 0x00020000;
export const SQLITE_OPEN_PRIVATECACHE = 0x00040000;
export const SQLITE_OPEN_WAL = 0x00080000;
export const SQLITE_OPEN_NOFOLLOW = 0x01000000;
// Locking levels.
// https://www.sqlite.org/c3ref/c_lock_exclusive.html
export const SQLITE_LOCK_NONE = 0;
export const SQLITE_LOCK_SHARED = 1;
export const SQLITE_LOCK_RESERVED = 2;
export const SQLITE_LOCK_PENDING = 3;
export const SQLITE_LOCK_EXCLUSIVE = 4;
// Device characteristics.
// https://www.sqlite.org/c3ref/c_iocap_atomic.html
export const SQLITE_IOCAP_ATOMIC = 0x00000001;
export const SQLITE_IOCAP_ATOMIC512 = 0x00000002;
export const SQLITE_IOCAP_ATOMIC1K = 0x00000004;
export const SQLITE_IOCAP_ATOMIC2K = 0x00000008;
export const SQLITE_IOCAP_ATOMIC4K = 0x00000010;
export const SQLITE_IOCAP_ATOMIC8K = 0x00000020;
export const SQLITE_IOCAP_ATOMIC16K = 0x00000040;
export const SQLITE_IOCAP_ATOMIC32K = 0x00000080;
export const SQLITE_IOCAP_ATOMIC64K = 0x00000100;
export const SQLITE_IOCAP_SAFE_APPEND = 0x00000200;
export const SQLITE_IOCAP_SEQUENTIAL = 0x00000400;
export const SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 0x00000800;
export const SQLITE_IOCAP_POWERSAFE_OVERWRITE = 0x00001000;
export const SQLITE_IOCAP_IMMUTABLE = 0x00002000;
export const SQLITE_IOCAP_BATCH_ATOMIC = 0x00004000;
// xAccess flags.
// https://www.sqlite.org/c3ref/c_access_exists.html
export const SQLITE_ACCESS_EXISTS = 0;
export const SQLITE_ACCESS_READWRITE = 1;
export const SQLITE_ACCESS_READ = 2;
// File control opcodes
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlbeginatomicwrite
export const SQLITE_FCNTL_LOCKSTATE = 1;
export const SQLITE_FCNTL_GET_LOCKPROXYFILE = 2;
export const SQLITE_FCNTL_SET_LOCKPROXYFILE = 3;
export const SQLITE_FCNTL_LAST_ERRNO = 4;
export const SQLITE_FCNTL_SIZE_HINT = 5;
export const SQLITE_FCNTL_CHUNK_SIZE = 6;
export const SQLITE_FCNTL_FILE_POINTER = 7;
export const SQLITE_FCNTL_SYNC_OMITTED = 8;
export const SQLITE_FCNTL_WIN32_AV_RETRY = 9;
export const SQLITE_FCNTL_PERSIST_WAL = 10;
export const SQLITE_FCNTL_OVERWRITE = 11;
export const SQLITE_FCNTL_VFSNAME = 12;
export const SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13;
export const SQLITE_FCNTL_PRAGMA = 14;
export const SQLITE_FCNTL_BUSYHANDLER = 15;
export const SQLITE_FCNTL_TEMPFILENAME = 16;
export const SQLITE_FCNTL_MMAP_SIZE = 18;
export const SQLITE_FCNTL_TRACE = 19;
export const SQLITE_FCNTL_HAS_MOVED = 20;
export const SQLITE_FCNTL_SYNC = 21;
export const SQLITE_FCNTL_COMMIT_PHASETWO = 22;
export const SQLITE_FCNTL_WIN32_SET_HANDLE = 23;
export const SQLITE_FCNTL_WAL_BLOCK = 24;
export const SQLITE_FCNTL_ZIPVFS = 25;
export const SQLITE_FCNTL_RBU = 26;
export const SQLITE_FCNTL_VFS_POINTER = 27;
export const SQLITE_FCNTL_JOURNAL_POINTER = 28;
export const SQLITE_FCNTL_WIN32_GET_HANDLE = 29;
export const SQLITE_FCNTL_PDB = 30;
export const SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31;
export const SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32;
export const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33;
export const SQLITE_FCNTL_LOCK_TIMEOUT = 34;
export const SQLITE_FCNTL_DATA_VERSION = 35;
export const SQLITE_FCNTL_SIZE_LIMIT = 36;
export const SQLITE_FCNTL_CKPT_DONE = 37;
export const SQLITE_FCNTL_RESERVE_BYTES = 38;
export const SQLITE_FCNTL_CKPT_START = 39;
// Fundamental datatypes.
// https://www.sqlite.org/c3ref/c_blob.html
export const SQLITE_INTEGER = 1;
export const SQLITE_FLOAT = 2;
export const SQLITE_TEXT = 3;
export const SQLITE_BLOB = 4;
export const SQLITE_NULL = 5;
// Special destructor behavior.
// https://www.sqlite.org/c3ref/c_static.html
export const SQLITE_STATIC = 0;
export const SQLITE_TRANSIENT = -1;
// Text encodings.
// https://sqlite.org/c3ref/c_any.html
export const SQLITE_UTF8 = 1; /* IMP: R-37514-35566 */
export const SQLITE_UTF16LE = 2; /* IMP: R-03371-37637 */
export const SQLITE_UTF16BE = 3; /* IMP: R-51971-34154 */
export const SQLITE_UTF16 = 4; /* Use native byte order */
// Module constraint ops.
export const SQLITE_INDEX_CONSTRAINT_EQ = 2;
export const SQLITE_INDEX_CONSTRAINT_GT = 4;
export const SQLITE_INDEX_CONSTRAINT_LE = 8;
export const SQLITE_INDEX_CONSTRAINT_LT = 16;
export const SQLITE_INDEX_CONSTRAINT_GE = 32;
export const SQLITE_INDEX_CONSTRAINT_MATCH = 64;
export const SQLITE_INDEX_CONSTRAINT_LIKE = 65;
export const SQLITE_INDEX_CONSTRAINT_GLOB = 66;
export const SQLITE_INDEX_CONSTRAINT_REGEXP = 67;
export const SQLITE_INDEX_CONSTRAINT_NE = 68;
export const SQLITE_INDEX_CONSTRAINT_ISNOT = 69;
export const SQLITE_INDEX_CONSTRAINT_ISNOTNULL = 70;
export const SQLITE_INDEX_CONSTRAINT_ISNULL = 71;
export const SQLITE_INDEX_CONSTRAINT_IS = 72;
export const SQLITE_INDEX_CONSTRAINT_FUNCTION = 150;
export const SQLITE_INDEX_SCAN_UNIQUE = 1; /* Scan visits at most = 1 row */
// Function flags
export const SQLITE_DETERMINISTIC = 0x000000800;
export const SQLITE_DIRECTONLY = 0x000080000;
export const SQLITE_SUBTYPE = 0x000100000;
export const SQLITE_INNOCUOUS = 0x000200000;
// Sync flags
export const SQLITE_SYNC_NORMAL = 0x00002;
export const SQLITE_SYNC_FULL = 0x00003;
export const SQLITE_SYNC_DATAONLY = 0x00010;
// Authorizer action codes
export const SQLITE_CREATE_INDEX = 1;
export const SQLITE_CREATE_TABLE = 2;
export const SQLITE_CREATE_TEMP_INDEX = 3;
export const SQLITE_CREATE_TEMP_TABLE = 4;
export const SQLITE_CREATE_TEMP_TRIGGER = 5;
export const SQLITE_CREATE_TEMP_VIEW = 6;
export const SQLITE_CREATE_TRIGGER = 7;
export const SQLITE_CREATE_VIEW = 8;
export const SQLITE_DELETE = 9;
export const SQLITE_DROP_INDEX = 10;
export const SQLITE_DROP_TABLE = 11;
export const SQLITE_DROP_TEMP_INDEX = 12;
export const SQLITE_DROP_TEMP_TABLE = 13;
export const SQLITE_DROP_TEMP_TRIGGER = 14;
export const SQLITE_DROP_TEMP_VIEW = 15;
export const SQLITE_DROP_TRIGGER = 16;
export const SQLITE_DROP_VIEW = 17;
export const SQLITE_INSERT = 18;
export const SQLITE_PRAGMA = 19;
export const SQLITE_READ = 20;
export const SQLITE_SELECT = 21;
export const SQLITE_TRANSACTION = 22;
export const SQLITE_UPDATE = 23;
export const SQLITE_ATTACH = 24;
export const SQLITE_DETACH = 25;
export const SQLITE_ALTER_TABLE = 26;
export const SQLITE_REINDEX = 27;
export const SQLITE_ANALYZE = 28;
export const SQLITE_CREATE_VTABLE = 29;
export const SQLITE_DROP_VTABLE = 30;
export const SQLITE_FUNCTION = 31;
export const SQLITE_SAVEPOINT = 32;
export const SQLITE_COPY = 0;
export const SQLITE_RECURSIVE = 33;
// Authorizer return codes
export const SQLITE_DENY = 1;
export const SQLITE_IGNORE = 2;
// Limit categories
export const SQLITE_LIMIT_LENGTH = 0;
export const SQLITE_LIMIT_SQL_LENGTH = 1;
export const SQLITE_LIMIT_COLUMN = 2;
export const SQLITE_LIMIT_EXPR_DEPTH = 3;
export const SQLITE_LIMIT_COMPOUND_SELECT = 4;
export const SQLITE_LIMIT_VDBE_OP = 5;
export const SQLITE_LIMIT_FUNCTION_ARG = 6;
export const SQLITE_LIMIT_ATTACHED = 7;
export const SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8;
export const SQLITE_LIMIT_VARIABLE_NUMBER = 9;
export const SQLITE_LIMIT_TRIGGER_DEPTH = 10;
export const SQLITE_LIMIT_WORKER_THREADS = 11;
export const SQLITE_PREPARE_PERSISTENT = 0x01;
export const SQLITE_PREPARE_NORMALIZED = 0x02;
export const SQLITE_PREPARE_NO_VTAB = 0x04;

View File

@@ -0,0 +1,62 @@
/* eslint-disable no-var */
declare namespace Asyncify {
function handleAsync(f: () => Promise<any>);
}
declare function UTF8ToString(ptr: number): string;
declare function lengthBytesUTF8(s: string): number;
declare function stringToUTF8(s: string, p: number, n: number);
declare function ccall(name: string, returns: string, args: Array<any>, options?: object): any;
declare function getValue(ptr: number, type: string): number;
declare function setValue(ptr: number, value: number, type: string): number;
declare function mergeInto(library: object, methods: object): void;
declare var HEAPU8: Uint8Array;
declare var HEAPU32: Uint32Array;
declare var LibraryManager;
declare var Module;
declare var _vfsAccess;
declare var _vfsCheckReservedLock;
declare var _vfsClose;
declare var _vfsDelete;
declare var _vfsDeviceCharacteristics;
declare var _vfsFileControl;
declare var _vfsFileSize;
declare var _vfsLock;
declare var _vfsOpen;
declare var _vfsRead;
declare var _vfsSectorSize;
declare var _vfsSync;
declare var _vfsTruncate;
declare var _vfsUnlock;
declare var _vfsWrite;
declare var _jsFunc;
declare var _jsStep;
declare var _jsFinal;
declare var _modStruct;
declare var _modCreate;
declare var _modConnect;
declare var _modBestIndex;
declare var _modDisconnect;
declare var _modDestroy;
declare var _modOpen;
declare var _modClose;
declare var _modFilter;
declare var _modNext;
declare var _modEof;
declare var _modColumn;
declare var _modRowid;
declare var _modUpdate;
declare var _modBegin;
declare var _modSync;
declare var _modCommit;
declare var _modRollback;
declare var _modFindFunction;
declare var _modRename;
declare var _jsAuth;
declare var _jsProgress;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.