SQLite Wasm

repository·main·Indexed 21 days ago

https://github.com/sqlite/sqlite-wasm

An 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.

Tokens
46.5K
Snippets
118
Records
175
Agent score
75%

What's inside @sqlite.org/sqlite-wasm

  1. Use SQLite Wasm in the main thread (without OPFS)

    main

    If 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.DB with the 'ct' (connection transient) mode.

    Implementation Pattern:

    1. Import sqlite3InitModule from @sqlite.org/sqlite-wasm.
    2. Await sqlite3InitModule() to get the sqlite3 object.
    3. 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();
  2. Use SQLite Wasm in a Web Worker with OPFS support

    main

    To 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-origin
    • Cross-Origin-Embedder-Policy: require-corp

    Implementation Pattern:

    1. Spawn a module-type worker from your main thread.
    2. In the worker, import sqlite3InitModule from @sqlite.org/sqlite-wasm.
    3. Use sqlite3.oo1.OpfsDb for persistent storage if 'opfs' in sqlite3 is true, otherwise fallback to sqlite3.oo1.DB for 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();
  3. Overview of kvvfs (Key/Value VFS)

    main

    The kvvfs (Key/Value VFS) is an SQLite3 VFS designed to delegate storage of database pages and metadata to a JavaScript Key/Value store (like localStorage or sessionStorage).

    Key Characteristics

    • Purpose: Designed to support storage in environments where a standard filesystem is unavailable, specifically targeting JS Storage objects.
    • 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 localStorage and sessionStorage with 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.

    Use Cases

    • Small databases (approx. 2-3MB) that fit within sessionStorage or localStorage.
    • Scenarios requiring JSON-friendly database portability.
    • Environments where POSIX I/O dependencies must be avoided.
  4. Understand the role of sqlite3-opfs-async-proxy.js

    main

    The sqlite3-opfs-async-proxy.js file 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 to sqlite3-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, and Atomics.
    • 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: opfs and opfs-wl (Web Locks version).
  5. Understand the Virtual File System (FS) Abstractions

    main

    The FS object 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:

    1. FSNode: Represents a file, directory, symbolic link, or character device. Each node has a mode (defining its type and permissions), an id (inode), and node_ops (methods for managing the node itself, like getattr, lookup, or rename).
    2. FSStream: Represents an open file descriptor (FD) and the current state of an I/O operation. It tracks the position (offset) and flags (read, write, append) and uses stream_ops for actual data movement (like read, write, llseek, or mmap).

    When a file is opened, an FSNode is associated with an FSStream. Operations on the stream (e.g., write) affect the underlying node's data.

  6. Manage memory for SQLiteStruct instances

    main

    The SQLiteStruct class 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 a WasmPointer to 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 ondispose property to handle additional resource cleanup (like C-allocated strings or other structs).

    ondispose can be:

    • A function: Called with the instance as this.
    • An array containing functions, other SQLiteStruct instances (calls their .dispose()), WasmPointer values (freed via sqlite3.wasm.dealloc()), or strings (used for documentation/annotation).
    const m = new MyStruct();
    // ... use m ...
    m.dispose();
  7. Understand the SQLite Wasm Module lifecycle

    main

    The sqlite3-bundler-friendly.mjs script manages the initialization of the Wasm runtime through a specific execution sequence:

    1. Dependency Fulfillment: The run() function waits until runDependencies reaches zero.
    2. preRun(): Executes any user-defined or system-defined pre-run logic.
    3. doRun():
      • Sets Module['calledRun'] = true.
      • Calls initRuntime() to initialize the Wasm environment.
      • Resolves the readyPromiseResolve to signal the Module is ready.
      • Triggers the Module['onRuntimeInitialized'] callback if provided.
    4. postRun(): Executes any logic required after the runtime is fully initialized.

    Developers should use the onRuntimeInitialized callback or await the readyPromise to ensure the SQLite engine is ready for API calls.

  8. How `exec` row callbacks work in `sqlite3Worker1Promiser`

    main

    When calling exec via the promiser, you can provide a callback function 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 by rowMode, 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: undefined
    • rowNumber: null

    Note: You must pass a function for the callback property. Passing a string (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);
      }
    });
  9. Use the pstack pseudo-stack for fast, short-lived allocations

    main

    The wasm.pstack is 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:

    1. Save the current position using pstack.pointer.
    2. Allocate memory using pstack.alloc(n) or pstack.allocChunks(n, sz).
    3. Use the memory.
    4. Must call pstack.restore(savedPosition) to release the memory.

    Key Methods:

    • pstack.alloc(n): Allocates n bytes (or an IR string like 'i32'). Always returns 8-byte aligned addresses.
    • pstack.allocChunks(n, sz): Allocates n chunks of sz bytes and returns an array of pointers.
    • pstack.allocPtr(n, safePtrSize): Convenience wrapper. If n > 1, returns chunks. If safePtrSize is true, it ensures 8-byte alignment (safe for 64-bit values).
    • pstack.call(f): A helper that automatically handles the pointer -> f(sqlite3) -> restore lifecycle.

    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
    });
  10. Use StructType to manage C-style structs in JavaScript

    main

    The StructType class (and instances created via StructBinder) 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 StructType instance that wraps the specific memory offset within the parent struct.
    • Memory Inspection: Use .memoryDump() to get a Uint8Array representing 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. If true, the instance takes ownership of the memory provided in wrap and will free it on disposal.
    • zeroOnDispose: Boolean. If true, 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