@crvouga/mockingbird-service-junction 1.0.0 → 1.0.2

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-TG2EOH3J.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-MS774AJA.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}. */
@@ -741,8 +769,6 @@ type OrderRecord = {
741
769
  notes: null;
742
770
  clinical_notes: string | null;
743
771
  passthrough: string | null;
744
- /** Echoed from the request's opaque provider account id; absent when the order had none. */
745
- lab_account_id?: string;
746
772
  created_at: string;
747
773
  updated_at: string;
748
774
  events: OrderEventRecord[];
@@ -842,14 +868,17 @@ type LabAccountRecord = {
842
868
  };
843
869
  /** The 50 `USState` values from the docs, alphabetical by state name. */
844
870
  declare const US_STATES: readonly string[];
871
+ /** `billing_type` values the vendored create-order body accepts. */
872
+ declare const BILLING_TYPES: readonly ["client_bill", "commercial_insurance", "patient_bill_passthrough", "patient_bill", "upfront_payment"];
873
+ type BillingType = (typeof BILLING_TYPES)[number];
845
874
  /** A team the mock is not, for fixtures that model another team's account. */
846
875
  declare const OTHER_TEAM_ID: string;
847
876
  declare const TEAM_LAB_ACCOUNTS: readonly LabAccountRecord[];
848
877
  /**
849
878
  * Whether `teamId` may use an account. An empty `team_id_allowlist` counts as linked:
850
- * a real team's listing returns such accounts (observed — Junction's own Quest, Labcorp
851
- * and BioReference accounts carry `[]`), so the mock treats them as the team's. Whether
852
- * 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.
853
882
  */
854
883
  declare const isLinkedToTeam: (entry: LabAccountRecord, teamId: string) => boolean;
855
884
  /**
@@ -887,7 +916,11 @@ declare const labAccountFromInput: (input: LabAccountInput, teamId?: string) =>
887
916
  *
888
917
  * Where a preset copies a recorded account, its states and billing types are the
889
918
  * recording's. `bioreference_ny_nj_delegated` is not a recorded account: it is the
890
- * NY/NJ client-bill shape a consumer asked for, kept because suites depend on it.
919
+ * NY/NJ client-bill shape a consumer asked for, kept because suites depend on it, and
920
+ * listed in `SYNTHETIC_LAB_ACCOUNT_PRESETS`.
921
+ *
922
+ * `*_platform` presets are Junction's own accounts (`org_id: null`, allowlist `[]`); every
923
+ * other active preset is customer-owned (it belongs to an Org and is linked to the team).
891
924
  */
892
925
 
893
926
  /** Junction's own Quest, Labcorp and BioReference accounts client-bill in these 47 states. */
@@ -900,6 +933,13 @@ type Preset = Omit<LabAccountInput, "id">;
900
933
  * configured team; the platform presets carry `[]`, as Junction's own accounts do.
901
934
  */
902
935
  declare const LAB_ACCOUNT_PRESETS: Readonly<Record<string, Preset>>;
936
+ /**
937
+ * Deprecated preset names and the preset each stands for. An alias keeps its own default id,
938
+ * so a suite that pinned `presetAccountId(alias)` still finds its account.
939
+ */
940
+ declare const LAB_ACCOUNT_PRESET_ALIASES: Readonly<Record<string, string>>;
941
+ /** Presets that model a shape consumers asked for, not an account recorded from a real team. */
942
+ declare const SYNTHETIC_LAB_ACCOUNT_PRESETS: readonly string[];
903
943
  /** A preset's id when none is given: stable across runs and namespaces. */
904
944
  declare const presetAccountId: (name: string) => string;
905
945
  /**
@@ -1431,6 +1471,11 @@ type JunctionRuntimeOptions = {
1431
1471
  limits?: JunctionLimitsInput;
1432
1472
  /** `adopt-users` creates unknown user ids on first use. Default `strict`. */
1433
1473
  identity?: IdentityMode;
1474
+ /**
1475
+ * Lab slug → `billing_type` for orders that omit it; see
1476
+ * {@link JunctionAPIOptions.defaultBillingTypes}. Per namespace via `PUT /__admin/default-billing-types`.
1477
+ */
1478
+ defaultBillingTypes?: Partial<Record<string, BillingType>>;
1434
1479
  /** Users and orders every namespace starts with, re-applied on each reset. */
1435
1480
  fixtures?: JunctionFixtures;
1436
1481
  /** Requests each namespace's journal keeps (`GET /__admin/requests`). Default 1000. */
@@ -1483,7 +1528,9 @@ declare const UNKNOWN_ZIP_ERROR_TYPE = "MOCKINGBIRD_UNKNOWN_ZIP";
1483
1528
  *
1484
1529
  * The scenario also replays every order-scoped operation against a random unknown order
1485
1530
  * (each 404 body compared exactly) and, with `orders` and a corpus holding an account whose
1486
- * `team_id_allowlist` is empty, orders through that account with and without its id.
1531
+ * `team_id_allowlist` is empty, orders through that account with and without its id. With
1532
+ * `orders`, it also orders through every other active account by id with `billing_type`
1533
+ * omitted, which shows the billing type the team defaults to.
1487
1534
  *
1488
1535
  * Only a sandbox team should be used: the scenario creates, and always deletes, one
1489
1536
  * user (and, with `orders`, places and cancels its orders).
@@ -1560,6 +1607,12 @@ type JunctionAPIOptions = APIOptions & {
1560
1607
  limits?: JunctionLimitsInput;
1561
1608
  /** `adopt-users` creates an unknown (well-formed) `user_id` on first use. Default `strict`. */
1562
1609
  identity?: IdentityMode;
1610
+ /**
1611
+ * Lab slug → the `billing_type` `create_order` uses when the request omits it, e.g.
1612
+ * `{ bioreference: "patient_bill_passthrough" }`. Unlisted labs use Junction's documented
1613
+ * default, `client_bill`.
1614
+ */
1615
+ defaultBillingTypes?: Partial<Record<string, BillingType>>;
1563
1616
  /** Users and orders to load into every namespace, re-applied on every `reset()`. */
1564
1617
  fixtures?: JunctionFixtures;
1565
1618
  };
@@ -1623,6 +1676,10 @@ declare class JunctionAPI implements FetchAPI {
1623
1676
  get limits(): JunctionLimits;
1624
1677
  /** Change some limits (`null` lifts a numeric one); kept across `reset()`. */
1625
1678
  configureLimits(input: JunctionLimitsInput): JunctionLimits;
1679
+ /** Lab slug → the `billing_type` an order gets when it omits one. Kept across `reset()`. */
1680
+ get defaultBillingTypes(): Record<string, BillingType>;
1681
+ /** Replace the per-lab default billing types (`{}` restores `client_bill` everywhere). */
1682
+ set defaultBillingTypes(input: Partial<Record<string, BillingType>>);
1626
1683
  get identity(): IdentityMode;
1627
1684
  set identity(mode: IdentityMode);
1628
1685
  /** Insert users with chosen ids, all or none. Throws `FixtureError` (400/404/409). */
@@ -1641,6 +1698,12 @@ declare class JunctionAPI implements FetchAPI {
1641
1698
  set geoMode(mode: GeoMode);
1642
1699
  order(id: string): OrderRecord | undefined;
1643
1700
  orders(): OrderRecord[];
1701
+ /**
1702
+ * The lab account an order was placed with: its id, `null` for Junction's platform account,
1703
+ * or `undefined` for an unknown order. Junction's order has no such field, so this is the
1704
+ * only place the mock reports it.
1705
+ */
1706
+ orderLabAccount(id: string): string | null | undefined;
1644
1707
  /**
1645
1708
  * Put an order straight into a status (a full `<status>.<method>.<event>` value) and
1646
1709
  * publish the webhook a real transition would. `flags` takes the same keys as
@@ -1666,5 +1729,5 @@ declare class LabAccountConflict extends Error {
1666
1729
  constructor(id: string);
1667
1730
  }
1668
1731
 
1669
- export { AVAILABILITY_ADDRESS, AVAILABILITY_START_DATE, COVERAGE_ZIPS, DEFAULT_JUNCTION_BASE_URL, DEFAULT_LIMITS, DELEGATED_ACCOUNT_STATES, FAULT_PRESETS, FixtureError, IDENTITY_MODES, JUNCTION_NAMESPACE, JunctionAPI, KNOWN_HEADER, LAB_ACCOUNT_PRESETS, LabAccountConflict, MISS_HEADER, MOCK_TEAM_ID, ORDER_NOT_FOUND, ORDER_STATUSES_BY_METHOD, OTHER_TEAM_ID, PHLEBOTOMY_AVAILABILITY_ZIPS, PLATFORM_ACCOUNT_STATES, PSC_AVAILABILITY_ZIPS, PSC_LAB_IDS, RESULT_FIXTURES, SANDBOX_KEY_PREFIXES, SEALED_CORPUS_VERSION, SUPPORTED_SEALED_CORPUS_VERSIONS, TEAM_LAB_ACCOUNTS, TRANSITION_ALIASES, UNKNOWN_ZIP_ERROR_TYPE, UNKNOWN_ZIP_STATUS, US_STATES, corpusLabel, createRuntime, createWebhookDispatcher, diffCorpus, document, fingerprintCorpus, isLinkedToTeam, isSandboxKey, labAccountFromInput, observationCacheKey, operationIds, parseSealedCorpus, prefetchCoverageObservations, presetAccountId, pullCorpus, reshapeCoverageGeoCommand, resolveTransition, sandboxUserQuotaBody, signSvix, supportedOperationIds, verifyAgainstReal, verifySvix };
1670
- export type { CorpusDiff, CorpusInfo, Divergence, FetchAPI, GeoMode, GetCacheEntry, IdentityMode, InsertOptions, JunctionAPIOptions, JunctionAdminOptions, JunctionFixtures, JunctionLimits, JunctionLimitsInput, JunctionRuntime, JunctionRuntimeOptions, JunctionWebhookEvent, JunctionWebhookOptions, LabAccountDelegatedFlow, LabAccountInput, LabAccountLayout, LabAccountRecord, LabAccountStatus, OperationId, OrderFixture, OrderRecord, PullCorpusOptions, ResultFixture, SealedCorpus, SeedObservations, SeedReport, SeedSource, SetDiff, SqliteClient, StoredLabAccount, SupportedOperationId, UserFixture, UserRecord, VerifyOptions, VerifyReport, WebhookAttempt, WebhookDelivery, WebhookDispatcher, WebhookEndpoint, WebhookPublisher };
1732
+ export { AVAILABILITY_ADDRESS, AVAILABILITY_START_DATE, BILLING_TYPES, COVERAGE_ZIPS, DEFAULT_JUNCTION_BASE_URL, DEFAULT_LIMITS, DELEGATED_ACCOUNT_STATES, FAULT_PRESETS, FixtureError, IDENTITY_MODES, JUNCTION_NAMESPACE, JunctionAPI, KNOWN_HEADER, LAB_ACCOUNT_PRESETS, LAB_ACCOUNT_PRESET_ALIASES, LabAccountConflict, MISS_HEADER, MOCK_TEAM_ID, ORDER_NOT_FOUND, ORDER_STATUSES_BY_METHOD, OTHER_TEAM_ID, PHLEBOTOMY_AVAILABILITY_ZIPS, PLATFORM_ACCOUNT_STATES, PSC_AVAILABILITY_ZIPS, PSC_LAB_IDS, RESULT_FIXTURES, SANDBOX_KEY_PREFIXES, SEALED_CORPUS_VERSION, SUPPORTED_SEALED_CORPUS_VERSIONS, SYNTHETIC_LAB_ACCOUNT_PRESETS, TEAM_LAB_ACCOUNTS, TRANSITION_ALIASES, UNKNOWN_ZIP_ERROR_TYPE, UNKNOWN_ZIP_STATUS, US_STATES, corpusLabel, createRuntime, createWebhookDispatcher, diffCorpus, document, fingerprintCorpus, isLinkedToTeam, isSandboxKey, labAccountFromInput, observationCacheKey, operationIds, parseSealedCorpus, prefetchCoverageObservations, presetAccountId, pullCorpus, reshapeCoverageGeoCommand, resolveTransition, sandboxUserQuotaBody, signSvix, supportedOperationIds, verifyAgainstReal, verifySvix };
1733
+ export type { BillingType, CorpusDiff, CorpusInfo, Divergence, FetchAPI, GeoMode, GetCacheEntry, IdentityMode, InsertOptions, JunctionAPIOptions, JunctionAdminOptions, JunctionFixtures, JunctionLimits, JunctionLimitsInput, JunctionRuntime, JunctionRuntimeOptions, JunctionWebhookEvent, JunctionWebhookOptions, LabAccountDelegatedFlow, LabAccountInput, LabAccountLayout, LabAccountRecord, LabAccountStatus, OperationId, OrderFixture, OrderRecord, PullCorpusOptions, ResultFixture, SealedCorpus, SeedObservations, SeedReport, SeedSource, SetDiff, SqliteClient, StoredLabAccount, SupportedOperationId, UserFixture, UserRecord, VerifyOptions, VerifyReport, WebhookAttempt, WebhookDelivery, WebhookDispatcher, WebhookEndpoint, WebhookPublisher };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AVAILABILITY_ADDRESS,
3
3
  AVAILABILITY_START_DATE,
4
+ BILLING_TYPES,
4
5
  COVERAGE_ZIPS,
5
6
  DEFAULT_JUNCTION_BASE_URL,
6
7
  DEFAULT_LIMITS,
@@ -12,6 +13,7 @@ import {
12
13
  JunctionAPI,
13
14
  KNOWN_HEADER,
14
15
  LAB_ACCOUNT_PRESETS,
16
+ LAB_ACCOUNT_PRESET_ALIASES,
15
17
  LabAccountConflict,
16
18
  MISS_HEADER,
17
19
  MOCK_TEAM_ID,
@@ -24,6 +26,7 @@ import {
24
26
  PSC_LAB_IDS,
25
27
  RESULT_FIXTURES,
26
28
  SANDBOX_KEY_PREFIXES,
29
+ SYNTHETIC_LAB_ACCOUNT_PRESETS,
27
30
  TEAM_LAB_ACCOUNTS,
28
31
  TRANSITION_ALIASES,
29
32
  UNKNOWN_ZIP_ERROR_TYPE,
@@ -50,7 +53,7 @@ import {
50
53
  supportedOperationIds,
51
54
  verifyAgainstReal,
52
55
  verifySvix
53
- } from "./chunk-MS774AJA.js";
56
+ } from "./chunk-GWXEPT6A.js";
54
57
  import {
55
58
  SEALED_CORPUS_VERSION,
56
59
  SUPPORTED_SEALED_CORPUS_VERSIONS,
@@ -59,6 +62,7 @@ import {
59
62
  export {
60
63
  AVAILABILITY_ADDRESS,
61
64
  AVAILABILITY_START_DATE,
65
+ BILLING_TYPES,
62
66
  COVERAGE_ZIPS,
63
67
  DEFAULT_JUNCTION_BASE_URL,
64
68
  DEFAULT_LIMITS,
@@ -70,6 +74,7 @@ export {
70
74
  JunctionAPI,
71
75
  KNOWN_HEADER,
72
76
  LAB_ACCOUNT_PRESETS,
77
+ LAB_ACCOUNT_PRESET_ALIASES,
73
78
  LabAccountConflict,
74
79
  MISS_HEADER,
75
80
  MOCK_TEAM_ID,
@@ -84,6 +89,7 @@ export {
84
89
  SANDBOX_KEY_PREFIXES,
85
90
  SEALED_CORPUS_VERSION,
86
91
  SUPPORTED_SEALED_CORPUS_VERSIONS,
92
+ SYNTHETIC_LAB_ACCOUNT_PRESETS,
87
93
  TEAM_LAB_ACCOUNTS,
88
94
  TRANSITION_ALIASES,
89
95
  UNKNOWN_ZIP_ERROR_TYPE,