@crvouga/sqlite-mem 0.2.0 → 1.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 CHANGED
@@ -107,9 +107,8 @@ Hot / large files: `parser/parser.ts`, `executor/select.ts`, `executor/dml.ts`.
107
107
  | `tests/fuzz/` | fast-check property tests (seeded); same two backends |
108
108
  | `tests/harness/` | Compare/normalize helpers + harness unit tests |
109
109
  | `tests/adapters/` | Wrappers for sqlite-mem and `bun:sqlite` |
110
- | `tests/browser/` | Playwright smoke only — **not** the SQL oracle |
111
110
 
112
- Examples of public API usage: `tests/contract/api/`, `tests/browser/run.ts`, `tests/contract/parameters/`, `tests/contract/determinism/`.
111
+ Examples of public API usage: `tests/contract/api/`, `tests/contract/parameters/`, `tests/contract/determinism/`, `examples/react-vite`.
113
112
 
114
113
  ### Fuzz replay
115
114
 
@@ -148,7 +147,6 @@ bun run typecheck
148
147
  bun run test:sqlite-compat
149
148
  bun test # contract + fuzz + harness
150
149
  bun run build
151
- bun run test:browser # Playwright smoke
152
150
  ```
153
151
 
154
152
  ## PR and commits
package/COMPATIBILITY.md CHANGED
@@ -86,7 +86,7 @@ Reference: **SQLite 3.51.0** (`bun:sqlite`). Inventory: `bun run scripts/fts-ora
86
86
  bun run test:sqlite-compat # requirements + gate + contract/fuzz/harness
87
87
  bun run inventory # oracle function/module inventory
88
88
  bun run requirements # refresh SQLite.org requirements + coverage
89
- bun run test:browser # Playwright smoke (not the SQL oracle)
89
+ bun run build && bun run verify-package # ESM browser build + isomorphic pack gates
90
90
  ```
91
91
 
92
92
  Do not treat isolated unit tests of internal modules as proof of SQLite compatibility. The differential matrix runner is authoritative for SQL behavior; `test:sqlite-compat` is the release gate.
package/README.md CHANGED
@@ -46,7 +46,7 @@ db.exec(`
46
46
  )
47
47
  `);
48
48
 
49
- db.exec(`INSERT INTO users (name) VALUES (?)`, ["Alice"]);
49
+ db.prepare(`INSERT INTO users (name) VALUES (?)`).run("Alice");
50
50
 
51
51
  const users = db.query<{ id: number; name: string }>(`SELECT * FROM users`);
52
52
  console.log(users);
@@ -78,28 +78,27 @@ import { Database, SqliteError } from "@crvouga/sqlite-mem";
78
78
  interface DatabaseOptions {
79
79
  seed?: number | bigint; // default 1 — PRNG for random() / randomblob()
80
80
  now?: Date | (() => Date); // default 2000-01-01T00:00:00.000Z
81
- prng?: Prng; // optional; overrides seed
82
81
  }
83
82
 
84
83
  interface Database {
85
84
  constructor(options?: DatabaseOptions);
86
- exec(sql: string, params?: BindValue[]): void;
85
+ exec(sql: string): void;
87
86
  query<T = QueryRow>(sql: string, params?: BindValue[]): T[];
88
87
  prepare(sql: string): Statement;
89
88
  transaction<T>(fn: () => T): T;
90
89
  snapshot(): Uint8Array;
91
90
  restore(snapshot: Uint8Array): void;
92
91
  close(): void;
92
+ [Symbol.dispose]?(): void; // alias for close() when Symbol.dispose exists
93
93
  readonly changes: number;
94
94
  readonly lastInsertRowid: number | bigint;
95
95
  }
96
96
 
97
97
  interface Statement {
98
- bind(...params: BindValue[]): Statement;
99
98
  run(...params: BindValue[]): RunResult;
100
99
  all<T = QueryRow>(...params: BindValue[]): T[];
101
100
  get<T = QueryRow>(...params: BindValue[]): T | undefined;
102
- result(...params: BindValue[]): ResultSet; // includes columns when zero rows
101
+ result(...params: BindValue[]): ResultSet; // includes columns + values when zero rows
103
102
  }
104
103
 
105
104
  interface RunResult {
@@ -110,46 +109,48 @@ interface RunResult {
110
109
  interface ResultSet {
111
110
  columns: string[];
112
111
  rows: QueryRow[];
113
- values?: QueryValue[][];
112
+ values: QueryValue[][]; // always present (empty array for zero rows)
114
113
  changes: number;
115
114
  lastInsertRowid: number | bigint;
116
115
  }
117
116
 
118
117
  class SqliteError extends Error {
119
118
  readonly category: ErrorCategory; // syntax, no_such_table, constraint_unique, misuse, …
120
- readonly sqliteCode?: string;
119
+ readonly sqliteCode: string; // always set; default "SQLITE_ERROR"
120
+ readonly code: string; // === sqliteCode (Node err.code convention)
121
121
  }
122
122
  ```
