SQLite Wasm
repository·main·Indexed 21 days ago
https://github.com/sqlite/sqlite-wasmAn ES Module wrapper around the official SQLite Wasm implementation, providing TypeScript types and a convenient way to use SQLite in web environments. It supports persistent storage via the Origin Private File System (OPFS) when run in a Web Worker, as well as transient databases in the main thread. The package includes a virtual filesystem (FS) API for POSIX-like file operations and provides the `sqlite3InitModule` factory function for initializing the Wasm environment.
What's inside @sqlite.org/sqlite-wasm
- Node.js is currently supported, but only for in-memory databases without persistence. You cannot use persistent file storage in a Node.js environment with this package.
Install @sqlite.org/sqlite-wasm via npm
mainInstall the SQLite Wasm ES Module wrapper using npm:
npm install @sqlite.org/sqlite-wasmUse SQLite Wasm in the main thread (without OPFS)
mainIf you run SQLite in the main thread, you cannot use the Origin Private File System (OPFS) for persistence. You must use the transient database mode via
sqlite3.oo1.DBwith the'ct'(connection transient) mode.Implementation Pattern:
- Import
sqlite3InitModulefrom@sqlite.org/sqlite-wasm. - Await
sqlite3InitModule()to get thesqlite3object. - Instantiate the database using
new sqlite3.oo1.DB('/path/to/db', 'ct').
import sqlite3InitModule from '@sqlite.org/sqlite-wasm'; const start = (sqlite3) => { console.log('Running SQLite3 version', sqlite3.version.libVersion); const db = new sqlite3.oo1.DB('/mydb.sqlite3', 'ct'); // Your SQLite code here; }; const initializeSQLite = async () => { try { console.log('Loading and initializing SQLite3 module...'); const sqlite3 = await sqlite3InitModule(); console.log('Done initializing. Running demo...'); start(sqlite3); } catch (err) { console.error('Initialization error:', err.name, err.message); } }; initializeSQLite();- Import
Use SQLite Wasm in a Web Worker with OPFS support
mainTo use the Origin Private File System (OPFS) for persistent storage, you must run SQLite in a Web Worker.
Requirements: Your server must serve the following HTTP headers to enable the necessary security context:
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp
Implementation Pattern:
- Spawn a module-type worker from your main thread.
- In the worker, import
sqlite3InitModulefrom@sqlite.org/sqlite-wasm. - Use
sqlite3.oo1.OpfsDbfor persistent storage if'opfs' in sqlite3is true, otherwise fallback tosqlite3.oo1.DBfor a transient database.
// In `main.js`. const worker = new Worker('worker.js', { type: 'module' }); // In `worker.js`. import sqlite3InitModule from '@sqlite.org/sqlite-wasm'; const start = (sqlite3) => { console.log('Running SQLite3 version', sqlite3.version.libVersion); const db = 'opfs' in sqlite3 ? new sqlite3.oo1.OpfsDb('/mydb.sqlite3') : new sqlite3.oo1.DB('/mydb.sqlite3', 'ct'); console.log( 'opfs' in sqlite3 ? `OPFS is available, created persisted database at ${db.filename}` : `OPFS is not available, created transient database ${db.filename}`, ); // Your SQLite code here. }; const initializeSQLite = async () => { try { console.log('Loading and initializing SQLite3 module...'); const sqlite3 = await sqlite3InitModule(); console.log('Done initializing. Running demo...'); start(sqlite3); } catch (err) { console.error('Initialization error:', err.name, err.message); } }; initializeSQLite();Overview of kvvfs (Key/Value VFS)
mainThe kvvfs (Key/Value VFS) is an SQLite3 VFS designed to delegate storage of database pages and metadata to a JavaScript Key/Value store (like
localStorageorsessionStorage).Key Characteristics
- Purpose: Designed to support storage in environments where a standard filesystem is unavailable, specifically targeting JS
Storageobjects. - Efficiency: It is less efficient than an in-memory database due to the overhead of encoding/decoding database pages into an ASCII format for storage.
- Interchange Format: The design provides a JSON-friendly format for exporting and importing databases.
- Versions:
- Version 1: Uses
localStorageandsessionStoragewith a specific ASCII encoding and key prefixing (kvvfs-NAME-). - Version 2: Supports arbitrary
Storage-compatible objects and uses a simplified, more space-efficient key format for transient storage objects.
- Version 1: Uses
Use Cases
- Small databases (approx. 2-3MB) that fit within
sessionStorageorlocalStorage. - Scenarios requiring JSON-friendly database portability.
- Environments where POSIX I/O dependencies must be avoided.
- Purpose: Designed to support storage in environments where a standard filesystem is unavailable, specifically targeting JS
Understand the role of sqlite3-opfs-async-proxy.js
mainThe
sqlite3-opfs-async-proxy.jsfile is a Worker implementation designed to manage asynchronous Origin Private File System (OPFS) handles on behalf of a synchronous SQLite API. It acts as an asynchronous counterpart tosqlite3-vfs-opfs.js.Key characteristics:
- Worker-only: This code must be loaded as a Web Worker; it cannot run on the main thread.
- Communication: It communicates with the synchronous side using a combination of Worker messages,
SharedArrayBuffer, andAtomics. - Implementation Detail: This file is considered an implementation detail of the larger SQLite Wasm ecosystem and is not a public interface. Its details may change without notice.
- VFS Support: It supports two types of Virtual File Systems (VFS) via URL parameters:
opfsandopfs-wl(Web Locks version).
Understand the Virtual File System (FS) Abstractions
mainThe
FSobject provides a Virtual File System (VFS) layer that allows SQLite Wasm to interact with different storage backends (like MEMFS, OPFS, or ProxyFS) through a unified interface. The system is built around two primary abstractions:- FSNode: Represents a file, directory, symbolic link, or character device. Each node has a
mode(defining its type and permissions), anid(inode), andnode_ops(methods for managing the node itself, likegetattr,lookup, orrename). - FSStream: Represents an open file descriptor (FD) and the current state of an I/O operation. It tracks the
position(offset) andflags(read, write, append) and usesstream_opsfor actual data movement (likeread,write,llseek, ormmap).
When a file is opened, an
FSNodeis associated with anFSStream. Operations on the stream (e.g.,write) affect the underlying node's data.- FSNode: Represents a file, directory, symbolic link, or character device. Each node has a
Manage memory for SQLiteStruct instances
mainThe
SQLiteStructclass is a base class for JS wrappers around WASM heap memory.- Creation: Calling
new SQLiteStruct()creates a new instance in the WASM heap (JS owns the memory). Passing aWasmPointerto the constructor creates a wrapper for an existing instance (JS does not own the memory). - Cleanup: You must call
.dispose()when finished with an instance to free its memory, provided no C-level code is still using it. Calling.dispose()multiple times is safe. - Custom Cleanup: You can define an
ondisposeproperty to handle additional resource cleanup (like C-allocated strings or other structs).
ondisposecan be:- A function: Called with the instance as
this. - An array containing functions, other
SQLiteStructinstances (calls their.dispose()),WasmPointervalues (freed viasqlite3.wasm.dealloc()), or strings (used for documentation/annotation).
const m = new MyStruct(); // ... use m ... m.dispose();- Creation: Calling
Understand the SQLite Wasm Module lifecycle
mainThe
sqlite3-bundler-friendly.mjsscript manages the initialization of the Wasm runtime through a specific execution sequence:- Dependency Fulfillment: The
run()function waits untilrunDependenciesreaches zero. - preRun(): Executes any user-defined or system-defined pre-run logic.
- doRun():
- Sets
Module['calledRun'] = true. - Calls
initRuntime()to initialize the Wasm environment. - Resolves the
readyPromiseResolveto signal theModuleis ready. - Triggers the
Module['onRuntimeInitialized']callback if provided.
- Sets
- postRun(): Executes any logic required after the runtime is fully initialized.
Developers should use the
onRuntimeInitializedcallback or await thereadyPromiseto ensure the SQLite engine is ready for API calls.- Dependency Fulfillment: The
How `exec` row callbacks work in `sqlite3Worker1Promiser`
mainWhen calling
execvia the promiser, you can provide acallbackfunction in the arguments. This allows you to process rows as they arrive from the worker.Callback Signature
The callback is invoked for each row with a message object containing:
type: An internally-synthesized message type string.row: The row data (format determined byrowMode, defaulting to'array').rowNumber: A 1-based integer.columnNames: An array of column names.
End-of-ResultSet Signal
To indicate the end of the result set, the callback is fired one final time with:
row:undefinedrowNumber:null
Note: You must pass a
functionfor thecallbackproperty. Passing astring(the standard Worker API way) will throw an error in this Promise-based interface.// Example of using the row callback await sq3Promiser('exec', { sql: 'SELECT name, age FROM users', callback: (msg) => { if (msg.rowNumber === null) { console.log('Finished processing rows.'); return; } console.log(`Row #${msg.rowNumber}:`, msg.row); } });Use the pstack pseudo-stack for fast, short-lived allocations
mainThe
wasm.pstackis a specialized, high-speed allocator intended for small, short-lived data (primarily for output pointers). It is much faster than the general-purpose allocator but must be used with a strict lifecycle pattern.Lifecycle Pattern:
- Save the current position using
pstack.pointer. - Allocate memory using
pstack.alloc(n)orpstack.allocChunks(n, sz). - Use the memory.
- Must call
pstack.restore(savedPosition)to release the memory.
Key Methods:
pstack.alloc(n): Allocatesnbytes (or an IR string like'i32'). Always returns 8-byte aligned addresses.pstack.allocChunks(n, sz): Allocatesnchunks ofszbytes and returns an array of pointers.pstack.allocPtr(n, safePtrSize): Convenience wrapper. Ifn > 1, returns chunks. IfsafePtrSizeis true, it ensures 8-byte alignment (safe for 64-bit values).pstack.call(f): A helper that automatically handles thepointer->f(sqlite3)->restorelifecycle.
Properties:
pstack.pointer: The current stack position.pstack.quota: Total bytes available in the pstack.pstack.remaining: Currently available bytes.
// Standard usage pattern const stackPos = wasm.pstack.pointer; try { const ptr = wasm.pstack.alloc(8); // ... use ptr } finally { wasm.pstack.restore(stackPos); } // Using the call helper wasm.pstack.call((sqlite3) => { // ... perform operations });- Save the current position using
Use StructType to manage C-style structs in JavaScript
mainThe
StructTypeclass (and instances created viaStructBinder) provides a way to wrap WASM memory in a JavaScript object that mimics a C struct. This allows you to read and write fields directly on the object, with the underlying data being synchronized to the WASM heap.Key Capabilities
- Field Access: Access struct members as standard JavaScript properties. The library handles the offset and size calculations automatically.
- Memory Management: Instances can be manually disposed using
.dispose()to free associated memory. - Embedded Structs: If a struct contains another struct as a member, accessing that member returns a new
StructTypeinstance that wraps the specific memory offset within the parent struct. - Memory Inspection: Use
.memoryDump()to get aUint8Arrayrepresenting the raw bytes of the struct in the heap.
Instance Configuration
When creating a new instance of a struct via its constructor, you can pass an options object:
extraBytes: An array (e.g.,[int=0]) to allocate additional bytes after the struct.wrap: A pointer to existing memory to wrap. If provided, the instance does not own the memory by default.ownsPointer: Boolean. Iftrue, the instance takes ownership of the memory provided inwrapand will free it on disposal.zeroOnDispose: Boolean. Iftrue, the memory is zeroed out when the object is disposed.
// Example conceptual usage of a StructType instance const f = new Foo(); const b = f.bar; // If 'bar' is an embedded struct, 'b' is a StructType console.log(b.pointer); // Points to the memory location of 'bar' inside 'f' b.dispose(); // Disposing 'b' is safe; 'f' still owns the memory