@crvouga/mockingbird-service-sqlite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +170 -0
- package/COMPATIBILITY-AUDIT.md +236 -0
- package/COMPATIBILITY.md +102 -0
- package/LICENSE +21 -0
- package/README.md +405 -0
- package/compat/coverage.json +22231 -0
- package/compat/divergences.json +145 -0
- package/compat/fts-oracle-surface.json +198 -0
- package/compat/requirements.json +20929 -0
- package/compat/scenario-types.ts +63 -0
- package/compat/scenarios.ts +871 -0
- package/compat/smoke-baseline.json +3 -0
- package/compat/sqllogictest-skip.json +5 -0
- package/dist/api/database.d.ts +112 -0
- package/dist/api/snapshot.d.ts +21 -0
- package/dist/api/statement.d.ts +75 -0
- package/dist/ast/nodes.d.ts +476 -0
- package/dist/constraints/check.d.ts +11 -0
- package/dist/errors/index.d.ts +45 -0
- package/dist/executor/attach.d.ts +5 -0
- package/dist/executor/ddl.d.ts +10 -0
- package/dist/executor/dml.d.ts +7 -0
- package/dist/executor/env.d.ts +66 -0
- package/dist/executor/execute.d.ts +4 -0
- package/dist/executor/pragma-engine.d.ts +31 -0
- package/dist/executor/pragma.d.ts +4 -0
- package/dist/executor/result.d.ts +22 -0
- package/dist/executor/select.d.ts +7 -0
- package/dist/executor/simple-select.d.ts +9 -0
- package/dist/executor/triggers.d.ts +11 -0
- package/dist/executor/vtable.d.ts +12 -0
- package/dist/expressions/context.d.ts +30 -0
- package/dist/expressions/equals.d.ts +3 -0
- package/dist/expressions/eval.d.ts +12 -0
- package/dist/expressions/index.d.ts +3 -0
- package/dist/expressions/like.d.ts +16 -0
- package/dist/functions/aggregate.d.ts +8 -0
- package/dist/functions/datetime.d.ts +2 -0
- package/dist/functions/extensions.d.ts +7 -0
- package/dist/functions/index.d.ts +8 -0
- package/dist/functions/json.d.ts +6 -0
- package/dist/functions/math.d.ts +2 -0
- package/dist/functions/pragma-tvf.d.ts +8 -0
- package/dist/functions/registry.d.ts +39 -0
- package/dist/functions/scalar.d.ts +9 -0
- package/dist/functions/table-valued-registry.d.ts +11 -0
- package/dist/functions/table-valued.d.ts +13 -0
- package/dist/functions/window.d.ts +23 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +16010 -0
- package/dist/index.js.map +7 -0
- package/dist/indexes/index.d.ts +48 -0
- package/dist/indexes/keys.d.ts +15 -0
- package/dist/json/index.d.ts +7 -0
- package/dist/json/jsonb.d.ts +42 -0
- package/dist/json/ops.d.ts +24 -0
- package/dist/json/parse.d.ts +13 -0
- package/dist/json/path.d.ts +26 -0
- package/dist/json/stringify.d.ts +4 -0
- package/dist/json/tvf.d.ts +13 -0
- package/dist/json/types.d.ts +29 -0
- package/dist/lexer/tokenize.d.ts +23 -0
- package/dist/parser/index.d.ts +17 -0
- package/dist/parser/parser.d.ts +115 -0
- package/dist/planner/access.d.ts +50 -0
- package/dist/planner/index.d.ts +3 -0
- package/dist/runtime/assert.d.ts +11 -0
- package/dist/runtime/catch.d.ts +3 -0
- package/dist/runtime/clock.d.ts +14 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/options.d.ts +25 -0
- package/dist/runtime/prng.d.ts +42 -0
- package/dist/schema/catalog.d.ts +14 -0
- package/dist/schema/master-sql.d.ts +10 -0
- package/dist/serialization/codec.d.ts +19 -0
- package/dist/serialization/index.d.ts +1 -0
- package/dist/serialization/wire.d.ts +61 -0
- package/dist/storage/columnar-slab.d.ts +58 -0
- package/dist/storage/database-state.d.ts +96 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/row.d.ts +13 -0
- package/dist/storage/table.d.ts +119 -0
- package/dist/transactions/manager.d.ts +19 -0
- package/dist/types/collation.d.ts +4 -0
- package/dist/types/sqlite-atof.d.ts +4 -0
- package/dist/types/sqlite-real-format.d.ts +5 -0
- package/dist/types/strict.d.ts +6 -0
- package/dist/types/value.d.ts +88 -0
- package/dist/unstable.d.ts +20 -0
- package/dist/unstable.js +10343 -0
- package/dist/unstable.js.map +7 -0
- package/dist/vtable/fts/options.d.ts +34 -0
- package/dist/vtable/fts/porter.d.ts +2 -0
- package/dist/vtable/fts/query.d.ts +39 -0
- package/dist/vtable/fts/table.d.ts +78 -0
- package/dist/vtable/fts/tokenize.d.ts +20 -0
- package/dist/vtable/fts5.d.ts +1 -0
- package/dist/vtable/index.d.ts +2 -0
- package/dist/vtable/modules.d.ts +65 -0
- package/package.json +122 -0
package/README.md
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# @crvouga/mockingbird-service-sqlite
|
|
2
|
+
|
|
3
|
+
Pure TypeScript, completely in-memory SQLite engine aiming for **full SQLite3 SQL dialect parity**
|
|
4
|
+
(same statements, same results). Use it in tests (or the browser) wherever you want real SQLite SQL
|
|
5
|
+
semantics without native bindings: schema + migrations, constraints, transactions, JSON functions,
|
|
6
|
+
FTS, and copy-on-write snapshots for per-test isolation. It is also the default storage engine
|
|
7
|
+
behind every Mockingbird HTTP mock (Stripe, Junction, GeneByGene, ...).
|
|
8
|
+
|
|
9
|
+
> Formerly [`@crvouga/sqlite-mem`](https://www.npmjs.com/package/@crvouga/sqlite-mem)
|
|
10
|
+
> ([archived repo](https://github.com/crvouga/sqlite-mem)). Migrate by replacing the package name;
|
|
11
|
+
> the API is unchanged.
|
|
12
|
+
|
|
13
|
+
- Runs in modern browsers, Node.js and Bun
|
|
14
|
+
- **Zero** WASM, native bindings, workers, or filesystem dependencies; the whole database lives in memory
|
|
15
|
+
- **Synchronous**, ESM-only API (no Promises, no `require`)
|
|
16
|
+
- **SQL dialect verified** against SQLite 3.51.0 / 3.53.0 (`bun:sqlite`) via differential contracts
|
|
17
|
+
and a fail-closed gate
|
|
18
|
+
- **Not** a drop-in for `sql.js` / `sqlite-wasm` APIs, on-disk `.sqlite` files, or user-defined functions
|
|
19
|
+
- Intentional differences: deterministic `random()` / `'now'` by default, and a custom snapshot
|
|
20
|
+
format (not `.sqlite` files)
|
|
21
|
+
|
|
22
|
+
### Documentation
|
|
23
|
+
|
|
24
|
+
Files marked (shipped) are included in the npm package next to this README.
|
|
25
|
+
|
|
26
|
+
| Doc | For |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| [COMPATIBILITY.md](./COMPATIBILITY.md) (shipped) | Feature matrix + verify commands |
|
|
29
|
+
| [COMPATIBILITY-AUDIT.md](./COMPATIBILITY-AUDIT.md) (shipped) | Audit evidence |
|
|
30
|
+
| [AGENTS.md](./AGENTS.md) (shipped) | Contributor docs: architecture, how to change code, test/compat gates |
|
|
31
|
+
| [DROP-IN-CONTRACT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/sqlite/docs/DROP-IN-CONTRACT.md) | Falsifiable drop-in claim (what "same" means) |
|
|
32
|
+
| [PROOF.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/sqlite/docs/PROOF.md) | Evidence argument + what is not proven |
|
|
33
|
+
| [GAP-ANALYSIS.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/sqlite/docs/GAP-ANALYSIS.md) | Phase 0 gap analysis vs the full drop-in catalog |
|
|
34
|
+
| [GAP-CATALOG.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/sqlite/docs/GAP-CATALOG.md) | Current unproven / thin / intentional inventory |
|
|
35
|
+
| [DIVERGENCES.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/sqlite/DIVERGENCES.md) | Auto-generated intentional divergences (machine-readable: `compat/divergences.json`, shipped) |
|
|
36
|
+
| [PERFORMANCE.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/sqlite/benchmarks/PERFORMANCE.md) | Performance notes |
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -D @crvouga/mockingbird-service-sqlite
|
|
42
|
+
# or: bun add -d @crvouga/mockingbird-service-sqlite
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requires Node.js >= 20 or Bun >= 1.1 (`engines`); the rest of Mockingbird targets Node >= 22 /
|
|
46
|
+
Bun >= 1.2. The package is **ESM only** and has no runtime dependencies. Install it as a regular
|
|
47
|
+
dependency instead of `-D` if you ship it to the browser. The Mockingbird HTTP mocks already depend
|
|
48
|
+
on it; install it directly only to use the engine yourself or to pass a shared `Database` to them.
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { Database, Snapshot } from "@crvouga/mockingbird-service-sqlite"
|
|
54
|
+
|
|
55
|
+
const db = new Database()
|
|
56
|
+
|
|
57
|
+
db.exec(`
|
|
58
|
+
CREATE TABLE users (
|
|
59
|
+
id INTEGER PRIMARY KEY,
|
|
60
|
+
name TEXT NOT NULL,
|
|
61
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
62
|
+
)
|
|
63
|
+
`)
|
|
64
|
+
|
|
65
|
+
const inserted = db.prepare(`INSERT INTO users (name) VALUES (?)`).run("Alice")
|
|
66
|
+
console.log(inserted) // { changes: 1, lastInsertRowid: 1 }
|
|
67
|
+
|
|
68
|
+
const users = db.query<{ id: number; name: string; created_at: string }>(`SELECT * FROM users`)
|
|
69
|
+
console.log(users) // [{ id: 1, name: "Alice", created_at: "2000-01-01 00:00:00" }]
|
|
70
|
+
|
|
71
|
+
// Snapshots: freeze a template, fork it cheaply, or persist it as bytes.
|
|
72
|
+
const seed = db.snapshot()
|
|
73
|
+
const db2 = seed.open()
|
|
74
|
+
const bytes = seed.encode()
|
|
75
|
+
const db3 = Snapshot.decode(bytes).open()
|
|
76
|
+
console.log(db2.query(`SELECT count(*) AS n FROM users`), db3.lastInsertRowid) // [{ n: 1 }] 1
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
All methods are **synchronous**; do not `await` them. Browser and Node/Bun share the same
|
|
80
|
+
in-memory surface (no filesystem; `ATTACH` opens a new empty in-memory schema, not a file).
|
|
81
|
+
|
|
82
|
+
### Per-test isolation with snapshots
|
|
83
|
+
|
|
84
|
+
Run migrations and fixtures once, `snapshot()` the result, and `open()` a copy-on-write fork per
|
|
85
|
+
test (microseconds, tables are shared until either side writes):
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { beforeEach, expect, test } from "bun:test"
|
|
89
|
+
import { Database, SqliteError } from "@crvouga/mockingbird-service-sqlite"
|
|
90
|
+
|
|
91
|
+
const template = new Database()
|
|
92
|
+
template.exec(`
|
|
93
|
+
CREATE TABLE accounts (id INTEGER PRIMARY KEY, email TEXT NOT NULL UNIQUE);
|
|
94
|
+
INSERT INTO accounts (email) VALUES ('seed@example.com');
|
|
95
|
+
`)
|
|
96
|
+
const seed = template.snapshot()
|
|
97
|
+
|
|
98
|
+
let db: Database
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
db = seed.open()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test("unique violation surfaces SQLITE_CONSTRAINT_UNIQUE", () => {
|
|
104
|
+
let error: unknown
|
|
105
|
+
try {
|
|
106
|
+
db.prepare(`INSERT INTO accounts (email) VALUES (?)`).run("seed@example.com")
|
|
107
|
+
} catch (caught) {
|
|
108
|
+
error = caught
|
|
109
|
+
}
|
|
110
|
+
expect(error).toBeInstanceOf(SqliteError)
|
|
111
|
+
expect((error as SqliteError).code).toBe("SQLITE_CONSTRAINT_UNIQUE")
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test("each test starts from the seed", () => {
|
|
115
|
+
expect(db.query(`SELECT email FROM accounts`)).toEqual([{ email: "seed@example.com" }])
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### As Mockingbird's storage
|
|
120
|
+
|
|
121
|
+
Every Mockingbird HTTP mock accepts a `sqlite` option typed as the `SqliteClient` port from
|
|
122
|
+
`@crvouga/mockingbird-sqlite` (`exec`, `prepare(sql).run/all/get`, `transaction`). This package's
|
|
123
|
+
`Database` satisfies it and is what a mock creates when you omit the option. Pass your own to share
|
|
124
|
+
one database between several mocks (each keeps its records under its own namespace, e.g.
|
|
125
|
+
`"stripe"`, `"junction"`), to inspect what a mock stored, or to snapshot a warmed-up mock:
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
import { Database } from "@crvouga/mockingbird-service-sqlite"
|
|
129
|
+
import { StripeAPI } from "@crvouga/mockingbird-service-stripe"
|
|
130
|
+
import { JunctionAPI } from "@crvouga/mockingbird-service-junction"
|
|
131
|
+
|
|
132
|
+
const sqlite = new Database({ now: "system" })
|
|
133
|
+
const stripe = new StripeAPI({ sqlite })
|
|
134
|
+
const junction = new JunctionAPI({ sqlite })
|
|
135
|
+
|
|
136
|
+
// ... drive the mocks to a fixture state, then fork it per test:
|
|
137
|
+
const warmed = sqlite.snapshot()
|
|
138
|
+
const freshStripe = () => new StripeAPI({ sqlite: warmed.open() })
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The mocks create their own tables (`mockingbird_records`, `mockingbird_sequences`,
|
|
142
|
+
`schema_migrations`) on construction, and each mock's `reset()` clears only its own namespace, so
|
|
143
|
+
resetting one mock leaves the others' data in a shared database intact. Any other client with the same sync surface (better-sqlite3, a
|
|
144
|
+
wrapped `bun:sqlite`) also satisfies the port.
|
|
145
|
+
|
|
146
|
+
### Method semantics
|
|
147
|
+
|
|
148
|
+
| Method | Behaviour |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| `exec(sql)` | Runs all semicolon-separated statements; **discards** row results (`void`). Does **not** accept bind parameters. Read `db.changes` / `db.lastInsertRowid` afterwards if needed (counters reflect the **most recent** completed statement, matching SQLite). |
|
|
151
|
+
| `query(sql, params?)` | **Single statement only** (trailing `;` is fine). Returns all rows. Multi-statement scripts throw `misuse`. |
|
|
152
|
+
| `prepare(sql)` | **Single statement only**. Parses immediately; the AST is reused. Pass binds as rest args to `run` / `all` / `get` / `result` on each call. |
|
|
153
|
+
| `transaction(fn)` | If idle: `BEGIN`, `fn()`, `COMMIT`, or `ROLLBACK` + rethrow. If already in a transaction: nested savepoint. A nested SQL `BEGIN` still errors. `close()` inside `fn` throws `misuse`. |
|
|
154
|
+
| `snapshot()` | Freeze a reusable `Snapshot` template (no encode). Illegal inside a transaction. |
|
|
155
|
+
| `Snapshot.open()` | Copy-on-write fork from a template. The parent stays open. |
|
|
156
|
+
| `Snapshot.encode()` | Lazy SQLM blob for persistence / worker boot (computed once, cached). |
|
|
157
|
+
| `Snapshot.decode(bytes)` | Decode a blob once per `Uint8Array` (WeakMap); later `open()` calls are copy-on-write. |
|
|
158
|
+
| `close()` | Idempotent; rolls back an open SQL transaction; further operations throw `misuse`. Also available as `[Symbol.dispose]` when the runtime defines `Symbol.dispose`. |
|
|
159
|
+
|
|
160
|
+
SQL `BEGIN` / `COMMIT` / `ROLLBACK` / `SAVEPOINT` / `RELEASE` are first-class. Empty or
|
|
161
|
+
comment-only SQL on `prepare` / `query` throws `misuse` (`empty statement`), matching SQLite
|
|
162
|
+
prepare failure.
|
|
163
|
+
|
|
164
|
+
### Parameter binding
|
|
165
|
+
|
|
166
|
+
Supported styles: `?`, `?NNN`, `:name`, `@name`, `$name`.
|
|
167
|
+
|
|
168
|
+
- The JS API takes **rest args** (or a positional array into `query`) only; there is **no** sticky
|
|
169
|
+
`bind()` and **no** `bind({ name: value })`.
|
|
170
|
+
- Named parameters occupy slots in **first-occurrence order**; repeated names share one slot.
|
|
171
|
+
- Prefixes are part of the name: `@x`, `$x`, and `:x` are **three different** parameters.
|
|
172
|
+
- Names are lowercased for lookup (`:Left` is `:left`).
|
|
173
|
+
- Bindable: `null`, `string`, finite `number`, `bigint`, `boolean` (stored as `0`/`1`),
|
|
174
|
+
`Uint8Array` / `ArrayBuffer`.
|
|
175
|
+
- Rejected (`misuse`): `DataView`, typed-array views other than `Uint8Array`, `SharedArrayBuffer` /
|
|
176
|
+
SAB-backed buffers.
|
|
177
|
+
- Rejected (`datatype_mismatch`): `undefined`, `Date`, plain objects, `NaN` / `Infinity`.
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { Database } from "@crvouga/mockingbird-service-sqlite"
|
|
181
|
+
|
|
182
|
+
const db = new Database()
|
|
183
|
+
console.log(db.query(`SELECT ? AS a, :name AS b`, [1, "Alice"])) // [{ a: 1, b: "Alice" }]
|
|
184
|
+
console.log(db.prepare(`SELECT @id AS id`).get(42)) // { id: 42 }
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Returned JavaScript types
|
|
188
|
+
|
|
189
|
+
| SQL storage | JS value | Notes |
|
|
190
|
+
| --- | --- | --- |
|
|
191
|
+
| NULL | `null` | Never `undefined` |
|
|
192
|
+
| INTEGER | `number` or `bigint` | `bigint` when outside `Number.MAX_SAFE_INTEGER` |
|
|
193
|
+
| REAL | `number` | Including integer-valued reals (`1.0` becomes `1`); use SQL `typeof()` to distinguish from INTEGER |
|
|
194
|
+
| TEXT | `string` | JSON subtype unwrapped to string |
|
|
195
|
+
| BLOB | `Uint8Array` | |
|
|
196
|
+
|
|
197
|
+
Duplicate column names collapse in row objects (last write wins). Use `stmt.result().values` for
|
|
198
|
+
positional cells.
|
|
199
|
+
|
|
200
|
+
### Snapshots
|
|
201
|
+
|
|
202
|
+
- `db.snapshot()` returns a frozen in-memory `Snapshot`. Per-test isolation should `seed.open()`
|
|
203
|
+
(copy-on-write, microseconds). Encoded bytes are **lazy** via `snapshot.encode()`.
|
|
204
|
+
- Format: magic `SQLM` followed by an explicit little-endian format-version `u32`. **Not** a
|
|
205
|
+
portable `.sqlite` file and not loadable by the SQLite CLI.
|
|
206
|
+
- Round-trips ordinary tables, views, indexes (SQLM v4 compact index keys; v3 persisted full
|
|
207
|
+
`IndexStore`; v1/v2 blobs rebuild indexes on hydrate), change counters, PRNG state, and clock.
|
|
208
|
+
- **Not** encoded: triggers, ATTACH'd schemas, virtual tables (FTS / RTREE / ...), `userVersion`.
|
|
209
|
+
- Cannot `snapshot()` while a transaction is open.
|
|
210
|
+
- `Snapshot.decode(bytes)` does not mutate the input `Uint8Array`. The same buffer object is
|
|
211
|
+
decoded once (WeakMap) and later opens are copy-on-write.
|
|
212
|
+
- `open()` shares frozen tables until either side writes; idle `open().snapshot().encode()` is
|
|
213
|
+
byte-identical to `snapshot().encode()`.
|
|
214
|
+
- `open()` uses a fixed clock from the snapshot unless you pass `{ now: "system" }`, which stays live.
|
|
215
|
+
- Equivalent databases produce byte-identical `encode()` output (schema/rows sorted) **within a
|
|
216
|
+
single library version**.
|
|
217
|
+
- **Compatibility policy:** newer library versions can always decode older snapshots; older
|
|
218
|
+
libraries cannot decode newer format versions (`snapshot_version` / `SQLITE_FORMAT`). Corrupt
|
|
219
|
+
magic yields a distinct error.
|
|
220
|
+
|
|
221
|
+
### Determinism
|
|
222
|
+
|
|
223
|
+
The engine is deterministic by default:
|
|
224
|
+
|
|
225
|
+
| Source | Default | Override / notes |
|
|
226
|
+
| --- | --- | --- |
|
|
227
|
+
| `random()` / `randomblob()` | Seeded xorshift64* (`seed: 1`) | `new Database({ seed })`, or `{ random: "os" }` for CSPRNG (not rolled back / not restored) |
|
|
228
|
+
| `date('now')` / friends | Fixed `2000-01-01T00:00:00.000Z` | `new Database({ now: Date \| (() => Date) \| "system" })`; `"system"` is wall clock and is **not** frozen by `open()` |
|
|
229
|
+
| Table scans | Rowid order | Same order after `snapshot`/`open` |
|
|
230
|
+
| Snapshots | Sorted schema/rows + PRNG state + clock | Applied by `open()` into PRNG and `now` |
|
|
231
|
+
| Transactions | PRNG rolls back with `ROLLBACK`/`SAVEPOINT` | Matches data rollback |
|
|
232
|
+
| Numbers | IEEE `-0` canonicalized to `+0` | Bind, affinity, and arithmetic |
|
|
233
|
+
|
|
234
|
+
### Compatibility notes for integrators
|
|
235
|
+
|
|
236
|
+
Goal: **SQL dialect** behavioural parity vs SQLite **3.51.0** / **3.53.0** for the sync API. Full
|
|
237
|
+
matrix: [COMPATIBILITY.md](./COMPATIBILITY.md). Contract:
|
|
238
|
+
[DROP-IN-CONTRACT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/sqlite/docs/DROP-IN-CONTRACT.md).
|
|
239
|
+
|
|
240
|
+
This is **not** a drop-in replacement for `sql.js`, `@sqlite.org/sqlite-wasm`, or better-sqlite3's
|
|
241
|
+
full Node API. There is no `.sqlite` file codec, no `create_function` / custom collations, no
|
|
242
|
+
`stmt.step()` / `iterate()`, and `ATTACH 'file'` opens an empty in-memory schema. Intentional
|
|
243
|
+
differences: custom `SQLM` snapshots; seeded `random()` / fixed `'now'` by default
|
|
244
|
+
(`{ random: "os" }` / `{ now: "system" }` match SQLite entropy and wall clock); no C API / on-disk
|
|
245
|
+
DB / VFS.
|
|
246
|
+
|
|
247
|
+
**Thin or partial areas** (do not assume full oracle fidelity):
|
|
248
|
+
|
|
249
|
+
- FTS3/4/5: largely implemented; shadow-table change counters intentionally diverge; some edges partial
|
|
250
|
+
- `EXPLAIN` / `EXPLAIN QUERY PLAN`: stub shapes, not real bytecode
|
|
251
|
+
- `INDEXED BY` / `NOT INDEXED`: parsed and discarded (missing indexes do not error)
|
|
252
|
+
- `ATTACH 'file'`: the filename is recorded; the schema is always a new empty in-memory database
|
|
253
|
+
- `MATERIALIZED` / `NOT MATERIALIZED`: both execute as materialized
|
|
254
|
+
- `PRAGMA compile_options` / `function_list`: this engine's set, not Bun's native build
|
|
255
|
+
- Unknown statement `PRAGMA` succeeds with an empty result (SQLite-like). All oracle-exposed
|
|
256
|
+
`pragma_*` eponymous table-valued functions are supported (`SELECT * FROM pragma_table_info('t')`,
|
|
257
|
+
bare `FROM pragma_database_list`, ...), including **correlated** args such as
|
|
258
|
+
`FROM table_list AS tl, pragma_table_info(tl.name) AS p` (Kysely SQLite introspector).
|
|
259
|
+
Storage/journal getters return bun `:memory:`-compatible defaults. `PRAGMA case_sensitive_like`
|
|
260
|
+
is implemented.
|
|
261
|
+
|
|
262
|
+
**Also supported (oracle parity):** boolean literals **`TRUE` / `FALSE`** (any case, as integers
|
|
263
|
+
`1` / `0`) and **`IS [NOT] TRUE` / `IS [NOT] FALSE`** (SQLite truthiness, including NULL). A column
|
|
264
|
+
named `true`/`false` shadows the literal.
|
|
265
|
+
|
|
266
|
+
### Common pitfalls
|
|
267
|
+
|
|
268
|
+
1. **Do not `await`**: the API is sync.
|
|
269
|
+
2. **No named-object binds and no sticky `bind()`**: pass positional rest args / arrays in declaration order to `query` / `run` / `all` / `get` / `result`.
|
|
270
|
+
3. **`query` / `prepare` are single-statement only**: multi-statement scripts belong in `exec()` (which does not take bind parameters).
|
|
271
|
+
4. **`exec` returns `void` and takes no params**: use `db.prepare(...).run(...)` or `db.query(...)` for binds; use `db.changes` / `stmt.run()` for counters.
|
|
272
|
+
5. **`'now'` is not wall-clock** unless you pass `{ now: "system" }` or `{ now: () => new Date() }`. The default is year 2000. `open()` freezes a snapshot clock except when constructed with `"system"`.
|
|
273
|
+
6. **`random()` is seeded**, not OS entropy, unless you pass `{ random: "os" }`. Snapshots restore the seeded PRNG; OS entropy is not rewound.
|
|
274
|
+
7. **Snapshots are not `.sqlite` files** and do not round-trip FTS / triggers / ATTACH.
|
|
275
|
+
8. **No better-sqlite3 extras**: no `iterate`, `pluck`/`raw`, `safeIntegers` option, `pragma()` helper, `loadExtension`, or SQLite-file `serialize()`.
|
|
276
|
+
9. **Do not bind `Date` objects**: store unixepoch integers or ISO text. Do not bind `DataView` / non-`Uint8Array` typed arrays.
|
|
277
|
+
10. **Do not use `Number.isInteger` for SQL REAL vs INTEGER**: use SQL `typeof()`.
|
|
278
|
+
11. **Do not import `@crvouga/mockingbird-service-sqlite/unstable` in application code** unless you accept breakage in any release.
|
|
279
|
+
12. **Known issue:** a column-level `UNIQUE` followed by another column constraint (for example
|
|
280
|
+
`email TEXT UNIQUE NOT NULL`, `UNIQUE DEFAULT ...`, `UNIQUE CHECK (...)`) is currently not
|
|
281
|
+
enforced. Put `UNIQUE` last (`email TEXT NOT NULL UNIQUE`), use a table constraint
|
|
282
|
+
(`UNIQUE (email)`), or `CREATE UNIQUE INDEX`; all of those are enforced.
|
|
283
|
+
|
|
284
|
+
## API
|
|
285
|
+
|
|
286
|
+
Stable runtime exports of the main entry:
|
|
287
|
+
|
|
288
|
+
| Export | Description |
|
|
289
|
+
| --- | --- |
|
|
290
|
+
| `Database` | Class. `new Database(options?: DatabaseOptions)` — one in-memory SQLite database. Satisfies Mockingbird's `SqliteClient` port. |
|
|
291
|
+
| `Snapshot` | Class. Frozen template from `db.snapshot()` or `Snapshot.decode(bytes)`; `open(options?)` forks a `Database`, `encode()` serializes. |
|
|
292
|
+
| `Statement` | Class returned by `db.prepare(sql)` (not constructed directly): `run`, `all`, `get`, `result`. |
|
|
293
|
+
| `SqliteError` | Error class thrown for SQL and API errors: `category` (`ErrorCategory`), `sqliteCode` / `code` (SQLite result-code name, e.g. `"SQLITE_CONSTRAINT_UNIQUE"`; default `"SQLITE_ERROR"`). |
|
|
294
|
+
|
|
295
|
+
Signatures (types are exported too: `DatabaseOptions`, `RunResult`, `ResultSet`, `ErrorCategory`,
|
|
296
|
+
`BindValue`, `QueryRow`, `QueryValue`):
|
|
297
|
+
|
|
298
|
+
```text
|
|
299
|
+
interface DatabaseOptions {
|
|
300
|
+
seed?: number | bigint // default 1; ignored when random is "os"
|
|
301
|
+
random?: "deterministic" | "os" // default "deterministic"; "os" is CSPRNG like SQLite
|
|
302
|
+
now?: Date | (() => Date) | "system" // default 2000-01-01T00:00:00.000Z; "system" is wall clock
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
class Database {
|
|
306
|
+
constructor(options?: DatabaseOptions)
|
|
307
|
+
exec(sql: string): void
|
|
308
|
+
query<T = QueryRow>(sql: string, params?: readonly BindValue[]): T[]
|
|
309
|
+
prepare(sql: string): Statement
|
|
310
|
+
transaction<T>(fn: () => T): T
|
|
311
|
+
snapshot(): Snapshot
|
|
312
|
+
close(): void // also [Symbol.dispose] when available
|
|
313
|
+
readonly changes: number
|
|
314
|
+
readonly lastInsertRowid: number | bigint
|
|
315
|
+
readonly totalChanges: number
|
|
316
|
+
readonly seed: number | bigint
|
|
317
|
+
readonly randomMode: "deterministic" | "os"
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
class Statement {
|
|
321
|
+
run(...params: BindValue[]): RunResult
|
|
322
|
+
all<T = QueryRow>(...params: BindValue[]): T[]
|
|
323
|
+
get<T = QueryRow>(...params: BindValue[]): T | undefined
|
|
324
|
+
result(...params: BindValue[]): ResultSet // includes columns + values when zero rows
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
interface RunResult { changes: number; lastInsertRowid: number | bigint }
|
|
328
|
+
interface ResultSet {
|
|
329
|
+
columns: string[]
|
|
330
|
+
rows: QueryRow[]
|
|
331
|
+
values: QueryValue[][] // always present (empty array for zero rows)
|
|
332
|
+
changes: number
|
|
333
|
+
lastInsertRowid: number | bigint
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
class Snapshot {
|
|
337
|
+
open(options?: DatabaseOptions): Database
|
|
338
|
+
encode(): Uint8Array
|
|
339
|
+
static decode(bytes: Uint8Array): Snapshot
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
class SqliteError extends Error {
|
|
343
|
+
readonly category: ErrorCategory // "syntax", "no_such_table", "constraint_unique", "misuse", ...
|
|
344
|
+
readonly sqliteCode: string // always set; default "SQLITE_ERROR"
|
|
345
|
+
readonly code: string // === sqliteCode (Node err.code convention)
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Stick to `Database`, `Snapshot`, `Statement`, and `SqliteError` in application code. Advanced
|
|
350
|
+
internals (`parse`, `tokenize`, `evalExpr`, snapshot codec pieces, `SqlValue` utilities, `Prng`,
|
|
351
|
+
...) are available only from `@crvouga/mockingbird-service-sqlite/unstable` and are **exempt from
|
|
352
|
+
semver**.
|
|
353
|
+
|
|
354
|
+
### Stability policy
|
|
355
|
+
|
|
356
|
+
The exports of the main entry (`@crvouga/mockingbird-service-sqlite`) are **frozen**:
|
|
357
|
+
|
|
358
|
+
- **Never** outside a major: removals, renames, signature changes, or changes to documented
|
|
359
|
+
behaviour of the stable surface.
|
|
360
|
+
- **Allowed in minors:** additions (new methods, new optional `DatabaseOptions` fields, new
|
|
361
|
+
`ErrorCategory` values). Consumers that `switch` on `category` must include a default case.
|
|
362
|
+
- **`@crvouga/mockingbird-service-sqlite/unstable`** is exempt from semver and may change or
|
|
363
|
+
disappear in any release.
|
|
364
|
+
- **Snapshots:** newer library versions restore older blobs; older library versions cannot restore
|
|
365
|
+
newer format versions; the byte-identical guarantee holds only within one library version.
|
|
366
|
+
|
|
367
|
+
## Development
|
|
368
|
+
|
|
369
|
+
For contributors to the mockingbird repo only. Requires [Bun](https://bun.sh). For
|
|
370
|
+
architecture, change checklists, and how to add contract tests, see [AGENTS.md](./AGENTS.md).
|
|
371
|
+
|
|
372
|
+
Parity is proven only by differential contracts against real SQLite (`bun:sqlite`). Isolated
|
|
373
|
+
internal unit tests are not SQLite compatibility proof.
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
bun install
|
|
377
|
+
bun run check:full # same gates as GitHub Actions CI (except publish)
|
|
378
|
+
bun run check # format + lint + typecheck + sqlite-compat suite
|
|
379
|
+
bun run format # write Biome formatting
|
|
380
|
+
bun run lint # Biome lint
|
|
381
|
+
bun run typecheck
|
|
382
|
+
bun run test:sqlite-compat # requirements + inventory gate + differential suite
|
|
383
|
+
bun test # contract + fuzz + harness
|
|
384
|
+
bun run build
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Fuzz / property tests use a fixed seed (`0x5a17e0e1`) and print it on failure:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
bun test tests/fuzz
|
|
391
|
+
bun run test:pbt:random -- 50 # N random seeds, fail fast on first mismatch
|
|
392
|
+
SQLITE_MEM_FUZZ_SEED=12345 bun test tests/fuzz
|
|
393
|
+
SQLITE_MEM_FUZZ_SEED=12345 SQLITE_MEM_FUZZ_PATH='0:1' bun test tests/fuzz # exact replay
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
A React + Vite SQL playground lives in
|
|
397
|
+
[`examples/react-vite`](https://github.com/crvouga/mockingbird/tree/main/packages/service/sqlite/examples/react-vite)
|
|
398
|
+
(`bun run example` from this package after `bun install` there). More working examples:
|
|
399
|
+
[`tests/contract/api/`](https://github.com/crvouga/mockingbird/tree/main/packages/service/sqlite/tests/contract/api)
|
|
400
|
+
and [`tests/contract/parameters/`](https://github.com/crvouga/mockingbird/tree/main/packages/service/sqlite/tests/contract/parameters).
|
|
401
|
+
|
|
402
|
+
Released automatically from the [Mockingbird monorepo](https://github.com/crvouga/mockingbird)
|
|
403
|
+
(see the root README, Releasing). License: MIT ([LICENSE](./LICENSE)).
|
|
404
|
+
|
|
405
|
+
Part of [mockingbird](https://github.com/crvouga/mockingbird) — agent integration guide: [`@crvouga/mockingbird`](https://github.com/crvouga/mockingbird/tree/main/packages/facade#readme).
|