@crvouga/mockingbird-service-postgres 1.5.1 → 1.7.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.
@@ -1,13 +1,12 @@
1
- import type { Database } from "../api/database.js";
1
+ import { Database } from "../api/database.js";
2
+ import type { Snapshot } from "../api/snapshot.js";
2
3
  /**
3
4
  * What every connection of one server shares: the engine, and the coordination the engine
4
5
  * (single-session, synchronous) does not do itself.
5
6
  *
6
- * - The **turn**: the engine holds one transaction at a time, so a connection inside an
7
- * explicit transaction block holds the turn until it commits or rolls back, and every other
8
- * connection's statement waits. A statement outside a block takes the turn for itself only.
9
- * Uncommitted rows are therefore never visible to another connection (read committed holds),
10
- * at the cost of running transaction blocks one at a time.
7
+ * - The **turn** serializes individual synchronous engine calls. Explicit transactions use
8
+ * per-session copy-on-write workspaces in the connection, so the turn is released after every
9
+ * statement and unrelated sessions keep progressing.
11
10
  * - **Advisory locks** per session, with waiters woken in order, and deadlock detection
12
11
  * between a lock and the turn (`40P01`).
13
12
  * - **LISTEN / NOTIFY** fan-out, delivered when a listening connection is idle.
@@ -15,7 +14,7 @@ import type { Database } from "../api/database.js";
15
14
  * (`pg_backend_pid`, `pg_advisory_lock`, `pg_notify`, …) know whom they serve.
16
15
  */
