Docs
CR-SQLite
JS & The Browser
WASM & Browser APIs

WASM & Browser APIs

Vulcan provides a wasm build of cr-sqlite that can be used in browser environments in WebWorkers (opens in a new tab), the Main Thread (opens in a new tab), ServiceWorkers (opens in a new tab) and SharedWorkers (opens in a new tab). The WASM build of cr-sqlite is available on npm (opens in a new tab) and esm.sh (opens in a new tab).

Basic Usage

Install the package:

> npm install @vlcn.io/crsqlite-wasm

Import the package:

import initWasm from "@vlcn.io/crsqlite-wasm";
import wasmUrl from "@vlcn.io/crsqlite-wasm/crsqlite.wasm?url";

The wasmUrl import assumes you are using a bundler which understands the ?url import syntax (such as vite (opens in a new tab)).

Use it:

const sqlite = await initWasm(() => wasmUrl);
const db = await sqlite.open("my-database.db");

For more examples, see this annotated notebook of using cr-sqlite in the browser (opens in a new tab).

API Docs

initWasm

Initializes the wasm module. Returns the top level SQLite API.

function initWasm(() => string): Promise<SQLite>;

Params:

  1. wasmUrl - points to where to fetch the wasm file from.

Example:

import initWasm from "@vlcn.io/crsqlite-wasm";
import wasmUrl from "@vlcn.io/crsqlite-wasm/crsqlite.wasm?url";
 
const sqlite = await initWasm(() => wasmUrl);

SQLite::open

Opens a database connection.

function open(path?: string): Promise<DB>;

Params:

  1. path - the path to the database file. If not provided, an in-memory database will be created.

Example:

const db = await sqlite.open("my-database.db");

DB::exec

Executes a SQL statement. Returns a promise that resolves when the statement has been executed. Returns no value. See execO and execA to receive values back. Rejects the promise on failure.

For better perfromance, preparing your queries is recommended.

function exec(sql: string, bind?: SQLiteCompatibleType[]): Promise<void>;

Params:

  1. sql - the sql to execute

Example:

await db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, name TEXT)");

DB::execO

Executes a SQL statement, returning the selected rows as objects.

function execO<T extends {}>(sql: string, bind?: SQLiteCompatibleType[]): Promise<T[]>;

Params:

  1. sql - the sql to execute
  2. bind - optional array of values to bind to the query

Example:

const users = await db.execO<{ id: bigint, name: string }>("SELECT * FROM users");

DB::execA

The same as execO but returns the results as arrays rather than objects.

function execA<T extends any[]>(sql: string, bind?: SQLiteCompatibleType[]): Promise<T[]>;

Example:

const users = await db.execA<[bigint, string]>("SELECT * FROM users");

DB::execMany

Executes multiple SQL statements. Returns a promise that resolves when all statements have been executed. Returns no value. Rejects the promise on failure.

function execMany(sql: string[]): Promise<void>;

Params:

  1. sql - the sql to execute

Example:

await db.execMany([
  `CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, name TEXT);`,
  `CREATE TABLE posts (id INTEGER PRIMARY KEY NOT NULL, user_id INTEGER, title TEXT);`,
]);

DB::prepare

Prepares a statement that is intended to be re-used. This can significantly improve performance for queries that are frequently executed as the query planning step is only run a single time rather than each time the query is run.

⚠️

Prepared statements must be finalized (see Stmt::finalize)! Not doing so will result in a memory leak. Future versions may include auto-finalization of prepared statements.

function prepare(sql: string): Promise<Stmt>;

Params:

  1. sql - the sql to prepare

Example:

const stmt = await db.prepare("SELECT * FROM users WHERE id = ?");

DB::tx

Executes the provided lambda in a transaction. Aside from making sure writes happen atomically and reads are isolated, transactions also provide a significant performance boost.

Wrapping a batch of reads or a batch of writes into a transaction will be an order of magnitude quicker than executing them outside of a transaction. This is due to the fact that SQLite implicitly opens and closes a transaction for each query if it is not already part of one. Grouping queries into a transaction eliminates this extra overhead for each query.

The React useQuery hook will do this batching for you on read.

⚠️

You must use the provided tx passed to the lambda to execute queries in the transaction. Failing to do so will deadlock the database.

function tx<T>(fn: (tx: TXAsync) => Promise<T>): Promise<T>;

Params:

  1. fn - the lambda to execute in a transaction. The lambda is passed a TXAsync instance which is identical to DB except that it is intended to be used within a transaction to execute queries.

Example:

