@tegmentum/sqlink
v0.5.0
Published
Ergonomic JS/TS API for SQLite-wasm in the browser: create -> connect -> query typed Result rows, conformance-gated extension loading, and result streaming. Runs in any modern browser via a JSPI fast-path (Chrome 137+) with an automatic non-JSPI Web Worke
Readme
@tegmentum/sqlink
Ergonomic JS/TS API for SQLite-wasm in the browser: create -> connect -> query
typed Result rows, conformance-gated extension resolution, and result streaming.
@tegmentum/sqlink is the SQLite parallel of @tegmentum/ducklink.
Both are thin facades over the shared, DB-agnostic
@tegmentum/datalink-browser
runtime (WASI polyfill + jco/JSPI component instantiation + conformance resolver),
so the two have the same method names and shapes — learn one, you know the other.
import { create } from '@tegmentum/sqlink'
const db = await create() // in-memory, main-thread
console.log(db.version()) // '3.53.2'
const conn = await db.connect()
const r = await conn.query("SELECT 42 AS answer, 'world' AS who")
r.toArray() // [{ answer: 42, who: 'world' }] (typed JS objects)
await conn.exec('CREATE TABLE t (id INTEGER, name TEXT)')
await conn.exec('INSERT INTO t VALUES (?, ?)', [1, 'a'])
const rows = (await conn.query('SELECT * FROM t WHERE id = ?', [1])).toArray()
for await (const row of conn.queryStream('SELECT x FROM big')) { /* row-at-a-time */ }
await conn.load('aba') // resolver-gated extension loadWhat's SQLite-specific (vs ducklink)
The shared @tegmentum/datalink-browser plumbing is reused verbatim; only three
SQLite-specific pieces live here:
- Value marshalling (
src/values.mjs) — SQLite's 5-classSqlValue(null | integer(s64) | real | text | blob) instead of duckvalue's 21 arms. BigInt-aware: anintegerreturns a JSNumberwhen it fits losslessly, aBigIntotherwise. A future@1.0.0wit-value arm passes through forward-compatibly. This is the one file a contract value-shape bump touches. - Core wiring (
src/sqlite-config.mjs) — the SQLite core component (sqlink:wasm/high-level) host imports, the JSPI async lists, and the adapter that normalizes the sqlink registry schema (artifact_url+checksum+wit_contract) into the provider/conformance shape the datalink resolver gate consumes. - The facade (
src/index.mjs) —create/SqlLink/Connection/PreparedStatementover the high-levelconnectionresource (query/execute/prepare/statement.step).
src/result.mjs (typed toArray/get/iterator/columns) and src/errors.mjs
(SqlLinkError/QueryError/ConformanceError/ContractError/NotFoundError/...) mirror
ducklink's, adapted to SQLite's database-error ({code, extended-code, message}).
API parity with @tegmentum/ducklink
| ducklink | sqlink | note |
| --- | --- | --- |
| create(opts) | create(opts) | same options shape |
| DuckLink | SqlLink | + version() / versionNumber() |
| conn.query(sql, params) | conn.query(sql, params) | typed Result |
| conn.prepare / PreparedStatement | same | + run() for non-SELECT |
| conn.queryStream(sql, opts) | same | SQLite steps row-at-a-time |
| conn.load/install/providerOf/loadedExtensions | same | resolver-gated |
| conn.registerFileBuffer/Text/Handle | same | WASI in-memory FS |
| Result.toArray/toRows/get/[Symbol.iterator]/columns/numRows | same | |
| conn.queryArrow / Result.toArrow | — | SQLite has no Arrow surface |
| — | conn.exec / begin/commit/rollback/inAutocommit | SQLite extras |
Verify
npm run test-values # marshalling + registry-normalizer unit tests (Node, no wasm)
npm run verify # headless-Chromium end-to-end (Vite + Playwright)The Chromium verify exercises the real flow against the SQLite core component:
create() -> connect() -> typed query.toArray() -> prepared params -> queryStream
(5000 rows) -> the conformance-gated load() path. All green.
Status / open items
- Extension dispatch host.
load()runs the full conformance gate (conformance.passed && conformance.at === wit_contract+ content-digest verify) in-browser today. The actual in-browsersqlite:extensiondispatch (the ABI in sqlink'swit/dispatch.wit+ theextension-loaderhost) is the SQLite parallel of datalink-browser's currently DuckDB-only extension-host, and is not yet wired through the clean high-level surface —load()enforces the gate, then throws a clearExtensionErroruntil aextensionHostis supplied. Wiring it (in datalink-browser, generalized off the duckdb host) is the next step. - Worker harness + JSPI fallback are follow-ons (as in ducklink).
