@crvouga/mockingbird-service-postgres 1.0.0 → 1.2.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 +8 -2
- package/CHANGELOG.md +20 -0
- package/COMPATIBILITY.md +1 -1
- package/README.md +65 -13
- package/dist/wire/cli.d.ts +2 -0
- package/dist/wire/cli.js +23770 -0
- package/dist/wire/cli.js.map +7 -0
- package/dist/wire/cluster.d.ts +71 -0
- package/dist/wire/connection.d.ts +107 -0
- package/dist/wire/index.d.ts +69 -0
- package/dist/wire/index.js +23715 -0
- package/dist/wire/index.js.map +7 -0
- package/dist/wire/protocol.d.ts +119 -0
- package/dist/wire/scram.d.ts +17 -0
- package/dist/wire/split.d.ts +6 -0
- package/package.json +18 -5
package/AGENTS.md
CHANGED
|
@@ -10,10 +10,15 @@ Guidance for humans and coding agents editing this repository. For install and c
|
|
|
10
10
|
|
|
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
|
-
3. Single-session engine
|
|
13
|
+
3. Single-session **engine**: no MVCC across connections, no aborted-transaction (`25P02`) state in the sync API
|
|
14
14
|
4. `NOT APPLICABLE` items: roles/auth enforcement, replication, VACUUM internals, storage params, full PL/pgSQL
|
|
15
15
|
|
|
16
|
-
**Non-goals:**
|
|
16
|
+
**Non-goals:** matching `pg`/`postgres.js` **client APIs**, full PL/pgSQL, or MVCC. The optional
|
|
17
|
+
wire-protocol server (`src/wire/`, a Node/Bun-only entry over the one engine) is in scope: it
|
|
18
|
+
speaks frontend/backend v3 and coordinates connections by serializing transaction blocks, adding
|
|
19
|
+
advisory locks, `LISTEN`/`NOTIFY`, `CancelRequest` and per-connection `25P02`. It deliberately does
|
|
20
|
+
**not** add row-level lock contention (`SELECT … FOR UPDATE SKIP LOCKED` distribution) or `COPY`
|
|
21
|
+
streaming.
|
|
17
22
|
|
|
18
23
|
## SQL pipeline
|
|
19
24
|
|
|
@@ -49,6 +54,7 @@ Everything is **typed values** (`TypedValue = { t: TypeId, v: Datum }` in [`src/
|
|
|
49
54
|
| `transactions/` | BEGIN / COMMIT / SAVEPOINT (clones state + PRNG) |
|
|
50
55
|
| `runtime/` | Clock, PRNG, `DatabaseOptions` |
|
|
51
56
|
| `serialization/` | `PGMM` snapshot codec |
|
|
57
|
+
| `wire/` | Wire-protocol (frontend/backend v3) TCP server: `serve()`, CLI, per-connection state machine, cross-connection `Cluster` (Node/Bun only) |
|
|
52
58
|
| `tsearch/` | `tsvector` / `tsquery` text search |
|
|
53
59
|
| `errors/` | `PostgresError` with SQLSTATE, `unsupported()` |
|
|
54
60
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-postgres
|
|
2
2
|
|
|
3
|
+
## 1.2.0 (2026-09-29)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- share one field-guide identity across the docs site and READMEs ([024f445](https://github.com/crvouga/mockingbird/commit/024f445c143e0d2d07a4dd0a3c063bca94c59623))
|
|
8
|
+
|
|
9
|
+
### Fixes and improvements
|
|
10
|
+
|
|
11
|
+
- typecheck the postgres and sqlite README signature blocks ([cd2080b](https://github.com/crvouga/mockingbird/commit/cd2080b1af98f2746de063f289382527e28acfb2))
|
|
12
|
+
|
|
13
|
+
## 1.1.0 (2026-09-25)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- wire-protocol server so separate processes can connect over TCP ([85000c8](https://github.com/crvouga/mockingbird/commit/85000c88fe57dc28c42caa19dcf3b825d94a361b))
|
|
18
|
+
|
|
19
|
+
### Fixes and improvements
|
|
20
|
+
|
|
21
|
+
- expose the wire server under ./wire, not the reserved ./server export ([12734c7](https://github.com/crvouga/mockingbird/commit/12734c7ecb39585ac3c7565d6367b3611d8c38a6))
|
|
22
|
+
|
|
3
23
|
## 1.0.0 (2026-09-24)
|
|
4
24
|
|
|
5
25
|
### ⚠️ Breaking changes
|
package/COMPATIBILITY.md
CHANGED
|
@@ -25,7 +25,7 @@ Intentional differences are finite and machine-readable in `compat/divergences.j
|
|
|
25
25
|
| **VERIFIED** | Differential contracts (+ fuzz where applicable) cover happy path **and** meaningful edges vs oracle |
|
|
26
26
|
| **PARTIALLY VERIFIED** | Implemented; coverage thin or known edges remain |
|
|
27
27
|
| **UNSUPPORTED** | Missing from SQL surface (must fail loud; gate fails if oracle-exposed and unregistered) |
|
|
28
|
-
| **NOT APPLICABLE** | Outside the
|
|
28
|
+
| **NOT APPLICABLE** | Outside the SQL-dialect surface (roles/auth, replication, storage). The wire protocol is provided by the optional `/server` entry, documented in the README. |
|
|
29
29
|
|
|
30
30
|
## Scope bound
|
|
31
31
|
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @crvouga/mockingbird-service-postgres
|
|
2
2
|
|
|
3
|
+
> Familiar calls. Faithful echoes. Part of [Mockingbird](https://github.com/crvouga/mockingbird).
|
|
4
|
+
|
|
3
5
|
Pure TypeScript, completely in-memory PostgreSQL engine aiming for **PostgreSQL 18 SQL dialect
|
|
4
6
|
parity** (same statements, same results). Use it in tests (or the browser) wherever you want real
|
|
5
7
|
PostgreSQL SQL semantics without a server: schema + migrations, constraints and SQLSTATE errors,
|
|
@@ -15,7 +17,7 @@ isolation.
|
|
|
15
17
|
- **Synchronous**, ESM-only API (no Promises, no `require`)
|
|
16
18
|
- **SQL dialect verified** against real PostgreSQL 18.3 (PGlite by default; optional native server)
|
|
17
19
|
via differential contracts and a fail-closed gate
|
|
18
|
-
- **
|
|
20
|
+
- In-process it is **not** a drop-in for the `pg` / `postgres.js` client APIs or on-disk clusters, but it also ships an **optional wire-protocol server** (`@crvouga/mockingbird-service-postgres/wire`, Node/Bun) that unmodified clients connect to over TCP
|
|
19
21
|
- Intentional differences: deterministic `random()` / `now()` by default, and a custom snapshot
|
|
20
22
|
format (not `pg_dump`)
|
|
21
23
|
|
|
@@ -79,7 +81,8 @@ console.log(db2.query(`SELECT count(*) AS n FROM users`), db3.changes) // [{ n:
|
|
|
79
81
|
```
|
|
80
82
|
|
|
81
83
|
All methods are **synchronous**; do not `await` them. Browser and Node/Bun share the same
|
|
82
|
-
in-memory surface (no filesystem
|
|
84
|
+
in-memory surface (no filesystem). For a real TCP listener separate processes can connect to, see
|
|
85
|
+
the [wire-protocol server](#wire-protocol-server-separate-processes) (Node/Bun only).
|
|
83
86
|
|
|
84
87
|
### Per-test isolation with snapshots
|
|
85
88
|
|
|
@@ -151,6 +154,53 @@ back as PostgreSQL text (node-postgres parses timestamps to `Date` and JSON to o
|
|
|
151
154
|
`prepare`/`query` accept one statement at a time (use `exec` for scripts); errors are
|
|
152
155
|
`PostgresError` with `code` set to the SQLSTATE, like `pg`'s `DatabaseError.code`.
|
|
153
156
|
|
|
157
|
+
### Wire-protocol server (separate processes)
|
|
158
|
+
|
|
159
|
+
When another process must connect over TCP — `pg`, `postgres.js`, a JDBC client, `psql`, a service
|
|
160
|
+
in a local multi-service stack — start the frontend/backend v3 server instead of a shim. It needs
|
|
161
|
+
`node:net`, so it is a separate Node/Bun entry (`/wire`) and the main package stays browser-safe.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { serve } from "@crvouga/mockingbird-service-postgres/wire"
|
|
165
|
+
|
|
166
|
+
const server = await serve({ port: 0 }) // 0 → a free port, reported as server.port
|
|
167
|
+
// postgres://postgres@127.0.0.1:${server.port}/db — no initdb, no OS user, pure TypeScript
|
|
168
|
+
await server.close()
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Or from the command line (installs a `mockingbird-postgres` bin):
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
mockingbird-postgres serve --port 55432 # trust auth
|
|
175
|
+
mockingbird-postgres serve --password secret --log # SCRAM-SHA-256, log each statement
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`serve({ database })` shares an existing `Database`, and `serve({ database: snapshot })` boots every
|
|
179
|
+
server from one frozen template, so a seeded stack starts from the same bytes each time.
|
|
180
|
+
`server.snapshot()` freezes the live state; `server.fault({ dropConnection | delayStatementMs |
|
|
181
|
+
failCommit })` arms the next statement or connection for a drop, a delay, or a `40001` commit
|
|
182
|
+
failure.
|
|
183
|
+
|
|
184
|
+
One engine is shared by every connection. The engine runs one statement at a time, so a connection
|
|
185
|
+
inside an explicit `BEGIN` block holds it until `COMMIT`/`ROLLBACK` and other connections queue
|
|
186
|
+
behind it — which keeps read-committed visibility (an uncommitted row is never seen by another
|
|
187
|
+
connection) by serializing transaction blocks rather than by MVCC. Across connections the server
|
|
188
|
+
adds what a single engine does not: per-session advisory locks (`pg_advisory_lock` /
|
|
189
|
+
`pg_try_advisory_lock` / `_xact_` / `_unlock`) with in-order waiters and deadlock detection
|
|
190
|
+
(`40P01`), `LISTEN`/`NOTIFY` delivered to idle listeners, `CancelRequest` (a blocked statement ends
|
|
191
|
+
`57014` and the session stays usable), and per-connection aborted-transaction state (a failed
|
|
192
|
+
statement in a block is `25P02` until `ROLLBACK`, with `ReadyForQuery` reporting `I`/`T`/`E`).
|
|
193
|
+
SCRAM-SHA-256 and trust auth, the extended query protocol (`Parse`/`Bind`/`Describe`/`Execute`/
|
|
194
|
+
`Sync`) with text and binary parameters and results, real type OIDs in `RowDescription`, and
|
|
195
|
+
`23505`/`23502` errors carrying the constraint, table and column the engine names, all work over
|
|
196
|
+
the wire.
|
|
197
|
+
|
|
198
|
+
**Not modelled by the server:** row-level lock contention, so `SELECT … FOR UPDATE SKIP LOCKED`
|
|
199
|
+
parses and returns rows but does not distribute disjoint rows across concurrent workers (there are
|
|
200
|
+
no row locks); `COPY` streaming (`CopyInResponse`/`CopyData`) — use `copyFrom` on a shared
|
|
201
|
+
`Database`; and the binary parameter formats beyond the common scalar types (a client that sends
|
|
202
|
+
another binary type gets `0A000`, and can switch that parameter to text).
|
|
203
|
+
|
|
154
204
|
### Method semantics
|
|
155
205
|
|
|
156
206
|
| Method | Behaviour |
|
|
@@ -247,20 +297,21 @@ Goal: **SQL dialect** behavioural parity vs PostgreSQL **18.3** for the sync API
|
|
|
247
297
|
[COMPATIBILITY.md](./COMPATIBILITY.md). Contract:
|
|
248
298
|
[DROP-IN-CONTRACT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/postgres/docs/DROP-IN-CONTRACT.md).
|
|
249
299
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
300
|
+
The in-process API has no async client, no connection pooling and no `pg_dump` codec; the optional
|
|
301
|
+
[wire-protocol server](#wire-protocol-server-separate-processes) adds a TCP listener and coordinates
|
|
302
|
+
several connections over the one engine, but by serializing transaction blocks, not by MVCC.
|
|
303
|
+
Intentional differences: custom `PGMM` snapshots; seeded `random()` / fixed `now()` by default
|
|
304
|
+
(`{ random: "os" }` / `{ now: "system" }` match PostgreSQL entropy and wall clock).
|
|
254
305
|
|
|
255
306
|
**Thin or partial areas** (do not assume full oracle fidelity):
|
|
256
307
|
|
|
257
308
|
- `EXPLAIN`: stub plan shapes, not real planner output
|
|
258
|
-
-
|
|
309
|
+
- The in-process API does **not** poison a transaction after a failed statement (no `25P02`); the wire-protocol server does, per connection
|
|
259
310
|
- Triggers fire in **creation order** (PostgreSQL: name order); `UPDATE OF` column lists are ignored; `INSTEAD OF` is unsupported
|
|
260
311
|
- `COMMENT ON` parses but comments are not stored
|
|
261
312
|
- `round(float8)` rounds ties away from zero (PostgreSQL: half-to-even); numeric `round()` has full parity
|
|
262
313
|
- `'1e400'::float8` saturates to `Infinity` instead of raising `22003`
|
|
263
|
-
- `MERGE`, `CALL`/procedures, cursors (`DECLARE`/`FETCH`), `LISTEN`/`NOTIFY`, and full PL/pgSQL (packages, NOTICE, cursors) fail loud (`0A000`)
|
|
314
|
+
- In the engine `MERGE`, `CALL`/procedures, cursors (`DECLARE`/`FETCH`), `LISTEN`/`NOTIFY`, and full PL/pgSQL (packages, NOTICE, cursors) fail loud (`0A000`); the wire-protocol server implements `LISTEN`/`NOTIFY` itself
|
|
264
315
|
- `VACUUM` / `ANALYZE` / `CLUSTER` / `REINDEX` / `CHECKPOINT` / `GRANT` / `REVOKE` / `LOCK` are parsed no-ops
|
|
265
316
|
- Collation is `C` semantics (byte order); locale/ICU-dependent ordering is out of scope
|
|
266
317
|
|
|
@@ -301,7 +352,7 @@ Stable runtime exports of the main entry:
|
|
|
301
352
|
Signatures (types are exported too: `DatabaseOptions`, `RegisterFunctionOptions`, `ResultSet`,
|
|
302
353
|
`RunResult`, `ErrorCategory`, `BindValue`, `JsValue`, `QueryRow`):
|
|
303
354
|
|
|
304
|
-
```
|
|
355
|
+
```ts
|
|
305
356
|
interface DatabaseOptions {
|
|
306
357
|
seed?: number | bigint // default 1; ignored when random is "os"
|
|
307
358
|
random?: "deterministic" | "os" // default "deterministic"; "os" is CSPRNG like PostgreSQL
|
|
@@ -309,7 +360,7 @@ interface DatabaseOptions {
|
|
|
309
360
|
int8?: "bigint" | "number" | "string" // default "bigint"; "number" is unsafe beyond MAX_SAFE_INTEGER
|
|
310
361
|
}
|
|
311
362
|
|
|
312
|
-
class Database {
|
|
363
|
+
declare class Database {
|
|
313
364
|
constructor(options?: DatabaseOptions)
|
|
314
365
|
exec(sql: string): void
|
|
315
366
|
registerFunction(spec: { name: string; args: string[]; returns: string; strict?: boolean;
|
|
@@ -328,13 +379,13 @@ class Database {
|
|
|
328
379
|
readonly int8Mode: "bigint" | "number" | "string"
|
|
329
380
|
}
|
|
330
381
|
|
|
331
|
-
class Snapshot {
|
|
382
|
+
declare class Snapshot {
|
|
332
383
|
open(options?: DatabaseOptions): Database
|
|
333
384
|
encode(): Uint8Array
|
|
334
385
|
static decode(bytes: Uint8Array): Snapshot
|
|
335
386
|
}
|
|
336
387
|
|
|
337
|
-
class Statement {
|
|
388
|
+
declare class Statement {
|
|
338
389
|
readonly sql: string
|
|
339
390
|
run(...params: BindValue[]): RunResult
|
|
340
391
|
all<T = QueryRow>(...params: BindValue[]): T[]
|
|
@@ -348,12 +399,13 @@ interface ResultSet { columns: string[]; columnTypes: string[]; rows: QueryR
|
|
|
348
399
|
interface TextResultSet { columns: string[]; columnTypes: string[]; rows: (string | null)[][]; rowCount: number; command: string }
|
|
349
400
|
// columnTypes are PostgreSQL internal type names, e.g. "int4", "numeric"
|
|
350
401
|
|
|
351
|
-
class PostgresError extends Error {
|
|
402
|
+
declare class PostgresError extends Error {
|
|
352
403
|
readonly category: ErrorCategory // "syntax", "undefined_table", "constraint_unique", "misuse", ...
|
|
353
404
|
readonly sqlState: string // five-character SQLSTATE
|
|
354
405
|
readonly code: string // === sqlState (node-postgres err.code convention)
|
|
355
406
|
}
|
|
356
407
|
|
|
408
|
+
type ErrorCategory = string // "syntax" | "undefined_table" | "constraint_unique" | "misuse" | ...
|
|
357
409
|
type BindValue = null | undefined | boolean | number | bigint | string | Uint8Array | Date
|
|
358
410
|
type JsValue = null | boolean | number | bigint | string | Uint8Array
|
|
359
411
|
type QueryRow = Record<string, JsValue>
|