17
16
  export declare class Cluster {
18
- readonly db: Database;
17
+ readonly defaultDatabaseName: string;
19
18
  turnHolder: Session | null;
20
19
  private readonly turnQueue;
21
20
  readonly locks: Map<string, {
@@ -28,7 +27,21 @@ export declare class Cluster {
28
27
  readonly sessions: Map<number, Session>;
29
28
  current: Session | null;
30
29
  private nextPid;
31
- constructor(db: Database);
30
+ private readonly databases;
31
+ constructor(db: Database, defaultDatabaseName?: string);
32
+ /** The initial database, retained for the server's backwards-compatible public handle. */
33
+ get db(): Database;
34
+ databaseNames(): string[];
35
+ snapshotAll(): Map<string, Snapshot>;
36
+ restoreAll(points: ReadonlyMap<string, Snapshot>): void;
37
+ getDatabase(name: string): Database | undefined;
38
+ requireDatabase(name: string): Database;
39
+ createDatabase(name: string, template?: string): void;
40
+ dropDatabase(name: string, requester: Session): void;
41
+ renameDatabase(from: string, to: string, requester: Session): void;
42
+ private requireTemplate;
43
+ private activeConnections;
44
+ private installDatabase;
32
45
  newPid(): number;
33
46
  /** Wait until this session may execute: at once when nobody holds the turn, or it does. */
34
47
  acquireTurn(session: Session): Promise<void>;
@@ -59,6 +72,8 @@ export declare class LockWait extends Error {
59
72
  /** What the cluster needs from a connection. */
60
73
  export interface Session {
61
74
  readonly pid: number;
75
+ database: Database;
76
+ databaseName: string;
62
77
  waitingForTurn: Waiter | null;
63
78
  waitingForLock: string | null;
64
79
  /** NOTIFYs issued inside the current transaction block, sent at commit. */
@@ -1,4 +1,5 @@
1
1
  import type { Socket } from "node:net";
2
+ import type { Database } from "../api/database.js";
2
3
  import { type Cluster, type Session } from "./cluster.js";
3
4
  export type ServerFaults = {
4
5
  /** Destroy the socket of the next connection that runs a statement inside a transaction block. */
@@ -43,6 +44,8 @@ export declare class Connection implements Session {
43
44
  channel: string;
44
45
  payload: string;
45
46
  }[];
47
+ database: Database;
48
+ databaseName: string;
46
49
  private readonly frames;
47
50
  private readonly inbox;
48
51
  private pumping;
@@ -59,6 +62,10 @@ export declare class Connection implements Session {
59
62
  private readonly prepared;
60
63
  private readonly portals;
61
64
  private readonly notifications;
65
+ private copyIn;
66
+ private copyDrain;
67
+ /** Successful writes in this session's READ COMMITTED transaction workspace. */
68
+ private transaction;
62
69
  private closed;
63
70
  constructor(socket: Socket, cluster: Cluster, options: ConnectionOptions);
64
71
  private receive;
@@ -88,12 +95,29 @@ export declare class Connection implements Session {
88
95
  private waitForLock;
89
96
  /** The engine call itself: statement-atomic, and transaction control handled as the server sees it. */
90
97
  private runStatement;
91
- /** Release the turn and transaction-scoped locks once no block is open; deliver NOTIFYs at commit. */
98
+ /** Release the statement turn; transaction-scoped locks live until COMMIT / ROLLBACK. */
92
99
  private afterStatement;
100
+ private transactionControl;
101
+ private findSavepoint;
102
+ /** Rebase this transaction's successful writes onto the latest committed state. */
103
+ private openWorkspace;
104
+ private replay;
105
+ private runQuery;
106
+ private lockRows;
107
+ private rowLockKey;
93
108
  private flushNotifies;
109
+ private executeClusterDdl;
94
110
  private listen;
95
111
  private unlisten;
96
112
  private query;
113
+ private copyStatement;
114
+ private copyColumnCount;
115
+ private handleCopyIn;
116
+ private beginCopyIn;
117
+ private finishCopyIn;
118
+ private flushCopyRecords;
119
+ private abortCopyIn;
120
+ private sendCopyOut;
97
121
  private sendOutcome;
98
122
  private columnFormats;
99
123
  private fields;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The single source of time for a service.
3
+ *
4
+ * Every timestamp a mock writes reads from here, so a suite moves time instead of
5
+ * sleeping: appointment windows, result delays and expiries become reachable in
6
+ * milliseconds. A frozen clock also makes timestamps reproducible from a seed.
7
+ */
8
+ type ClockState = {
9
+ /** Current epoch milliseconds. */
10
+ now: number;
11
+ /** True while time does not advance on its own. */
12
+ frozen: boolean;
13
+ /** Milliseconds this clock adds to its underlying source. */
14
+ offsetMs: number;
15
+ };
16
+ type Clock = {
17
+ now(): number;
18
+ /** Pin the clock to an exact instant, keeping it frozen if it already was. */
19
+ set(epochMs: number): void;
20
+ /** Move the clock forward, or back with a negative delta. */
21
+ advance(deltaMs: number): void;
22
+ /** Stop time at the current instant. */
23
+ freeze(): void;
24
+ /** Resume from the current instant. */
25
+ unfreeze(): void;
26
+ /** Drop back to the underlying source, live. */
27
+ reset(): void;
28
+ state(): ClockState;
29
+ };
30
+
31
+ type FleetDiagnostics = {
32
+ activeRequests: number;
33
+ activeConnections: number;
34
+ blockedCommands: number;
35
+ pendingJobs: number;
36
+ activeJobs?: number;
37
+ pendingWebhooks: number;
38
+ unmatchedRequests: number;
39
+ };
40
+ /** Protocol packages own their state, listeners, credentials and namespace selection. */
41
+ type FleetChild = {
42
+ protocol: "http" | "postgres" | "redis";
43
+ url: string;
44
+ connection?: string;
45
+ connections?: Record<string, string>;
46
+ healthUrl?: string;
47
+ adminUrl?: string;
48
+ namespaces: Record<string, unknown>;
49
+ ready(): Promise<boolean>;
50
+ close(): Promise<void>;
51
+ /** Fence new work synchronously; diagnostics must also account for work already in flight. */
52
+ lock(namespace: string): () => void;
53
+ diagnostics(namespace: string): FleetDiagnostics;
54
+ checkpoint(namespace: string): Promise<unknown>;
55
+ restore(namespace: string, checkpoint: unknown): Promise<void>;
56
+ reset(namespace: string): Promise<void>;
57
+ clock(namespace: string): Clock;
58
+ settle?(namespace: string): Promise<void>;
59
+ };
60
+
61
+ /** A service entry in `mockingbird.json`. */
62
+ type ConfigService = {
63
+ protocol?: "http" | "postgres" | "redis";
64
+ port?: number;
65
+ host?: string;
66
+ adminKey?: string;
67
+ seed?: string;
68
+ password?: string;
69
+ user?: string;
70
+ database?: string;
71
+ /** Protocol namespace selectors: database names for Postgres, logical DB indexes for Redis. */
72
+ namespaces?: Record<string, string | number>;
73
+ /** Service-specific serve flags, by long name: `{ "webhook-url": "…" }`. */
74
+ options?: Record<string, string | boolean>;
75
+ };
76
+
77
+ /** Separate database listeners make the protocol namespace explicit in discovery. */
78
+ declare function startProtocol(entry: ConfigService): Promise<FleetChild>;
79
+
80
+ export { startProtocol };
@@ -55,6 +55,8 @@ export type ServeOptions = {
55
55
  * SQLSTATE `3D000`. Set from `url` when the path is non-empty. Unset accepts any name.
56
56
  */
57
57
  databaseName?: string;
58
+ /** Accept other databases in this listener's catalog while retaining its default URL. */
59
+ allowDatabaseSelection?: boolean;
58
60
  /** Role shown in `connectionString`. A URI's username fills this. Default `postgres`. */
59
61
  user?: string;
60
62
  };
@@ -67,14 +69,22 @@ export type PostgresServer = {
67
69
  readonly connectionString: string;
68
70
  /** The shared engine, for seeding, snapshotting or asserting from the test process. */
69
71
  readonly database: Database;
72
+ /** Names in the cluster catalog, in deterministic order. */
73
+ databaseNames(): readonly string[];
74
+ /** Resolve a named database, or throw SQLSTATE `3D000`. */
75
+ getDatabase(name: string): Database;
70
76
  /** The underlying `net.Server`. */
71
77
  readonly server: Server;
72
78
  /** Open connections right now. */
73
79
  readonly connections: number;
74
80
  /** Arm a fault preset for the next statement / connection (see {@link ServerFaults}). */
75
81
  fault(fault: ServerFaults): void;
82
+ clearFaults(): void;
76
83
  /** Freeze the live state (the admin `snapshot()` control). */
77
- snapshot(): Snapshot;
84
+ snapshot(name?: string): Snapshot;
85
+ snapshotAll(): Map<string, Snapshot>;
86
+ restoreAll(points: ReadonlyMap<string, Snapshot>): void;
87
+ faultState(): ServerFaults;
78
88
  /** Stop listening and close every connection. */
79
89
  close(): Promise<void>;
80
90
  };