123
123
 
124
- Stick to `Database`, `Statement`, and `SqliteError` for application code. The package also exports lower-level helpers (`parse`, `tokenize`, `evalExpr`, snapshot codec pieces, `SqlValue` utilities) for advanced use.
124
+ Stick to `Database`, `Statement`, and `SqliteError` for application code. Advanced internals (`parse`, `tokenize`, `evalExpr`, snapshot codec pieces, `SqlValue` utilities, `Prng`, …) are available only from `@crvouga/sqlite-mem/unstable` and are **exempt from semver**.
125
125
 
126
126
  ### Method semantics
127
127
 
128
128
  | Method | Behavior |
129
129
  | --- | --- |
130
- | `exec(sql, params?)` | Runs all semicolon-separated statements; **discards** row results (`void`). Read `db.changes` / `db.lastInsertRowid` afterward if needed. |
131
- | `query(sql, params?)` | Same parse/run; returns rows of the **last** statement that has columns. Earlier SELECTs in a multi-statement script are dropped. |
132
- | `prepare(sql)` | Parses immediately; AST is reused. `run` / `all` / `get` / `result` with args override prior `bind()`. |
133
- | `transaction(fn)` | If idle: `BEGIN` → `fn()` → `COMMIT`, or `ROLLBACK` + rethrow. If already in a transaction: nested savepoint. Nested SQL `BEGIN` still errors. |
130
+ | `exec(sql)` | Runs all semicolon-separated statements; **discards** row results (`void`). Does **not** accept bind parameters. Read `db.changes` / `db.lastInsertRowid` afterward if needed (counters reflect the **most recent** completed statement, matching SQLite). |
131
+ | `query(sql, params?)` | **Single statement only** (trailing `;` is fine). Returns all rows. Multi-statement scripts throw `misuse`. |
132
+ | `prepare(sql)` | **Single statement only**. Parses immediately; AST is reused. Pass binds as rest args to `run` / `all` / `get` / `result` on each call. |
133
+ | `transaction(fn)` | If idle: `BEGIN` → `fn()` → `COMMIT`, or `ROLLBACK` + rethrow. If already in a transaction: nested savepoint. Nested SQL `BEGIN` still errors. `close()` inside `fn` throws `misuse`. |
134
134
  | `snapshot` / `restore` | Custom binary format (see below). |
135
- | `close()` | Idempotent; rolls back an open transaction; further ops throw `misuse`. |
135
+ | `close()` | Idempotent; rolls back an open SQL transaction; further ops throw `misuse`. Also available as `[Symbol.dispose]` when supported. |
136
136
 
137
- SQL `BEGIN` / `COMMIT` / `ROLLBACK` / `SAVEPOINT` / `RELEASE` are first-class. Empty SQL throws `misuse` (`empty statement`).
137
+ SQL `BEGIN` / `COMMIT` / `ROLLBACK` / `SAVEPOINT` / `RELEASE` are first-class. Empty / comment-only SQL on `prepare` / `query` throws `misuse` (`empty statement`), matching SQLite prepare failure.
138
138
 
139
139
  ### Parameter binding
140
140
 
141
141
  Supported styles: `?`, `?NNN`, `:name`, `@name`, `$name`.
142
142
 
143
- - The JS API takes a **positional array** (or rest args) only — there is **no** `bind({ name: value })`.
143
+ - The JS API takes **rest args** (or a positional array into `query`) only — there is **no** sticky `bind()` and **no** `bind({ name: value })`.
144
144
  - Named parameters occupy slots in **first-occurrence order**; repeated names share one slot.
145
145
  - Prefixes are part of the name: `@x`, `$x`, and `:x` are **three different** parameters.
