SQLite for WebAssembly
v3.53.4WebAssemblySQLite 3.53.4 for browsers, Node.js and edge runtimes, precompiled for wasm32, single-threaded and multi-threaded as @crossbind/port-sqlite3-wasm.
npm install @crossbind/port-sqlite3-wasm@betaInstall
crossbind itself arrives with your bundler plugin, or with a new project from npm create crossbind@beta; Bundlers has Vite, Webpack, Rspack and Rollup.
Usage
Each example runs here, in this tab, and prints what the site build checked.
Each example also has a JavaScript only tab: the same task with no C++ file, calling SQLite's own headers from @crossbind/port-sqlite3 directly. All 5 work that way.
Imported straight from JavaScript, the headers need this configuration today; its comments say why.
Insert and query with prepared statements
The calls behind most SQLite code: sqlite3_prepare_v2, sqlite3_bind_text, sqlite3_step and sqlite3_column_*. Values go in as bound parameters, so the apostrophe in "parser's" is data, not SQL.
3.53.4 3 2: write the README 3: fix the parser's bug
The same calls on sqlite3.h as SQLite ships it; its constants are macros without bindings, so the result codes are written out and SQLITE_TRANSIENT, ((sqlite3_destructor_type)-1), is read from a slot holding -1. null (SQLITE_STATIC) is no stand-in: the binding frees its copy of a JavaScript string when the call returns, and a test that allocated before sqlite3_step stored other text. Text columns come back as handles for readCString, and every statement is finalized by hand.
3.53.4 3 2: write the README 3: fix the parser's bug
Apply a batch in one transaction
BEGIN, one prepared upsert (ON CONFLICT DO UPDATE) reset and re-bound for every row, then COMMIT. A row that breaks a CHECK constraint makes the wrapper ROLLBACK, which undoes the rows before it as well.
10000 rows applied alice 13333, bob 13330, carol 13331 rolled back: CHECK constraint failed: total >= 0 alice 13333, bob 13330, carol 13331
The same BEGIN, reset-and-rebind loop and COMMIT; the failing row now makes the code finalize and ROLLBACK by hand, and the rows stay JavaScript arrays instead of one "account,amount" string. sqlite3_column_int64 returns a BigInt. Each call is its own round trip to the worker, so the 10000 rows take about 40000 of them (0.85 s in headless Chromium when this was checked) where the C++ version crossed once.
10000 rows applied alice 13333, bob 13330, carol 13331 rolled back: CHECK constraint failed: total >= 0 alice 13333, bob 13330, carol 13331
Query JSON documents with SQL
Keep JSON as it arrives and ask questions in SQL: ->> reads a field, json_each turns an array into rows, and json_group_object and json_group_array build JSON answers.
{"ada":13.75,"linus":15.0}
{"pen":12,"pad":3,"ink":1}
["ada","linus"]The SQL does all the JSON work, so nothing changes but the plumbing: each query is prepared, bound, stepped once and finalized by hand, and its one text column is read with readCString before sqlite3_finalize frees it.
{"ada":13.75,"linus":15.0}
{"pen":12,"pad":3,"ink":1}
["ada","linus"]Search text with FTS4
A full-text index with CREATE VIRTUAL TABLE … USING fts4, queried with MATCH and cut into snippet()s. Stemming, phrases, prefixes, NOT and NEAR come with it. This build has FTS3 and FTS4 but not FTS5.
parse -> libcurl [parses] and normalizes URLs exactly like the… | [Parsing] XML with expat "signing request" -> …key, a certificate [signing] [request] and a self… brows* -> SQLite in the [browser] parser NOT expat -> curl URL [parser] tag NEAR/2 text -> …start tag, end [tag] and [text] run as…
FTS4, MATCH and snippet() are SQL, so the search is the same; JavaScript binds the query, steps through the matches and reads each snippet with readCString. Errors come back as codes: a malformed query ends the loop, sqlite3_finalize returns 1 and sqlite3_errmsg says "malformed MATCH expression", which the code throws itself where the C++ wrapper threw.
parse -> libcurl [parses] and normalizes URLs exactly like the… | [Parsing] XML with expat "signing request" -> …key, a certificate [signing] [request] and a self… brows* -> SQLite in the [browser] parser NOT expat -> curl URL [parser] tag NEAR/2 text -> …start tag, end [tag] and [text] run as…
Save a database to bytes and open it again
sqlite3_serialize copies a database out as the bytes of its file, and sqlite3_deserialize opens such bytes as a database: the way to download, upload or cache a whole database. The copy is then listed with sqlite_schema and pragma_table_info.
8192 B, starts with "SQLite format 3" readings(sensor TEXT, celsius REAL): 3 rows
The memory handling the C++ wrapper hid is written out: sqlite3_serialize returns a handle to memory that JavaScript copies with readBytes and frees with sqlite3_free, and writes the size through a sqlite3_int64 *, here an 8-byte allocBuffer read as int64. sqlite3_deserialize takes its bytes in memory from sqlite3_malloc64, which SQLite frees with the connection; the SQLITE_DESERIALIZE_* flags are macros, so their values are written out.
8192 B, starts with "SQLite format 3" readings(sensor TEXT, celsius REAL): 3 rows
What is different on WebAssembly
- In a browser the module runs in a Worker by default (
useWorker), so every call returns a promise:awaitcalls and constructors alike. - The module has its own filesystem:
m.FSwrites files,m.getFileBytesreads them back andm.autoMountFilesmountsFileobjects from an<input type=file>./memfslives in memory;/opfspersists across reloads and needs the Worker. See Filesystem. - In Node.js,
m.FSis the real disk, so use real paths there. - Multi-threaded builds (
runtime: 'mt') need COOP and COEP headers in production. See Threading.
Other platforms
- SQLite overview: the apps, every platform's setup and the packages.
- SQLite for Android: React Native apps on Android.
- SQLite for iOS: React Native apps on iOS.
- SQLite for WASI: command-line programs under wasmtime.
Facts on this page come from the port manifests in the repository and from what npm served on beta when the site was built. See the Libraries guide for the full consumer flow.