SQLite
You author plain SQLite SQL inside one or more .sql files. The platform runs an in-browser SQLite instance via the official @sqlite.org/sqlite-wasm build — every non-test .sql file gets executed automatically on save against the same in-memory database, in lexicographic order.
The seed ships a script.sql that creates a greetings table, inserts the row (1, 'Hello, World!'), and selects it back — extend it by adding more schema, seed data, and queries.
Entry file
Solution files live at the project root:
script.sql— editable solution file. The starter contains a TODO walkthrough for creating thegreetingstable and selecting the message.- You can rename
script.sqlto anything, and you can create additional.sqlfiles (organize them under subfolders if you like). The auto-runner discovers every non-test.sqlfile in the tree.
Files run in lexicographic order by full path. Prefix file names (01-schema.sql, 02-data.sql, 03-queries.sql) when execution order matters.
Database playground
The right panel is a live Database tab that talks to the same in-memory SQLite instance:
- Auto-run result — every SELECT (or other row-producing statement) from your
.sqlfiles renders here, grouped by source file, on every save. - Tables — schema sidebar populated from
sqlite_masterlisting every table plus its columns + types (viaPRAGMA table_info). Click Preview on a table to instantlySELECT * FROM <table> LIMIT 20in the ad-hoc tab. - Ad-hoc query — Monaco-powered SQL editor for one-off queries against the live database. Ctrl/⌘ + Enter runs the query without touching your source files.
The database is in-memory: refreshing the preview wipes it. To clean up without reloading, run DROP TABLE … in any source file or in the ad-hoc tab.
Version
Running sqlite-wasm v3.46.1-build3 (SQLite WASM) with Vitest v3.2.4 on Node v22.
Supported languages
SQL (SQLite dialect)
Testing framework
Vitest with the @dojocode/sql-test-helpers helper.
Special reminders and implementation details
- Standard SQLite dialect. Use
INTEGER PRIMARY KEY AUTOINCREMENT(NOT PostgreSQL'sSERIAL),INSERT OR IGNORE/INSERT OR REPLACEfor upserts. - Schema introspection uses
sqlite_master(SELECT name FROM sqlite_master WHERE type = 'table') andPRAGMA table_info(<table>)for column metadata. - Files matching
*.test.sqlare skipped by auto-run — reserved for a future raw-SQL test runner. - Tests are TypeScript (
*.test.ts) using the sharedcreateSqliteTestDb(import.meta.url)helper. The helper boots a fresh sqlite-wasm instance (with the OPFS init warning silenced) and auto-loads every non-test.sqlfile alongside the test before assertions run. query()andexec()are synchronous here — unlike the PGlite helper, noawaitis required for individual calls (the helper itself is async only because the underlying sqlite-wasm module init returns a Promise).
Example with Vitest:
import { describe, it, expect, beforeAll } from 'vitest';
import { createSqliteTestDb, type SqliteTestDb } from '@dojocode/sql-test-helpers/sqlite';
let db: SqliteTestDb;
beforeAll(async () => {
db = await createSqliteTestDb(import.meta.url);
});
describe('greetings', () => {
it("contains the row with message 'Hello, World!'", () => {
const rows = db.query<{ message: string }>(
'SELECT message FROM greetings WHERE id = 1'
);
expect(rows[0]?.message).toBe('Hello, World!');
});
it('has exactly one row', () => {
const rows = db.query<{ n: number }>('SELECT COUNT(*) AS n FROM greetings');
expect(rows[0].n).toBe(1);
});
});The helper returns { query<T>(sql): T[], exec(sql): void, raw: sqlite3.oo1.DB }. Both query and exec are synchronous — they return / mutate immediately. Use raw only for advanced cases (prepared statements with bind parameters, transactions, the wasm.poke* family).
Included libraries
- @sqlite.org/sqlite-wasm
- @dojocode/sql-test-helpers
- hono (in-WC server runtime)
- @hono/node-server
- vitest
How to debug
Three ways to inspect what your SQL is doing:
1. The Auto-run result tab
Every SELECT result is rendered there with its source file label. If a query returns nothing, the section is omitted — that itself is a useful signal that a row didn't get inserted or a WHERE filter is too tight.
2. The Tables tab
Confirms which tables exist and their column types right now. If you're hitting "no such table" errors, the table didn't get created — usually a syntax error in a CREATE statement above it.
3. The Ad-hoc query tab
Run targeted SELECTs against the live database without modifying your source files:
SELECT name FROM sqlite_master WHERE type = 'table';
PRAGMA table_info(greetings);
SELECT * FROM greetings WHERE id = 1;Common pitfalls
- Using PostgreSQL syntax —
SERIALdoesn't exist in SQLite; useINTEGER PRIMARY KEY AUTOINCREMENT.ON CONFLICT (col) DO NOTHINGworks too, butINSERT OR IGNOREis more idiomatic in SQLite. - Forgetting
INSERT OR IGNORE— re-running your script will fail with unique-constraint violations if a previous run already inserted the same rows. - Cross-folder execution order — files are sorted by their full path, so
a/02-init.sqlruns BEFOREb/01-init.sql. Prefix folders too when order matters across directories.