146
146
  - Names are lowercased for lookup (`:Left` ≡ `:left`).
147
147
  - Bindable: `null`, `string`, finite `number`, `bigint`, `boolean` → `0`/`1`, `Uint8Array` / `ArrayBuffer`.
148
- - Rejected: `undefined`, `Date`, plain objects, `NaN` / `Infinity`.
148
+ - Rejected (`misuse`): `DataView`, typed-array views other than `Uint8Array`, `SharedArrayBuffer` / SAB-backed buffers.
149
+ - Rejected (`datatype_mismatch`): `undefined`, `Date`, plain objects, `NaN` / `Infinity`.
149
150
 
150
151
  ```ts
151
152
  db.query(`SELECT ? AS a, :name AS b`, [1, "Alice"]);
152
- db.prepare(`SELECT @id AS id`).bind(42).get();
153
+ db.prepare(`SELECT @id AS id`).get(42);
153
154
  ```
154
155
 
155
156
  ### Returned JavaScript types
@@ -166,12 +167,13 @@ Duplicate column names collapse in row objects (last write wins). Use `stmt.resu
166
167
 
167
168
  ### Snapshots
168
169
 
169
- - Format magic `SQLM` — **not** a portable `.sqlite` file and not loadable by the SQLite CLI.
170
+ - Format magic `SQLM` followed by an explicit little-endian format-version `u32` — **not** a portable `.sqlite` file and not loadable by the SQLite CLI.
170
171
  - Round-trips ordinary tables, views, indexes, change counters, PRNG state, and clock.
171
172
  - **Not** encoded: triggers, ATTACH’d schemas, virtual tables (FTS / RTREE / …), `userVersion`.
172
173
  - Cannot `restore()` while a transaction is open.
173
174
  - `restore()` replaces `now` with a fixed clock from the snapshot (a live `() => Date` is overwritten).
174
- - Equivalent databases produce byte-identical snapshots (schema/rows sorted).
175
+ - Equivalent databases produce byte-identical snapshots (schema/rows sorted) **within a single library version**.
176
+ - **Compatibility policy:** newer library versions can always restore older snapshots; older libraries cannot restore newer format versions (`snapshot_version` / `SQLITE_FORMAT`). Corrupt magic yields a distinct error.
175
177
 
176
178
  ## Determinism
177
179
 
@@ -179,7 +181,7 @@ The engine is deterministic by default. Invariants:
179
181
 
180
182
  | Source | Default | Override / notes |
181
183
  | --- | --- | --- |
182
- | `random()` / `randomblob()` | Seeded xorshift64* (`seed: 1`) | `new Database({ seed })` or `{ prng }` |
184
+ | `random()` / `randomblob()` | Seeded xorshift64* (`seed: 1`) | `new Database({ seed })` |
183
185
  | `date('now')` / friends | Fixed `2000-01-01T00:00:00.000Z` | `new Database({ now: Date \| (() => Date) })` |
184
186
  | Table scans | Rowid order | Same order after `snapshot`/`restore` |
185
187
  | Snapshots | Sorted schema/rows + PRNG state + clock | Restored into PRNG and `now` |
@@ -194,6 +196,15 @@ SQLITE_MEM_FUZZ_SEED=12345 bun test tests/fuzz
194
196
  SQLITE_MEM_FUZZ_SEED=12345 SQLITE_MEM_FUZZ_PATH='0:1' bun test tests/fuzz # exact replay
195
197
  ```
196
198
 
199
+ ## Stability policy
200
+
201
+ The exports of the main entry (`@crvouga/sqlite-mem`) are **frozen**:
202
+
203
+ - **Never** outside a major: removals, renames, signature changes, or changes to documented behavior of the stable surface.
204
+ - **Allowed in minors:** additions (new methods, new optional `DatabaseOptions` fields, new `ErrorCategory` values). Consumers that `switch` on `category` must include a default case — new categories may appear without a major bump.
205
+ - **`@crvouga/sqlite-mem/unstable`** is exempt from semver and may change or disappear in any release.
206
+ - **Snapshots:** newer library → can restore older blobs; older library → cannot restore newer format versions; byte-identical snapshot guarantee holds only within one library version.
207
+
197
208
  ## Compatibility notes for integrators
198
209
 
199
210
  Goal: drop-in SQL behavior vs SQLite **3.51.0**. Full matrix: [COMPATIBILITY.md](COMPATIBILITY.md).
