@crvouga/postgres-mem 1.0.1 → 1.1.1
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 +11 -7
- package/COMPATIBILITY-AUDIT.md +6 -5
- package/COMPATIBILITY.md +4 -4
- package/README.md +54 -29
- package/compat/coverage.json +1 -1
- package/compat/divergences.json +16 -5
- package/compat/sections/api.ts +6 -0
- package/compat/sections/fzz.ts +5 -0
- package/compat/sections/snp.ts +3 -2
- package/compat/unsupported-register.json +0 -1
- package/dist/api/bind.d.ts +2 -1
- package/dist/api/database.d.ts +25 -7
- package/dist/api/snapshot.d.ts +21 -0
- package/dist/ast/nodes.d.ts +2 -0
- package/dist/constraints/enforce.d.ts +1 -0
- package/dist/executor/plpgsql.d.ts +69 -0
- package/dist/executor/select.d.ts +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9202 -7320
- package/dist/index.js.map +4 -4
- package/dist/indexes/index.d.ts +14 -0
- package/dist/indexes/maintain.d.ts +11 -0
- package/dist/parser/parser.d.ts +2 -0
- package/dist/planner/access.d.ts +15 -0
- package/dist/runtime/assert.d.ts +5 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/options.d.ts +9 -0
- package/dist/serialization/codec.d.ts +1 -8
- package/dist/serialization/wire.d.ts +63 -0
- package/dist/storage/columnar-slab.d.ts +37 -0
- package/dist/storage/database-state.d.ts +36 -1
- package/dist/transactions/manager.d.ts +5 -5
- package/dist/types/jsonpath.d.ts +16 -0
- package/dist/unstable.js +1659 -540
- package/dist/unstable.js.map +4 -4
- package/package.json +16 -2
package/AGENTS.md
CHANGED
|
@@ -11,9 +11,9 @@ Guidance for humans and coding agents editing this repository. For install and c
|
|
|
11
11
|
1. Custom snapshot codec (`PGMM`), not `pg_dump` / on-disk clusters
|
|
12
12
|
2. Deterministic `random()` / `gen_random_uuid()` and fixed `now()` by default (injectable)
|
|
13
13
|
3. Single-session engine: no MVCC across connections, no wire protocol, no aborted-transaction (`25P02`) state
|
|
14
|
-
4. `NOT APPLICABLE` items: roles/auth enforcement, replication, VACUUM internals, storage params, PL/pgSQL
|
|
14
|
+
4. `NOT APPLICABLE` items: roles/auth enforcement, replication, VACUUM internals, storage params, full PL/pgSQL
|
|
15
15
|
|
|
16
|
-
**Non-goals:** speaking the wire protocol, matching `pg`/`postgres.js` client APIs, PL/pgSQL, or multi-session concurrency.
|
|
16
|
+
**Non-goals:** speaking the wire protocol, matching `pg`/`postgres.js` client APIs, full PL/pgSQL, or multi-session concurrency.
|
|
17
17
|
|
|
18
18
|
## SQL pipeline
|
|
19
19
|
|
|
@@ -39,7 +39,7 @@ Everything is **typed values** (`TypedValue = { t: TypeId, v: Datum }` in [`src/
|
|
|
39
39
|
| `ast/` | Discriminated-union AST (`nodes.ts`) |
|
|
40
40
|
| `lexer/` | Tokenizer (dollar quoting, E-strings, `::`, operators) |
|
|
41
41
|
| `parser/` | Recursive-descent parser |
|
|
42
|
-
| `executor/` | Statement dispatch, SELECT, DML, DDL, session (SET/SHOW/COPY/PREPARE), triggers, window functions |
|
|
42
|
+
| `executor/` | Statement dispatch, SELECT, DML, DDL, session (SET/SHOW/COPY/PREPARE), triggers, plpgsql-lite UDFs, window functions |
|
|
43
43
|
| `expressions/` | `evalExpr`, operators, pattern matching, `EngineCtx` |
|
|
44
44
|
| `functions/` | Scalar / aggregate / window / datetime / JSON / array / SRF / tsearch registries |
|
|
45
45
|
| `types/` | `TypedValue`, casts, comparison, numeric, datetime, jsonb, timezone |
|
|
@@ -101,10 +101,10 @@ Hot / large files: `parser/parser.ts`, `executor/select.ts`, `executor/dml.ts`,
|
|
|
101
101
|
|
|
102
102
|
| Path | Role |
|
|
103
103
|
| --- | --- |
|
|
104
|
-
| `tests/contract/` | Differential SQL vs
|
|
104
|
+
| `tests/contract/` | Differential SQL vs oracle (**authoritative**; default PGlite, optional native via `test:postgres-native`) |
|
|
105
105
|
| `tests/fuzz/` | fast-check property tests (seeded); differential, NoREC/TLP metamorphic, stateful DST |
|
|
106
106
|
| `tests/harness/` | Compare/normalize/classify helpers + harness unit tests |
|
|
107
|
-
| `tests/adapters/` | Wrappers for postgres-mem and
|
|
107
|
+
| `tests/adapters/` | Wrappers for postgres-mem, PGlite, and native Postgres (`ContractDb`) |
|
|
108
108
|
| `tests/corpus/` | Fuzz regression corpus |
|
|
109
109
|
| `tests/meta/` | Canary definitions, skip register |
|
|
110
110
|
|
|
@@ -124,7 +124,8 @@ POSTGRES_MEM_FUZZ_SEED=12345 POSTGRES_MEM_FUZZ_PATH='0:1' bun test tests/fuzz
|
|
|
124
124
|
|
|
125
125
|
| Command | Role |
|
|
126
126
|
| --- | --- |
|
|
127
|
-
| `bun run test:postgres-compat` | Requirements + fail-closed gate + construct catalog + smoke ratchet + contract/fuzz/harness |
|
|
127
|
+
| `bun run test:postgres-compat` | Requirements + fail-closed gate + construct catalog + smoke ratchet + contract/fuzz/harness (PGlite oracle) |
|
|
128
|
+
| `bun run test:postgres-native` | Same differential suite vs real PostgreSQL 18.3 (embedded-postgres, or `POSTGRES_MEM_ORACLE_URL`) |
|
|
128
129
|
| `bun run inventory` | Oracle `pg_proc` / `pg_operator` vs engine registries (+ `--write-register`) |
|
|
129
130
|
| `bun run scenarios` | Construct-level scenario catalog (`compat/scenarios.ts`) + smoke gate |
|
|
130
131
|
| `bun run requirements` | Refresh PostgreSQL 18 SQL-commands docs → `compat/requirements.json` + `compat/coverage.json` |
|
|
@@ -136,6 +137,8 @@ Statuses: **VERIFIED** / **PARTIALLY VERIFIED** / **UNSUPPORTED** / **NOT APPLIC
|
|
|
136
137
|
|
|
137
138
|
**PGlite quirk:** PGlite's WASM boot leaks `process.exitCode = 99` under Bun ([pglite#975](https://github.com/electric-sql/pglite/issues/975)); the adapter and scripts reset it — keep that workaround when touching `tests/adapters/pglite.ts`.
|
|
138
139
|
|
|
140
|
+
**Native oracle:** `POSTGRES_MEM_ORACLE=server` + `POSTGRES_MEM_ORACLE_URL` selects `PostgresServerAdapter`. Prefer `bun run test:postgres-native` (starts embedded-postgres 18.3). Optional Docker: `docker-compose.oracle.yml`.
|
|
141
|
+
|
|
139
142
|
Details: [COMPATIBILITY.md](COMPATIBILITY.md), audit: [COMPATIBILITY-AUDIT.md](COMPATIBILITY-AUDIT.md).
|
|
140
143
|
|
|
141
144
|
## Local gates
|
|
@@ -150,10 +153,11 @@ bun run format
|
|
|
150
153
|
bun run lint
|
|
151
154
|
bun run typecheck
|
|
152
155
|
bun run test:postgres-compat
|
|
156
|
+
bun run test:postgres-native # optional second oracle (real PostgreSQL 18.3)
|
|
153
157
|
bun test # contract + fuzz + harness
|
|
154
158
|
bun run build
|
|
155
159
|
```
|
|
156
160
|
|
|
157
161
|
## PR and commits
|
|
158
162
|
|
|
159
|
-
Use [Conventional Commits](https://www.conventionalcommits.org/) for commits and PR titles (enforced
|
|
163
|
+
Use [Conventional Commits](https://www.conventionalcommits.org/) for commits and PR titles (enforced on PRs). Prefer squash merges with a conventional title so the npm bump is `feat` → minor / `fix` → patch. Direct pushes to `main` with any other subject still publish a patch. See [README.md](README.md#releasing).
|
package/COMPATIBILITY-AUDIT.md
CHANGED
|
@@ -32,8 +32,8 @@ Coverage statuses (SQL_BEHAVIOR):
|
|
|
32
32
|
|
|
33
33
|
Oracle builtin inventory:
|
|
34
34
|
pg_catalog functions exposed by oracle: 2787
|
|
35
|
-
implemented in engine registries:
|
|
36
|
-
registered unsupported (with reasons):
|
|
35
|
+
implemented in engine registries: 302
|
|
36
|
+
registered unsupported (with reasons): 2485
|
|
37
37
|
silently missing: 0 (gate fails otherwise)
|
|
38
38
|
pg_catalog operators exposed by oracle: 74
|
|
39
39
|
implemented: 41
|
|
@@ -53,7 +53,7 @@ Types / casts / NULL:
|
|
|
53
53
|
3VL through operators/aggregates/DISTINCT
|
|
54
54
|
|
|
55
55
|
Functions (oracle surface):
|
|
56
|
-
VERIFIED for the implemented
|
|
56
|
+
VERIFIED for the implemented 302; every other oracle builtin is an
|
|
57
57
|
explicit register entry (never silent)
|
|
58
58
|
|
|
59
59
|
JSON / JSONB:
|
|
@@ -74,10 +74,11 @@ Schemas / search_path / catalogs:
|
|
|
74
74
|
Sequences / serial / identity / enums / domains / generated columns:
|
|
75
75
|
VERIFIED
|
|
76
76
|
|
|
77
|
-
Triggers / LANGUAGE sql functions:
|
|
77
|
+
Triggers / LANGUAGE sql / plpgsql-lite functions:
|
|
78
78
|
PARTIALLY VERIFIED — row-level triggers fire in creation order
|
|
79
79
|
(PostgreSQL: name order), UPDATE OF ignored, INSTEAD OF unsupported —
|
|
80
|
-
all pinned divergences
|
|
80
|
+
all pinned divergences. plpgsql-lite UDFs: DECLARE, EXCEPTION WHEN
|
|
81
|
+
others, CASE, FOR-IN-SELECT, RETURN NEXT.
|
|
81
82
|
|
|
82
83
|
Text search:
|
|
83
84
|
PARTIALLY VERIFIED — tsvector/tsquery/@@/ts_rank with simple-style
|
package/COMPATIBILITY.md
CHANGED
|
@@ -34,9 +34,9 @@ Anything a PostgreSQL application can invoke through SQL against the PGlite **18
|
|
|
34
34
|
1. **Snapshot format** — custom binary codec (`PGMM`), not `pg_dump` / on-disk clusters (logical state still round-trips).
|
|
35
35
|
2. **Deterministic `random()` / `now()`** — seeded PRNG and fixed clock by default (injectable).
|
|
36
36
|
3. **Single session** — no MVCC across connections, no isolation levels beyond one session, no `25P02` aborted-transaction state (documented divergence).
|
|
37
|
-
4. **NOT APPLICABLE** rows in `compat/coverage.json` (roles, replication, VACUUM internals, LISTEN/NOTIFY, cursors, PL/pgSQL, extensions).
|
|
37
|
+
4. **NOT APPLICABLE** rows in `compat/coverage.json` (roles, replication, VACUUM internals, LISTEN/NOTIFY, cursors, full PL/pgSQL, extensions).
|
|
38
38
|
|
|
39
|
-
The oracle exposes **2787 builtin functions** and **74 operators** in `pg_catalog`; postgres-mem implements **
|
|
39
|
+
The oracle exposes **2787 builtin functions** and **74 operators** in `pg_catalog`; postgres-mem implements **302 functions** and **41 operators**, and every remaining item is an explicit entry in [`compat/unsupported-register.json`](compat/unsupported-register.json) with a reason (trigger/internal plumbing, admin/monitoring, unsupported type families, …). The gate fails closed on silence.
|
|
40
40
|
|
|
41
41
|
## Requirements coverage (PostgreSQL 18 SQL commands)
|
|
42
42
|
|
|
@@ -60,7 +60,7 @@ The oracle exposes **2787 builtin functions** and **74 operators** in `pg_catalo
|
|
|
60
60
|
| Schemas + search_path + pg_catalog / information_schema | VERIFIED | Catalog columns are the commonly-queried subset |
|
|
61
61
|
| Enums, domains, generated columns | VERIFIED | |
|
|
62
62
|
| Triggers (row-level, LANGUAGE sql-expressible) | PARTIALLY VERIFIED | Creation-order firing, `UPDATE OF` ignored, no INSTEAD OF (documented) |
|
|
63
|
-
| CREATE FUNCTION LANGUAGE sql | PARTIALLY VERIFIED | Scalar + set-returning;
|
|
63
|
+
| CREATE FUNCTION LANGUAGE sql / plpgsql-lite | PARTIALLY VERIFIED | Scalar + set-returning; plpgsql subset: DECLARE, EXCEPTION WHEN others, CASE, FOR-IN-SELECT, RETURN NEXT |
|
|
64
64
|
| Text search (tsvector / tsquery / @@ / ts_rank) | PARTIALLY VERIFIED | `simple`-style config; no ispell/synonym dictionaries |
|
|
65
65
|
| COPY FROM/TO (text, csv) | VERIFIED | Via `copyFrom` API hook / rows out |
|
|
66
66
|
| PREPARE / EXECUTE / DEALLOCATE, SET / SHOW / RESET | VERIFIED | GUC subset |
|
|
@@ -68,7 +68,7 @@ The oracle exposes **2787 builtin functions** and **74 operators** in `pg_catalo
|
|
|
68
68
|
| Collation / ordering | PARTIALLY VERIFIED | `C` semantics pinned; locale/ICU out of scope |
|
|
69
69
|
| Regex (`~`, `~*`, POSIX functions) | PARTIALLY VERIFIED | JS regex flavor mapped to POSIX ERE; documented edges |
|
|
70
70
|
| EXPLAIN | PARTIALLY VERIFIED | Stub plan shapes |
|
|
71
|
-
| MERGE / CALL / cursors / LISTEN / PL/pgSQL | UNSUPPORTED | Fail loud `0A000`, registered |
|
|
71
|
+
| MERGE / CALL / cursors / LISTEN / full PL/pgSQL | UNSUPPORTED | Fail loud `0A000`, registered |
|
|
72
72
|
| Roles / GRANT / VACUUM / ANALYZE / LOCK | NOT APPLICABLE | Parsed no-ops where harmless |
|
|
73
73
|
| Wire protocol / multi-session MVCC / on-disk format | NOT APPLICABLE | |
|
|
74
74
|
|
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# postgres-mem
|
|
2
2
|
|
|
3
|
+
[npm](https://www.npmjs.com/package/@crvouga/postgres-mem) · [GitHub](https://github.com/crvouga/postgres-mem)
|
|
4
|
+
|
|
3
5
|
Pure TypeScript, completely in-memory PostgreSQL implementation aiming for **PostgreSQL 18 SQL dialect parity** (same statements → same results).
|
|
4
6
|
|
|
5
7
|
- Runs in modern browsers and Node.js / Bun
|
|
6
8
|
- **Zero** WASM, native bindings, workers, or filesystem dependencies
|
|
7
9
|
- Entire database stored in memory
|
|
8
10
|
- **Synchronous** ESM-only API (no Promises, no `require`)
|
|
9
|
-
- **SQL dialect verified** against real PostgreSQL 18.3 (PGlite) via differential contracts + fail-closed gate
|
|
11
|
+
- **SQL dialect verified** against real PostgreSQL 18.3 (PGlite by default; optional native server via `test:postgres-native`) via differential contracts + fail-closed gate
|
|
10
12
|
- **Not** a drop-in for `pg` / `postgres.js` client APIs, the wire protocol, or on-disk clusters
|
|
11
13
|
- Intentional differences: deterministic `random()` / `now()` by default, and a custom snapshot format (not `pg_dump`)
|
|
12
14
|
|
|
@@ -41,7 +43,7 @@ Requires Node.js ≥ 20 or Bun ≥ 1.1. The published package is **ESM only** (`
|
|
|
41
43
|
## Usage
|
|
42
44
|
|
|
43
45
|
```ts
|
|
44
|
-
import { Database } from "@crvouga/postgres-mem";
|
|
46
|
+
import { Database, Snapshot } from "@crvouga/postgres-mem";
|
|
45
47
|
|
|
46
48
|
const db = new Database();
|
|
47
49
|
|
|
@@ -57,9 +59,10 @@ db.prepare(`INSERT INTO users (name) VALUES ($1)`).run("Alice");
|
|
|
57
59
|
const users = db.query<{ id: number; name: string }>(`SELECT * FROM users`);
|
|
58
60
|
console.log(users);
|
|
59
61
|
|
|
60
|
-
const
|
|
61
|
-
const db2 =
|
|
62
|
-
|
|
62
|
+
const seed = db.snapshot();
|
|
63
|
+
const db2 = seed.open();
|
|
64
|
+
const bytes = seed.encode();
|
|
65
|
+
const db3 = Snapshot.decode(bytes).open();
|
|
63
66
|
```
|
|
64
67
|
|
|
65
68
|
All methods are **synchronous** — do not `await` them. Browser and Node/Bun share the same in-memory JS surface (no filesystem, no server, no wire protocol).
|
|
@@ -79,28 +82,41 @@ From the repo root after that install: `bun run example`.
|
|
|
79
82
|
## API
|
|
80
83
|
|
|
81
84
|
```ts
|
|
82
|
-
import { Database, PostgresError } from "@crvouga/postgres-mem";
|
|
85
|
+
import { Database, Snapshot, PostgresError } from "@crvouga/postgres-mem";
|
|
83
86
|
|
|
84
87
|
interface DatabaseOptions {
|
|
85
88
|
seed?: number | bigint; // default 1 — ignored when random is "os"
|
|
86
89
|
random?: "deterministic" | "os"; // default "deterministic"; "os" is CSPRNG like PostgreSQL
|
|
87
90
|
now?: Date | (() => Date) | "system"; // default 2000-01-01T00:00:00.000Z; "system" is wall clock
|
|
91
|
+
int8?: "bigint" | "number" | "string"; // default "bigint"; "number" is unsafe beyond MAX_SAFE_INTEGER
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
interface Database {
|
|
91
95
|
constructor(options?: DatabaseOptions);
|
|
92
96
|
exec(sql: string): void;
|
|
97
|
+
registerFunction(spec: {
|
|
98
|
+
name: string;
|
|
99
|
+
args: string[];
|
|
100
|
+
returns: string;
|
|
101
|
+
strict?: boolean;
|
|
102
|
+
fn: (...args: JsValue[]) => JsValue;
|
|
103
|
+
}): void;
|
|
93
104
|
query<T = QueryRow>(sql: string, params?: BindValue[]): T[];
|
|
94
105
|
prepare(sql: string): Statement;
|
|
95
106
|
transaction<T>(fn: () => T): T;
|
|
96
107
|
copyFrom(sql: string, data: string): number; // COPY t FROM STDIN payload (\copy analog)
|
|
97
|
-
snapshot():
|
|
98
|
-
restore(snapshot: Uint8Array): void;
|
|
108
|
+
snapshot(): Snapshot;
|
|
99
109
|
close(): void;
|
|
100
110
|
[Symbol.dispose]?(): void; // alias for close() when Symbol.dispose exists
|
|
101
111
|
readonly changes: number; // rows affected by the most recent INSERT/UPDATE/DELETE
|
|
102
112
|
}
|
|
103
113
|
|
|
114
|
+
class Snapshot {
|
|
115
|
+
open(options?: DatabaseOptions): Database;
|
|
116
|
+
encode(): Uint8Array;
|
|
117
|
+
static decode(bytes: Uint8Array): Snapshot;
|
|
118
|
+
}
|
|
119
|
+
|
|
104
120
|
interface Statement {
|
|
105
121
|
run(...params: BindValue[]): RunResult;
|
|
106
122
|
all<T = QueryRow>(...params: BindValue[]): T[];
|
|
@@ -129,18 +145,22 @@ class PostgresError extends Error {
|
|
|
129
145
|
}
|
|
130
146
|
```
|
|
131
147
|
|
|
132
|
-
Stick to `Database`, `Statement`, and `PostgresError` for application code. Advanced internals (`parse`, `tokenize`, `executeStatement`, snapshot codec pieces, `Prng`, …) are available only from `@crvouga/postgres-mem/unstable` and are **exempt from semver**.
|
|
148
|
+
Stick to `Database`, `Snapshot`, `Statement`, and `PostgresError` for application code. Advanced internals (`parse`, `tokenize`, `executeStatement`, snapshot codec pieces, `Prng`, …) are available only from `@crvouga/postgres-mem/unstable` and are **exempt from semver**.
|
|
133
149
|
|
|
134
150
|
### Method semantics
|
|
135
151
|
|
|
136
152
|
| Method | Behavior |
|
|
137
153
|
| --- | --- |
|
|
138
|
-
| `exec(sql)` | Runs all semicolon-separated statements; **discards** row results (`void`). Does **not** accept bind parameters. Read `db.changes` afterward if needed (reflects the **most recent** completed DML statement). |
|
|
154
|
+
| `exec(sql)` | Runs all semicolon-separated statements; **discards** row results (`void`). Does **not** accept bind parameters. Read `db.changes` afterward if needed (reflects the **most recent** completed DML statement). Dump-only `DO` blocks and `ALTER TABLE … SET (` storage parameters are no-ops. |
|
|
155
|
+
| `registerFunction(spec)` | Install a JavaScript scalar. Not stored in PGMM snapshots; `open()` of a live snapshot copies the impl by reference. |
|
|
139
156
|
| `query(sql, params?)` | **Single statement only** (trailing `;` is fine). Returns all rows. Multi-statement scripts throw `misuse`. |
|
|
140
157
|
| `prepare(sql)` | **Single statement only**. Parses immediately; AST is reused. Pass binds as rest args to `run` / `all` / `get` / `result` on each call. |
|
|
141
158
|
| `transaction(fn)` | If idle: `BEGIN` → `fn()` → `COMMIT`, or `ROLLBACK` + rethrow. If already in a transaction: nested savepoint. Nested SQL `BEGIN` inside is a no-op warning like PostgreSQL. `close()` inside `fn` throws `misuse`. |
|
|
142
159
|
| `copyFrom(sql, data)` | Executes `COPY table [(cols)] FROM STDIN` with `data` as the copy-in payload (text or csv per the COPY options). Returns rows copied. `COPY ... TO STDOUT` output is returned as result rows by `query`. |
|
|
143
|
-
| `snapshot`
|
|
160
|
+
| `snapshot()` | Freeze a reusable {@link Snapshot} template (no encode). Illegal inside a transaction (`25P01`). |
|
|
161
|
+
| `Snapshot.open()` | Copy-on-write fork from a template. Parent stays open. |
|
|
162
|
+
| `Snapshot.encode()` | Lazy PGMM blob for persistence / worker boot (computed once, cached). |
|
|
163
|
+
| `Snapshot.decode(bytes)` | Decode a blob once per `Uint8Array` (WeakMap); later `open()` calls are CoW. |
|
|
144
164
|
| `close()` | Idempotent; rolls back an open SQL transaction; further ops throw `misuse`. Also available as `[Symbol.dispose]` when supported. |
|
|
145
165
|
|
|
146
166
|
SQL `BEGIN` / `COMMIT` / `ROLLBACK` / `SAVEPOINT` / `RELEASE` are first-class. Empty / comment-only SQL on `prepare` / `query` / `exec` throws `misuse` (`empty statement`).
|
|
@@ -174,12 +194,16 @@ Duplicate column names collapse in row objects (last write wins). Use `stmt.resu
|
|
|
174
194
|
|
|
175
195
|
### Snapshots
|
|
176
196
|
|
|
197
|
+
- `db.snapshot()` returns a frozen in-memory {@link Snapshot}. Per-test isolation should `seed.open()` (copy-on-write, ~µs). Encoded bytes are **lazy** via `snapshot.encode()`.
|
|
177
198
|
- Format magic `PGMM` followed by an explicit little-endian format-version `u32` — **not** `pg_dump` output and not loadable by real PostgreSQL.
|
|
178
|
-
- Round-trips schemas, tables, rows, sequences (counters included), indexes, views, enums, domains, SQL functions, change counters, PRNG state, and clock.
|
|
179
|
-
- Cannot `
|
|
180
|
-
- `
|
|
181
|
-
-
|
|
182
|
-
-
|
|
199
|
+
- Round-trips schemas, tables, rows, sequences (counters included), indexes, views, enums, domains, SQL functions, change counters, PRNG state, and clock. JavaScript `registerFunction` impls are omitted.
|
|
200
|
+
- Cannot `snapshot()` while a transaction is open (`25P01`).
|
|
201
|
+
- `Snapshot.decode(bytes)` does not mutate the input `Uint8Array`. The same buffer object is decoded once (WeakMap) and later opens are CoW.
|
|
202
|
+
- `open()` shares frozen tables until either side writes; idle `open().snapshot().encode()` is byte-identical to `snapshot().encode()`.
|
|
203
|
+
- `open()` uses a fixed clock from the snapshot unless you pass `{ now: "system" }`, which stays live.
|
|
204
|
+
- Equivalent databases produce byte-identical `encode()` output (schema/rows sorted) **within a single library version**.
|
|
205
|
+
- Per-test isolation (CI-tier, 200 users + 800 items): `Snapshot.open` ~µs; `encode()` / `decode().open()` is the persistence path. See [benchmarks/PERFORMANCE.md](benchmarks/PERFORMANCE.md).
|
|
206
|
+
- **Compatibility policy:** newer library versions can always decode older snapshots; older libraries cannot decode newer format versions (`snapshot_version`). Corrupt magic yields a distinct error.
|
|
183
207
|
|
|
184
208
|
## Determinism
|
|
185
209
|
|
|
@@ -188,7 +212,7 @@ The engine is deterministic by default. Invariants:
|
|
|
188
212
|
| Source | Default | Override / notes |
|
|
189
213
|
| --- | --- | --- |
|
|
190
214
|
| `random()` / `gen_random_uuid()` | Seeded xorshift64* (`seed: 1`) | `new Database({ seed })` or `{ random: "os" }` for CSPRNG (not rolled back / not restored) |
|
|
191
|
-
| `now()` / `current_timestamp` / friends | Fixed `2000-01-01T00:00:00.000Z` | `new Database({ now: Date \| (() => Date) \| "system" })` — `"system"` is wall clock and is **not** frozen by `
|
|
215
|
+
| `now()` / `current_timestamp` / friends | Fixed `2000-01-01T00:00:00.000Z` | `new Database({ now: Date \| (() => Date) \| "system" })` — `"system"` is wall clock and is **not** frozen by `open()` |
|
|
192
216
|
| `setseed()` / `random()` | Deterministic stream | Matches the engine PRNG, repeatable |
|
|
193
217
|
| Table scans | Insertion order | Same order after `snapshot`/`restore` |
|
|
194
218
|
| Snapshots | Sorted schema/rows + PRNG state + clock | Restored into PRNG and `now` |
|
|
@@ -228,11 +252,11 @@ This is **not** a drop-in replacement for `pg`, `postgres.js`, or PGlite's clien
|
|
|
228
252
|
- `COMMENT ON` parses but comments are not stored
|
|
229
253
|
- `round(float8)` rounds ties away from zero (PostgreSQL: half-to-even); numeric `round()` has full parity
|
|
230
254
|
- `'1e400'::float8` saturates to `Infinity` instead of raising `22003`
|
|
231
|
-
- `MERGE`, `CALL`/procedures, cursors (`DECLARE`/`FETCH`), `LISTEN`/`NOTIFY`, PL/pgSQL
|
|
255
|
+
- `MERGE`, `CALL`/procedures, cursors (`DECLARE`/`FETCH`), `LISTEN`/`NOTIFY`, and full PL/pgSQL (packages, NOTICE, cursors) fail loud (`0A000`)
|
|
232
256
|
- `VACUUM` / `ANALYZE` / `CLUSTER` / `REINDEX` / `CHECKPOINT` / `GRANT` / `REVOKE` / `LOCK` are parsed no-ops
|
|
233
257
|
- Collation is `C` semantics (byte order); locale/ICU-dependent ordering is out of scope
|
|
234
258
|
|
|
235
|
-
**Also supported (oracle-parity):** schemas + `search_path`, `pg_catalog` / `information_schema` introspection, sequences (`serial`, identity, `nextval`/`currval`/`setval`), enums, domains, `LANGUAGE sql` functions, row-level triggers, recursive + data-modifying CTEs, window functions with full frame specs, `GROUPING SETS`/`ROLLUP`/`CUBE`, `DISTINCT ON`, `LATERAL`, arrays + `unnest` + subscripting, JSON/JSONB operator + function surface
|
|
259
|
+
**Also supported (oracle-parity):** schemas + `search_path`, `pg_catalog` / `information_schema` introspection, sequences (`serial`, identity, `nextval`/`currval`/`setval`), enums, domains, `LANGUAGE sql` functions, plpgsql-lite UDFs (`DECLARE`, `EXCEPTION WHEN others`, `RETURN NEXT`), row-level triggers, recursive + data-modifying CTEs, window functions with full frame specs, `GROUPING SETS`/`ROLLUP`/`CUBE`, `DISTINCT ON`, `LATERAL`, arrays + `unnest` + subscripting, JSON/JSONB operator + function surface including `jsonb_path_query_first`, `tsvector` text search, `ON CONFLICT DO NOTHING/UPDATE`, `RETURNING`, `PREPARE`/`EXECUTE`/`DEALLOCATE`, `SET`/`SHOW`/`RESET` GUCs, `COPY` text and csv.
|
|
236
260
|
|
|
237
261
|
## Common pitfalls
|
|
238
262
|
|
|
@@ -240,10 +264,10 @@ This is **not** a drop-in replacement for `pg`, `postgres.js`, or PGlite's clien
|
|
|
240
264
|
2. **Parameters are `$1..$n` only** — no `?` placeholders, no named parameters, no sticky `bind()`.
|
|
241
265
|
3. **`query` / `prepare` are single-statement only** — multi-statement scripts belong in `exec()` (which does not take bind parameters).
|
|
242
266
|
4. **`exec` returns `void` and takes no params** — use `db.prepare(…).run(…)` or `db.query(…)` for binds; use `db.changes` / `stmt.run().rowCount` for counters.
|
|
243
|
-
5. **`now()` is not wall-clock** unless you pass `{ now: "system" }` or `{ now: () => new Date() }`. Default is year 2000. `
|
|
267
|
+
5. **`now()` is not wall-clock** unless you pass `{ now: "system" }` or `{ now: () => new Date() }`. Default is year 2000. `open()` freezes a snapshot clock except when constructed with `"system"`.
|
|
244
268
|
6. **`random()` is seeded**, not OS entropy, unless you pass `{ random: "os" }`. Snapshots restore the seeded PRNG; OS entropy is not rewound.
|
|
245
269
|
7. **Snapshots are not `pg_dump` output** and cannot be loaded into real PostgreSQL.
|
|
246
|
-
8. **`int8` comes back as `bigint
|
|
270
|
+
8. **`int8` comes back as `bigint` by default** (`{ int8: "number" | "string" }` opts in). `numeric`/dates/json come back as **text** — parse them explicitly if you need JS numbers/objects.
|
|
247
271
|
9. **A failed statement does not abort the transaction** — real PostgreSQL rejects everything after an error inside `BEGIN` until `ROLLBACK`; postgres-mem keeps executing (documented divergence).
|
|
248
272
|
10. **Unquoted identifiers fold to lowercase** (PostgreSQL rule — not uppercase like the SQL standard).
|
|
249
273
|
11. **Do not import `@crvouga/postgres-mem/unstable` in application code** unless you accept breakage in any release.
|
|
@@ -254,7 +278,7 @@ Working examples beyond this README: `examples/react-vite`, `tests/contract/api/
|
|
|
254
278
|
|
|
255
279
|
Requires [Bun](https://bun.sh). For architecture, change checklists, and how to add contract tests, see **[AGENTS.md](AGENTS.md)**.
|
|
256
280
|
|
|
257
|
-
Parity is proven
|
|
281
|
+
Parity is proven by differential contracts against real PostgreSQL — default oracle PGlite (18.3 in WASM), plus optional native PostgreSQL 18.3 via `bun run test:postgres-native`. Isolated internal unit tests are not PostgreSQL compatibility proof.
|
|
258
282
|
|
|
259
283
|
```bash
|
|
260
284
|
bun install
|
|
@@ -263,7 +287,8 @@ bun run check # format + lint + typecheck + postgres-compat sui
|
|
|
263
287
|
bun run format # write Biome formatting
|
|
264
288
|
bun run lint # Biome lint
|
|
265
289
|
bun run typecheck
|
|
266
|
-
bun run test:postgres-compat # requirements + inventory gate + differential suite
|
|
290
|
+
bun run test:postgres-compat # requirements + inventory gate + differential suite (PGlite)
|
|
291
|
+
bun run test:postgres-native # same differential suite vs real PostgreSQL 18.3
|
|
267
292
|
bun test # contract + fuzz + harness
|
|
268
293
|
bun run build
|
|
269
294
|
```
|
|
@@ -276,16 +301,16 @@ Publishing is fully automated. You never bump `version` or run `npm publish` by
|
|
|
276
301
|
|
|
277
302
|
### How a release happens
|
|
278
303
|
|
|
279
|
-
1. Push or merge to `main
|
|
304
|
+
1. Push or merge to `main`. Prefer [Conventional Commits](https://www.conventionalcommits.org/) so the bump is `feat` → minor / `fix` → patch / `BREAKING` → major; any other subject still publishes a patch.
|
|
280
305
|
2. CI runs commitlint, format/lint/typecheck, build, package verification, tests, browser smoke, and benchmarks.
|
|
281
306
|
3. If every gate is green, [semantic-release](https://semantic-release.gitbook.io/) analyzes commits since the last git tag, bumps semver, publishes to npm, and creates a GitHub Release.
|
|
282
307
|
|
|
283
308
|
| Commit | Version bump |
|
|
284
309
|
| --- | --- |
|
|
285
|
-
| `fix: …` | patch (`0.1
|
|
286
|
-
| `feat: …` | minor (`0.1
|
|
287
|
-
| `feat!: …` or `BREAKING CHANGE:` footer | major (`
|
|
288
|
-
|
|
|
310
|
+
| `fix: …` / `perf: …` | patch (`1.0.1` → `1.0.2`) |
|
|
311
|
+
| `feat: …` | minor (`1.0.1` → `1.1.0`) |
|
|
312
|
+
| `feat!: …` or `BREAKING CHANGE:` footer | major (`1.1.0` → `2.0.0`) |
|
|
313
|
+
| any other message on `main` (including Cursor-style subjects) | patch |
|
|
289
314
|
|
|
290
315
|
PR titles must also follow Conventional Commits (enforced in CI). Prefer squash merges with a conventional title.
|
|
291
316
|
|
|
@@ -318,7 +343,7 @@ Do this once so CI can publish. Full checklist: **[docs/SECRETS.md](./docs/SECRE
|
|
|
318
343
|
|
|
319
344
|
Validate the checklist anytime with `bun run secrets:doctor`.
|
|
320
345
|
|
|
321
|
-
After that, every green push to `main`
|
|
346
|
+
After that, every green push to `main` updates npm automatically (`feat`/`fix`/`BREAKING` pick the bump; anything else is a patch).
|
|
322
347
|
|
|
323
348
|
## License
|
|
324
349
|
|
package/compat/coverage.json
CHANGED
|
@@ -375,7 +375,7 @@
|
|
|
375
375
|
"evidence": [
|
|
376
376
|
"tests/contract/functions/"
|
|
377
377
|
],
|
|
378
|
-
"notes": "LANGUAGE sql
|
|
378
|
+
"notes": "LANGUAGE sql plus plpgsql-lite (DECLARE, EXCEPTION WHEN others, CASE, FOR-IN-SELECT, RETURN NEXT); no cursors/NOTICE/packages"
|
|
379
379
|
},
|
|
380
380
|
"sql-creategroup": {
|
|
381
381
|
"status": "NOT_APPLICABLE",
|
package/compat/divergences.json
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
{
|
|
5
5
|
"id": "oracle-pglite-version",
|
|
6
6
|
"scope": "oracle",
|
|
7
|
-
"predicate": "The differential oracle is PGlite (embedded PostgreSQL 18.x WASM); server_version must be 18.3 or 18.1",
|
|
8
|
-
"specifiedBehavior": "Harness bootstrap asserts current_setting('server_version') is in {18.3, 18.1}.
|
|
9
|
-
"pinnedBy": ["scripts/postgres-compat-gate.ts", "tests/harness/oracle-versions.ts"]
|
|
7
|
+
"predicate": "The default differential oracle is PGlite (embedded PostgreSQL 18.x WASM); a secondary native-server oracle is available via POSTGRES_MEM_ORACLE=server. server_version must be 18.3 or 18.1 for either path.",
|
|
8
|
+
"specifiedBehavior": "Harness bootstrap asserts current_setting('server_version') is in {18.3, 18.1}. Default CI uses PGlite; `bun run test:postgres-native` (and CI job test-native-oracle) runs the same suite against a real PostgreSQL 18.3 process.",
|
|
9
|
+
"pinnedBy": ["scripts/postgres-compat-gate.ts", "tests/harness/oracle-versions.ts", "scripts/run-postgres-native-tests.ts"]
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"id": "deterministic-runtime",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
{
|
|
31
31
|
"id": "pgmm-snapshot-codec",
|
|
32
32
|
"scope": "engine",
|
|
33
|
-
"predicate": "snapshot()/
|
|
33
|
+
"predicate": "snapshot()/encode()/decode() use the custom PGMM binary codec (magic 'PGMM' + LE u32 version), not pg_dump or the on-disk PostgreSQL format",
|
|
34
34
|
"specifiedBehavior": "Snapshots are byte-identical for equivalent logical state, refuse corrupt/truncated/future-version blobs, and cannot run inside a transaction. No PostgreSQL equivalent exists; tests assert memory behavior only.",
|
|
35
35
|
"pinnedBy": [
|
|
36
36
|
"SNP-rt-01",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"SNP-hdr-03",
|
|
45
45
|
"SNP-hdr-04",
|
|
46
46
|
"SNP-txn-01",
|
|
47
|
-
"SNP-
|
|
47
|
+
"SNP-open-01",
|
|
48
|
+
"SNP-open-02",
|
|
48
49
|
"tests/contract/snapshots/basic.test.ts"
|
|
49
50
|
]
|
|
50
51
|
},
|
|
@@ -93,9 +94,19 @@
|
|
|
93
94
|
"API-txn-02",
|
|
94
95
|
"API-sync-01",
|
|
95
96
|
"API-copy-01",
|
|
97
|
+
"API-int8-01",
|
|
98
|
+
"API-int8-02",
|
|
99
|
+
"API-fn-01",
|
|
96
100
|
"tests/contract/api/basic.test.ts"
|
|
97
101
|
]
|
|
98
102
|
},
|
|
103
|
+
{
|
|
104
|
+
"id": "dump-compat-noop",
|
|
105
|
+
"scope": "sql",
|
|
106
|
+
"predicate": "DO blocks and ALTER TABLE SET (storage/reloptions) execute as no-ops so schema dumps can load without intercepts; PL/pgSQL and storage parameters are NOT APPLICABLE",
|
|
107
|
+
"specifiedBehavior": "exec() loads schema dumps. DO $$ … $$ and ALTER TABLE … SET (fillfactor=…) succeed and change nothing. PGlite executes plpgsql; these cases are memory-only.",
|
|
108
|
+
"pinnedBy": ["API-dump-01", "API-do-01", "API-set-01"]
|
|
109
|
+
},
|
|
99
110
|
{
|
|
100
111
|
"id": "empty-script-rejected",
|
|
101
112
|
"scope": "sql",
|
package/compat/sections/api.ts
CHANGED
|
@@ -25,4 +25,10 @@ export const API_SECTION: CatalogSection = section("API", "JavaScript surface co
|
|
|
25
25
|
["txn-02", "nested transaction(fn) uses savepoints", D, "JS API surface, no oracle analog", undefined, DIV],
|
|
26
26
|
["sync-01", "methods return values, not Promises", D, "JS API surface, no oracle analog", undefined, DIV],
|
|
27
27
|
["copy-01", "copyFrom API loads COPY FROM STDIN data", D, "API-level COPY hook", ["tests/contract/copy/"], DIV],
|
|
28
|
+
["int8-01", "int8 mode number surfaces IEEE number", D, "JS API overlay, no oracle analog", undefined, DIV],
|
|
29
|
+
["int8-02", "int8 mode string surfaces decimal text", D, "JS API overlay, no oracle analog", undefined, DIV],
|
|
30
|
+
["fn-01", "registerFunction installs a JS scalar", D, "JS API overlay, not in PGMM", undefined, DIV],
|
|
31
|
+
["dump-01", "exec loads dump no-ops", D, "dump-compat overlay", undefined, "dump-compat-noop"],
|
|
32
|
+
["do-01", "DO blocks are no-ops", D, "PL/pgSQL is NOT APPLICABLE", undefined, "dump-compat-noop"],
|
|
33
|
+
["set-01", "ALTER TABLE SET storage parameters are no-ops", D, "storage params are NOT APPLICABLE", undefined, "dump-compat-noop"],
|
|
28
34
|
]);
|
package/compat/sections/fzz.ts
CHANGED
|
@@ -26,8 +26,13 @@ const rows: ScenarioRow[] = [
|
|
|
26
26
|
["comb-01", "feature combination fuzz", F, undefined, ["tests/fuzz/combinations.test.ts"]],
|
|
27
27
|
["dst-01", "stateful DST dump-after-each engine", F, undefined, ["tests/fuzz/stateful.test.ts"]],
|
|
28
28
|
["mix-01", "mixed DDL/DML stateful simulation", F, undefined, ["tests/fuzz/mixed-stateful.test.ts"]],
|
|
29
|
+
["snap-01", "snapshot restore probe fuzz", F, undefined, ["tests/fuzz/snapshot.test.ts"]],
|
|
30
|
+
["snap-02", "snapshot object round-trip contracts", F, undefined, ["tests/contract/snapshots/objects.test.ts"]],
|
|
31
|
+
["snap-03", "extended logical dump (views/matviews/sequences)", F, undefined, ["tests/harness/state-dump.ts"]],
|
|
29
32
|
["robust-01", "PostgresError-only / snapshot bit-flip robustness", F, undefined, ["tests/fuzz/robustness.test.ts"]],
|
|
30
33
|
["corpus-01", "corpus regression replay", F, undefined, ["tests/fuzz/corpus.test.ts"]],
|
|
34
|
+
["num-01", "numeric arithmetic differential fuzz", F, undefined, ["tests/fuzz/numeric.test.ts"]],
|
|
35
|
+
["cpy-01", "COPY FROM STDIN differential fuzz", F, undefined, ["tests/fuzz/copy.test.ts"]],
|
|
31
36
|
["seed-01", "default seed 0x5a17e0e1", P, undefined, ["tests/fuzz/config.ts"]],
|
|
32
37
|
["replay-01", "POSTGRES_MEM_FUZZ_SEED / PATH replay", P, undefined, ["tests/fuzz/config.ts"]],
|
|
33
38
|
["min-01", "DST minimizer and repro script", P, undefined, ["tests/fuzz/dst/minimize.ts"]],
|
package/compat/sections/snp.ts
CHANGED
|
@@ -16,6 +16,7 @@ export const SNP_SECTION: CatalogSection = section("SNP", "Snapshot / restore",
|
|
|
16
16
|
["hdr-02", "corrupt magic errors distinctly", D, SNAP_NOTE, EXTRA, DIV],
|
|
17
17
|
["hdr-03", "truncated blob errors", D, SNAP_NOTE, EXTRA, DIV],
|
|
18
18
|
["hdr-04", "future version raises snapshot_version", D, SNAP_NOTE, EXTRA, DIV],
|
|
19
|
-
["txn-01", "
|
|
20
|
-
["
|
|
19
|
+
["txn-01", "snapshot during transaction errors", D, SNAP_NOTE, EXTRA, DIV],
|
|
20
|
+
["open-01", "Snapshot.open CoW fork", D, SNAP_NOTE, EXTRA, DIV],
|
|
21
|
+
["open-02", "encode does not mutate; decode reuses buffer", D, SNAP_NOTE, EXTRA, DIV],
|
|
21
22
|
]);
|
|
@@ -1116,7 +1116,6 @@
|
|
|
1116
1116
|
"jsonb_path_query": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
|
1117
1117
|
"jsonb_path_query_array": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
|
1118
1118
|
"jsonb_path_query_array_tz": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
|
1119
|
-
"jsonb_path_query_first": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
|
1120
1119
|
"jsonb_path_query_first_tz": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
|
1121
1120
|
"jsonb_path_query_tz": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
|
1122
1121
|
"jsonb_populate_record": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
package/dist/api/bind.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Int8Mode } from "../runtime/options.js";
|
|
1
2
|
import { type Datum, type OutputCtx, type TypedValue, type TypeId } from "../types/value.js";
|
|
2
3
|
/** JavaScript values accepted as query parameters (`$1..$n`). */
|
|
3
4
|
export type BindValue = null | undefined | boolean | number | bigint | string | Uint8Array | Date;
|
|
@@ -7,4 +8,4 @@ export type JsValue = null | boolean | number | bigint | string | Uint8Array;
|
|
|
7
8
|
export type QueryRow = Record<string, JsValue>;
|
|
8
9
|
export declare function bindValueToTyped(value: BindValue, index: number): TypedValue;
|
|
9
10
|
/** Convert an engine datum to the public JS value for result rows. */
|
|
10
|
-
export declare function datumToJs(t: TypeId, v: Datum, ctx: OutputCtx): JsValue;
|
|
11
|
+
export declare function datumToJs(t: TypeId, v: Datum, ctx: OutputCtx, int8?: Int8Mode): JsValue;
|
package/dist/api/database.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type DatabaseOptions, type RandomMode } from "../runtime/index.js";
|
|
2
|
-
import type { BindValue, QueryRow } from "./bind.js";
|
|
1
|
+
import { type DatabaseOptions, type Int8Mode, type RandomMode } from "../runtime/index.js";
|
|
2
|
+
import type { BindValue, JsValue, QueryRow } from "./bind.js";
|
|
3
|
+
import { type Snapshot } from "./snapshot.js";
|
|
3
4
|
import { Statement } from "./statement.js";
|
|
4
5
|
/**
|
|
5
6
|
* Pure TypeScript in-memory PostgreSQL database.
|
|
@@ -24,6 +25,8 @@ export declare class Database {
|
|
|
24
25
|
readonly seed: number | bigint;
|
|
25
26
|
/** Entropy mode for `random()` / `gen_random_uuid()`. */
|
|
26
27
|
readonly randomMode: RandomMode;
|
|
28
|
+
/** How `int8` columns surface in query rows. */
|
|
29
|
+
readonly int8Mode: Int8Mode;
|
|
27
30
|
private closed;
|
|
28
31
|
private transactionSequence;
|
|
29
32
|
private apiTransactionDepth;
|
|
@@ -33,6 +36,13 @@ export declare class Database {
|
|
|
33
36
|
* are allowed. Does not accept bind parameters — use {@link prepare} / {@link query}.
|
|
34
37
|
*/
|
|
35
38
|
exec(sql: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* Register a JavaScript function callable from SQL. Not encoded in PGMM
|
|
41
|
+
* snapshots — re-register after {@link Snapshot.decode} / {@link Snapshot.open}.
|
|
42
|
+
* {@link Snapshot.open} copies the implementation by reference when opening
|
|
43
|
+
* a live in-memory snapshot.
|
|
44
|
+
*/
|
|
45
|
+
registerFunction(spec: RegisterFunctionOptions): void;
|
|
36
46
|
/** Execute a single-statement query and return all rows keyed by column name. */
|
|
37
47
|
query<T = QueryRow>(sql: string, params?: readonly BindValue[]): T[];
|
|
38
48
|
/** Compile a single SQL statement into a reusable {@link Statement}. */
|
|
@@ -43,12 +53,11 @@ export declare class Database {
|
|
|
43
53
|
*/
|
|
44
54
|
transaction<T>(fn: () => T): T;
|
|
45
55
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
56
|
+
* Freeze this database into a reusable {@link Snapshot} template.
|
|
57
|
+
* Does not encode PGMM bytes. Call {@link Snapshot.encode} to persist, or
|
|
58
|
+
* {@link Snapshot.open} for a copy-on-write fork.
|
|
48
59
|
*/
|
|
49
|
-
snapshot():
|
|
50
|
-
/** Replace this database's contents with a blob from {@link snapshot}. */
|
|
51
|
-
restore(snapshot: Uint8Array): void;
|
|
60
|
+
snapshot(): Snapshot;
|
|
52
61
|
/** Close the database. Further SQL throws. Idempotent. */
|
|
53
62
|
close(): void;
|
|
54
63
|
/**
|
|
@@ -66,4 +75,13 @@ export declare class Database {
|
|
|
66
75
|
get changes(): number;
|
|
67
76
|
private prepareSingle;
|
|
68
77
|
}
|
|
78
|
+
export { Snapshot } from "./snapshot.js";
|
|
79
|
+
/** Options for {@link Database.registerFunction}. */
|
|
80
|
+
export interface RegisterFunctionOptions {
|
|
81
|
+
name: string;
|
|
82
|
+
args: string[];
|
|
83
|
+
returns: string;
|
|
84
|
+
strict?: boolean;
|
|
85
|
+
fn: (...args: JsValue[]) => JsValue;
|
|
86
|
+
}
|
|
69
87
|
export type { DatabaseOptions };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type DatabaseOptions } from "../runtime/index.js";
|
|
2
|
+
import { type Database } from "./database.js";
|
|
3
|
+
/**
|
|
4
|
+
* Frozen in-memory database template. {@link open} is a copy-on-write fork.
|
|
5
|
+
* Encoded PGMM bytes are produced lazily via {@link encode}.
|
|
6
|
+
*/
|
|
7
|
+
export declare class Snapshot {
|
|
8
|
+
private cachedBytes;
|
|
9
|
+
/** Encoded PGMM blob. Computed once; never mutates a buffer passed to {@link decode}. */
|
|
10
|
+
encode(): Uint8Array;
|
|
11
|
+
/**
|
|
12
|
+
* Copy-on-write database from this frozen template. Does not re-encode or re-decode.
|
|
13
|
+
*/
|
|
14
|
+
open(options?: DatabaseOptions): Database;
|
|
15
|
+
/**
|
|
16
|
+
* Decode `bytes` once and freeze the result. The same `Uint8Array` object
|
|
17
|
+
* returns the same {@link Snapshot} (WeakMap), so later {@link open} calls
|
|
18
|
+
* are copy-on-write forks after the first hydrate.
|
|
19
|
+
*/
|
|
20
|
+
static decode(bytes: Uint8Array): Snapshot;
|
|
21
|
+
}
|
package/dist/ast/nodes.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ interface UniqueSpec {
|
|
|
18
18
|
isPrimary: boolean;
|
|
19
19
|
}
|
|
20
20
|
export declare function uniqueSpecsFor(env: ExecEnv, table: TableData): UniqueSpec[];
|
|
21
|
+
export declare function uniqueKeyOf(env: ExecEnv, table: TableData, spec: UniqueSpec, row: Datum[]): string | null;
|
|
21
22
|
/**
|
|
22
23
|
* Unique / primary key enforcement (23505). `selfIdx` is the row's own index
|
|
23
24
|
* in table.rows (already inserted / updated in place).
|