@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.
Files changed (100) hide show
  1. package/AGENTS.md +170 -0
  2. package/COMPATIBILITY-AUDIT.md +236 -0
  3. package/COMPATIBILITY.md +102 -0
  4. package/LICENSE +21 -0
  5. package/README.md +405 -0
  6. package/compat/coverage.json +22231 -0
  7. package/compat/divergences.json +145 -0
  8. package/compat/fts-oracle-surface.json +198 -0
  9. package/compat/requirements.json +20929 -0
  10. package/compat/scenario-types.ts +63 -0
  11. package/compat/scenarios.ts +871 -0
  12. package/compat/smoke-baseline.json +3 -0
  13. package/compat/sqllogictest-skip.json +5 -0
  14. package/dist/api/database.d.ts +112 -0
  15. package/dist/api/snapshot.d.ts +21 -0
  16. package/dist/api/statement.d.ts +75 -0
  17. package/dist/ast/nodes.d.ts +476 -0
  18. package/dist/constraints/check.d.ts +11 -0
  19. package/dist/errors/index.d.ts +45 -0
  20. package/dist/executor/attach.d.ts +5 -0
  21. package/dist/executor/ddl.d.ts +10 -0
  22. package/dist/executor/dml.d.ts +7 -0
  23. package/dist/executor/env.d.ts +66 -0
  24. package/dist/executor/execute.d.ts +4 -0
  25. package/dist/executor/pragma-engine.d.ts +31 -0
  26. package/dist/executor/pragma.d.ts +4 -0
  27. package/dist/executor/result.d.ts +22 -0
  28. package/dist/executor/select.d.ts +7 -0
  29. package/dist/executor/simple-select.d.ts +9 -0
  30. package/dist/executor/triggers.d.ts +11 -0
  31. package/dist/executor/vtable.d.ts +12 -0
  32. package/dist/expressions/context.d.ts +30 -0
  33. package/dist/expressions/equals.d.ts +3 -0
  34. package/dist/expressions/eval.d.ts +12 -0
  35. package/dist/expressions/index.d.ts +3 -0
  36. package/dist/expressions/like.d.ts +16 -0
  37. package/dist/functions/aggregate.d.ts +8 -0
  38. package/dist/functions/datetime.d.ts +2 -0
  39. package/dist/functions/extensions.d.ts +7 -0
  40. package/dist/functions/index.d.ts +8 -0
  41. package/dist/functions/json.d.ts +6 -0
  42. package/dist/functions/math.d.ts +2 -0
  43. package/dist/functions/pragma-tvf.d.ts +8 -0
  44. package/dist/functions/registry.d.ts +39 -0
  45. package/dist/functions/scalar.d.ts +9 -0
  46. package/dist/functions/table-valued-registry.d.ts +11 -0
  47. package/dist/functions/table-valued.d.ts +13 -0
  48. package/dist/functions/window.d.ts +23 -0
  49. package/dist/index.d.ts +28 -0
  50. package/dist/index.js +16010 -0
  51. package/dist/index.js.map +7 -0
  52. package/dist/indexes/index.d.ts +48 -0
  53. package/dist/indexes/keys.d.ts +15 -0
  54. package/dist/json/index.d.ts +7 -0
  55. package/dist/json/jsonb.d.ts +42 -0
  56. package/dist/json/ops.d.ts +24 -0
  57. package/dist/json/parse.d.ts +13 -0
  58. package/dist/json/path.d.ts +26 -0
  59. package/dist/json/stringify.d.ts +4 -0
  60. package/dist/json/tvf.d.ts +13 -0
  61. package/dist/json/types.d.ts +29 -0
  62. package/dist/lexer/tokenize.d.ts +23 -0
  63. package/dist/parser/index.d.ts +17 -0
  64. package/dist/parser/parser.d.ts +115 -0
  65. package/dist/planner/access.d.ts +50 -0
  66. package/dist/planner/index.d.ts +3 -0
  67. package/dist/runtime/assert.d.ts +11 -0
  68. package/dist/runtime/catch.d.ts +3 -0
  69. package/dist/runtime/clock.d.ts +14 -0
  70. package/dist/runtime/index.d.ts +3 -0
  71. package/dist/runtime/options.d.ts +25 -0
  72. package/dist/runtime/prng.d.ts +42 -0
  73. package/dist/schema/catalog.d.ts +14 -0
  74. package/dist/schema/master-sql.d.ts +10 -0
  75. package/dist/serialization/codec.d.ts +19 -0
  76. package/dist/serialization/index.d.ts +1 -0
  77. package/dist/serialization/wire.d.ts +61 -0
  78. package/dist/storage/columnar-slab.d.ts +58 -0
  79. package/dist/storage/database-state.d.ts +96 -0
  80. package/dist/storage/index.d.ts +6 -0
  81. package/dist/storage/row.d.ts +13 -0
  82. package/dist/storage/table.d.ts +119 -0
  83. package/dist/transactions/manager.d.ts +19 -0
  84. package/dist/types/collation.d.ts +4 -0
  85. package/dist/types/sqlite-atof.d.ts +4 -0
  86. package/dist/types/sqlite-real-format.d.ts +5 -0
  87. package/dist/types/strict.d.ts +6 -0
  88. package/dist/types/value.d.ts +88 -0
  89. package/dist/unstable.d.ts +20 -0
  90. package/dist/unstable.js +10343 -0
  91. package/dist/unstable.js.map +7 -0
  92. package/dist/vtable/fts/options.d.ts +34 -0
  93. package/dist/vtable/fts/porter.d.ts +2 -0
  94. package/dist/vtable/fts/query.d.ts +39 -0
  95. package/dist/vtable/fts/table.d.ts +78 -0
  96. package/dist/vtable/fts/tokenize.d.ts +20 -0
  97. package/dist/vtable/fts5.d.ts +1 -0
  98. package/dist/vtable/index.d.ts +2 -0
  99. package/dist/vtable/modules.d.ts +65 -0
  100. 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).