@@ -205,20 +216,21 @@ Goal: drop-in SQL behavior vs SQLite **3.51.0**. Full matrix: [COMPATIBILITY.md]
205
216
  - FTS3/4/5 — largely implemented; shadow-table change counters intentionally diverge; some edges partial
206
217
  - `EXPLAIN` / `EXPLAIN QUERY PLAN` — stub shapes, not real bytecode
207
218
  - `INDEXED BY` / `NOT INDEXED` — parsed and discarded
208
- - Unknown `PRAGMA` succeeds with an empty result (SQLite-like); storage/journal/WAL pragmas N/A or no-op
219
+ - Unknown statement `PRAGMA` succeeds with an empty result (SQLite-like). All oracle-exposed `pragma_*` eponymous TVFs are supported (`SELECT * FROM pragma_table_info('t')`, bare `FROM pragma_database_list`, …). Storage/journal getters return bun `:memory:`-compatible defaults.
209
220
 
210
221
  ## Common pitfalls
211
222
 
212
223
  1. **Do not `await`** — the API is sync.
213
- 2. **No named-object binds**use positional arrays in declaration order.
214
- 3. **Multi-statement `query`** returns only the last result set with columns.
215
- 4. **`exec` returns `void`** — use `db.changes` / `stmt.run()` for counters.
224
+ 2. **No named-object binds and no sticky `bind()`** pass positional rest args / arrays in declaration order to `query` / `run` / `all` / `get` / `result`.
225
+ 3. **`query` / `prepare` are single-statement only** — multi-statement scripts belong in `exec()` (which does not take bind parameters).
226
+ 4. **`exec` returns `void` and takes no params** — use `db.prepare(…).run(…)` or `db.query(…)` for binds; use `db.changes` / `stmt.run()` for counters.
216
227
  5. **`'now'` is not wall-clock** unless you pass `{ now: () => new Date() }`. Default is year 2000.
217
228
  6. **`random()` is seeded**, not OS entropy; snapshots restore the PRNG.
218
229
  7. **Snapshots are not `.sqlite` files** and do not round-trip FTS / triggers / ATTACH.
219
230
  8. **No better-sqlite3 extras** — no `iterate`, `pluck`/`raw`, `safeIntegers` option, `pragma()` helper, `loadExtension`, or SQLite-file `serialize()`.
220
- 9. **Do not bind `Date` objects** — store unixepoch integers or ISO text.
231
+ 9. **Do not bind `Date` objects** — store unixepoch integers or ISO text. Do not bind `DataView` / non-`Uint8Array` typed arrays.
221
232
  10. **Do not use `Number.isInteger` for SQL REAL vs INTEGER** — use SQL `typeof()`.
233
+ 11. **Do not import `@crvouga/sqlite-mem/unstable` in application code** unless you accept breakage in any release.
222
234
 
223
235
  Working examples beyond this README: `examples/react-vite`, `tests/contract/api/`, `tests/contract/parameters/`, `tests/browser/run.ts`.
224
236
 
@@ -14,15 +14,17 @@ import { Statement } from "./statement.js";
14
14
  *
15
15
  * const db = new Database();
16
16
  * db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)");
17
- * db.exec("INSERT INTO users (name) VALUES (?)", ["Alice"]);
17
+ * db.prepare("INSERT INTO users (name) VALUES (?)").run("Alice");
18
18
  * const users = db.query<{ id: number; name: string }>("SELECT * FROM users");
