@crvouga/mockingbird-service-junction 1.0.1 → 1.1.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/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  runCli,
5
5
  serveCommand,
6
6
  serveTarget
7
- } from "./chunk-2YNUCD2S.js";
7
+ } from "./chunk-A4KJJ5V3.js";
8
8
  import {
9
9
  DEFAULT_JUNCTION_BASE_URL,
10
10
  createRuntime,
@@ -12,7 +12,7 @@ import {
12
12
  isSandboxKey,
13
13
  pullCorpus,
14
14
  verifyAgainstReal
15
- } from "./chunk-KEP42KKR.js";
15
+ } from "./chunk-GWXEPT6A.js";
16
16
  import {
17
17
  defaultCorpus
18
18
  } from "./chunk-C2UGIPPM.js";
@@ -23,11 +23,11 @@ import {
23
23
  // src/cli.ts
24
24
  import { readFile, writeFile } from "node:fs/promises";
25
25
  var asString = (value) => typeof value === "string" && value !== "" ? value : void 0;
26
- var realKeyOf = (values) => asString(values["real-key"]) ?? asString(process.env.MOCKINGBIRD_JUNCTION_API_KEY) ?? asString(process.env.JUNCTION_API_KEY);
26
+ var realKeyOf = (values) => asString(values["real-key"]) ?? asString(process.env.JUNCTION_API_KEY) ?? asString(process.env.JUNCTION_API_KEY);
27
27
  var readCorpus = async (source) => source === "default" ? defaultCorpus : parseSealedCorpus(JSON.parse(await readFile(source, "utf8")));
28
28
  var guardKey = (key, values) => {
29
29
  if (key === void 0) {
30
- console.error("missing --real-key (or env MOCKINGBIRD_JUNCTION_API_KEY)");
30
+ console.error("missing --real-key (or env JUNCTION_API_KEY)");
31
31
  return void 0;
32
32
  }
33
33
  if (!isSandboxKey(key) && values["allow-any-key"] !== true) {
@@ -42,7 +42,7 @@ var REAL_OPTIONS = {
42
42
  "real-key": {
43
43
  type: "string",
44
44
  value: "<key>",
45
- description: "Junction API key (env MOCKINGBIRD_JUNCTION_API_KEY)"
45
+ description: "Junction API key (env JUNCTION_API_KEY)"
46
46
  },
47
47
  "real-url": {
48
48
  type: "string",
package/dist/cli.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/cli.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n/// <reference types=\"node\" />\nimport { readFile, writeFile } from \"node:fs/promises\"\nimport { type CliValues, runCli, serveCommand } from \"@crvouga/mockingbird-adapter-node\"\nimport { defaultCorpus } from \"./corpus.js\"\nimport { DEFAULT_JUNCTION_BASE_URL, diffCorpus, isSandboxKey, pullCorpus } from \"./corpus-tools.js\"\nimport { createRuntime } from \"./runtime.js\"\nimport { parseSealedCorpus, type SealedCorpus } from \"./sealed-corpus.js\"\nimport { loadCorpus, serveTarget } from \"./server.js\"\nimport { verifyAgainstReal } from \"./verify.js\"\n\nconst asString = (value: string | boolean | undefined): string | undefined =>\n typeof value === \"string\" && value !== \"\" ? value : undefined\n\nconst realKeyOf = (values: CliValues): string | undefined =>\n asString(values[\"real-key\"]) ??\n asString(process.env.MOCKINGBIRD_JUNCTION_API_KEY) ??\n asString(process.env.JUNCTION_API_KEY)\n\nconst readCorpus = async (source: string): Promise<SealedCorpus> =>\n source === \"default\"\n ? defaultCorpus\n : parseSealedCorpus(JSON.parse(await readFile(source, \"utf8\")))\n\nconst guardKey = (key: string | undefined, values: CliValues): string | undefined => {\n if (key === undefined) {\n console.error(\"missing --real-key (or env MOCKINGBIRD_JUNCTION_API_KEY)\")\n return undefined\n }\n if (!isSandboxKey(key) && values[\"allow-any-key\"] !== true) {\n console.error(\n \"refusing a key that is not a sandbox team key (sk_us_\u2026 / sk_eu_\u2026); pass --allow-any-key to override\",\n )\n return undefined\n }\n return key\n}\n\nconst REAL_OPTIONS = {\n \"real-key\": {\n type: \"string\",\n value: \"<key>\",\n description: \"Junction API key (env MOCKINGBIRD_JUNCTION_API_KEY)\",\n },\n \"real-url\": {\n type: \"string\",\n value: \"<url>\",\n description: \"Junction API base URL\",\n default: DEFAULT_JUNCTION_BASE_URL,\n },\n \"allow-any-key\": {\n type: \"boolean\",\n description: \"Allow a key that is not a sandbox team key\",\n },\n} as const\n\nconst code = await runCli(\n {\n bin: \"mockingbird-junction\",\n description: \"stateful Junction (Vital) API mock\",\n commands: {\n serve: serveCommand(serveTarget),\n\n \"corpus pull\": {\n summary: \"Record a corpus from a real Junction team (read-only GETs)\",\n usage: \"mockingbird-junction corpus pull --out <file> [--zip 10001,94105] [--base <file>]\",\n options: {\n ...REAL_OPTIONS,\n out: { type: \"string\", value: \"<file>\", description: \"Where to write the corpus\" },\n zip: {\n type: \"string\",\n value: \"<zip,zip,\u2026>\",\n description: \"ZIPs to record coverage for (default: the shipped set)\",\n },\n base: {\n type: \"string\",\n value: \"<file|default>\",\n description: \"Only refresh --zip on top of this corpus\",\n },\n \"coverage-only\": {\n type: \"boolean\",\n description: \"Skip the team's catalog and lab accounts\",\n },\n },\n async run(values) {\n const key = guardKey(realKeyOf(values), values)\n if (key === undefined) return 2\n const out = asString(values.out)\n if (out === undefined) {\n console.error(\"missing --out <file>\")\n return 2\n }\n const zips = asString(values.zip)\n ?.split(\",\")\n .map((zip) => zip.trim())\n .filter(Boolean)\n if (zips?.some((zip) => !/^\\d{5}$/.test(zip))) {\n console.error(\"--zip takes comma-separated 5-digit ZIPs\")\n return 2\n }\n const baseSource = asString(values.base)\n const corpus = await pullCorpus({\n apiKey: key,\n baseUrl: asString(values[\"real-url\"]) ?? DEFAULT_JUNCTION_BASE_URL,\n ...(zips ? { zips } : {}),\n ...(baseSource ? { base: await readCorpus(baseSource) } : {}),\n coverageOnly: values[\"coverage-only\"] === true,\n onProgress: (message) => console.log(`corpus pull: ${message}`),\n })\n await writeFile(out, `${JSON.stringify(corpus, null, 2)}\\n`)\n console.log(\n `corpus pull: wrote ${out} \u2014 ${Object.keys(corpus.observations).length} observations, ${corpus.catalog.labTests.length} lab tests, ${corpus.labAccounts.length} lab accounts, fingerprint ${corpus.fingerprint?.slice(0, 12)}`,\n )\n return 0\n },\n },\n\n \"corpus diff\": {\n summary: \"Show what changed between two corpora (exit 1 when they differ)\",\n usage: \"mockingbird-junction corpus diff <before|default> <after|default> [--json]\",\n options: { json: { type: \"boolean\", description: \"Print the diff as JSON\" } },\n async run(values, positionals) {\n const [beforePath, afterPath] = positionals\n if (beforePath === undefined || afterPath === undefined) {\n console.error(\"usage: mockingbird-junction corpus diff <before> <after>\")\n return 2\n }\n const diff = diffCorpus(await readCorpus(beforePath), await readCorpus(afterPath))\n if (values.json === true) {\n console.log(JSON.stringify(diff, null, 2))\n return diff.identical ? 0 : 1\n }\n if (diff.identical) {\n console.log(\"corpora are identical\")\n return 0\n }\n const section = (\n title: string,\n d: { added: string[]; removed: string[]; changed?: string[] },\n ) => {\n const total = d.added.length + d.removed.length + (d.changed?.length ?? 0)\n if (total === 0) return\n console.log(`${title}:`)\n for (const id of d.added) console.log(` + ${id}`)\n for (const id of d.removed) console.log(` - ${id}`)\n for (const id of d.changed ?? []) console.log(` ~ ${id}`)\n }\n section(\"ZIPs\", diff.zips)\n section(\"lab tests\", diff.labTests)\n section(\"lab accounts\", diff.labAccounts)\n const o = diff.observations\n console.log(\n `observations: +${o.added.length} -${o.removed.length} ~${o.changed.length} (--json lists them)`,\n )\n return 1\n },\n },\n\n verify: {\n summary: \"Replay the corpus and a stateful scenario against real Junction and the mock\",\n usage: \"mockingbird-junction verify --real-key <sk_us_\u2026> [--corpus <file>] [--orders]\",\n options: {\n ...REAL_OPTIONS,\n corpus: {\n type: \"string\",\n value: \"<default|file>\",\n description: \"Corpus the mock serves and drift is checked against\",\n default: \"default\",\n },\n sample: {\n type: \"string\",\n value: \"<n>\",\n description: \"Re-fetch at most n recorded observations (default: all)\",\n },\n \"skip-drift\": { type: \"boolean\", description: \"Only run the stateful scenario\" },\n orders: {\n type: \"boolean\",\n description: \"Also place and cancel an order on the real team\",\n },\n json: { type: \"boolean\", description: \"Print the report as JSON\" },\n },\n async run(values) {\n const key = guardKey(realKeyOf(values), values)\n if (key === undefined) return 2\n const corpus = await loadCorpus(asString(values.corpus) ?? \"default\")\n if (!corpus) {\n console.error(\"verify needs a corpus (--corpus default or a file)\")\n return 2\n }\n const sample = asString(values.sample)\n const report = await verifyAgainstReal({\n realKey: key,\n realUrl: asString(values[\"real-url\"]) ?? DEFAULT_JUNCTION_BASE_URL,\n mock: createRuntime({ corpus }),\n corpus,\n ...(sample !== undefined ? { sample: Number.parseInt(sample, 10) } : {}),\n skipDrift: values[\"skip-drift\"] === true,\n orders: values.orders === true,\n ...(values.json === true\n ? {}\n : { onProgress: (message: string) => console.log(`verify: ${message}`) }),\n })\n if (values.json === true) {\n console.log(JSON.stringify(report, null, 2))\n } else {\n for (const d of report.divergences) {\n console.log(`DIVERGED ${d.check} (${d.kind}${d.at ? ` at ${d.at}` : \"\"})`)\n console.log(` real: ${JSON.stringify(d.real)?.slice(0, 400)}`)\n console.log(` mock: ${JSON.stringify(d.mock)?.slice(0, 400)}`)\n }\n console.log(\n `verify: drift ${report.drift.stale}/${report.drift.checked} stale; scenario ${report.scenario.divergent}/${report.scenario.steps} divergent \u2014 ${report.ok ? \"OK\" : \"DIVERGED\"}`,\n )\n }\n return report.ok ? 0 : 1\n },\n },\n },\n },\n process.argv.slice(2),\n)\nif (code !== 0) process.exitCode = code\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["#!/usr/bin/env node\n/// <reference types=\"node\" />\nimport { readFile, writeFile } from \"node:fs/promises\"\nimport { type CliValues, runCli, serveCommand } from \"@crvouga/mockingbird-adapter-node\"\nimport { defaultCorpus } from \"./corpus.js\"\nimport { DEFAULT_JUNCTION_BASE_URL, diffCorpus, isSandboxKey, pullCorpus } from \"./corpus-tools.js\"\nimport { createRuntime } from \"./runtime.js\"\nimport { parseSealedCorpus, type SealedCorpus } from \"./sealed-corpus.js\"\nimport { loadCorpus, serveTarget } from \"./server.js\"\nimport { verifyAgainstReal } from \"./verify.js\"\n\nconst asString = (value: string | boolean | undefined): string | undefined =>\n typeof value === \"string\" && value !== \"\" ? value : undefined\n\nconst realKeyOf = (values: CliValues): string | undefined =>\n asString(values[\"real-key\"]) ??\n asString(process.env.JUNCTION_API_KEY) ??\n asString(process.env.JUNCTION_API_KEY)\n\nconst readCorpus = async (source: string): Promise<SealedCorpus> =>\n source === \"default\"\n ? defaultCorpus\n : parseSealedCorpus(JSON.parse(await readFile(source, \"utf8\")))\n\nconst guardKey = (key: string | undefined, values: CliValues): string | undefined => {\n if (key === undefined) {\n console.error(\"missing --real-key (or env JUNCTION_API_KEY)\")\n return undefined\n }\n if (!isSandboxKey(key) && values[\"allow-any-key\"] !== true) {\n console.error(\n \"refusing a key that is not a sandbox team key (sk_us_\u2026 / sk_eu_\u2026); pass --allow-any-key to override\",\n )\n return undefined\n }\n return key\n}\n\nconst REAL_OPTIONS = {\n \"real-key\": {\n type: \"string\",\n value: \"<key>\",\n description: \"Junction API key (env JUNCTION_API_KEY)\",\n },\n \"real-url\": {\n type: \"string\",\n value: \"<url>\",\n description: \"Junction API base URL\",\n default: DEFAULT_JUNCTION_BASE_URL,\n },\n \"allow-any-key\": {\n type: \"boolean\",\n description: \"Allow a key that is not a sandbox team key\",\n },\n} as const\n\nconst code = await runCli(\n {\n bin: \"mockingbird-junction\",\n description: \"stateful Junction (Vital) API mock\",\n commands: {\n serve: serveCommand(serveTarget),\n\n \"corpus pull\": {\n summary: \"Record a corpus from a real Junction team (read-only GETs)\",\n usage: \"mockingbird-junction corpus pull --out <file> [--zip 10001,94105] [--base <file>]\",\n options: {\n ...REAL_OPTIONS,\n out: { type: \"string\", value: \"<file>\", description: \"Where to write the corpus\" },\n zip: {\n type: \"string\",\n value: \"<zip,zip,\u2026>\",\n description: \"ZIPs to record coverage for (default: the shipped set)\",\n },\n base: {\n type: \"string\",\n value: \"<file|default>\",\n description: \"Only refresh --zip on top of this corpus\",\n },\n \"coverage-only\": {\n type: \"boolean\",\n description: \"Skip the team's catalog and lab accounts\",\n },\n },\n async run(values) {\n const key = guardKey(realKeyOf(values), values)\n if (key === undefined) return 2\n const out = asString(values.out)\n if (out === undefined) {\n console.error(\"missing --out <file>\")\n return 2\n }\n const zips = asString(values.zip)\n ?.split(\",\")\n .map((zip) => zip.trim())\n .filter(Boolean)\n if (zips?.some((zip) => !/^\\d{5}$/.test(zip))) {\n console.error(\"--zip takes comma-separated 5-digit ZIPs\")\n return 2\n }\n const baseSource = asString(values.base)\n const corpus = await pullCorpus({\n apiKey: key,\n baseUrl: asString(values[\"real-url\"]) ?? DEFAULT_JUNCTION_BASE_URL,\n ...(zips ? { zips } : {}),\n ...(baseSource ? { base: await readCorpus(baseSource) } : {}),\n coverageOnly: values[\"coverage-only\"] === true,\n onProgress: (message) => console.log(`corpus pull: ${message}`),\n })\n await writeFile(out, `${JSON.stringify(corpus, null, 2)}\\n`)\n console.log(\n `corpus pull: wrote ${out} \u2014 ${Object.keys(corpus.observations).length} observations, ${corpus.catalog.labTests.length} lab tests, ${corpus.labAccounts.length} lab accounts, fingerprint ${corpus.fingerprint?.slice(0, 12)}`,\n )\n return 0\n },\n },\n\n \"corpus diff\": {\n summary: \"Show what changed between two corpora (exit 1 when they differ)\",\n usage: \"mockingbird-junction corpus diff <before|default> <after|default> [--json]\",\n options: { json: { type: \"boolean\", description: \"Print the diff as JSON\" } },\n async run(values, positionals) {\n const [beforePath, afterPath] = positionals\n if (beforePath === undefined || afterPath === undefined) {\n console.error(\"usage: mockingbird-junction corpus diff <before> <after>\")\n return 2\n }\n const diff = diffCorpus(await readCorpus(beforePath), await readCorpus(afterPath))\n if (values.json === true) {\n console.log(JSON.stringify(diff, null, 2))\n return diff.identical ? 0 : 1\n }\n if (diff.identical) {\n console.log(\"corpora are identical\")\n return 0\n }\n const section = (\n title: string,\n d: { added: string[]; removed: string[]; changed?: string[] },\n ) => {\n const total = d.added.length + d.removed.length + (d.changed?.length ?? 0)\n if (total === 0) return\n console.log(`${title}:`)\n for (const id of d.added) console.log(` + ${id}`)\n for (const id of d.removed) console.log(` - ${id}`)\n for (const id of d.changed ?? []) console.log(` ~ ${id}`)\n }\n section(\"ZIPs\", diff.zips)\n section(\"lab tests\", diff.labTests)\n section(\"lab accounts\", diff.labAccounts)\n const o = diff.observations\n console.log(\n `observations: +${o.added.length} -${o.removed.length} ~${o.changed.length} (--json lists them)`,\n )\n return 1\n },\n },\n\n verify: {\n summary: \"Replay the corpus and a stateful scenario against real Junction and the mock\",\n usage: \"mockingbird-junction verify --real-key <sk_us_\u2026> [--corpus <file>] [--orders]\",\n options: {\n ...REAL_OPTIONS,\n corpus: {\n type: \"string\",\n value: \"<default|file>\",\n description: \"Corpus the mock serves and drift is checked against\",\n default: \"default\",\n },\n sample: {\n type: \"string\",\n value: \"<n>\",\n description: \"Re-fetch at most n recorded observations (default: all)\",\n },\n \"skip-drift\": { type: \"boolean\", description: \"Only run the stateful scenario\" },\n orders: {\n type: \"boolean\",\n description: \"Also place and cancel an order on the real team\",\n },\n json: { type: \"boolean\", description: \"Print the report as JSON\" },\n },\n async run(values) {\n const key = guardKey(realKeyOf(values), values)\n if (key === undefined) return 2\n const corpus = await loadCorpus(asString(values.corpus) ?? \"default\")\n if (!corpus) {\n console.error(\"verify needs a corpus (--corpus default or a file)\")\n return 2\n }\n const sample = asString(values.sample)\n const report = await verifyAgainstReal({\n realKey: key,\n realUrl: asString(values[\"real-url\"]) ?? DEFAULT_JUNCTION_BASE_URL,\n mock: createRuntime({ corpus }),\n corpus,\n ...(sample !== undefined ? { sample: Number.parseInt(sample, 10) } : {}),\n skipDrift: values[\"skip-drift\"] === true,\n orders: values.orders === true,\n ...(values.json === true\n ? {}\n : { onProgress: (message: string) => console.log(`verify: ${message}`) }),\n })\n if (values.json === true) {\n console.log(JSON.stringify(report, null, 2))\n } else {\n for (const d of report.divergences) {\n console.log(`DIVERGED ${d.check} (${d.kind}${d.at ? ` at ${d.at}` : \"\"})`)\n console.log(` real: ${JSON.stringify(d.real)?.slice(0, 400)}`)\n console.log(` mock: ${JSON.stringify(d.mock)?.slice(0, 400)}`)\n }\n console.log(\n `verify: drift ${report.drift.stale}/${report.drift.checked} stale; scenario ${report.scenario.divergent}/${report.scenario.steps} divergent \u2014 ${report.ok ? \"OK\" : \"DIVERGED\"}`,\n )\n }\n return report.ok ? 0 : 1\n },\n },\n },\n },\n process.argv.slice(2),\n)\nif (code !== 0) process.exitCode = code\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/index.d.ts CHANGED
@@ -169,82 +169,6 @@ type FaultRegistry = {
169
169
  take(candidate: FaultCandidate): Promise<FaultHit[]>;
170
170
  };
171
171
 
172
- /** One handled request, as the structured log sees it. */
173
- type RequestLog = {
174
- service: string;
175
- namespace: string;
176
- operationId: string | undefined;
177
- method: string;
178
- path: string;
179
- status: number;
180
- durationMs: number;
181
- /** True when the path matched no operation in the contract. */
182
- unmatched: boolean;
183
- /** Set when a fault rule produced the response. */
184
- faultId?: string;
185
- /** Resource ids the handler touched (`userId`, `orderId`, …), when the service reports them. */
186
- ids?: Record<string, string>;
187
- /** Set when the service created a resource the request referred to but that did not exist. */
188
- adopted?: boolean;
189
- };
190
- type MetricsReport = {
191
- requests: number;
192
- /** Counts keyed `<operationId> <status>`. */
193
- byOperation: Record<string, number>;
194
- /**
195
- * Paths that matched no operation, most frequent first.
196
- *
197
- * This is the early-warning signal: a consumer calling something the mock does
198
- * not implement shows up here as a count, before it fails a suite as a 404.
199
- */
200
- unmatched: {
201
- method: string;
202
- path: string;
203
- count: number;
204
- }[];
205
- faults: number;
206
- totalDurationMs: number;
207
- };
208
- type Metrics = {
209
- record(entry: RequestLog): void;
210
- report(): MetricsReport;
211
- reset(): void;
212
- };
213
-
214
- /** One journal entry: a request log stamped with when (on the mock clock) it was handled. */
215
- type JournalEntry = RequestLog & {
216
- at: string;
217
- };
218
- type JournalQuery = {
219
- /** Only this namespace. Omit for every namespace, oldest first across all of them. */
220
- namespace?: string;
221
- operationId?: string;
222
- status?: number;
223
- /** Only entries at or after this instant (epoch ms). */
224
- since?: number;
225
- /** At most this many, the most recent kept. */
226
- limit?: number;
227
- };
228
- type Journal = {
229
- readonly size: number;
230
- record(entry: JournalEntry): void;
231
- list(query?: JournalQuery): JournalEntry[];
232
- /** Forget one namespace's entries, or every namespace's. */
233
- clear(namespace?: string): void;
234
- };
235
-
236
- /** Credential → namespace mapping behind `PUT /__admin/credentials`. */
237
- type CredentialRegistry = {
238
- set(credential: string, namespace: string): void;
239
- get(credential: string): string | undefined;
240
- remove(credential: string): boolean;
241
- clear(): void;
242
- entries(): {
243
- credential: string;
244
- namespace: string;
245
- }[];
246
- };
247
-
248
172
  /** A stable identifier for a point in a {@link Timeline}. */
249
173
  type CheckpointId = string;
250
174
  /** An immutable node in a timeline's checkpoint DAG. */
@@ -335,6 +259,123 @@ interface FetchAPI$1 {
335
259
  fetch(request: Request): Promise<Response>;
336
260
  }
337
261
 
262
+ /** Options every provider constructor accepts. */
263
+ type APIOptions = {
264
+ /** Sync SQLite client. Defaults to `@crvouga/mockingbird-service-sqlite`. */
265
+ sqlite?: SqliteClient$1;
266
+ /** Clock used for `created`-style fields. Default `Date.now`. */
267
+ now?: () => number;
268
+ /**
269
+ * Storage namespace for this instance's records. Instances sharing one SQLite
270
+ * client stay isolated when their namespaces differ. Defaults to the service name.
271
+ */
272
+ namespace?: string;
273
+ };
274
+
275
+ /**
276
+ * One problem with a request body, at a dotted path (`patient.address.city`, `items.0.sku`).
277
+ * `kind` is `media_type` when the body is present but its media type is not one the operation
278
+ * accepts (a `415` on most vendors), `syntax` when it could not be decoded, `required` when
279
+ * the operation needs a body and none arrived, and `schema` for a contract violation.
280
+ */
281
+ type BodyIssue = {
282
+ path: string;
283
+ message: string;
284
+ kind?: "media_type" | "syntax" | "required" | "schema";
285
+ };
286
+
287
+ /**
288
+ * What the mock received from a request it rejected, so a 4xx can be attributed after the
289
+ * fact (an empty body, a missing `content-type`, a schema violation) without logging the body.
290
+ */
291
+ type RejectedRequest = {
292
+ /** The raw `content-type` header, or `null` when the request sent none. */
293
+ contentType: string | null;
294
+ /** Bytes of body received from `content-length` (`0` for none), or `null` when the client sent no length (a streamed / chunked body). */
295
+ bodyBytes: number | null;
296
+ /** The raw `transfer-encoding` header (`chunked`), or `null`. */
297
+ transferEncoding: string | null;
298
+ };
299
+ /** One handled request, as the structured log sees it. */
300
+ type RequestLog = {
301
+ service: string;
302
+ namespace: string;
303
+ operationId: string | undefined;
304
+ method: string;
305
+ path: string;
306
+ status: number;
307
+ durationMs: number;
308
+ /** True when the path matched no operation in the contract. */
309
+ unmatched: boolean;
310
+ /** Set when a fault rule produced the response. */
311
+ faultId?: string;
312
+ /** Resource ids the handler touched (`userId`, `orderId`, …), when the service reports them. */
313
+ ids?: Record<string, string>;
314
+ /** Set when the service created a resource the request referred to but that did not exist. */
315
+ adopted?: boolean;
316
+ /** Set on a rejection (status ≥ 400) the mock produced itself, never on a scripted fault. */
317
+ request?: RejectedRequest;
318
+ /** The validation issues behind a rejection, when the service found any. */
319
+ issues?: BodyIssue[];
320
+ };
321
+ type MetricsReport = {
322
+ requests: number;
323
+ /** Counts keyed `<operationId> <status>`. */
324
+ byOperation: Record<string, number>;
325
+ /**
326
+ * Paths that matched no operation, most frequent first.
327
+ *
328
+ * This is the early-warning signal: a consumer calling something the mock does
329
+ * not implement shows up here as a count, before it fails a suite as a 404.
330
+ */
331
+ unmatched: {
332
+ method: string;
333
+ path: string;
334
+ count: number;
335
+ }[];
336
+ faults: number;
337
+ totalDurationMs: number;
338
+ };
339
+ type Metrics = {
340
+ record(entry: RequestLog): void;
341
+ report(): MetricsReport;
342
+ reset(): void;
343
+ };
344
+
345
+ /** One journal entry: a request log stamped with when (on the mock clock) it was handled. */
346
+ type JournalEntry = RequestLog & {
347
+ at: string;
348
+ };
349
+ type JournalQuery = {
350
+ /** Only this namespace. Omit for every namespace, oldest first across all of them. */
351
+ namespace?: string;
352
+ operationId?: string;
353
+ status?: number;
354
+ /** Only entries at or after this instant (epoch ms). */
355
+ since?: number;
356
+ /** At most this many, the most recent kept. */
357
+ limit?: number;
358
+ };
359
+ type Journal = {
360
+ readonly size: number;
361
+ record(entry: JournalEntry): void;
362
+ list(query?: JournalQuery): JournalEntry[];
363
+ /** Forget one namespace's entries, or every namespace's. */
364
+ clear(namespace?: string): void;
365
+ };
366
+
367
+ /** Credential → namespace mapping behind `PUT /__admin/credentials`. */
368
+ type CredentialRegistry = {
369
+ set(credential: string, namespace: string): void;
370
+ get(credential: string): string | undefined;
371
+ remove(credential: string): boolean;
372
+ clear(): void;
373
+ entries(): {
374
+ credential: string;
375
+ namespace: string;
376
+ }[];
377
+ };
378
+
338
379
  /**
339
380
  * A point-in-time copy of everything a service namespace holds.
340
381
  *
@@ -485,19 +526,6 @@ type ServiceRuntime<T extends ServiceInstance> = FetchAPI$1 & {
485
526
  timeline(namespace?: string): Timeline<ServiceTimelineState>;
486
527
  };
487
528
 
488
- /** Options every provider constructor accepts. */
489
- type APIOptions = {
490
- /** Sync SQLite client. Defaults to `@crvouga/mockingbird-service-sqlite`. */
491
- sqlite?: SqliteClient$1;
492
- /** Clock used for `created`-style fields. Default `Date.now`. */
493
- now?: () => number;
494
- /**
495
- * Storage namespace for this instance's records. Instances sharing one SQLite
496
- * client stay isolated when their namespaces differ. Defaults to the service name.
497
- */
498
- namespace?: string;
499
- };
500
-
501
529
  /** Bind values accepted by Mockingbird's SQLite port (matches sqlite-mem / better-sqlite3). */
502
530
  type SqliteValue = null | number | bigint | string | Uint8Array | boolean;
503
531
  /** Mutation counters returned by {@link SqliteStatement.run}. */
@@ -848,9 +876,9 @@ declare const OTHER_TEAM_ID: string;
848
876
  declare const TEAM_LAB_ACCOUNTS: readonly LabAccountRecord[];
849
877
  /**
850
878
  * Whether `teamId` may use an account. An empty `team_id_allowlist` counts as linked:
851
- * a real team's listing returns such accounts (observed — Junction's own Quest, Labcorp
852
- * and BioReference accounts carry `[]`), so the mock treats them as the team's. Whether
853
- * ordering through one by explicit id behaves the same is unverified; SUPPORT.md tracks it.
879
+ * a real team's listing returns such accounts (Junction's own Quest, Labcorp and
880
+ * BioReference accounts carry `[]`), and the sandbox places an order that names one by id
881
+ * (corpus/lab-account-probes.json), so the mock treats them as the team's.
854
882
  */
855
883
  declare const isLinkedToTeam: (entry: LabAccountRecord, teamId: string) => boolean;
856
884
  /**
package/dist/index.js CHANGED
@@ -53,7 +53,7 @@ import {
53
53
  supportedOperationIds,
54
54
  verifyAgainstReal,
55
55
  verifySvix
56
- } from "./chunk-KEP42KKR.js";
56
+ } from "./chunk-GWXEPT6A.js";
57
57
  import {
58
58
  SEALED_CORPUS_VERSION,
59
59
  SUPPORTED_SEALED_CORPUS_VERSIONS,