Skip to content

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 the greetings table and selecting the message.
  • You can rename script.sql to anything, and you can create additional .sql files (organize them under subfolders if you like). The auto-runner discovers every non-test .sql file 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 .sql files renders here, grouped by source file, on every save.
  • Tables — schema sidebar populated from sqlite_master listing every table plus its columns + types (via PRAGMA table_info). Click Preview on a table to instantly SELECT * FROM <table> LIMIT 20 in 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's SERIAL), INSERT OR IGNORE / INSERT OR REPLACE for upserts.
  • Schema introspection uses sqlite_master (SELECT name FROM sqlite_master WHERE type = 'table') and PRAGMA table_info(<table>) for column metadata.
  • Files matching *.test.sql are skipped by auto-run — reserved for a future raw-SQL test runner.
  • Tests are TypeScript (*.test.ts) using the shared createSqliteTestDb(import.meta.url) helper. The helper boots a fresh sqlite-wasm instance (with the OPFS init warning silenced) and auto-loads every non-test .sql file alongside the test before assertions run.
  • query() and exec() are synchronous here — unlike the PGlite helper, no await is required for individual calls (the helper itself is async only because the underlying sqlite-wasm module init returns a Promise).

Example with Vitest: ​

typescript
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 ​

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:

sql
SELECT name FROM sqlite_master WHERE type = 'table';
PRAGMA table_info(greetings);
SELECT * FROM greetings WHERE id = 1;

Common pitfalls ​

  • Using PostgreSQL syntax — SERIAL doesn't exist in SQLite; use INTEGER PRIMARY KEY AUTOINCREMENT. ON CONFLICT (col) DO NOTHING works too, but INSERT OR IGNORE is 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.sql runs BEFORE b/01-init.sql. Prefix folders too when order matters across directories.