19
19
  * ```
20
20
  */
21
21
  export declare class Database {
22
- /** Seed used to construct the PRNG when `options.prng` is omitted. */
22
+ /** Seed used to construct the PRNG. */
23
23
  readonly seed: number | bigint;
24
24
  private closed;
25
25
  private transactionSequence;
26
+ /** Depth of active {@link transaction} callbacks (not SQL BEGIN). */
27
+ private apiTransactionDepth;
26
28
  /**
27
29
  * Create an empty in-memory database.
28
30
  *
@@ -32,33 +34,34 @@ export declare class Database {
32
34
  /**
33
35
  * Execute SQL for its side effects (DDL/DML). Multiple statements are allowed.
34
36
  *
37
+ * Does not accept bind parameters — use {@link prepare} or {@link query}.
38
+ *
35
39
  * @param sql - SQL to run (semicolon-separated statements are ok).
36
- * @param params - Bound parameters for `?` / `:name` placeholders.
37
- * @throws {SqliteError} If the database is closed or the SQL fails.
40
+ * @throws {SqliteError} If the database is closed, extra arguments are passed, or the SQL fails.
38
41
  */
39
- exec(sql: string, params?: readonly BindValue[]): void;
42
+ exec(sql: string): void;
40
43
  /**
41
- * Execute a query and return all rows as objects keyed by column name.
44
+ * Execute a single-statement query and return all rows as objects keyed by column name.
42
45
  *
43
46
  * @typeParam T - Row shape. Defaults to {@link QueryRow}.
44
- * @param sql - SQL SELECT (or any statement that produces a result set).
47
+ * @param sql - A single SQL statement (trailing `;` is fine).
45
48
  * @param params - Bound parameters for `?` / `:name` placeholders.
46
49
  * @returns All result rows.
47
- * @throws {SqliteError} If the database is closed or the SQL fails.
50
+ * @throws {SqliteError} If the database is closed, `sql` is not a single statement, or execution fails.
48
51
  */
49
52
  query<T = QueryRow>(sql: string, params?: readonly BindValue[]): T[];
50
53
  /**
51
- * Compile `sql` into a reusable {@link Statement}.
54
+ * Compile a single SQL statement into a reusable {@link Statement}.
52
55
  *
53
- * @param sql - SQL to prepare.
54
- * @throws {SqliteError} If the database is closed or `sql` cannot be parsed.
56
+ * @param sql - A single SQL statement (trailing `;` is fine). Multi-statement scripts are rejected.
57
+ * @throws {SqliteError} If the database is closed or `sql` cannot be prepared as one statement.
55
58
  */
56
59
  prepare(sql: string): Statement;
57
60
  /**
58
61
  * Run `fn` inside a transaction. Commits on success; rolls back if `fn` throws.
59
62
  *
60
63
  * Nested calls use SAVEPOINTs so an inner failure does not abort the outer
61
- * transaction.
64
+ * transaction. Calling {@link close} from inside `fn` throws `misuse`.
62
65
  *
63
66
  * @param fn - Work to run while the transaction is open.
64
67
  * @returns The value returned by `fn`.
@@ -77,6 +80,8 @@ export declare class Database {
77
80
  * Replace this database's contents with a blob from {@link snapshot}.
78
81
  *
79
82
  * Restores PRNG state and the clock when the snapshot includes them (v2).
83
+ * Newer library versions can restore older snapshots; older libraries cannot
84
+ * restore newer format versions.
80
85
  *
81
86
  * @param snapshot - Bytes previously returned by {@link snapshot}.
82
87
  * @throws {SqliteError} If the database is closed, a transaction is open, or the blob is invalid.
@@ -85,7 +90,8 @@ export declare class Database {
85
90
  /**
86
91
  * Close the database. Further SQL throws {@link SqliteError}. Idempotent.
87
92
  *
88
- * Rolls back an open transaction, if any.
93
+ * Rolls back an open SQL transaction, if any. Throws if called from inside
94
+ * a {@link transaction} callback.
89
95
  */
90
96
  close(): void;
91
97
  /**
@@ -100,5 +106,6 @@ export declare class Database {
100
106
  * @throws {SqliteError} If the database is closed.
101
107
  */
102
108
  get lastInsertRowid(): number | bigint;
109
+ private prepareSingle;
103
110
  }
104
111
  export type { DatabaseOptions };
@@ -10,9 +10,9 @@ export interface RunResult {
10
10
  /**
11
11
  * Prepared SQL statement bound to a {@link Database}.
12
12
  *
13
- * Create with {@link Database.prepare}. Extra arguments to {@link run},
14
- * {@link all}, {@link get}, or {@link result} override values from {@link bind};
15
- * otherwise the last `bind()` parameters are used.
13
+ * Create with {@link Database.prepare}. Pass bind values as rest arguments to
14
+ * {@link run}, {@link all}, {@link get}, or {@link result} on each call
15
+ * (stateless there is no sticky `bind()`).
16
16
  *
17
17
  * @example
18
18
  * ```ts
