@crvouga/mockingbird-service-postgres 3.3.0 → 3.4.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/CHANGELOG.md +13 -1
- package/COMPATIBILITY.md +1 -1
- package/README.md +72 -53
- package/dist/admin.js +1535 -365
- package/dist/admin.js.map +4 -4
- package/dist/index.js +7 -5
- package/dist/index.js.map +2 -2
- package/dist/server.js +13 -6
- package/dist/server.js.map +2 -2
- package/dist/storage/columnar-slab.d.ts +2 -0
- package/dist/wire/cli.js +1323 -780
- package/dist/wire/cli.js.map +4 -4
- package/dist/wire/fleet.d.ts +210 -1
- package/dist/wire/index.js +7 -5
- package/dist/wire/index.js.map +2 -2
- package/package.json +1 -1
package/dist/wire/fleet.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ type ClockState = {
|
|
|
10
10
|
now: number;
|
|
11
11
|
/** True while time does not advance on its own. */
|
|
12
12
|
frozen: boolean;
|
|
13
|
-
/** Milliseconds
|
|
13
|
+
/** Milliseconds a running clock adds to its underlying source. `0` while frozen. */
|
|
14
14
|
offsetMs: number;
|
|
15
15
|
};
|
|
16
16
|
type Clock = {
|
|
@@ -28,6 +28,213 @@ type Clock = {
|
|
|
28
28
|
state(): ClockState;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* A deliberate failure injected in front of an operation.
|
|
33
|
+
*
|
|
34
|
+
* This is how a suite reaches the vendor's failure modes without the vendor: the
|
|
35
|
+
* quota error that only appears when a shared sandbox is full, the 429 that only
|
|
36
|
+
* appears under load, the 5xx that proves a retry path works.
|
|
37
|
+
*
|
|
38
|
+
* A rule has three parts. What it **matches** (`operationId`, `method`, `path`, …), **when**
|
|
39
|
+
* it fires among the matching requests (`after`, `every`, `count`, `rate`, `from`, `until`),
|
|
40
|
+
* and what it **does** (`status`, `delay`, `timeout`, `drop`, `truncate`, `effect`).
|
|
41
|
+
*/
|
|
42
|
+
type FaultRule = {
|
|
43
|
+
/** Stable id, so a suite can retire exactly the rule it added. Unique within a namespace. */
|
|
44
|
+
id: string;
|
|
45
|
+
/** Fault only this operation. Omit to match every operation. */
|
|
46
|
+
operationId?: string;
|
|
47
|
+
/** Fault only this HTTP method, case-insensitive. Omit to match every method. */
|
|
48
|
+
method?: string;
|
|
49
|
+
/** Fault only this exact path. */
|
|
50
|
+
path?: string;
|
|
51
|
+
/** Fault only paths starting with this prefix. Omit to match every path. */
|
|
52
|
+
pathPrefix?: string;
|
|
53
|
+
/** Fault only paths matching this regular expression (JavaScript syntax, unanchored). */
|
|
54
|
+
pathPattern?: string;
|
|
55
|
+
/** Fault only requests carrying these headers. `"*"` matches any value. */
|
|
56
|
+
requestHeaders?: Record<string, string>;
|
|
57
|
+
/** Fault only requests carrying these query parameters. `"*"` matches any value. */
|
|
58
|
+
query?: Record<string, string>;
|
|
59
|
+
/**
|
|
60
|
+
* Fault only this namespace. Omit (or `"*"`) to fault every namespace — which is what
|
|
61
|
+
* an in-process caller usually wants, and what a parallel worker usually does not:
|
|
62
|
+
* rules added through `POST /__admin/faults` default to the calling namespace.
|
|
63
|
+
*/
|
|
64
|
+
namespace?: string;
|
|
65
|
+
/** Let this many matching requests through first. */
|
|
66
|
+
after?: number;
|
|
67
|
+
/** Then fault only every nth matching request: `3` faults the 3rd, 6th, 9th. */
|
|
68
|
+
every?: number;
|
|
69
|
+
/** Retire the rule after this many faults. Omit to keep it until removed. */
|
|
70
|
+
count?: number;
|
|
71
|
+
/** Fault this fraction of the remaining requests, `0`–`1`, drawn from the seed. Default `1`. */
|
|
72
|
+
rate?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Fault only from this instant on the namespace clock. A duration (`"5m"`) counts from the
|
|
75
|
+
* clock when the rule was added. Listed as epoch ms.
|
|
76
|
+
*/
|
|
77
|
+
from?: Instant | Duration;
|
|
78
|
+
/** Stop faulting at this instant on the namespace clock. A duration counts from when added. */
|
|
79
|
+
until?: Instant | Duration;
|
|
80
|
+
/**
|
|
81
|
+
* Status of the injected response. Omit for a rule that only delays, times out, drops,
|
|
82
|
+
* truncates, or switches on an `effect`: the request then still reaches the service.
|
|
83
|
+
*/
|
|
84
|
+
status?: number;
|
|
85
|
+
/** Response body, serialized as JSON. A string is sent as-is. */
|
|
86
|
+
body?: unknown;
|
|
87
|
+
/** Response headers. */
|
|
88
|
+
headers?: Record<string, string>;
|
|
89
|
+
/** Hold the response back this long. Listed as `delayMs`. */
|
|
90
|
+
delay?: Duration;
|
|
91
|
+
/** `delay`, in milliseconds. */
|
|
92
|
+
delayMs?: number;
|
|
93
|
+
/** Alias of `delayMs`. */
|
|
94
|
+
latencyMs?: number;
|
|
95
|
+
/** Extra seeded delay in `[0, jitter]`. Listed as `jitterMs`. */
|
|
96
|
+
jitter?: Duration;
|
|
97
|
+
jitterMs?: number;
|
|
98
|
+
/**
|
|
99
|
+
* Never answer: hold the request this long, then drop the connection. Listed as
|
|
100
|
+
* `timeoutMs`. With virtual time the wait is spent on the mock clock.
|
|
101
|
+
*/
|
|
102
|
+
timeout?: Duration;
|
|
103
|
+
timeoutMs?: number;
|
|
104
|
+
/**
|
|
105
|
+
* Drop the connection instead of answering: an in-process `fetch` rejects with a
|
|
106
|
+
* `TypeError`, and a served mock destroys the socket. Models "unknown outcome" failures.
|
|
107
|
+
*/
|
|
108
|
+
drop?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Cut the response body off after this many bytes: the status and headers arrive, then
|
|
111
|
+
* the connection dies mid-body.
|
|
112
|
+
*/
|
|
113
|
+
truncate?: number;
|
|
114
|
+
/**
|
|
115
|
+
* A named service behaviour to switch on for the matching request instead of (or
|
|
116
|
+
* before) a canned response, e.g. `created_but_500` or `numeric_tracking_id`. Services
|
|
117
|
+
* read it with `faultEffects(request)`.
|
|
118
|
+
*/
|
|
119
|
+
effect?: string;
|
|
120
|
+
/** Parameters for `effect`. */
|
|
121
|
+
params?: Record<string, unknown>;
|
|
122
|
+
/** The preset this rule was expanded from, if any. */
|
|
123
|
+
fromPreset?: string;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The admin controls every mock shares, as one document.
|
|
128
|
+
*
|
|
129
|
+
* The same shape is accepted in three places, and means the same thing in each:
|
|
130
|
+
*
|
|
131
|
+
* - at boot: `createRuntime({ adminControls })`, `serve --admin-controls`,
|
|
132
|
+
* `MOCKINGBIRD_ADMIN_CONTROLS`
|
|
133
|
+
* - over HTTP: `PUT /__admin/controls`
|
|
134
|
+
* - per request: the `x-mockingbird-*` headers in {@link CONTROL_HEADERS}
|
|
135
|
+
*
|
|
136
|
+
* Every control is deterministic: given the same seed, clock and requests, a mock answers
|
|
137
|
+
* the same bytes. Nothing here reads the wall clock or `Math.random`.
|
|
138
|
+
*/
|
|
139
|
+
/** Milliseconds, or a string like `"250ms"`, `"2s"`, `"15m"`, `"1h"`, `"3d"`. */
|
|
140
|
+
type Duration = number | string;
|
|
141
|
+
/** Epoch milliseconds, or an ISO-8601 string. */
|
|
142
|
+
type Instant = number | string;
|
|
143
|
+
type ClockControls = {
|
|
144
|
+
/** Move the clock to this instant. */
|
|
145
|
+
now?: Instant;
|
|
146
|
+
/** Stop time. A frozen clock moves only when told to, so timestamps replay exactly. */
|
|
147
|
+
frozen?: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Step the clock forward by this much before every vendor request. With a frozen clock
|
|
150
|
+
* each request still gets its own, reproducible timestamp.
|
|
151
|
+
*/
|
|
152
|
+
autoAdvance?: Duration;
|
|
153
|
+
};
|
|
154
|
+
/** How long a response takes: a fixed delay plus seeded jitter in `[0, jitter]`. */
|
|
155
|
+
type LatencyRuleInput = {
|
|
156
|
+
delay?: Duration;
|
|
157
|
+
jitter?: Duration;
|
|
158
|
+
};
|
|
159
|
+
type LatencyControls = LatencyRuleInput & {
|
|
160
|
+
/** Per-operation overrides, keyed by operationId. A bare duration is its `delay`. */
|
|
161
|
+
operations?: Record<string, Duration | LatencyRuleInput>;
|
|
162
|
+
};
|
|
163
|
+
/** A fault to add: a rule (the id is assigned when omitted), or a named preset with overrides. */
|
|
164
|
+
type FaultInput = (Omit<FaultRule, "id"> & {
|
|
165
|
+
id?: string;
|
|
166
|
+
}) | ({
|
|
167
|
+
preset: string;
|
|
168
|
+
} & Partial<FaultRule>);
|
|
169
|
+
/** Identifies a {@link SnapshotDocument}. */
|
|
170
|
+
declare const SNAPSHOT_FORMAT = "mockingbird.snapshot/v1";
|
|
171
|
+
/**
|
|
172
|
+
* A namespace, as portable JSON: what `GET /__admin/snapshots/:id` returns and what
|
|
173
|
+
* `PUT /__admin/snapshots/:id` and the boot `snapshot` control accept. Only `format` and
|
|
174
|
+
* `records` are required, so a hand-written seed file is a valid snapshot.
|
|
175
|
+
*/
|
|
176
|
+
type SnapshotDocument = {
|
|
177
|
+
format: typeof SNAPSHOT_FORMAT;
|
|
178
|
+
/** The service that wrote it. A snapshot is rejected by any other service. */
|
|
179
|
+
service?: string;
|
|
180
|
+
/**
|
|
181
|
+
* Stored rows. `seq` orders a collection; omitted, rows keep their listed order. `raw` is
|
|
182
|
+
* the stored text of a row that is not a plain record, and replaces `value` when present.
|
|
183
|
+
*/
|
|
184
|
+
records: {
|
|
185
|
+
collection: string;
|
|
186
|
+
id: string;
|
|
187
|
+
seq?: number;
|
|
188
|
+
value?: unknown;
|
|
189
|
+
raw?: string;
|
|
190
|
+
}[];
|
|
191
|
+
/** Id counters, so the next generated id continues where the snapshot left off. */
|
|
192
|
+
sequences?: {
|
|
193
|
+
name: string;
|
|
194
|
+
kind: string;
|
|
195
|
+
value: number;
|
|
196
|
+
}[];
|
|
197
|
+
clock?: {
|
|
198
|
+
now: number;
|
|
199
|
+
frozen: boolean;
|
|
200
|
+
autoAdvanceMs?: number;
|
|
201
|
+
};
|
|
202
|
+
seed?: number | string;
|
|
203
|
+
/** Positions of the seeded random streams. */
|
|
204
|
+
rng?: {
|
|
205
|
+
state: number;
|
|
206
|
+
faults?: number;
|
|
207
|
+
latency?: number;
|
|
208
|
+
};
|
|
209
|
+
faults?: unknown;
|
|
210
|
+
latency?: unknown;
|
|
211
|
+
virtualTime?: boolean;
|
|
212
|
+
};
|
|
213
|
+
/** Controls for one namespace. */
|
|
214
|
+
type NamespaceControls = {
|
|
215
|
+
/** Seeds every random choice: fault rates, jitter, and whatever the service invents. */
|
|
216
|
+
seed?: number | string;
|
|
217
|
+
clock?: ClockControls;
|
|
218
|
+
/**
|
|
219
|
+
* Spend injected waits (latency, fault delays, timeouts) on the mock clock instead of the
|
|
220
|
+
* wall clock: the request returns at once, the clock has moved, and the journal records
|
|
221
|
+
* the wait exactly. Default `false`: waits are real, so a client's own timeout can fire.
|
|
222
|
+
*/
|
|
223
|
+
virtualTime?: boolean;
|
|
224
|
+
/** Baseline response time for every vendor request. A bare duration is its `delay`. */
|
|
225
|
+
latency?: Duration | LatencyControls;
|
|
226
|
+
faults?: readonly FaultInput[];
|
|
227
|
+
/** Start from this state instead of the service's own seed data. */
|
|
228
|
+
snapshot?: SnapshotDocument;
|
|
229
|
+
};
|
|
230
|
+
/** {@link NamespaceControls} for every namespace, with per-namespace overrides. */
|
|
231
|
+
type MockControls = NamespaceControls & {
|
|
232
|
+
/** Vendor credential → namespace, for SDKs that cannot send a namespace header. */
|
|
233
|
+
credentials?: Record<string, string>;
|
|
234
|
+
/** Overrides for named namespaces. A key given here replaces the shared value. */
|
|
235
|
+
namespaces?: Record<string, NamespaceControls>;
|
|
236
|
+
};
|
|
237
|
+
|
|
31
238
|
type FleetDiagnostics = {
|
|
32
239
|
activeRequests: number;
|
|
33
240
|
activeConnections: number;
|
|
@@ -66,6 +273,8 @@ type ConfigService = {
|
|
|
66
273
|
adminPrefix?: string;
|
|
67
274
|
adminKey?: string;
|
|
68
275
|
seed?: string;
|
|
276
|
+
/** Admin controls this service boots with. A key given here replaces the fleet-wide one. */
|
|
277
|
+
adminControls?: MockControls;
|
|
69
278
|
fixtures?: Record<string, unknown>;
|
|
70
279
|
baseUrl?: string;
|
|
71
280
|
password?: string;
|
package/dist/wire/index.js
CHANGED
|
@@ -26967,7 +26967,7 @@ function readSlabCell(column, rowIndex, intern, rowCount) {
|
|
|
26967
26967
|
}
|
|
26968
26968
|
if (pack === PACK_INT) {
|
|
26969
26969
|
const integer2 = view.getBigInt64(nonNullIndex * 8, true);
|
|
26970
|
-
return integer2 <= BigInt(Number.MAX_SAFE_INTEGER) && integer2 >= BigInt(Number.MIN_SAFE_INTEGER) ? Number(integer2) : integer2;
|
|
26970
|
+
return !column.integerBigInt && integer2 <= BigInt(Number.MAX_SAFE_INTEGER) && integer2 >= BigInt(Number.MIN_SAFE_INTEGER) ? Number(integer2) : integer2;
|
|
26971
26971
|
}
|
|
26972
26972
|
if (pack === PACK_TEXT_INTERN) {
|
|
26973
26973
|
const id = view.getUint32(nonNullIndex * 4, true);
|
|
@@ -27528,7 +27528,8 @@ function decodeInner(snapshot, prng, clock) {
|
|
|
27528
27528
|
if (meta.stats) state.tableStats.set(table.oid, meta.stats);
|
|
27529
27529
|
const rowCount = readVarintU32(r);
|
|
27530
27530
|
const slabColumns = [];
|
|
27531
|
-
for (let c = 0; c < meta.columns.length; c++)
|
|
27531
|
+
for (let c = 0; c < meta.columns.length; c++)
|
|
27532
|
+
slabColumns.push(readPackedColumn(r, rowCount, intern, meta.columns[c].type.id));
|
|
27532
27533
|
table.attachSlab(new ColumnarSlab(snapshot, rowCount, slabColumns, intern));
|
|
27533
27534
|
schema.tables.set(table.name, table);
|
|
27534
27535
|
}
|
|
@@ -27541,7 +27542,7 @@ function decodeInner(snapshot, prng, clock) {
|
|
|
27541
27542
|
const matRowCount = readVarintU32(r);
|
|
27542
27543
|
const width = readVarintU32(r);
|
|
27543
27544
|
const cols = [];
|
|
27544
|
-
for (let c = 0; c < width; c++) cols.push(readPackedColumn(r, matRowCount, intern));
|
|
27545
|
+
for (let c = 0; c < width; c++) cols.push(readPackedColumn(r, matRowCount, intern, meta.matColumns?.[c]?.type));
|
|
27545
27546
|
const slab = new ColumnarSlab(snapshot, matRowCount, cols, intern);
|
|
27546
27547
|
matRows = slab.materialize();
|
|
27547
27548
|
}
|
|
@@ -27813,7 +27814,7 @@ function writePackedColumn(w, rows, col, internId, forceId) {
|
|
|
27813
27814
|
writeTaggedDatum(w, rows[i][col], internId, forceId);
|
|
27814
27815
|
}
|
|
27815
27816
|
}
|
|
27816
|
-
function readPackedColumn(r, n, intern) {
|
|
27817
|
+
function readPackedColumn(r, n, intern, type) {
|
|
27817
27818
|
const pack = r.u8();
|
|
27818
27819
|
if (pack === PACK_NULL) return { pack, nullBitmap: null, payload: new Uint8Array(0) };
|
|
27819
27820
|
const bits = r.raw(n + 7 >> 3);
|
|
@@ -27826,7 +27827,8 @@ function readPackedColumn(r, n, intern) {
|
|
|
27826
27827
|
r.skipAlign4();
|
|
27827
27828
|
const width = 8;
|
|
27828
27829
|
const payload = r.raw(nonNull * width);
|
|
27829
|
-
|
|
27830
|
+
const integerBigInt = type !== void 0 && ["int8", "money", "time", "timestamp", "timestamptz"].includes(type);
|
|
27831
|
+
return { pack, nullBitmap: bits, payload, integerBigInt };
|
|
27830
27832
|
}
|
|
27831
27833
|
if (pack === PACK_TEXT_INTERN) {
|
|
27832
27834
|
r.skipAlign4();
|