await db.tx(async (tx) => {
  await tx.exec("INSERT INTO users (name) VALUES ('Alice')");
  await tx.exec("INSERT INTO users (name) VALUES ('Bob')");
});

Example with a prepared statement:

// statements can be prepared outside the tx if desired
const stmt = await db.prepare("INSERT INTO users (id, name) VALUES (?, ?)");
 
await db.tx(async (tx) => {
  // run the statement with the tx
  await stmt.run(tx, [shortuuid(), "Alice"]);
  await stmt.run(tx, [shortuuid(), "Bob"]);
});
 
await stmt.finalize(null);

DB::close

Close the database.

function close(): Promise<void>;

Example:

await db.close();

DB::onUpdate

Register for update notifications whenever a row is inserted, updated, or deleted. See the sqlite update_hook docs (opens in a new tab) for more information.

Unlike the sqlite update_hook, however, any number of callbacks may be registered. Each callback will be called in the order it was registered.

function onUpdate(fn: (type: UpdateType, dbName: string, tblName: string, rowid: bigint) => void): void;

onUpdate will eventually be superseeded by a native reactive query layer that allows you to subscribe to specific queries. A proof of concept implementation of this is available in the react integration via the useQuery hook.

Params:

  1. fn - the callback to register

Example:

db.onUpdate((type, dbName, tblName, rowid) => {
  console.log(`row ${rowid} in ${dbName}.${tblName} was ${type}`);
});

Stmt::run

Runs the statement, returning nothing. See get and all for variants that return data.

function run(tx: TXAsync | null, ...bind?: SQLiteCompatibleType[]): Promise<void>;

Params:

  1. tx - the transaction to run the statement in. null will run the statement against the root database.
  2. bind - optional values to bind to the query

Example:

const stmt = await db.prepare("INSERT INTO users (id, name) VALUES (?, ?)");
await stmt.run(null, [shortuuid(), "Alice"]);
await stmt.run(null, [shortuuid(), "Bob"]);

Stmt::get

Runs the statement, returning the first row as an object.

function get<T extends {}>(tx: TXAsync | null, ...bind?: SQLiteCompatibleType[]): Promise<T | null>;

Params:

  1. tx - the transaction to run the statement in. null will run the statement against the root database.
  2. bind - optional values to bind to the query

Example:

const stmt = await db.prepare("SELECT * FROM users WHERE id = ?");
const user = await stmt.get<{ id: bigint, name: string }>(null, [id]);

Stmt::all

Runs the statement, returning all rows as an array of objects.

function all<T extends {}>(tx: TXAsync | null, ...bind?: SQLiteCompatibleType[]): Promise<T[]>;

Params:

  1. tx - the transaction to run the statement in. null will run the statement against the root database.
  2. bind - optional values to bind to the query

Example:

const stmt = await db.prepare("SELECT * FROM users");
const users = await stmt.all<{ id: bigint, name: string }>(null);

Stmt::raw

Toggle raw mode. When on the statement will return arrays of arrays rather than arrays of objects.

Note: an API that mutates an object to change its behavior is not good. This will be changed in a future release and was only added for compatibility with better-sqlite3 (opens in a new tab).

function raw(isRaw?: boolean): this;

Params:

  1. isRaw - whether to enable raw mode. If not provided, raw mode will be disabled.

Stmt::bind

Bind values to the prepared statement.

function bind(args: SQLiteCompatibleType[]): this;

Params:

  1. bind - values to bind to the query

Example:

const stmt = await db.prepare("SELECT * FROM users WHERE id = ?");
stmt.bind([id]);
const user = await stmt.get<{ id: bigint, name: string }>(null);

Stmt::finalize

Reclaims the resources used by the prepared statement. Must be called when you are done with the statement in order to prevent resource leaks. Future versions may hook this up to the garbage collector (opens in a new tab) to prevent leaks automatically.

function finalize(tx: TXAsync | null): Promise<void>;

Params:

  1. tx - If you prepares the statement within a transaction and want to finalize it in the same transaction, pass that transaction in here. Use null otherwise. Generally you should prepare and finalize statements against the root db rather than in a transaction. Statements prepared at the root can still be used inside of transactions.

Example:

const stmt = await db.prepare("INSERT INTO users (id, name) VALUES (?, ?)");
await stmt.run(null, [shortuuid(), "Alice"]);
await stmt.run(null, [shortuuid(), "Bob"]);
await stmt.finalize(null);

TXAsync

All methods available on DB are also avaialble on TXAsync. The only difference is that TXAsync is intended to be used within a transaction.