@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 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: no MVCC across connections, no wire protocol, no aborted-transaction (`25P02`) state
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:** speaking the wire protocol, matching `pg`/`postgres.js` client APIs, full PL/pgSQL, or multi-session concurrency.
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 in-memory single-session dialect surface (roles/auth, replication, storage, wire protocol) |
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
- - **Not** a drop-in for the `pg` / `postgres.js` client APIs, the wire protocol, or on-disk clusters
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, no server, no wire protocol).
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
- There is no wire protocol, no async client, no connection pooling, no `pg_dump` codec, and no
251
- multi-session concurrency. Intentional differences: custom `PGMM` snapshots; seeded `random()` /
252
- fixed `now()` by default (`{ random: "os" }` / `{ now: "system" }` match PostgreSQL entropy and
253
- wall clock); single session, no MVCC across connections.
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
- - Failed statements inside `BEGIN` do **not** poison the transaction (`25P02` aborted-state is not implemented)
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
- ```text
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>
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};