@@ -28,25 +28,15 @@ export interface RunResult {
28
28
  export declare class Statement {
29
29
  private readonly database;
30
30
  private readonly sql;
31
- private bound;
32
31
  private namedPlan;
33
32
  private env;
34
33
  private statements;
35
34
  private schemaVersion;
36
35
  private constructor();
37
- /**
38
- * Store parameters for later {@link run} / {@link all} / {@link get} / {@link result}.
39
- *
40
- * Supports positional `?` / `?NNN` and named `:name` / `@name` / `$name` placeholders.
41
- *
42
- * @param params - Values to bind, in placeholder order.
43
- * @returns `this` for chaining.
44
- */
45
- bind(...params: BindValue[]): Statement;
46
36
  /**
47
37
  * Execute for side effects (INSERT / UPDATE / DELETE / DDL).
48
38
  *
49
- * @param params - If provided, override the last {@link bind}; otherwise bound values are used.
39
+ * @param params - Bind values for this call only.
50
40
  * @returns Mutation counters for this execution.
51
41
  * @throws {SqliteError} If the database is closed, the statement is empty, or execution fails.
52
42
  */
@@ -55,7 +45,7 @@ export declare class Statement {
55
45
  * Execute and return every result row as an object keyed by column name.
56
46
  *
57
47
  * @typeParam T - Row shape. Defaults to {@link QueryRow}.
58
- * @param params - If provided, override the last {@link bind}; otherwise bound values are used.
48
+ * @param params - Bind values for this call only.
59
49
  * @throws {SqliteError} If the database is closed, the statement is empty, or execution fails.
60
50
  */
61
51
  all<T = QueryRow>(...params: BindValue[]): T[];
@@ -65,7 +55,7 @@ export declare class Statement {
65
55
  * Use this when you need metadata for an empty result (column names with zero rows).
66
56
  * {@link all} only returns row objects.
67
57
  *
68
- * @param params - If provided, override the last {@link bind}; otherwise bound values are used.
58
+ * @param params - Bind values for this call only.
69
59
  * @throws {SqliteError} If the database is closed, the statement is empty, or execution fails.
70
60
  */
71
61
  result(...params: BindValue[]): ResultSet;
@@ -73,7 +63,7 @@ export declare class Statement {
73
63
  * Execute and return the first row, or `undefined` if there are no rows.
74
64
  *
75
65
  * @typeParam T - Row shape. Defaults to {@link QueryRow}.
76
- * @param params - If provided, override the last {@link bind}; otherwise bound values are used.
66
+ * @param params - Bind values for this call only.
77
67
  * @throws {SqliteError} If the database is closed, the statement is empty, or execution fails.
78
68
  */
79
69
  get<T = QueryRow>(...params: BindValue[]): T | undefined;
@@ -1,8 +1,15 @@
1
- /** Coarse classification of {@link SqliteError} for catch-site branching. */
2
- export type ErrorCategory = "syntax" | "no_such_table" | "no_such_column" | "constraint_unique" | "constraint_primary" | "constraint_notnull" | "constraint_check" | "constraint_foreign" | "constraint" | "transaction" | "datatype_mismatch" | "unsupported" | "misuse" | "other";
1
+ /** Coarse classification of {@link SqliteError} for catch-site branching.
2
+ *
3
+ * New categories may be added in minor releases. Consumers that switch on
4
+ * `category` must include a default case.
5
+ */
6
+ export type ErrorCategory = "syntax" | "no_such_table" | "no_such_column" | "constraint_unique" | "constraint_primary" | "constraint_notnull" | "constraint_check" | "constraint_foreign" | "constraint" | "transaction" | "datatype_mismatch" | "unsupported" | "misuse" | "snapshot_version" | "other" | (string & {});
3
7
  /**
4
8
  * Engine error. `name` is always `"SqliteError"`.
5
9
  *
10
+ * `sqliteCode` and Node's conventional `code` property are always set (default
11
+ * `"SQLITE_ERROR"` when no more specific code applies).
12
+ *
6
13
  * @example
7
14
  * ```ts
8
15
  * import { Database, SqliteError } from "@crvouga/sqlite-mem";
@@ -20,12 +27,14 @@ export type ErrorCategory = "syntax" | "no_such_table" | "no_such_column" | "con
20
27
  export declare class SqliteError extends Error {
21
28
  /** Coarse error class (constraint vs syntax vs missing object, …). */
22
29
  readonly category: ErrorCategory;
23
- /** Optional SQLite result-code name such as `SQLITE_CONSTRAINT`. */
24
- readonly sqliteCode?: string;
30
+ /** SQLite result-code name such as `SQLITE_CONSTRAINT_UNIQUE`. */
31
+ readonly sqliteCode: string;
32
+ /** Same value as {@link sqliteCode} (Node `err.code` convention). */
33
+ readonly code: string;
25
34
  /**
26
35
  * @param message - Human-readable error text.
27
36
  * @param category - Coarse class; defaults to `"other"`.
28
- * @param sqliteCode - Optional SQLite result-code name.
37
+ * @param sqliteCode - SQLite result-code name; defaults to `"SQLITE_ERROR"`.
29
38
  */
30
39
  constructor(message: string, category?: ErrorCategory, sqliteCode?: string);
31
40
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shared PRAGMA query engine used by statement-form `PRAGMA …` and `pragma_*` TVFs.
3
+ */
4
+ import type { Expr } from "../ast/nodes.js";
5
+ import { type SqlValue } from "../types/value.js";
6
+ import type { ExecutionEnv } from "./env.js";
7
+ export interface PragmaQueryResult {
8
+ columns: string[];
9
+ rows: SqlValue[][];
10
+ }
11
+ /** Oracle-exposed pragma_* TVF base names (without the `pragma_` prefix). */
12
+ export declare const PRAGMA_TVF_NAMES: readonly ["analysis_limit", "application_id", "auto_vacuum", "automatic_index", "busy_timeout", "cache_size", "cache_spill", "cell_size_check", "checkpoint_fullfsync", "collation_list", "compile_options", "count_changes", "data_version", "database_list", "default_cache_size", "defer_foreign_keys", "empty_result_callbacks", "encoding", "foreign_key_check", "foreign_key_list", "foreign_keys", "freelist_count", "full_column_names", "fullfsync", "function_list", "hard_heap_limit", "ignore_check_constraints", "index_info", "index_list", "index_xinfo", "integrity_check", "journal_mode", "journal_size_limit", "legacy_alter_table", "locking_mode", "max_page_count", "module_list", "optimize", "page_count", "page_size", "pragma_list", "query_only", "quick_check", "read_uncommitted", "recursive_triggers", "reverse_unordered_selects", "schema_version", "secure_delete", "short_column_names", "soft_heap_limit", "synchronous", "table_info", "table_list", "table_xinfo", "temp_store", "threads", "trusted_schema", "user_version", "writable_schema"];
13
+ /** Full pragma name list for `pragma_pragma_list` (includes names without TVFs). */
14
+ export declare const PRAGMA_LIST_NAMES: string[];
15
+ export declare function isPragmaTvfName(name: string): boolean;
16
+ /**
17
+ * Query a pragma by name with optional SQL values (TVF args or evaluated statement args).
18
+ * Read-only — writers stay in {@link executePragma}.
19
+ */
20
+ export declare function queryPragma(name: string, args: readonly SqlValue[], env: ExecutionEnv): PragmaQueryResult;
21
+ /** Evaluate a statement-form pragma argument expression to a SQL value list. */
22
+ export declare function evalPragmaArgs(expr: Expr | null, env: ExecutionEnv): SqlValue[];
23
+ export declare function evalPragmaSetValue(expr: Expr, env: ExecutionEnv): SqlValue;
24
+ export declare function coercePragmaInt(value: SqlValue): number;
25
+ export declare function coercePragmaTruthy(value: SqlValue): boolean;
@@ -5,8 +5,8 @@ export interface ResultSet {
5
5
  columns: string[];
6
6
  /** Named rows. Duplicate column names keep the last value. */
7
7
  rows: QueryRow[];
8
- /** Positional rows preserve duplicate result-column names for internal use. */
9
- values?: QueryValue[][];
8
+ /** Positional rows; always present (empty array when there are zero rows). */
9
+ values: QueryValue[][];
10
10
  /** Rows changed by the most recent mutating statement in this execution. */
11
11
  changes: number;
12
12
  /** Rowid of the most recent INSERT in this execution. */
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Register all oracle-exposed `pragma_*` eponymous table-valued functions.
3
+ * Imported for side effects from table-valued / select.
4
+ */
5
+ import { isPragmaTvfName } from "../executor/pragma-engine.js";
6
+ /** Idempotent registration of every oracle `pragma_*` TVF. */
7
+ export declare function ensurePragmaTvfsRegistered(): void;
8
+ export { isPragmaTvfName };
@@ -0,0 +1,11 @@
1
+ import type { ExecutionEnv, ScopeRow } from "../executor/env.js";
2
+ import type { SqlValue } from "../types/value.js";
3
+ export interface TableValuedResult {
4
+ columns: string[];
5
+ rows: ScopeRow[];
6
+ }
7
+ export type TableValuedFn = (args: SqlValue[], alias: string | null, env: ExecutionEnv) => TableValuedResult;
8
+ export declare function registerTableValuedFunction(name: string, fn: TableValuedFn): void;
9
+ export declare function getTableValuedFunction(name: string): TableValuedFn | undefined;
10
+ export declare function listRegisteredTableValuedFunctions(): string[];
11
+ export declare function hasRegisteredTableValuedFunction(name: string): boolean;
@@ -1,8 +1,8 @@
1
1
  import type { Expr } from "../ast/nodes.js";
2
2
  import type { ExecutionEnv, ScopeRow } from "../executor/env.js";
3
- export interface TableValuedResult {
4
- columns: string[];
5
- rows: ScopeRow[];
6
- }
3
+ import { registerTableValuedFunction, type TableValuedResult } from "./table-valued-registry.js";
4
+ export type { TableValuedResult };
5
+ export { registerTableValuedFunction };
7
6
  export declare function evaluateTableFunction(name: string, args: Expr[], alias: string | null, env: ExecutionEnv, scope?: ScopeRow | null, parent?: import("../expressions/context.js").EvalContext): TableValuedResult;
8
7
  export declare function listTableValuedFunctions(): string[];
8
+ export declare function hasTableValuedFunction(name: string): boolean;
package/dist/index.d.ts CHANGED
@@ -5,29 +5,23 @@
5
5
  * Deterministic by default (`random()` seed `1`, `'now'` = `2000-01-01T00:00:00.000Z`).
6
6
  * Zero WASM, native bindings, or filesystem.
7
7
  *
8
+ * Advanced / internal helpers live under `@crvouga/sqlite-mem/unstable` and are
9
+ * exempt from semver.
10
+ *
8
11
  * @example
9
12
  * ```ts
10
13
  * import { Database } from "@crvouga/sqlite-mem";
11
14
  *
12
15
  * const db = new Database();
13
16
  * db.exec("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT)");
14
- * db.exec("INSERT INTO t (name) VALUES (?)", ["Ada"]);
17
+ * db.prepare("INSERT INTO t (name) VALUES (?)").run("Ada");
15
18
  * const rows = db.query<{ id: number; name: string }>("SELECT * FROM t");
16
19
  * ```
17
20
  *
18
21
  * @module
19
22
  */
20
- export * from "./api/database.js";
23
+ export { Database, type DatabaseOptions } from "./api/database.js";
21
24
  export { Statement, type RunResult } from "./api/statement.js";
22
- export type { Expr } from "./ast/nodes.js";
23
25
  export { SqliteError, type ErrorCategory } from "./errors/index.js";
24
26
  export type { ResultSet } from "./executor/result.js";
25
- export type { EvalContext } from "./expressions/context.js";
26
- export { evalExpr } from "./expressions/eval.js";
27
- export { globMatch, likeMatch } from "./expressions/like.js";
28
- export { tokenize, type Token, type TokenKind } from "./lexer/tokenize.js";
29
- export { parse, type ParsedStatement } from "./parser/index.js";
30
- export { type Clock, DEFAULT_DATABASE_SEED, DEFAULT_NOW, deriveSeed, fixedClock, Prng, resolveClock, } from "./runtime/index.js";
31
- export { type DecodedSnapshot, decodeDatabaseState, encodeDatabaseState, type SnapshotRuntime, } from "./serialization/index.js";
32
- export type { DatabaseState } from "./storage/database-state.js";
33
- export { type Affinity, affinityFromTypeName, applyAffinity, asSqlJsonText, asSqlReal, type BindValue, canonicalizeNumber, cloneSqlValue, coerceToNumber, compareSql, isSqlJsonText, isSqlReal, isTruthySql, type QueryRow, type QueryValue, SqlJsonText, SqlReal, type SqlValue, type StorageClass, sqlValueEquals, storageClassOf, toInteger, typeofSql, utf8Decode, utf8Encode, } from "./types/value.js";
27
+ export type { BindValue, QueryRow, QueryValue } from "./types/value.js";