@crvouga/mockingbird-service-postgres 1.3.1 → 1.5.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/CHANGELOG.md CHANGED
@@ -1,6 +1,27 @@
1
1
  # Changelog — @crvouga/mockingbird-service-postgres
2
2
 
3
- ## 1.3.1 (2026-09-30)
3
+ ## 1.5.0 (2026-10-01)
4
+
5
+ ### Features
6
+
7
+ - record SQL timelines and serve the admin explorer ([8ced17a](https://github.com/crvouga/mockingbird/commit/8ced17ac398e386ef4e9d7e01fc7744c34b95d07))
8
+
9
+ ### Fixes and improvements
10
+
11
+ - typecheck SQL admin timelines without a built sqlite port ([072daf4](https://github.com/crvouga/mockingbird/commit/072daf439f3967823149d7b0a8a9ce664d25097c))
12
+ - keep admin buttons pressed until the action finishes ([0aabfcb](https://github.com/crvouga/mockingbird/commit/0aabfcb5f392aa51e193f9c3f817d5877d92bc6a))
13
+ - size admin select fields like the text inputs beside them ([0fb3ef2](https://github.com/crvouga/mockingbird/commit/0fb3ef2ddbf43376c93a2ae6d87c6c9bf890dafc))
14
+ - keep admin section tabs wired when boot is renamed ([4eda25e](https://github.com/crvouga/mockingbird/commit/4eda25eacd59da048d196f7cc8369d7a02b47bfc))
15
+
16
+ ## 1.4.0 (2026-09-29)
17
+
18
+ ### Features
19
+
20
+ - allow unpopulated materialized views to reference each other ([3ce1d8f](https://github.com/crvouga/mockingbird/commit/3ce1d8f555ed4008dd894aed20ebf64ab0253774))
21
+ - support CREATE EXTENSION pg_trgm ([2296ba2](https://github.com/crvouga/mockingbird/commit/2296ba2f3d1a9ea7ce8ec9f528e979034692ebf1))
22
+ - install pgcrypto digest() ([b5f3ccf](https://github.com/crvouga/mockingbird/commit/b5f3ccf79c6fedcabab157f1ee07af18a7871057))
23
+
24
+ ## 1.3.1 (2026-09-29)
4
25
 
5
26
  ### Fixes and improvements
6
27
 
@@ -11,16 +32,23 @@
11
32
  ### Features
12
33
 
13
34
  - state each mock's parity and share one CSS reset ([264b06e](https://github.com/crvouga/mockingbird/commit/264b06ef15efc1d5131bbf78c89f45e2ce86b557))
35
+ - share admin state and UI on every mock ([9194173](https://github.com/crvouga/mockingbird/commit/9194173ebeb4150a6e87e1187429a64bfc09b84c))
14
36
 
15
37
  ## 1.2.0 (2026-09-29)
16
38
 
17
39
  ### Features
18
40
 
19
41
  - share one field-guide identity across the docs site and READMEs ([024f445](https://github.com/crvouga/mockingbird/commit/024f445c143e0d2d07a4dd0a3c063bca94c59623))
42
+ - let a scalar naming a later branch's enum value pick that branch ([30ade3a](https://github.com/crvouga/mockingbird/commit/30ade3a28a5b6b273ffbf261216b821acac8e961))
20
43
 
21
44
  ### Fixes and improvements
22
45
 
23
46
  - typecheck the postgres and sqlite README signature blocks ([cd2080b](https://github.com/crvouga/mockingbird/commit/cd2080b1af98f2746de063f289382527e28acfb2))
47
+ - sign and retry webhooks on the injected clock ([1091bde](https://github.com/crvouga/mockingbird/commit/1091bde0df69d9cc3eba2062d8523755e6caca14))
48
+ - stop cleared webhook deliveries from rescheduling forever ([424b78f](https://github.com/crvouga/mockingbird/commit/424b78f29317b162e3bf7d0049cc19be5b977026))
49
+ - serialize immediate webhook attempts to fix reorder flake ([625c82b](https://github.com/crvouga/mockingbird/commit/625c82b5a8a7992c1cf93e9df68e77af08b353e3))
50
+ - flush drains webhook retries scheduled while it runs ([c58cede](https://github.com/crvouga/mockingbird/commit/c58cede1b6ba7ae5ed3f8721ad9b7cd7bc0b1ca7))
51
+ - treat enum [""] branches as unset markers in form coercion ([7177835](https://github.com/crvouga/mockingbird/commit/7177835cc510e14245d6648ad2821daa694bb80c))
24
52
 
25
53
  ## 1.1.0 (2026-09-25)
26
54
 
@@ -31,6 +59,8 @@
31
59
  ### Fixes and improvements
32
60
 
33
61
  - expose the wire server under ./wire, not the reserved ./server export ([12734c7](https://github.com/crvouga/mockingbird/commit/12734c7ecb39585ac3c7565d6367b3611d8c38a6))
62
+ - take the rejected-request byte count from content-length, never by reading the body ([a0e5fb2](https://github.com/crvouga/mockingbird/commit/a0e5fb2fc80ab725c3552b612f3f091fd6c857fb))
63
+ - reject a present body by media type, and log what a rejected request sent ([12e351a](https://github.com/crvouga/mockingbird/commit/12e351aba53cfb7b41e31a7809419455fc2902eb))
34
64
 
35
65
  ## 1.0.0 (2026-09-24)
36
66
 
@@ -49,6 +79,8 @@
49
79
  ### Features
50
80
 
51
81
  - agent issue reporting for parity, features and new services; in-process medplum mock ([fc3a53b](https://github.com/crvouga/mockingbird/commit/fc3a53be83e2840e55aef78970326c27982311a8))
82
+ - add vendor mocks for the full service catalog ([f870287](https://github.com/crvouga/mockingbird/commit/f8702874c807b1be4ce4ae2c5b46c8fd59a5e8eb))
83
+ - team identity, lab-account admin, sandbox limits, fixtures and request journal ([6cb1be3](https://github.com/crvouga/mockingbird/commit/6cb1be324a048ac9fe832061632c0ee9c9d01f8b))
52
84
 
53
85
  ### Fixes and improvements
54
86
 
@@ -56,8 +88,14 @@
56
88
 
57
89
  ## 0.1.3 (2026-09-20)
58
90
 
91
+ ### Features
92
+
93
+ - one service contract — health, admin, namespaces, clock, faults, metrics ([fea7dd1](https://github.com/crvouga/mockingbird/commit/fea7dd169cc16d11baebba38dced7f630f725714))
94
+
59
95
  ### Fixes and improvements
60
96
 
97
+ - scope admin-injected faults to the calling namespace ([3c1d573](https://github.com/crvouga/mockingbird/commit/3c1d573ac0c404324bbd20855a5ac7a2cd50f738))
98
+ - take the admin namespace override only on admin routes ([37836a5](https://github.com/crvouga/mockingbird/commit/37836a5b0c51d17dd24e7b01c12dd6d8049646da))
61
99
  - ship installable dependency pins and a changelog per package ([33264f2](https://github.com/crvouga/mockingbird/commit/33264f2c455c976303e07b72a3809e82ab70c803))
62
100
 
63
101
  ## 0.1.2 (2026-09-19)
package/COMPATIBILITY.md CHANGED
@@ -34,7 +34,7 @@ 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, full PL/pgSQL, extensions).
37
+ 4. **NOT APPLICABLE** rows in `compat/coverage.json` (roles, replication, VACUUM internals, LISTEN/NOTIFY, cursors, full PL/pgSQL, extensions other than `pgcrypto`'s `digest()` and `pg_trgm`'s `similarity` / `<%` / `gin_trgm_ops`).
38
38
 
39
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
 
@@ -62,6 +62,8 @@ The oracle exposes **2787 builtin functions** and **74 operators** in `pg_catalo
62
62
  | Triggers (row-level, LANGUAGE sql-expressible) | PARTIALLY VERIFIED | Creation-order firing, `UPDATE OF` ignored, no INSTEAD OF (documented) |
63
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
+ | `CREATE EXTENSION pgcrypto` / `digest()` | PARTIALLY VERIFIED | `digest(bytea, text)` and `digest(text, text)` for md5, sha1, sha224, sha256, sha384, sha512. `crypt`, `hmac`, `gen_salt`, and PGP functions are not installed. OpenSSL-only names (sha3, blake2, ripemd160, sm3) are not available. Extensions other than `pgcrypto` and `pg_trgm` still fail loud (`0A000`) |
66
+ | `CREATE EXTENSION pg_trgm` | PARTIALLY VERIFIED | `similarity(text, text)`, `<%` at the default word-similarity threshold 0.6, and a single-column `gin_trgm_ops` index on `text` or `varchar`. `gist_trgm_ops`, multicolumn `gin_trgm_ops`, and non-default thresholds are not installed. |
65
67
  | COPY FROM/TO (text, csv) | VERIFIED | Via `copyFrom` API hook / rows out |
66
68
  | PREPARE / EXECUTE / DEALLOCATE, SET / SHOW / RESET | VERIFIED | GUC subset |
67
69
  | Transactions / savepoints | VERIFIED | No `25P02` aborted state (documented divergence) |
package/README.md CHANGED
@@ -164,19 +164,28 @@ in a local multi-service stack — start the frontend/backend v3 server instead
164
164
  import { serve } from "@crvouga/mockingbird-service-postgres/wire"
165
165
 
166
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
167
+ // server.connectionString — postgres://postgres@127.0.0.1:<port>/postgres
168
168
  await server.close()
169
+
170
+ const fromUri = await serve("postgres://postgres:secret@127.0.0.1:0/app")
171
+ // fromUri.connectionString uses the bound port. A client that asks for another
172
+ // database name is rejected with SQLSTATE 3D000.
173
+ await fromUri.close()
169
174
  ```
170
175
 
171
176
  Or from the command line (installs a `mockingbird-postgres` bin):
172
177
 
173
178
  ```bash
174
179
  mockingbird-postgres serve --port 55432 # trust auth
175
- mockingbird-postgres serve --password secret --log # SCRAM-SHA-256, log each statement
180
+ mockingbird-postgres serve postgres://postgres:secret@0.0.0.0:55432/app --log
176
181
  ```
177
182
 
178
183
  `serve({ database })` shares an existing `Database`, and `serve({ database: snapshot })` boots every
179
184
  server from one frozen template, so a seeded stack starts from the same bytes each time.
185
+ `createAdmin` from `@crvouga/mockingbird-service-postgres/admin` serves the same `/__admin`
186
+ surface as every other mock, including the table explorer (`GET /sql/tables`, `POST /sql/query`).
187
+ The wire server does not speak that HTTP API.
188
+
180
189
  `server.snapshot()` freezes the live state; `server.fault({ dropConnection | delayStatementMs |
181
190
  failCommit })` arms the next statement or connection for a drop, a delay, or a `40001` commit
182
191
  failure.
@@ -207,12 +216,13 @@ another binary type gets `0A000`, and can switch that parameter to text).
207
216
  | --- | --- |
208
217
  | `exec(sql)` | Runs all semicolon-separated statements; **discards** row results (`void`). Does **not** accept bind parameters. Read `db.changes` afterwards if needed (reflects the **most recent** completed DML statement). Dump-only `DO` blocks and `ALTER TABLE ... SET (` storage parameters are no-ops. |
209
218
  | `registerFunction(spec)` | Install a JavaScript scalar. Not stored in PGMM snapshots; `open()` of a live snapshot copies the implementation by reference. |
210
- | `query(sql, params?, { at? }?)` | **Single statement only** (trailing `;` is fine). Returns all rows. `at` queries an immutable checkpoint without changing live state. Multi-statement scripts throw `misuse`. |
219
+ | `query(sql, params?, { at? }?)` | **Single statement only** (trailing `;` is fine). Returns all rows. `at` is a `Snapshot` or a timeline checkpoint id and queries that checkpoint without changing live state. Multi-statement scripts throw `misuse`. |
211
220
  | `prepare(sql)` | **Single statement only**. Parses immediately; the AST is reused. Pass binds as rest args to `run` / `all` / `get` / `result` / `textResult` on each call. |
212
221
  | `transaction(fn)` | If idle: `BEGIN`, `fn()`, `COMMIT`, or `ROLLBACK` + rethrow. If already in a transaction: nested savepoint. A nested SQL `BEGIN` inside is a no-op warning like PostgreSQL. `close()` inside `fn` throws `misuse`. |
213
222
  | `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`. |
214
- | `snapshot()` | Freeze a reusable `Snapshot` template (no encode). Illegal inside a transaction (`25P01`). |
215
- | `checkpoint()` / `branch(at?)` | Name a COW snapshot as a checkpoint; open an isolated branch from it (or current state). |
223
+ | `snapshot()` | Freeze a reusable `Snapshot` template (no encode) and commit it on the followed timeline branch. Illegal inside a transaction (`25P01`). |
224
+ | `checkpoint()` / `branch(at?)` | `checkpoint` records the same timeline point as `snapshot`. `branch` opens an isolated database from a snapshot (or the current state). |
225
+ | `record(branch?)` / `fork(name, at?)` / `checkout(id, branch?)` / `reset()` | Timeline history on this database. `record` commits the live state. `fork` adds a branch pointer and does not switch live state. `checkout` installs a checkpoint. `reset` returns to the origin checkpoint on `main`. `history` lists heads and checkpoints. |
216
226
  | `Snapshot.open()` | Copy-on-write fork from a template. The parent stays open. |
217
227
  | `Snapshot.encode()` | Lazy PGMM blob for persistence / worker boot (computed once, cached). |
218
228
  | `Snapshot.decode(bytes)` | Decode a blob once per `Uint8Array` (WeakMap); later `open()` calls are copy-on-write. |
@@ -349,8 +359,9 @@ Stable runtime exports of the main entry:
349
359
  | `Statement` | Class returned by `db.prepare(sql)` (not constructed directly): `run`, `all`, `get`, `result`, `textResult`, and `sql`. |
350
360
  | `PostgresError` | Error class thrown for SQL and API errors: `category` (`ErrorCategory`), `sqlState` / `code` (five-character SQLSTATE, e.g. `"42P01"`, `"23505"`). |
351
361
 
352
- Signatures (types are exported too: `DatabaseOptions`, `RegisterFunctionOptions`, `ResultSet`,
353
- `RunResult`, `ErrorCategory`, `BindValue`, `JsValue`, `QueryRow`):
362
+ Signatures (types are exported too: `DatabaseOptions`, `HistoryCheckpoint`, `DatabaseHistory`,
363
+ `RegisterFunctionOptions`, `ResultSet`, `RunResult`, `ErrorCategory`, `BindValue`, `JsValue`,
364
+ `QueryRow`):
354
365
 
355
366
  ```ts
356
367
  interface DatabaseOptions {
@@ -358,6 +369,21 @@ interface DatabaseOptions {
358
369
  random?: "deterministic" | "os" // default "deterministic"; "os" is CSPRNG like PostgreSQL
359
370
  now?: Date | (() => Date) | "system" // default 2000-01-01T00:00:00.000Z; "system" is wall clock
360
371
  int8?: "bigint" | "number" | "string" // default "bigint"; "number" is unsafe beyond MAX_SAFE_INTEGER
372
+ maxCheckpoints?: number // timeline checkpoints to retain; default 1000
373
+ }
374
+
375
+ interface HistoryCheckpoint {
376
+ id: string
377
+ branch: string
378
+ parent: string | null
379
+ at: number
380
+ }
381
+
382
+ interface DatabaseHistory {
383
+ readonly size: number
384
+ head(branch?: string): HistoryCheckpoint | undefined
385
+ branches(): Readonly<Record<string, string>>
386
+ checkpoints(): readonly HistoryCheckpoint[]
361
387
  }
362
388
 
363
389
  declare class Database {
@@ -365,12 +391,17 @@ declare class Database {
365
391
  exec(sql: string): void
366
392
  registerFunction(spec: { name: string; args: string[]; returns: string; strict?: boolean;
367
393
  fn: (...args: JsValue[]) => JsValue }): void
368
- query<T = QueryRow>(sql: string, params?: readonly BindValue[], options?: { at?: Snapshot }): T[]
394
+ query<T = QueryRow>(sql: string, params?: readonly BindValue[], options?: { at?: Snapshot | string }): T[]
369
395
  prepare(sql: string): Statement
370
396
  transaction<T>(fn: () => T): T
371
397
  copyFrom(sql: string, data: string): number // COPY t FROM STDIN payload (\copy analog)
372
398
  snapshot(): Snapshot
373
399
  checkpoint(): Snapshot
400
+ record(branch?: string): HistoryCheckpoint
401
+ fork(name: string, at?: string): HistoryCheckpoint
402
+ checkout(id: string, branch?: string): HistoryCheckpoint
403
+ reset(): HistoryCheckpoint
404
+ readonly history: DatabaseHistory
374
405
  branch(at?: Snapshot): Database
375
406
  close(): void // also [Symbol.dispose] when available
376
407
  readonly changes: number // rows affected by the most recent INSERT/UPDATE/DELETE
@@ -399,7 +399,7 @@
399
399
  "evidence": [
400
400
  "tests/contract/views/"
401
401
  ],
402
- "notes": "materialized views are eager snapshots refreshed by REFRESH MATERIALIZED VIEW"
402
+ "notes": "eager snapshots; WITH NO DATA sets relispopulated false and may read an unpopulated matview; REFRESH populates; CONCURRENTLY is not modelled"
403
403
  },
404
404
  "sql-createoperator": {
405
405
  "status": "UNSUPPORTED",
@@ -6,6 +6,7 @@ export const CAT_SECTION: CatalogSection = section("CAT", "pg_catalog and inform
6
6
  ["class-01", "user table appears in pg_class with relkind r"],
7
7
  ["class-02", "view appears in pg_class with relkind v"],
8
8
  ["class-03", "sequence appears in pg_class with relkind S"],
9
+ ["class-06", "materialized view appears in pg_class with relkind m"],
9
10
  ["class-04", "pg_class joined to pg_namespace resolves the schema"],
10
11
  ["class-05", "pg_class row disappears after DROP TABLE"],
11
12
  ["attr-01", "pg_attribute lists user columns in order"],
@@ -0,0 +1,13 @@
1
+ import type { Database, DatabaseOptions } from "./api/database.js";
2
+
3
+ export interface AdminOptions {
4
+ database?: Database;
5
+ adminKey?: string;
6
+ databaseOptions?: DatabaseOptions;
7
+ }
8
+
9
+ export interface AdminServer {
10
+ fetch(request: Request): Promise<Response>;
11
+ }
12
+
13
+ export declare function createAdmin(options?: AdminOptions): AdminServer;