@crvouga/mockingbird-service-junction 1.0.1 → 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/CHANGELOG.md +8 -0
- package/README.md +26 -23
- package/dist/{chunk-2YNUCD2S.js → chunk-A4KJJ5V3.js} +5 -3
- package/dist/{chunk-2YNUCD2S.js.map → chunk-A4KJJ5V3.js.map} +2 -2
- package/dist/{chunk-KEP42KKR.js → chunk-GWXEPT6A.js} +67 -31
- package/dist/chunk-GWXEPT6A.js.map +7 -0
- package/dist/cli.js +5 -5
- package/dist/cli.js.map +2 -2
- package/dist/index.d.ts +120 -92
- package/dist/index.js +1 -1
- package/dist/server.d.ts +221 -165
- package/dist/server.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-KEP42KKR.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-junction
|
|
2
2
|
|
|
3
|
+
## 1.0.2 (2026-09-26)
|
|
4
|
+
|
|
5
|
+
### Fixes and improvements
|
|
6
|
+
|
|
7
|
+
- take the rejected-request byte count from content-length, never by reading the body ([a0e5fb2](https://github.com/crvouga/mockingbird/commit/a0e5fb2fc80ab725c3552b612f3f091fd6c857fb))
|
|
8
|
+
- place orders through another lab's account and with several active accounts, as the sandbox does ([6f7d756](https://github.com/crvouga/mockingbird/commit/6f7d7569a1c78e32f259fbcd2f5f76dac106c7d6))
|
|
9
|
+
- reject a present body by media type, and log what a rejected request sent ([12e351a](https://github.com/crvouga/mockingbird/commit/12e351aba53cfb7b41e31a7809419455fc2902eb))
|
|
10
|
+
|
|
3
11
|
## 1.0.1 (2026-09-25)
|
|
4
12
|
|
|
5
13
|
### Fixes and improvements
|
package/README.md
CHANGED
|
@@ -336,11 +336,23 @@ invented.
|
|
|
336
336
|
|
|
337
337
|
## Lab accounts
|
|
338
338
|
|
|
339
|
-
`create_order` routes, accepts and rejects `lab_account_id` against the team's lab accounts,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
339
|
+
`create_order` routes, accepts and rejects `lab_account_id` against the team's lab accounts, the
|
|
340
|
+
way the sandbox does: an explicit id must exist, be linked to the team and be active; with no id,
|
|
341
|
+
one active account for the lab is selected, several select the first listed, and a lab with none
|
|
342
|
+
falls back to Junction's platform account. The billing type must be allowed in the patient's
|
|
343
|
+
state by the account's `allowed_billing`.
|
|
344
|
+
|
|
345
|
+
Two of Junction's documented rules are **not** enforced by the sandbox, so the mock does not
|
|
346
|
+
enforce them either. An id naming another lab's account is accepted and the order is placed with
|
|
347
|
+
that account (the guide says it must be "associated with the lab selected for the order"):
|
|
348
|
+
recorded in `corpus/lab-account-probes.json`, where a Labcorp test ordered through Junction's
|
|
349
|
+
BioReference account was placed, and reported live by a consumer's team
|
|
350
|
+
([#138](https://github.com/crvouga/mockingbird/issues/138)). An omitted id while several active
|
|
351
|
+
accounts are linked for the lab places the order (the guide says it "may be rejected"): reported
|
|
352
|
+
live by a consumer's team ([#136](https://github.com/crvouga/mockingbird/issues/136)); the
|
|
353
|
+
sandbox team the parity run uses has one account per lab, so `verify --orders` checks it whenever
|
|
354
|
+
a corpus has such a lab. Which of the several accounts the sandbox bills through is not visible
|
|
355
|
+
from outside; the mock uses the first listed.
|
|
344
356
|
|
|
345
357
|
Junction's order (`ClientFacingOrder`) has no `lab_account_id`, so neither does the mock's: not on
|
|
346
358
|
the create response, `GET /v3/order/{id}`, `GET /v3/orders` or webhooks. The account an order was
|
|
@@ -367,7 +379,7 @@ const junction = createRuntime({
|
|
|
367
379
|
console.log(junction.instance().labAccounts().length) // 3
|
|
368
380
|
```
|
|
369
381
|
|
|
370
|
-
Rejections keep Junction's body shape, `400 {"detail": "Lab account is not
|
|
382
|
+
Rejections keep Junction's body shape, `400 {"detail": "Lab account is not linked to your team"}`.
|
|
371
383
|
`delegated_flow` is stored and listed, but see [not modelled](#what-is-and-is-not-modelled).
|
|
372
384
|
|
|
373
385
|
Availability reads that take a `lab_account_id` (`GET /v3/order/area/info`) check it against the
|
|
@@ -451,21 +463,12 @@ linked to answers `400 {"detail": "Lab account is not linked to your team"}` exa
|
|
|
451
463
|
live. A version-1 corpus (no `teamId`) still loads and behaves as 0.2.0 did: every recorded account
|
|
452
464
|
is also linked to the mock team.
|
|
453
465
|
|
|
454
|
-
**
|
|
466
|
+
**Empty allowlists.** Junction's own Quest, Labcorp and BioReference accounts carry
|
|
455
467
|
`team_id_allowlist: []`, and a real team's listing returns them, so the mock lists them and treats
|
|
456
|
-
them as linked.
|
|
457
|
-
active
|
|
458
|
-
`verify --orders` checks it whenever the corpus has such
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
Two sandbox observations disagree with Junction's lab-accounts guide and are **not** modelled
|
|
462
|
-
until a probe settles which account the sandbox used: an order with `lab_account_id` omitted
|
|
463
|
-
while several active accounts are linked for the lab was placed (the guide says Junction "does
|
|
464
|
-
not select an account" and the order "may be rejected"; the mock rejects), and an order naming
|
|
465
|
-
another lab's platform account was placed (the guide says the account must "be associated with
|
|
466
|
-
the lab selected for the order"; the mock rejects with `Lab account is not associated with lab …`).
|
|
467
|
-
Tracked in [#136](https://github.com/crvouga/mockingbird/issues/136) and
|
|
468
|
-
[#138](https://github.com/crvouga/mockingbird/issues/138).
|
|
468
|
+
them as linked. Ordering through one by explicit id, and with the id omitted when it is the lab's
|
|
469
|
+
only active account, places the order on the sandbox (`corpus/lab-account-probes.json`, recorded
|
|
470
|
+
by the parity run), as the mock does. `verify --orders` re-checks it whenever the corpus has such
|
|
471
|
+
an account.
|
|
469
472
|
|
|
470
473
|
## Sandbox limits
|
|
471
474
|
|
|
@@ -708,8 +711,8 @@ A mock that is silent about its gaps is how a green suite starts lying. Specific
|
|
|
708
711
|
accounts is not reproduced. Run `verify --orders` with your team's corpus to check what you rely on.
|
|
709
712
|
- Rate limits and outages, except when injected as faults or switched on as
|
|
710
713
|
[limits](#sandbox-limits).
|
|
711
|
-
-
|
|
712
|
-
[
|
|
714
|
+
- Which of several active accounts for a lab the sandbox bills an order through when
|
|
715
|
+
`lab_account_id` is omitted (see [Lab accounts](#lab-accounts)); the mock uses the first listed.
|
|
713
716
|
- Adopted users (`adopt-users`) have `client_user_id` equal to their `user_id`; nothing in
|
|
714
717
|
Junction corresponds to adoption.
|
|
715
718
|
- Real 429 / 5xx bodies: the presets are plausible, not recorded.
|
|
@@ -874,7 +877,7 @@ bun test junction.sdk.property.test.ts # drives the served mock through @try
|
|
|
874
877
|
|
|
875
878
|
bun run mock:serve # env-configured `serve`: HOST, PORT, MOCKINGBIRD_JUNCTION_CORPUS,
|
|
876
879
|
# MOCKINGBIRD_JUNCTION_WEBHOOK_URL / _SECRET / _SCOPE
|
|
877
|
-
bun run corpus:record -- --force # re-record the shipped corpus/sandbox-sealed.json (
|
|
880
|
+
bun run corpus:record -- --force # re-record the shipped corpus/sandbox-sealed.json (JUNCTION_API_KEY)
|
|
878
881
|
bun run verify -- --real-key "$KEY" # `mockingbird-junction verify` from source
|
|
879
882
|
```
|
|
880
883
|
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
createRuntime,
|
|
4
4
|
defaultBillingTypesFrom,
|
|
5
5
|
pullCorpus
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GWXEPT6A.js";
|
|
7
7
|
import {
|
|
8
8
|
defaultCorpus
|
|
9
9
|
} from "./chunk-C2UGIPPM.js";
|
|
@@ -220,7 +220,9 @@ var formatLog = (format) => {
|
|
|
220
220
|
const ns = entry.namespace === "default" ? "" : ` [${entry.namespace}]`;
|
|
221
221
|
const fault = entry.faultId ? ` fault=${entry.faultId}` : "";
|
|
222
222
|
const adopted = entry.adopted ? " adopted" : "";
|
|
223
|
-
|
|
223
|
+
const received = entry.request ? ` received=${entry.request.bodyBytes === null ? "?" : `${entry.request.bodyBytes}B`} ct=${entry.request.contentType ?? "-"}${entry.request.transferEncoding ? ` te=${entry.request.transferEncoding}` : ""}` : "";
|
|
224
|
+
const issues = entry.issues?.length ? ` issues=${entry.issues.slice(0, 3).map((issue) => `${issue.path || "$"}: ${issue.message}`).join("; ")}${entry.issues.length > 3 ? ` (+${entry.issues.length - 3})` : ""}` : "";
|
|
225
|
+
console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}${received}${issues}`);
|
|
224
226
|
};
|
|
225
227
|
};
|
|
226
228
|
var asString = (value) => typeof value === "string" ? value : void 0;
|
|
@@ -533,4 +535,4 @@ export {
|
|
|
533
535
|
createServer2 as createServer,
|
|
534
536
|
serveTarget
|
|
535
537
|
};
|
|
536
|
-
//# sourceMappingURL=chunk-
|
|
538
|
+
//# sourceMappingURL=chunk-A4KJJ5V3.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/server.ts", "../../../adapters/node/src/cli.ts", "../../../adapters/node/src/serve.ts", "../../../adapters/node/src/listen.ts"],
|
|
4
|
-
"sourcesContent": ["/// <reference types=\"node\" />\nimport { readFile } from \"node:fs/promises\"\nimport {\n type CliValues,\n type Listening,\n listen,\n type ServeTarget,\n} from \"@crvouga/mockingbird-adapter-node\"\nimport { defaultCorpus } from \"./corpus.js\"\nimport { pullCorpus } from \"./corpus-tools.js\"\nimport { IDENTITY_MODES, type IdentityMode, type JunctionFixtures } from \"./fixtures.js\"\nimport type { LabAccountLayout } from \"./lab-account-presets.js\"\nimport { type BillingType, defaultBillingTypesFrom } from \"./lab-accounts.js\"\nimport { createRuntime, type JunctionRuntime, type JunctionRuntimeOptions } from \"./runtime.js\"\nimport { parseSealedCorpus, type SealedCorpus } from \"./sealed-corpus.js\"\nimport type { GeoMode } from \"./state.js\"\n\n/** Port `mockingbird-junction serve` listens on when none is given. */\nexport const DEFAULT_PORT = 8787\n\n/** A corpus to serve: the shipped one, none (synthetic), a file path, or a loaded corpus. */\nexport type CorpusSource = \"default\" | \"none\" | (string & {}) | SealedCorpus\n\nexport type JunctionServerOptions = Omit<JunctionRuntimeOptions, \"corpus\"> & {\n /** Default `0`: the OS picks a free port (read it from `url` / `port`). */\n port?: number\n /** Default `127.0.0.1`. */\n host?: string\n /** Default `\"default\"`: the corpus shipped with this package. */\n corpus?: CorpusSource\n}\n\nexport type JunctionServer = Listening & { runtime: JunctionRuntime }\n\n/** Resolve a {@link CorpusSource} to a corpus, or `undefined` for synthetic data. */\nexport const loadCorpus = async (source: CorpusSource): Promise<SealedCorpus | undefined> => {\n if (typeof source !== \"string\") return source\n if (source === \"none\") return undefined\n if (source === \"default\") return defaultCorpus\n const raw = await readFile(source, \"utf8\").catch(() => undefined)\n if (raw === undefined) throw new Error(`junction mock corpus not found: ${source}`)\n return parseSealedCorpus(JSON.parse(raw))\n}\n\n/**\n * Serve the Junction mock over `node:http`. Resolves once it is listening.\n *\n * const server = await createServer()\n * // point the Vital SDK at server.url; await server.close() when done\n */\nexport const createServer = async (\n options: JunctionServerOptions = {},\n): Promise<JunctionServer> => {\n const { port, host, corpus: source, ...rest } = options\n const corpus = await loadCorpus(source ?? \"default\")\n const runtime = createRuntime({ ...rest, ...(corpus ? { corpus } : {}) })\n const listening = await listen(runtime, {\n port: port ?? 0,\n ...(host !== undefined ? { host } : {}),\n })\n return { ...listening, runtime }\n}\n\nconst asString = (value: string | boolean | undefined): string | undefined =>\n typeof value === \"string\" && value !== \"\" ? value : undefined\n\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nconst count = (value: string | undefined, flag: string): number | undefined => {\n if (value === undefined) return undefined\n if (!/^\\d+$/.test(value)) throw new Error(`${flag} must be a non-negative integer (got ${value})`)\n return Number.parseInt(value, 10)\n}\n\nconst readJson = async <T>(path: string): Promise<T> => {\n const raw = await readFile(path, \"utf8\").catch(() => undefined)\n if (raw === undefined) throw new Error(`junction mock: file not found: ${path}`)\n try {\n return JSON.parse(raw) as T\n } catch (error) {\n throw new Error(`junction mock: ${path} is not JSON (${(error as Error).message})`)\n }\n}\n\nconst retryDelays = (value: string | undefined): number[] | undefined => {\n if (value === undefined) return undefined\n const delays = value.split(\",\").map((part) => Number.parseInt(part.trim(), 10))\n if (delays.some((delay) => !Number.isFinite(delay) || delay < 0)) {\n throw new Error(`--webhook-retry-delays must be comma-separated milliseconds (got ${value})`)\n }\n return delays\n}\n\n/** `--default-billing-type bioreference=patient_bill_passthrough,quest=client_bill`. */\nconst billingDefaults = (value: string | undefined): Record<string, BillingType> | undefined => {\n if (value === undefined) return undefined\n const entries = value.split(\",\").map((part) => part.split(\"=\").map((side) => side.trim()))\n if (entries.some((entry) => entry.length !== 2 || !entry[0] || !entry[1])) {\n throw new Error(`--default-billing-type must be lab=billing_type pairs (got ${value})`)\n }\n try {\n return defaultBillingTypesFrom(Object.fromEntries(entries))\n } catch (error) {\n throw new Error(`--default-billing-type: ${(error as Error).message}`)\n }\n}\n\n/** How `serve` (and `serve --config`) builds the Junction mock from flags. */\nexport const serveTarget: ServeTarget = {\n name: \"junction\",\n defaultPort: DEFAULT_PORT,\n options: {\n corpus: {\n type: \"string\",\n value: \"<default|none|file>\",\n description: \"Corpus to serve: the shipped one, none (synthetic), or a pulled file\",\n default: \"default\",\n },\n geo: {\n type: \"string\",\n value: \"<corpus|synthetic>\",\n description: \"Unknown ZIPs: refuse (corpus) or invent coverage (synthetic)\",\n },\n \"lab-accounts\": {\n type: \"string\",\n value: \"<file>\",\n description:\n 'Lab accounts to route orders through: a JSON array, or { \"presets\": [...], \"accounts\": [...] }',\n },\n \"team-id\": {\n type: \"string\",\n value: \"<uuid>\",\n description: \"Team the mock answers as (default: the corpus's recorded team)\",\n },\n \"max-users\": {\n type: \"string\",\n value: \"<n>\",\n description: \"Enforce the sandbox's live-user cap (default: unlimited)\",\n },\n identity: {\n type: \"string\",\n value: \"<strict|adopt-users>\",\n description: \"Unknown user ids: 404 (strict) or create on first use\",\n default: \"strict\",\n },\n \"default-billing-type\": {\n type: \"string\",\n value: \"<lab=type,\u2026>\",\n description:\n \"billing_type an order for that lab gets when it omits one (default: client_bill), e.g. bioreference=patient_bill_passthrough\",\n },\n fixtures: {\n type: \"string\",\n value: \"<file>\",\n description:\n 'Users and orders every namespace starts with: { \"users\": [...], \"orders\": [...] }',\n },\n \"journal-size\": {\n type: \"string\",\n value: \"<n>\",\n description: \"Requests each namespace's journal keeps (GET /__admin/requests)\",\n default: \"1000\",\n },\n \"seed-url\": {\n type: \"string\",\n value: \"<url>\",\n description: \"Pull a corpus from this Junction team at boot (prefer a committed corpus)\",\n },\n \"seed-key\": {\n type: \"string\",\n value: \"<key>\",\n description: \"API key for --seed-url (env MOCKINGBIRD_JUNCTION_SEED_KEY)\",\n },\n \"webhook-url\": { type: \"string\", value: \"<url>\", description: \"Deliver signed webhooks here\" },\n \"webhook-secret\": {\n type: \"string\",\n value: \"<whsec_\u2026>\",\n description: \"Svix signing secret (env MOCKINGBIRD_JUNCTION_WEBHOOK_SECRET)\",\n },\n \"webhook-retry-delays\": {\n type: \"string\",\n value: \"<ms,ms,\u2026>\",\n description: \"Delay before each delivery attempt (default: Svix's schedule)\",\n },\n \"webhook-scope\": {\n type: \"string\",\n value: \"<scope>\",\n description: \"Sent as x-mockingbird-scope on every delivery\",\n },\n },\n async create(values: CliValues, common) {\n const seedUrl = asString(values[\"seed-url\"])\n const seedKey = asString(values[\"seed-key\"]) ?? process.env.MOCKINGBIRD_JUNCTION_SEED_KEY\n let corpus: SealedCorpus | undefined\n if (seedUrl !== undefined) {\n if (seedKey === undefined) throw new Error(\"--seed-url needs --seed-key\")\n console.log(\n `junction pulling a corpus from ${seedUrl} (commit one with \\`corpus pull\\` to skip this)`,\n )\n corpus = await pullCorpus({ baseUrl: seedUrl, apiKey: seedKey })\n } else {\n corpus = await loadCorpus(asString(values.corpus) ?? \"default\")\n }\n const geo = asString(values.geo)\n if (geo !== undefined && geo !== \"corpus\" && geo !== \"synthetic\") {\n throw new Error(`--geo must be corpus or synthetic (got ${geo})`)\n }\n const accountsFile = asString(values[\"lab-accounts\"])\n const labAccounts =\n accountsFile === undefined ? undefined : await readJson<LabAccountLayout>(accountsFile)\n const fixturesFile = asString(values.fixtures)\n const fixtures =\n fixturesFile === undefined ? undefined : await readJson<JunctionFixtures>(fixturesFile)\n const teamId = asString(values[\"team-id\"])\n if (teamId !== undefined && !UUID.test(teamId)) {\n throw new Error(`--team-id must be a UUID (got ${teamId})`)\n }\n const maxUsers = count(asString(values[\"max-users\"]), \"--max-users\")\n const journalSize = count(asString(values[\"journal-size\"]), \"--journal-size\")\n const identity = asString(values.identity)\n if (identity !== undefined && !IDENTITY_MODES.includes(identity as IdentityMode)) {\n throw new Error(`--identity must be one of ${IDENTITY_MODES.join(\", \")} (got ${identity})`)\n }\n const defaultBillingTypes = billingDefaults(asString(values[\"default-billing-type\"]))\n const webhookUrl = asString(values[\"webhook-url\"])\n const webhookSecret =\n asString(values[\"webhook-secret\"]) ?? process.env.MOCKINGBIRD_JUNCTION_WEBHOOK_SECRET\n if (webhookUrl !== undefined && webhookSecret === undefined) {\n throw new Error(\"--webhook-url needs --webhook-secret (the whsec_\u2026 your receiver verifies)\")\n }\n const delays = retryDelays(asString(values[\"webhook-retry-delays\"]))\n const scope = asString(values[\"webhook-scope\"])\n return createRuntime({\n ...(corpus ? { corpus } : {}),\n ...(geo ? { geo: geo as GeoMode } : {}),\n ...(labAccounts ? { labAccounts } : {}),\n ...(fixtures ? { fixtures } : {}),\n ...(teamId !== undefined ? { teamId } : {}),\n ...(maxUsers !== undefined ? { limits: { maxUsers } } : {}),\n ...(identity !== undefined ? { identity: identity as IdentityMode } : {}),\n ...(defaultBillingTypes ? { defaultBillingTypes } : {}),\n ...(journalSize !== undefined ? { journalSize } : {}),\n ...(webhookUrl !== undefined && webhookSecret !== undefined\n ? {\n webhooks: {\n url: webhookUrl,\n secret: webhookSecret,\n ...(delays ? { retryDelaysMs: delays } : {}),\n ...(scope ? { headers: { \"x-mockingbird-scope\": scope } } : {}),\n },\n }\n : {}),\n ...(common.adminKey !== undefined ? { adminKey: common.adminKey } : {}),\n ...(common.seed !== undefined ? { seed: common.seed } : {}),\n ...(common.onLog ? { onLog: common.onLog } : {}),\n })\n },\n banner(runtime) {\n const junction = runtime as JunctionRuntime\n const api = junction.instance()\n const info = api.corpusInfo()\n return [\n info\n ? `corpus ${info.label}: ${info.observations} observations, ${info.zips} ZIPs, ${info.labTests} lab tests, ${info.labAccounts} lab accounts (recorded ${info.recordedAt} from ${info.source})`\n : \"corpus none: synthetic catalog and coverage\",\n `geo ${api.geoMode}${api.geoMode === \"corpus\" ? \" (unknown ZIPs answer 424 MOCKINGBIRD_UNKNOWN_ZIP)\" : \"\"}`,\n `team ${api.teamId}`,\n `lab accounts: ${api.labAccounts().length}`,\n `limits: ${\n Object.entries(api.limits)\n .filter(([, value]) => value !== null && value !== false)\n .map(([key, value]) => `${key}=${String(value)}`)\n .join(\", \") || \"none (sandbox caps off)\"\n }`,\n `identity ${api.identity}`,\n `webhooks: ${junction.webhooks ? \"signed delivery on\" : \"off\"}`,\n \"auth: any x-vital-api-key value (e.g. sk_us_mockingbird)\",\n ]\n },\n}\n", "import { readFile } from \"node:fs/promises\"\nimport { type ParseArgsConfig, parseArgs } from \"node:util\"\nimport type { RequestLog, ServiceInstance, ServiceRuntime } from \"@crvouga/mockingbird-service\"\nimport { type Listening, listen } from \"./listen.js\"\n\nexport type CliOption = {\n type: \"string\" | \"boolean\"\n description: string\n /** Shown in help; the value placeholder, e.g. `<port>`. */\n value?: string\n default?: string | boolean\n}\n\nexport type CliValues = Record<string, string | boolean | undefined>\n\nexport type CliCommand = {\n summary: string\n usage?: string\n options?: Record<string, CliOption>\n /** Resolves to an exit code; a server command resolves only when it stops. */\n run(values: CliValues, positionals: string[]): Promise<number>\n}\n\nexport type CliSpec = {\n bin: string\n description: string\n commands: Record<string, CliCommand>\n}\n\nconst optionHelp = (options: Record<string, CliOption>): string[] =>\n Object.entries(options).map(([name, option]) => {\n const flag = `--${name}${option.type === \"string\" ? ` ${option.value ?? \"<value>\"}` : \"\"}`\n const fallback = option.default !== undefined ? ` (default: ${String(option.default)})` : \"\"\n return ` ${flag.padEnd(30)} ${option.description}${fallback}`\n })\n\nconst help = (spec: CliSpec): string =>\n [\n `${spec.bin} \u2014 ${spec.description}`,\n \"\",\n \"Usage:\",\n ` ${spec.bin} <command> [options]`,\n \"\",\n \"Commands:\",\n ...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),\n \"\",\n `Run \\`${spec.bin} <command> --help\\` for a command's options.`,\n ].join(\"\\n\")\n\nconst commandHelp = (spec: CliSpec, name: string, command: CliCommand): string =>\n [\n `${spec.bin} ${name} \u2014 ${command.summary}`,\n \"\",\n \"Usage:\",\n ` ${command.usage ?? `${spec.bin} ${name} [options]`}`,\n ...(command.options ? [\"\", \"Options:\", ...optionHelp(command.options)] : []),\n ].join(\"\\n\")\n\n/** Parse `argv` against `spec` and run the chosen command. Resolves to an exit code. */\nexport const runCli = async (spec: CliSpec, argv: string[]): Promise<number> => {\n // Two-word commands (`corpus pull`) win over one-word ones.\n const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : undefined\n const words = pair !== undefined && spec.commands[pair] ? 2 : 1\n const name = words === 2 ? pair : argv[0]\n const rest = argv.slice(words)\n if (name === undefined || name === \"--help\" || name === \"-h\" || name === \"help\") {\n console.log(help(spec))\n return 0\n }\n const command = spec.commands[name]\n if (!command) {\n console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}\\n\\n${help(spec)}`)\n return 2\n }\n if (rest.includes(\"--help\") || rest.includes(\"-h\")) {\n console.log(commandHelp(spec, name, command))\n return 0\n }\n let parsed: { values: CliValues; positionals: string[] }\n try {\n parsed = parseArgs({\n args: rest,\n allowPositionals: true,\n strict: true,\n options: Object.fromEntries(\n Object.entries(command.options ?? {}).map(([key, option]) => [\n key,\n {\n type: option.type,\n ...(option.default !== undefined ? { default: option.default } : {}),\n },\n ]),\n ) as ParseArgsConfig[\"options\"],\n }) as { values: CliValues; positionals: string[] }\n } catch (error) {\n console.error(\n `${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}\\n\\n${commandHelp(spec, name, command)}`,\n )\n return 2\n }\n return command.run(parsed.values, parsed.positionals)\n}\n\n// \u2500\u2500 serve \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type LogFormat = \"pretty\" | \"json\" | \"off\"\n\nexport type CommonServeOptions = {\n adminKey: string | undefined\n seed: string | undefined\n onLog: ((entry: RequestLog) => void) | undefined\n}\n\n/**\n * What a service contributes to `serve`: how to build its runtime from CLI flags,\n * and what to say at startup. Every service's `./server` entry exports one as\n * `serveTarget`, which is also how `serve --config` finds services by name.\n */\nexport type ServeTarget = {\n name: string\n defaultPort: number\n /** Serve flags beyond the common ones. */\n options?: Record<string, CliOption>\n create(\n values: CliValues,\n common: CommonServeOptions,\n ): Promise<ServiceRuntime<ServiceInstance>> | ServiceRuntime<ServiceInstance>\n /** Startup lines after the listen address, e.g. the loaded corpus version. */\n banner?(runtime: ServiceRuntime<ServiceInstance>): string[]\n}\n\nconst COMMON_SERVE_OPTIONS: Record<string, CliOption> = {\n port: { type: \"string\", value: \"<port>\", description: \"Port to listen on\" },\n host: { type: \"string\", value: \"<host>\", description: \"Interface to bind\", default: \"127.0.0.1\" },\n \"admin-key\": {\n type: \"string\",\n value: \"<key>\",\n description: \"Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)\",\n },\n seed: { type: \"string\", value: \"<seed>\", description: \"Seed for every random choice\" },\n log: {\n type: \"string\",\n value: \"<pretty|json|off>\",\n description: \"Request log format\",\n default: \"pretty\",\n },\n \"log-requests\": {\n type: \"boolean\",\n description:\n \"One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json\",\n },\n config: {\n type: \"string\",\n value: \"<file>\",\n description: \"Serve every service in a mockingbird.json config instead\",\n },\n}\n\nconst formatLog = (format: LogFormat) => {\n if (format === \"off\") return undefined\n if (format === \"json\") return (entry: RequestLog) => console.log(JSON.stringify(entry))\n return (entry: RequestLog) => {\n const op = entry.operationId ?? (entry.unmatched ? \"UNMATCHED\" : \"-\")\n const ns = entry.namespace === \"default\" ? \"\" : ` [${entry.namespace}]`\n const fault = entry.faultId ? ` fault=${entry.faultId}` : \"\"\n const adopted = entry.adopted ? \" adopted\" : \"\"\n console.log(\n `${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}`,\n )\n }\n}\n\nconst asString = (value: string | boolean | undefined): string | undefined =>\n typeof value === \"string\" ? value : undefined\n\n/** A service entry in `mockingbird.json`. */\nexport type ConfigService = {\n port?: number\n host?: string\n adminKey?: string\n seed?: string\n /** Service-specific serve flags, by long name: `{ \"webhook-url\": \"\u2026\" }`. */\n options?: Record<string, string | boolean>\n}\n\nexport type MockingbirdConfig = {\n /** Keyed by service name: `junction` loads `@crvouga/mockingbird-service-junction`. */\n services: Record<string, ConfigService>\n log?: LogFormat\n}\n\nconst loadTarget = async (name: string, own: ServeTarget): Promise<ServeTarget> => {\n if (name === own.name) return own\n const specifier = `@crvouga/mockingbird-service-${name}/server`\n try {\n const mod = (await import(specifier)) as { serveTarget?: ServeTarget }\n if (!mod.serveTarget) throw new Error(`${specifier} exports no serveTarget`)\n return mod.serveTarget\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n throw new Error(\n `cannot load service \"${name}\": ${reason}. Install @crvouga/mockingbird-service-${name}.`,\n )\n }\n}\n\nconst start = async (\n target: ServeTarget,\n values: CliValues,\n config: { port: number; host: string; adminKey?: string; seed?: string; log: LogFormat },\n): Promise<Listening> => {\n const runtime = await target.create(values, {\n adminKey: config.adminKey,\n seed: config.seed,\n onLog: formatLog(config.log),\n })\n const listening = await listen(runtime, { port: config.port, host: config.host })\n console.log(`${target.name} mock listening on ${listening.url}`)\n console.log(`${target.name} health: GET ${listening.url}/health`)\n console.log(\n `${target.name} admin: ${listening.url}/__admin (${config.adminKey ? \"x-mockingbird-admin-key required\" : \"open \u2014 pass --admin-key to lock\"})`,\n )\n for (const line of target.banner?.(runtime) ?? []) console.log(`${target.name} ${line}`)\n return listening\n}\n\nconst untilSignal = async (servers: Listening[]): Promise<number> =>\n new Promise((resolve) => {\n const stop = async () => {\n await Promise.allSettled(servers.map((s) => s.close()))\n resolve(0)\n }\n process.once(\"SIGINT\", stop)\n process.once(\"SIGTERM\", stop)\n })\n\n/** The standard `serve` command for a service, including multi-service `--config`. */\nexport const serveCommand = (target: ServeTarget): CliCommand => ({\n summary: `Serve the ${target.name} mock over HTTP`,\n options: { ...COMMON_SERVE_OPTIONS, ...target.options },\n async run(values) {\n const log = (\n values[\"log-requests\"] === true ? \"json\" : (asString(values.log) ?? \"pretty\")\n ) as LogFormat\n if (![\"pretty\", \"json\", \"off\"].includes(log)) {\n console.error(`--log must be pretty, json or off (got ${log})`)\n return 2\n }\n const configPath = asString(values.config)\n if (configPath !== undefined) {\n const config = JSON.parse(await readFile(configPath, \"utf8\")) as MockingbirdConfig\n const servers: Listening[] = []\n try {\n for (const [name, entry] of Object.entries(config.services ?? {})) {\n const each = await loadTarget(name, target)\n servers.push(\n await start(each, entry.options ?? {}, {\n port: entry.port ?? each.defaultPort,\n host: entry.host ?? \"127.0.0.1\",\n ...(entry.adminKey !== undefined ? { adminKey: entry.adminKey } : {}),\n ...(entry.seed !== undefined ? { seed: entry.seed } : {}),\n log: config.log ?? log,\n }),\n )\n }\n } catch (error) {\n await Promise.allSettled(servers.map((s) => s.close()))\n console.error(error instanceof Error ? error.message : String(error))\n return 1\n }\n return untilSignal(servers)\n }\n const port = asString(values.port)\n const adminKey = asString(values[\"admin-key\"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY\n const seed = asString(values.seed)\n let listening: Listening\n try {\n listening = await start(target, values, {\n port: port === undefined ? target.defaultPort : Number.parseInt(port, 10),\n host: asString(values.host) ?? \"127.0.0.1\",\n ...(adminKey !== undefined ? { adminKey } : {}),\n ...(seed !== undefined ? { seed } : {}),\n log,\n })\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n return 1\n }\n return untilSignal([listening])\n },\n})\n", "import { createServer } from \"node:http\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\n\n/** Options for {@link serve}. */\nexport type NodeServeOptions = {\n port?: number\n host?: string\n}\n\n/**\n * Serve any Mockingbird {@link FetchAPI} over `node:http`.\n * Port defaults to `0`, so the OS assigns an ephemeral port (read from `server.address()`).\n */\nexport const serve = async (api: FetchAPI, options: NodeServeOptions = {}) => {\n const server = createServer(async (req, res) => {\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n chunks.push(Buffer.from(chunk))\n }\n const body = Buffer.concat(chunks)\n const address = server.address()\n const port = typeof address === \"object\" && address !== null ? address.port : undefined\n const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`\n const raw = req.url ?? \"/\"\n const url = new URL(raw.replace(/^\\/+/, \"/\"), base)\n const method = req.method ?? \"GET\"\n const init: RequestInit = { method, headers: req.headers as Record<string, string> }\n if (method !== \"GET\" && method !== \"HEAD\" && body.length > 0) {\n init.body = body\n }\n const aborted = new AbortController()\n res.once(\"close\", () => {\n if (!res.writableFinished) aborted.abort()\n })\n init.signal = aborted.signal\n const request = new Request(url, init)\n let response: Response\n try {\n response = await api.fetch(request)\n } catch (error) {\n // A `drop` fault: destroy the socket so the client sees the connection die.\n if ((error as { code?: string }).code === \"MOCKINGBIRD_DROP\") {\n req.socket.destroy()\n return\n }\n res.writeHead(500, { \"content-type\": \"application/json\" })\n res.end(\n JSON.stringify({\n error: {\n type: \"mockingbird_internal\",\n message: error instanceof Error ? error.message : String(error),\n },\n }),\n )\n return\n }\n // Headers#entries() joins repeated headers; Set-Cookie must stay one header per cookie.\n const headers: Record<string, string | string[]> = Object.fromEntries(response.headers)\n const cookies = response.headers.getSetCookie()\n if (cookies.length > 0) headers[\"set-cookie\"] = cookies\n if (!response.body) {\n res.writeHead(response.status, headers)\n res.end()\n return\n }\n // Stream the body chunk by chunk: event streams and long-polls must not be buffered.\n res.writeHead(response.status, headers)\n res.flushHeaders()\n const reader = response.body.getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (!res.write(value)) await new Promise<void>((resolve) => res.once(\"drain\", resolve))\n }\n res.end()\n } catch {\n res.destroy()\n } finally {\n reader.releaseLock()\n }\n })\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject)\n server.listen(options.port ?? 0, options.host, resolve)\n })\n return server\n}\n", "import type { Server } from \"node:http\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\nimport { serve } from \"./serve.js\"\n\nexport type ListenOptions = {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`: a mock should not be reachable off the machine by accident. */\n host?: string\n}\n\n/** A running server, with the address it actually bound. */\nexport type Listening = {\n url: string\n port: number\n host: string\n server: Server\n close(): Promise<void>\n}\n\nexport const listen = async (api: FetchAPI, options: ListenOptions = {}): Promise<Listening> => {\n const host = options.host ?? \"127.0.0.1\"\n const server = await serve(api, { port: options.port ?? 0, host })\n const address = server.address()\n const port = typeof address === \"object\" && address !== null ? address.port : (options.port ?? 0)\n const shown = host.includes(\":\") ? `[${host}]` : host\n return {\n url: `http://${shown}:${port}`,\n port,\n host,\n server,\n close: () =>\n new Promise<void>((resolve, reject) => {\n // Stop accepting first, then drop keep-alive sockets so close() can finish.\n server.close((error) => {\n if (error && (error as { code?: string }).code !== \"ERR_SERVER_NOT_RUNNING\") reject(error)\n else resolve()\n })\n server.closeAllConnections?.()\n }),\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/// <reference types=\"node\" />\nimport { readFile } from \"node:fs/promises\"\nimport {\n type CliValues,\n type Listening,\n listen,\n type ServeTarget,\n} from \"@crvouga/mockingbird-adapter-node\"\nimport { defaultCorpus } from \"./corpus.js\"\nimport { pullCorpus } from \"./corpus-tools.js\"\nimport { IDENTITY_MODES, type IdentityMode, type JunctionFixtures } from \"./fixtures.js\"\nimport type { LabAccountLayout } from \"./lab-account-presets.js\"\nimport { type BillingType, defaultBillingTypesFrom } from \"./lab-accounts.js\"\nimport { createRuntime, type JunctionRuntime, type JunctionRuntimeOptions } from \"./runtime.js\"\nimport { parseSealedCorpus, type SealedCorpus } from \"./sealed-corpus.js\"\nimport type { GeoMode } from \"./state.js\"\n\n/** Port `mockingbird-junction serve` listens on when none is given. */\nexport const DEFAULT_PORT = 8787\n\n/** A corpus to serve: the shipped one, none (synthetic), a file path, or a loaded corpus. */\nexport type CorpusSource = \"default\" | \"none\" | (string & {}) | SealedCorpus\n\nexport type JunctionServerOptions = Omit<JunctionRuntimeOptions, \"corpus\"> & {\n /** Default `0`: the OS picks a free port (read it from `url` / `port`). */\n port?: number\n /** Default `127.0.0.1`. */\n host?: string\n /** Default `\"default\"`: the corpus shipped with this package. */\n corpus?: CorpusSource\n}\n\nexport type JunctionServer = Listening & { runtime: JunctionRuntime }\n\n/** Resolve a {@link CorpusSource} to a corpus, or `undefined` for synthetic data. */\nexport const loadCorpus = async (source: CorpusSource): Promise<SealedCorpus | undefined> => {\n if (typeof source !== \"string\") return source\n if (source === \"none\") return undefined\n if (source === \"default\") return defaultCorpus\n const raw = await readFile(source, \"utf8\").catch(() => undefined)\n if (raw === undefined) throw new Error(`junction mock corpus not found: ${source}`)\n return parseSealedCorpus(JSON.parse(raw))\n}\n\n/**\n * Serve the Junction mock over `node:http`. Resolves once it is listening.\n *\n * const server = await createServer()\n * // point the Vital SDK at server.url; await server.close() when done\n */\nexport const createServer = async (\n options: JunctionServerOptions = {},\n): Promise<JunctionServer> => {\n const { port, host, corpus: source, ...rest } = options\n const corpus = await loadCorpus(source ?? \"default\")\n const runtime = createRuntime({ ...rest, ...(corpus ? { corpus } : {}) })\n const listening = await listen(runtime, {\n port: port ?? 0,\n ...(host !== undefined ? { host } : {}),\n })\n return { ...listening, runtime }\n}\n\nconst asString = (value: string | boolean | undefined): string | undefined =>\n typeof value === \"string\" && value !== \"\" ? value : undefined\n\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nconst count = (value: string | undefined, flag: string): number | undefined => {\n if (value === undefined) return undefined\n if (!/^\\d+$/.test(value)) throw new Error(`${flag} must be a non-negative integer (got ${value})`)\n return Number.parseInt(value, 10)\n}\n\nconst readJson = async <T>(path: string): Promise<T> => {\n const raw = await readFile(path, \"utf8\").catch(() => undefined)\n if (raw === undefined) throw new Error(`junction mock: file not found: ${path}`)\n try {\n return JSON.parse(raw) as T\n } catch (error) {\n throw new Error(`junction mock: ${path} is not JSON (${(error as Error).message})`)\n }\n}\n\nconst retryDelays = (value: string | undefined): number[] | undefined => {\n if (value === undefined) return undefined\n const delays = value.split(\",\").map((part) => Number.parseInt(part.trim(), 10))\n if (delays.some((delay) => !Number.isFinite(delay) || delay < 0)) {\n throw new Error(`--webhook-retry-delays must be comma-separated milliseconds (got ${value})`)\n }\n return delays\n}\n\n/** `--default-billing-type bioreference=patient_bill_passthrough,quest=client_bill`. */\nconst billingDefaults = (value: string | undefined): Record<string, BillingType> | undefined => {\n if (value === undefined) return undefined\n const entries = value.split(\",\").map((part) => part.split(\"=\").map((side) => side.trim()))\n if (entries.some((entry) => entry.length !== 2 || !entry[0] || !entry[1])) {\n throw new Error(`--default-billing-type must be lab=billing_type pairs (got ${value})`)\n }\n try {\n return defaultBillingTypesFrom(Object.fromEntries(entries))\n } catch (error) {\n throw new Error(`--default-billing-type: ${(error as Error).message}`)\n }\n}\n\n/** How `serve` (and `serve --config`) builds the Junction mock from flags. */\nexport const serveTarget: ServeTarget = {\n name: \"junction\",\n defaultPort: DEFAULT_PORT,\n options: {\n corpus: {\n type: \"string\",\n value: \"<default|none|file>\",\n description: \"Corpus to serve: the shipped one, none (synthetic), or a pulled file\",\n default: \"default\",\n },\n geo: {\n type: \"string\",\n value: \"<corpus|synthetic>\",\n description: \"Unknown ZIPs: refuse (corpus) or invent coverage (synthetic)\",\n },\n \"lab-accounts\": {\n type: \"string\",\n value: \"<file>\",\n description:\n 'Lab accounts to route orders through: a JSON array, or { \"presets\": [...], \"accounts\": [...] }',\n },\n \"team-id\": {\n type: \"string\",\n value: \"<uuid>\",\n description: \"Team the mock answers as (default: the corpus's recorded team)\",\n },\n \"max-users\": {\n type: \"string\",\n value: \"<n>\",\n description: \"Enforce the sandbox's live-user cap (default: unlimited)\",\n },\n identity: {\n type: \"string\",\n value: \"<strict|adopt-users>\",\n description: \"Unknown user ids: 404 (strict) or create on first use\",\n default: \"strict\",\n },\n \"default-billing-type\": {\n type: \"string\",\n value: \"<lab=type,\u2026>\",\n description:\n \"billing_type an order for that lab gets when it omits one (default: client_bill), e.g. bioreference=patient_bill_passthrough\",\n },\n fixtures: {\n type: \"string\",\n value: \"<file>\",\n description:\n 'Users and orders every namespace starts with: { \"users\": [...], \"orders\": [...] }',\n },\n \"journal-size\": {\n type: \"string\",\n value: \"<n>\",\n description: \"Requests each namespace's journal keeps (GET /__admin/requests)\",\n default: \"1000\",\n },\n \"seed-url\": {\n type: \"string\",\n value: \"<url>\",\n description: \"Pull a corpus from this Junction team at boot (prefer a committed corpus)\",\n },\n \"seed-key\": {\n type: \"string\",\n value: \"<key>\",\n description: \"API key for --seed-url (env MOCKINGBIRD_JUNCTION_SEED_KEY)\",\n },\n \"webhook-url\": { type: \"string\", value: \"<url>\", description: \"Deliver signed webhooks here\" },\n \"webhook-secret\": {\n type: \"string\",\n value: \"<whsec_\u2026>\",\n description: \"Svix signing secret (env MOCKINGBIRD_JUNCTION_WEBHOOK_SECRET)\",\n },\n \"webhook-retry-delays\": {\n type: \"string\",\n value: \"<ms,ms,\u2026>\",\n description: \"Delay before each delivery attempt (default: Svix's schedule)\",\n },\n \"webhook-scope\": {\n type: \"string\",\n value: \"<scope>\",\n description: \"Sent as x-mockingbird-scope on every delivery\",\n },\n },\n async create(values: CliValues, common) {\n const seedUrl = asString(values[\"seed-url\"])\n const seedKey = asString(values[\"seed-key\"]) ?? process.env.MOCKINGBIRD_JUNCTION_SEED_KEY\n let corpus: SealedCorpus | undefined\n if (seedUrl !== undefined) {\n if (seedKey === undefined) throw new Error(\"--seed-url needs --seed-key\")\n console.log(\n `junction pulling a corpus from ${seedUrl} (commit one with \\`corpus pull\\` to skip this)`,\n )\n corpus = await pullCorpus({ baseUrl: seedUrl, apiKey: seedKey })\n } else {\n corpus = await loadCorpus(asString(values.corpus) ?? \"default\")\n }\n const geo = asString(values.geo)\n if (geo !== undefined && geo !== \"corpus\" && geo !== \"synthetic\") {\n throw new Error(`--geo must be corpus or synthetic (got ${geo})`)\n }\n const accountsFile = asString(values[\"lab-accounts\"])\n const labAccounts =\n accountsFile === undefined ? undefined : await readJson<LabAccountLayout>(accountsFile)\n const fixturesFile = asString(values.fixtures)\n const fixtures =\n fixturesFile === undefined ? undefined : await readJson<JunctionFixtures>(fixturesFile)\n const teamId = asString(values[\"team-id\"])\n if (teamId !== undefined && !UUID.test(teamId)) {\n throw new Error(`--team-id must be a UUID (got ${teamId})`)\n }\n const maxUsers = count(asString(values[\"max-users\"]), \"--max-users\")\n const journalSize = count(asString(values[\"journal-size\"]), \"--journal-size\")\n const identity = asString(values.identity)\n if (identity !== undefined && !IDENTITY_MODES.includes(identity as IdentityMode)) {\n throw new Error(`--identity must be one of ${IDENTITY_MODES.join(\", \")} (got ${identity})`)\n }\n const defaultBillingTypes = billingDefaults(asString(values[\"default-billing-type\"]))\n const webhookUrl = asString(values[\"webhook-url\"])\n const webhookSecret =\n asString(values[\"webhook-secret\"]) ?? process.env.MOCKINGBIRD_JUNCTION_WEBHOOK_SECRET\n if (webhookUrl !== undefined && webhookSecret === undefined) {\n throw new Error(\"--webhook-url needs --webhook-secret (the whsec_\u2026 your receiver verifies)\")\n }\n const delays = retryDelays(asString(values[\"webhook-retry-delays\"]))\n const scope = asString(values[\"webhook-scope\"])\n return createRuntime({\n ...(corpus ? { corpus } : {}),\n ...(geo ? { geo: geo as GeoMode } : {}),\n ...(labAccounts ? { labAccounts } : {}),\n ...(fixtures ? { fixtures } : {}),\n ...(teamId !== undefined ? { teamId } : {}),\n ...(maxUsers !== undefined ? { limits: { maxUsers } } : {}),\n ...(identity !== undefined ? { identity: identity as IdentityMode } : {}),\n ...(defaultBillingTypes ? { defaultBillingTypes } : {}),\n ...(journalSize !== undefined ? { journalSize } : {}),\n ...(webhookUrl !== undefined && webhookSecret !== undefined\n ? {\n webhooks: {\n url: webhookUrl,\n secret: webhookSecret,\n ...(delays ? { retryDelaysMs: delays } : {}),\n ...(scope ? { headers: { \"x-mockingbird-scope\": scope } } : {}),\n },\n }\n : {}),\n ...(common.adminKey !== undefined ? { adminKey: common.adminKey } : {}),\n ...(common.seed !== undefined ? { seed: common.seed } : {}),\n ...(common.onLog ? { onLog: common.onLog } : {}),\n })\n },\n banner(runtime) {\n const junction = runtime as JunctionRuntime\n const api = junction.instance()\n const info = api.corpusInfo()\n return [\n info\n ? `corpus ${info.label}: ${info.observations} observations, ${info.zips} ZIPs, ${info.labTests} lab tests, ${info.labAccounts} lab accounts (recorded ${info.recordedAt} from ${info.source})`\n : \"corpus none: synthetic catalog and coverage\",\n `geo ${api.geoMode}${api.geoMode === \"corpus\" ? \" (unknown ZIPs answer 424 MOCKINGBIRD_UNKNOWN_ZIP)\" : \"\"}`,\n `team ${api.teamId}`,\n `lab accounts: ${api.labAccounts().length}`,\n `limits: ${\n Object.entries(api.limits)\n .filter(([, value]) => value !== null && value !== false)\n .map(([key, value]) => `${key}=${String(value)}`)\n .join(\", \") || \"none (sandbox caps off)\"\n }`,\n `identity ${api.identity}`,\n `webhooks: ${junction.webhooks ? \"signed delivery on\" : \"off\"}`,\n \"auth: any x-vital-api-key value (e.g. sk_us_mockingbird)\",\n ]\n },\n}\n", "import { readFile } from \"node:fs/promises\"\nimport { type ParseArgsConfig, parseArgs } from \"node:util\"\nimport type { RequestLog, ServiceInstance, ServiceRuntime } from \"@crvouga/mockingbird-service\"\nimport { type Listening, listen } from \"./listen.js\"\n\nexport type CliOption = {\n type: \"string\" | \"boolean\"\n description: string\n /** Shown in help; the value placeholder, e.g. `<port>`. */\n value?: string\n default?: string | boolean\n}\n\nexport type CliValues = Record<string, string | boolean | undefined>\n\nexport type CliCommand = {\n summary: string\n usage?: string\n options?: Record<string, CliOption>\n /** Resolves to an exit code; a server command resolves only when it stops. */\n run(values: CliValues, positionals: string[]): Promise<number>\n}\n\nexport type CliSpec = {\n bin: string\n description: string\n commands: Record<string, CliCommand>\n}\n\nconst optionHelp = (options: Record<string, CliOption>): string[] =>\n Object.entries(options).map(([name, option]) => {\n const flag = `--${name}${option.type === \"string\" ? ` ${option.value ?? \"<value>\"}` : \"\"}`\n const fallback = option.default !== undefined ? ` (default: ${String(option.default)})` : \"\"\n return ` ${flag.padEnd(30)} ${option.description}${fallback}`\n })\n\nconst help = (spec: CliSpec): string =>\n [\n `${spec.bin} \u2014 ${spec.description}`,\n \"\",\n \"Usage:\",\n ` ${spec.bin} <command> [options]`,\n \"\",\n \"Commands:\",\n ...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),\n \"\",\n `Run \\`${spec.bin} <command> --help\\` for a command's options.`,\n ].join(\"\\n\")\n\nconst commandHelp = (spec: CliSpec, name: string, command: CliCommand): string =>\n [\n `${spec.bin} ${name} \u2014 ${command.summary}`,\n \"\",\n \"Usage:\",\n ` ${command.usage ?? `${spec.bin} ${name} [options]`}`,\n ...(command.options ? [\"\", \"Options:\", ...optionHelp(command.options)] : []),\n ].join(\"\\n\")\n\n/** Parse `argv` against `spec` and run the chosen command. Resolves to an exit code. */\nexport const runCli = async (spec: CliSpec, argv: string[]): Promise<number> => {\n // Two-word commands (`corpus pull`) win over one-word ones.\n const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : undefined\n const words = pair !== undefined && spec.commands[pair] ? 2 : 1\n const name = words === 2 ? pair : argv[0]\n const rest = argv.slice(words)\n if (name === undefined || name === \"--help\" || name === \"-h\" || name === \"help\") {\n console.log(help(spec))\n return 0\n }\n const command = spec.commands[name]\n if (!command) {\n console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}\\n\\n${help(spec)}`)\n return 2\n }\n if (rest.includes(\"--help\") || rest.includes(\"-h\")) {\n console.log(commandHelp(spec, name, command))\n return 0\n }\n let parsed: { values: CliValues; positionals: string[] }\n try {\n parsed = parseArgs({\n args: rest,\n allowPositionals: true,\n strict: true,\n options: Object.fromEntries(\n Object.entries(command.options ?? {}).map(([key, option]) => [\n key,\n {\n type: option.type,\n ...(option.default !== undefined ? { default: option.default } : {}),\n },\n ]),\n ) as ParseArgsConfig[\"options\"],\n }) as { values: CliValues; positionals: string[] }\n } catch (error) {\n console.error(\n `${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}\\n\\n${commandHelp(spec, name, command)}`,\n )\n return 2\n }\n return command.run(parsed.values, parsed.positionals)\n}\n\n// \u2500\u2500 serve \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type LogFormat = \"pretty\" | \"json\" | \"off\"\n\nexport type CommonServeOptions = {\n adminKey: string | undefined\n seed: string | undefined\n onLog: ((entry: RequestLog) => void) | undefined\n}\n\n/**\n * What a service contributes to `serve`: how to build its runtime from CLI flags,\n * and what to say at startup. Every service's `./server` entry exports one as\n * `serveTarget`, which is also how `serve --config` finds services by name.\n */\nexport type ServeTarget = {\n name: string\n defaultPort: number\n /** Serve flags beyond the common ones. */\n options?: Record<string, CliOption>\n create(\n values: CliValues,\n common: CommonServeOptions,\n ): Promise<ServiceRuntime<ServiceInstance>> | ServiceRuntime<ServiceInstance>\n /** Startup lines after the listen address, e.g. the loaded corpus version. */\n banner?(runtime: ServiceRuntime<ServiceInstance>): string[]\n}\n\nconst COMMON_SERVE_OPTIONS: Record<string, CliOption> = {\n port: { type: \"string\", value: \"<port>\", description: \"Port to listen on\" },\n host: { type: \"string\", value: \"<host>\", description: \"Interface to bind\", default: \"127.0.0.1\" },\n \"admin-key\": {\n type: \"string\",\n value: \"<key>\",\n description: \"Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)\",\n },\n seed: { type: \"string\", value: \"<seed>\", description: \"Seed for every random choice\" },\n log: {\n type: \"string\",\n value: \"<pretty|json|off>\",\n description: \"Request log format\",\n default: \"pretty\",\n },\n \"log-requests\": {\n type: \"boolean\",\n description:\n \"One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json\",\n },\n config: {\n type: \"string\",\n value: \"<file>\",\n description: \"Serve every service in a mockingbird.json config instead\",\n },\n}\n\nconst formatLog = (format: LogFormat) => {\n if (format === \"off\") return undefined\n if (format === \"json\") return (entry: RequestLog) => console.log(JSON.stringify(entry))\n return (entry: RequestLog) => {\n const op = entry.operationId ?? (entry.unmatched ? \"UNMATCHED\" : \"-\")\n const ns = entry.namespace === \"default\" ? \"\" : ` [${entry.namespace}]`\n const fault = entry.faultId ? ` fault=${entry.faultId}` : \"\"\n const adopted = entry.adopted ? \" adopted\" : \"\"\n // A rejection says what arrived (never the body) and why the service refused it.\n const received = entry.request\n ? ` received=${entry.request.bodyBytes === null ? \"?\" : `${entry.request.bodyBytes}B`} ct=${entry.request.contentType ?? \"-\"}${\n entry.request.transferEncoding ? ` te=${entry.request.transferEncoding}` : \"\"\n }`\n : \"\"\n const issues = entry.issues?.length\n ? ` issues=${entry.issues\n .slice(0, 3)\n .map((issue) => `${issue.path || \"$\"}: ${issue.message}`)\n .join(\"; \")}${entry.issues.length > 3 ? ` (+${entry.issues.length - 3})` : \"\"}`\n : \"\"\n console.log(\n `${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}${received}${issues}`,\n )\n }\n}\n\nconst asString = (value: string | boolean | undefined): string | undefined =>\n typeof value === \"string\" ? value : undefined\n\n/** A service entry in `mockingbird.json`. */\nexport type ConfigService = {\n port?: number\n host?: string\n adminKey?: string\n seed?: string\n /** Service-specific serve flags, by long name: `{ \"webhook-url\": \"\u2026\" }`. */\n options?: Record<string, string | boolean>\n}\n\nexport type MockingbirdConfig = {\n /** Keyed by service name: `junction` loads `@crvouga/mockingbird-service-junction`. */\n services: Record<string, ConfigService>\n log?: LogFormat\n}\n\nconst loadTarget = async (name: string, own: ServeTarget): Promise<ServeTarget> => {\n if (name === own.name) return own\n const specifier = `@crvouga/mockingbird-service-${name}/server`\n try {\n const mod = (await import(specifier)) as { serveTarget?: ServeTarget }\n if (!mod.serveTarget) throw new Error(`${specifier} exports no serveTarget`)\n return mod.serveTarget\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n throw new Error(\n `cannot load service \"${name}\": ${reason}. Install @crvouga/mockingbird-service-${name}.`,\n )\n }\n}\n\nconst start = async (\n target: ServeTarget,\n values: CliValues,\n config: { port: number; host: string; adminKey?: string; seed?: string; log: LogFormat },\n): Promise<Listening> => {\n const runtime = await target.create(values, {\n adminKey: config.adminKey,\n seed: config.seed,\n onLog: formatLog(config.log),\n })\n const listening = await listen(runtime, { port: config.port, host: config.host })\n console.log(`${target.name} mock listening on ${listening.url}`)\n console.log(`${target.name} health: GET ${listening.url}/health`)\n console.log(\n `${target.name} admin: ${listening.url}/__admin (${config.adminKey ? \"x-mockingbird-admin-key required\" : \"open \u2014 pass --admin-key to lock\"})`,\n )\n for (const line of target.banner?.(runtime) ?? []) console.log(`${target.name} ${line}`)\n return listening\n}\n\nconst untilSignal = async (servers: Listening[]): Promise<number> =>\n new Promise((resolve) => {\n const stop = async () => {\n await Promise.allSettled(servers.map((s) => s.close()))\n resolve(0)\n }\n process.once(\"SIGINT\", stop)\n process.once(\"SIGTERM\", stop)\n })\n\n/** The standard `serve` command for a service, including multi-service `--config`. */\nexport const serveCommand = (target: ServeTarget): CliCommand => ({\n summary: `Serve the ${target.name} mock over HTTP`,\n options: { ...COMMON_SERVE_OPTIONS, ...target.options },\n async run(values) {\n const log = (\n values[\"log-requests\"] === true ? \"json\" : (asString(values.log) ?? \"pretty\")\n ) as LogFormat\n if (![\"pretty\", \"json\", \"off\"].includes(log)) {\n console.error(`--log must be pretty, json or off (got ${log})`)\n return 2\n }\n const configPath = asString(values.config)\n if (configPath !== undefined) {\n const config = JSON.parse(await readFile(configPath, \"utf8\")) as MockingbirdConfig\n const servers: Listening[] = []\n try {\n for (const [name, entry] of Object.entries(config.services ?? {})) {\n const each = await loadTarget(name, target)\n servers.push(\n await start(each, entry.options ?? {}, {\n port: entry.port ?? each.defaultPort,\n host: entry.host ?? \"127.0.0.1\",\n ...(entry.adminKey !== undefined ? { adminKey: entry.adminKey } : {}),\n ...(entry.seed !== undefined ? { seed: entry.seed } : {}),\n log: config.log ?? log,\n }),\n )\n }\n } catch (error) {\n await Promise.allSettled(servers.map((s) => s.close()))\n console.error(error instanceof Error ? error.message : String(error))\n return 1\n }\n return untilSignal(servers)\n }\n const port = asString(values.port)\n const adminKey = asString(values[\"admin-key\"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY\n const seed = asString(values.seed)\n let listening: Listening\n try {\n listening = await start(target, values, {\n port: port === undefined ? target.defaultPort : Number.parseInt(port, 10),\n host: asString(values.host) ?? \"127.0.0.1\",\n ...(adminKey !== undefined ? { adminKey } : {}),\n ...(seed !== undefined ? { seed } : {}),\n log,\n })\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n return 1\n }\n return untilSignal([listening])\n },\n})\n", "import { createServer } from \"node:http\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\n\n/** Options for {@link serve}. */\nexport type NodeServeOptions = {\n port?: number\n host?: string\n}\n\n/**\n * Serve any Mockingbird {@link FetchAPI} over `node:http`.\n * Port defaults to `0`, so the OS assigns an ephemeral port (read from `server.address()`).\n */\nexport const serve = async (api: FetchAPI, options: NodeServeOptions = {}) => {\n const server = createServer(async (req, res) => {\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n chunks.push(Buffer.from(chunk))\n }\n const body = Buffer.concat(chunks)\n const address = server.address()\n const port = typeof address === \"object\" && address !== null ? address.port : undefined\n const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`\n const raw = req.url ?? \"/\"\n const url = new URL(raw.replace(/^\\/+/, \"/\"), base)\n const method = req.method ?? \"GET\"\n const init: RequestInit = { method, headers: req.headers as Record<string, string> }\n if (method !== \"GET\" && method !== \"HEAD\" && body.length > 0) {\n init.body = body\n }\n const aborted = new AbortController()\n res.once(\"close\", () => {\n if (!res.writableFinished) aborted.abort()\n })\n init.signal = aborted.signal\n const request = new Request(url, init)\n let response: Response\n try {\n response = await api.fetch(request)\n } catch (error) {\n // A `drop` fault: destroy the socket so the client sees the connection die.\n if ((error as { code?: string }).code === \"MOCKINGBIRD_DROP\") {\n req.socket.destroy()\n return\n }\n res.writeHead(500, { \"content-type\": \"application/json\" })\n res.end(\n JSON.stringify({\n error: {\n type: \"mockingbird_internal\",\n message: error instanceof Error ? error.message : String(error),\n },\n }),\n )\n return\n }\n // Headers#entries() joins repeated headers; Set-Cookie must stay one header per cookie.\n const headers: Record<string, string | string[]> = Object.fromEntries(response.headers)\n const cookies = response.headers.getSetCookie()\n if (cookies.length > 0) headers[\"set-cookie\"] = cookies\n if (!response.body) {\n res.writeHead(response.status, headers)\n res.end()\n return\n }\n // Stream the body chunk by chunk: event streams and long-polls must not be buffered.\n res.writeHead(response.status, headers)\n res.flushHeaders()\n const reader = response.body.getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (!res.write(value)) await new Promise<void>((resolve) => res.once(\"drain\", resolve))\n }\n res.end()\n } catch {\n res.destroy()\n } finally {\n reader.releaseLock()\n }\n })\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject)\n server.listen(options.port ?? 0, options.host, resolve)\n })\n return server\n}\n", "import type { Server } from \"node:http\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\nimport { serve } from \"./serve.js\"\n\nexport type ListenOptions = {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`: a mock should not be reachable off the machine by accident. */\n host?: string\n}\n\n/** A running server, with the address it actually bound. */\nexport type Listening = {\n url: string\n port: number\n host: string\n server: Server\n close(): Promise<void>\n}\n\nexport const listen = async (api: FetchAPI, options: ListenOptions = {}): Promise<Listening> => {\n const host = options.host ?? \"127.0.0.1\"\n const server = await serve(api, { port: options.port ?? 0, host })\n const address = server.address()\n const port = typeof address === \"object\" && address !== null ? address.port : (options.port ?? 0)\n const shown = host.includes(\":\") ? `[${host}]` : host\n return {\n url: `http://${shown}:${port}`,\n port,\n host,\n server,\n close: () =>\n new Promise<void>((resolve, reject) => {\n // Stop accepting first, then drop keep-alive sockets so close() can finish.\n server.close((error) => {\n if (error && (error as { code?: string }).code !== \"ERR_SERVER_NOT_RUNNING\") reject(error)\n else resolve()\n })\n server.closeAllConnections?.()\n }),\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["readFile", "readFile", "createServer", "asString"]
|
|
7
7
|
}
|
|
@@ -1172,6 +1172,16 @@ var operationMetadata = (operation) => {
|
|
|
1172
1172
|
|
|
1173
1173
|
// ../core/dist/service.js
|
|
1174
1174
|
import { Hono } from "hono";
|
|
1175
|
+
|
|
1176
|
+
// ../core/dist/validation.js
|
|
1177
|
+
var recorded = /* @__PURE__ */ new WeakMap();
|
|
1178
|
+
var recordedIssues = (request2) => recorded.get(request2);
|
|
1179
|
+
|
|
1180
|
+
// ../core/dist/service.js
|
|
1181
|
+
var withIssues = (request2, response) => {
|
|
1182
|
+
const issues = response.status >= 400 ? recordedIssues(request2) : void 0;
|
|
1183
|
+
return issues && issues.length > 0 ? annotateResponse(response, { issues }) : response;
|
|
1184
|
+
};
|
|
1175
1185
|
var defineOperations = (handlers) => handlers;
|
|
1176
1186
|
var OperationRegistryError = class extends Error {
|
|
1177
1187
|
problems;
|
|
@@ -1257,9 +1267,7 @@ var createService = (options) => {
|
|
|
1257
1267
|
now
|
|
1258
1268
|
};
|
|
1259
1269
|
const short = await options.before?.(context);
|
|
1260
|
-
|
|
1261
|
-
return short;
|
|
1262
|
-
return handler(context);
|
|
1270
|
+
return withIssues(request2, short ?? await handler(context));
|
|
1263
1271
|
};
|
|
1264
1272
|
app.on(operation.method.toUpperCase(), honoPath(operation.path), route);
|
|
1265
1273
|
}
|
|
@@ -1296,7 +1304,7 @@ var restoreNamespace = (sqlite, namespace, snapshot) => {
|
|
|
1296
1304
|
};
|
|
1297
1305
|
|
|
1298
1306
|
// ../core/dist/version.js
|
|
1299
|
-
var PACKAGE_VERSION = true ? "1.0.
|
|
1307
|
+
var PACKAGE_VERSION = true ? "1.0.2" : UNRELEASED_VERSION;
|
|
1300
1308
|
|
|
1301
1309
|
// ../core/dist/webhooks.js
|
|
1302
1310
|
var json2 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
@@ -1734,8 +1742,15 @@ var createRuntime = (options) => {
|
|
|
1734
1742
|
const started = monotonicNow();
|
|
1735
1743
|
const url = new URL(request2.url);
|
|
1736
1744
|
const operationId = operationIdFor(request2, url.pathname);
|
|
1745
|
+
const contentLength = request2.headers.get("content-length");
|
|
1746
|
+
const received = {
|
|
1747
|
+
contentType: request2.headers.get("content-type"),
|
|
1748
|
+
bodyBytes: contentLength !== null && /^\d+$/.test(contentLength) ? Number(contentLength) : null,
|
|
1749
|
+
transferEncoding: request2.headers.get("transfer-encoding")
|
|
1750
|
+
};
|
|
1737
1751
|
const log = (status, faultId, response2) => {
|
|
1738
1752
|
const noted = response2 ? responseNotes(response2) : void 0;
|
|
1753
|
+
const rejected = status >= 400 && faultId === void 0;
|
|
1739
1754
|
const entry = {
|
|
1740
1755
|
service: options.name,
|
|
1741
1756
|
namespace,
|
|
@@ -1747,7 +1762,9 @@ var createRuntime = (options) => {
|
|
|
1747
1762
|
unmatched: options.document !== void 0 && operationId === void 0,
|
|
1748
1763
|
...faultId !== void 0 ? { faultId } : {},
|
|
1749
1764
|
...noted?.ids && Object.keys(noted.ids).length > 0 ? { ids: noted.ids } : {},
|
|
1750
|
-
...noted?.adopted ? { adopted: true } : {}
|
|
1765
|
+
...noted?.adopted ? { adopted: true } : {},
|
|
1766
|
+
...rejected ? { request: { ...received } } : {},
|
|
1767
|
+
...rejected && noted?.issues && noted.issues.length > 0 ? { issues: noted.issues.map((issue) => ({ ...issue })) } : {}
|
|
1751
1768
|
};
|
|
1752
1769
|
metrics.record(entry);
|
|
1753
1770
|
journal.record({ ...entry, at: new Date(clock.now()).toISOString() });
|
|
@@ -3304,8 +3321,8 @@ var account = (key, lab, fields) => ({
|
|
|
3304
3321
|
team_id_allowlist: fields.team_id_allowlist
|
|
3305
3322
|
});
|
|
3306
3323
|
var TEAM_LAB_ACCOUNTS = [
|
|
3307
|
-
//
|
|
3308
|
-
//
|
|
3324
|
+
// Exactly one active account linked for `quest`; the commercial-insurance state rule
|
|
3325
|
+
// lives on this account.
|
|
3309
3326
|
account("quest-primary", "quest", {
|
|
3310
3327
|
status: "active",
|
|
3311
3328
|
account_name: "Quest Diagnostics \u2014 primary",
|
|
@@ -3323,7 +3340,7 @@ var TEAM_LAB_ACCOUNTS = [
|
|
|
3323
3340
|
allowed_billing: CLIENT_BILL_ONLY,
|
|
3324
3341
|
team_id_allowlist: [MOCK_TEAM_ID]
|
|
3325
3342
|
}),
|
|
3326
|
-
//
|
|
3343
|
+
// Several active accounts linked for `nexus`: an omitted id selects the first.
|
|
3327
3344
|
account("nexus-a", "nexus", {
|
|
3328
3345
|
status: "active",
|
|
3329
3346
|
account_name: "Nexus \u2014 A",
|
|
@@ -3336,7 +3353,7 @@ var TEAM_LAB_ACCOUNTS = [
|
|
|
3336
3353
|
allowed_billing: CLIENT_BILL_ONLY,
|
|
3337
3354
|
team_id_allowlist: [MOCK_TEAM_ID]
|
|
3338
3355
|
}),
|
|
3339
|
-
//
|
|
3356
|
+
// Linked accounts, none active.
|
|
3340
3357
|
account("mtl-suspended", "mtl", {
|
|
3341
3358
|
status: "suspended",
|
|
3342
3359
|
account_name: "Molecular Testing Labs \u2014 suspended",
|
|
@@ -3373,8 +3390,6 @@ var selectLabAccount = (labSlug, requestedId, teamId, accounts) => {
|
|
|
3373
3390
|
if (!linked) throw new HttpError(400, { detail: "Lab account does not exist" });
|
|
3374
3391
|
if (!isLinkedToTeam(linked, teamId))
|
|
3375
3392
|
throw new HttpError(400, { detail: "Lab account is not linked to your team" });
|
|
3376
|
-
if (linked.lab !== labSlug)
|
|
3377
|
-
throw new HttpError(400, { detail: `Lab account is not associated with lab ${labSlug}` });
|
|
3378
3393
|
if (linked.status !== "active")
|
|
3379
3394
|
throw new HttpError(400, { detail: "Lab account is not active" });
|
|
3380
3395
|
return linked;
|
|
@@ -3384,12 +3399,8 @@ var selectLabAccount = (labSlug, requestedId, teamId, accounts) => {
|
|
|
3384
3399
|
if (PLATFORM_ACCOUNT_LABS.includes(labSlug)) return "platform";
|
|
3385
3400
|
throw new HttpError(400, { detail: `No active lab account is available for lab ${labSlug}` });
|
|
3386
3401
|
}
|
|
3387
|
-
const active = candidates.
|
|
3388
|
-
if (active
|
|
3389
|
-
if (active.length > 1)
|
|
3390
|
-
throw new HttpError(400, {
|
|
3391
|
-
detail: "Multiple active lab accounts are linked to your team for this lab; provide lab_account_id"
|
|
3392
|
-
});
|
|
3402
|
+
const active = candidates.find((entry) => entry.status === "active");
|
|
3403
|
+
if (active) return active;
|
|
3393
3404
|
throw new HttpError(400, { detail: "No active lab account is available for this lab" });
|
|
3394
3405
|
};
|
|
3395
3406
|
var renderLabAccount = (entry) => ({
|
|
@@ -4484,15 +4495,15 @@ var derivedFromRecording = (state, context, radius) => {
|
|
|
4484
4495
|
if (!context.url.pathname.endsWith("/psc/info")) return void 0;
|
|
4485
4496
|
for (const wider of ALLOWED_RADII) {
|
|
4486
4497
|
if (wider <= radius) continue;
|
|
4487
|
-
const
|
|
4488
|
-
if (!
|
|
4489
|
-
const body =
|
|
4490
|
-
if (
|
|
4498
|
+
const recorded2 = recordedAtRadius(state, context, wider);
|
|
4499
|
+
if (!recorded2) continue;
|
|
4500
|
+
const body = recorded2.body;
|
|
4501
|
+
if (recorded2.status !== 200 || !body || !Array.isArray(body.patient_service_centers))
|
|
4491
4502
|
return void 0;
|
|
4492
4503
|
const centers = body.patient_service_centers.filter(
|
|
4493
4504
|
(center) => Number(center.distance) < radius
|
|
4494
4505
|
);
|
|
4495
|
-
return responseFromCache({ ...
|
|
4506
|
+
return responseFromCache({ ...recorded2, body: { ...body, patient_service_centers: centers } });
|
|
4496
4507
|
}
|
|
4497
4508
|
return void 0;
|
|
4498
4509
|
};
|
|
@@ -9170,6 +9181,31 @@ var scenario = (corpus, withOrders) => {
|
|
|
9170
9181
|
}
|
|
9171
9182
|
);
|
|
9172
9183
|
}
|
|
9184
|
+
if (withOrders) {
|
|
9185
|
+
const byLab = /* @__PURE__ */ new Map();
|
|
9186
|
+
for (const account2 of corpus.labAccounts) {
|
|
9187
|
+
if (account2.status !== "active") continue;
|
|
9188
|
+
const lab = String(account2.lab).toLowerCase();
|
|
9189
|
+
byLab.set(lab, (byLab.get(lab) ?? 0) + 1);
|
|
9190
|
+
}
|
|
9191
|
+
for (const [lab, count] of byLab) {
|
|
9192
|
+
if (count < 2 || lab === String(openAccount?.lab ?? "").toLowerCase()) continue;
|
|
9193
|
+
const labTest = corpus.catalog.labTests.find(
|
|
9194
|
+
(test) => String(test.lab?.slug ?? "").toLowerCase() === lab
|
|
9195
|
+
);
|
|
9196
|
+
if (!labTest) continue;
|
|
9197
|
+
steps.push({
|
|
9198
|
+
name: `order.create for ${lab} with lab_account_id omitted (${count} active accounts)`,
|
|
9199
|
+
compare: "shape",
|
|
9200
|
+
request: (ctx) => ({
|
|
9201
|
+
method: "POST",
|
|
9202
|
+
path: "/v3/order",
|
|
9203
|
+
body: orderBody(ctx.userId, labTest.id)
|
|
9204
|
+
}),
|
|
9205
|
+
capture: captureExtraOrder
|
|
9206
|
+
});
|
|
9207
|
+
}
|
|
9208
|
+
}
|
|
9173
9209
|
if (withOrders) {
|
|
9174
9210
|
const labTestFor = (lab) => corpus.catalog.labTests.find(
|
|
9175
9211
|
(test) => String(test.lab?.slug ?? "").toLowerCase() === String(lab).toLowerCase()
|
|
@@ -9230,21 +9266,21 @@ var verifyAgainstReal = async (options) => {
|
|
|
9230
9266
|
progress(`drift: re-fetching ${picked.length} of ${keys.length} recorded observations`);
|
|
9231
9267
|
for (const key of picked) {
|
|
9232
9268
|
const path = key.slice("GET ".length);
|
|
9233
|
-
const
|
|
9234
|
-
if (!
|
|
9269
|
+
const recorded2 = options.corpus.observations[key];
|
|
9270
|
+
if (!recorded2) continue;
|
|
9235
9271
|
checked++;
|
|
9236
9272
|
try {
|
|
9237
9273
|
const live = await call("real", "GET", path);
|
|
9238
|
-
if (live.status !==
|
|
9274
|
+
if (live.status !== recorded2.status) {
|
|
9239
9275
|
stale++;
|
|
9240
9276
|
divergences.push({
|
|
9241
9277
|
check: `drift ${key}`,
|
|
9242
9278
|
kind: "status",
|
|
9243
9279
|
real: live.status,
|
|
9244
|
-
mock:
|
|
9280
|
+
mock: recorded2.status
|
|
9245
9281
|
});
|
|
9246
9282
|
} else {
|
|
9247
|
-
const at = firstDifference(live.body,
|
|
9283
|
+
const at = firstDifference(live.body, recorded2.body);
|
|
9248
9284
|
if (at) {
|
|
9249
9285
|
stale++;
|
|
9250
9286
|
divergences.push({
|
|
@@ -9252,7 +9288,7 @@ var verifyAgainstReal = async (options) => {
|
|
|
9252
9288
|
kind: "body",
|
|
9253
9289
|
at,
|
|
9254
9290
|
real: live.body,
|
|
9255
|
-
mock:
|
|
9291
|
+
mock: recorded2.body
|
|
9256
9292
|
});
|
|
9257
9293
|
}
|
|
9258
9294
|
}
|
|
@@ -9452,9 +9488,9 @@ var JunctionAPI = class {
|
|
|
9452
9488
|
* recorded from a team with no accounts leaves in place).
|
|
9453
9489
|
*/
|
|
9454
9490
|
seedLabAccounts() {
|
|
9455
|
-
const
|
|
9491
|
+
const recorded2 = (this.corpus?.labAccounts ?? []).map((entry) => labAccountFromClientFacing(entry, this.corpus?.teamId)).filter((entry) => entry !== void 0);
|
|
9456
9492
|
this.state.replaceLabAccounts(
|
|
9457
|
-
this.labAccountConfig ?? (
|
|
9493
|
+
this.labAccountConfig ?? (recorded2.length > 0 ? recorded2 : [...TEAM_LAB_ACCOUNTS])
|
|
9458
9494
|
);
|
|
9459
9495
|
}
|
|
9460
9496
|
/** The team this namespace answers as. */
|
|
@@ -9759,4 +9795,4 @@ export {
|
|
|
9759
9795
|
JunctionAPI,
|
|
9760
9796
|
LabAccountConflict
|
|
9761
9797
|
};
|
|
9762
|
-
//# sourceMappingURL=chunk-
|
|
9798
|
+
//# sourceMappingURL=chunk-GWXEPT6A.js.map
|