@crvouga/mockingbird-service-junction 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -4
- package/README.md +63 -6
- package/dist/{chunk-TG2EOH3J.js → chunk-2YNUCD2S.js} +22 -2
- package/dist/chunk-2YNUCD2S.js.map +7 -0
- package/dist/{chunk-MS774AJA.js → chunk-KEP42KKR.js} +170 -19
- package/dist/chunk-KEP42KKR.js.map +7 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +41 -6
- package/dist/index.js +7 -1
- package/dist/server.d.ts +29 -3
- package/dist/server.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-MS774AJA.js.map +0 -7
- package/dist/chunk-TG2EOH3J.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-junction
|
|
2
2
|
|
|
3
|
+
## 1.0.1 (2026-09-25)
|
|
4
|
+
|
|
5
|
+
### Fixes and improvements
|
|
6
|
+
|
|
7
|
+
- match the sandbox on lab_test_id orders, order rendering, platform preset, radii and billing default ([e5497c7](https://github.com/crvouga/mockingbird/commit/e5497c7b78e99595e3226679f8494e9e29677a7d))
|
|
8
|
+
|
|
3
9
|
## 1.0.0 (2026-09-24)
|
|
4
10
|
|
|
5
11
|
### ⚠️ Breaking changes
|
|
6
12
|
|
|
7
13
|
- vendor branding for every service; purge internal mocks and consumer names ([f689534](https://github.com/crvouga/mockingbird/commit/f6895343543550dafcb7d76b426cae7635e03226))
|
|
8
14
|
|
|
9
|
-
### Dependencies
|
|
10
|
-
|
|
11
|
-
- `@crvouga/mockingbird-service-sqlite`
|
|
12
|
-
|
|
13
15
|
## 0.8.0 (2026-09-23)
|
|
14
16
|
|
|
15
17
|
### Features
|
package/README.md
CHANGED
|
@@ -59,6 +59,7 @@ npx mockingbird-junction serve --help
|
|
|
59
59
|
| `--team-id <uuid>` | the corpus's team | The [team](#team-identity) the mock answers as |
|
|
60
60
|
| `--max-users <n>` | unlimited | Enforce the sandbox's [live-user cap](#sandbox-limits) |
|
|
61
61
|
| `--identity <strict\|adopt-users>` | `strict` | Unknown `user_id`s: 404, or [create on first use](#fixtures-and-identity) |
|
|
62
|
+
| `--default-billing-type <lab=type,…>` | `client_bill` | The `billing_type` an order for that lab gets when it omits one ([billing](#default-billing-type)) |
|
|
62
63
|
| `--fixtures <file>` | — | [Users and orders](#fixtures-and-identity) every namespace starts with |
|
|
63
64
|
| `--journal-size <n>` | `1000` | Requests each namespace's [journal](#is-this-the-mock) keeps |
|
|
64
65
|
| `--webhook-url <url>` / `--webhook-secret <whsec_…>` | off | Deliver [signed webhooks](#webhooks) (secret also from `MOCKINGBIRD_JUNCTION_WEBHOOK_SECRET`) |
|
|
@@ -250,17 +251,18 @@ Every Mockingbird service answers the same control surface, outside the vendor's
|
|
|
250
251
|
| `GET` / `POST` / `DELETE /__admin/faults` | List, add or clear [fault rules](#faults-and-error-shapes) (`DELETE ?id=` removes one) |
|
|
251
252
|
| `POST /__admin/faults/presets/{name}` | Add a named Junction fault; body may override `count`, `rate`, `operationId`… |
|
|
252
253
|
| `GET /__admin/metrics` | Requests by operation and status, fault count, and **unmatched routes** |
|
|
253
|
-
| `GET /__admin/orders` | Orders in the namespace with their current status |
|
|
254
|
+
| `GET /__admin/orders` | Orders in the namespace with their current status and the `lab_account_id` each was placed with (`null`: Junction's platform account) |
|
|
254
255
|
| `POST /__admin/orders/{id}/transition` | Move an order: `{ "to": "completed", "result": "abnormal" }` ([order control](#order-control)) |
|
|
255
256
|
| `GET` / `PUT /__admin/results/{id}` | Read or install an exact result payload / PDF for an order |
|
|
256
257
|
| `GET /__admin/result-fixtures` | The named results |
|
|
257
258
|
| `GET` / `PUT /__admin/lab-accounts` | Read or replace the namespace's [lab accounts](#lab-accounts) (`{ "accounts": [...], "presets": [...] }`; `{ "accounts": null }` restores the default) |
|
|
258
259
|
| `POST /__admin/lab-accounts` | Add one account; 409 if the id exists |
|
|
259
260
|
| `PATCH` / `DELETE /__admin/lab-accounts/{id}` | Merge fields into an account (`{ "status": "suspended" }`, `{ "states": [...] }`) / remove it; 404 if absent |
|
|
260
|
-
| `GET /__admin/lab-accounts/presets` | Every [preset](#lab-account-presets), with its full record |
|
|
261
|
+
| `GET /__admin/lab-accounts/presets` | Every [preset](#lab-account-presets), with its full record, plus `aliases` (deprecated names) and `synthetic` (presets that are not a recorded account) |
|
|
261
262
|
| `POST /__admin/lab-accounts/presets/{name}` | Add a preset account; body `{ "id": "…" }` overrides its id |
|
|
262
263
|
| `GET /__admin/team` | `{ "teamId" }` — the [team](#team-identity) the namespace answers as |
|
|
263
264
|
| `GET` / `PUT /__admin/limits` | Read or change the namespace's [sandbox limits](#sandbox-limits) |
|
|
265
|
+
| `GET` / `PUT /__admin/default-billing-types` | Read or replace `{ "defaultBillingTypes": { "<lab slug>": "<billing_type>" } }` ([billing](#default-billing-type)); `{}` restores `client_bill` |
|
|
264
266
|
| `GET` / `PUT /__admin/identity` | Read or set `{ "mode": "strict" \| "adopt-users" }` ([identity](#fixtures-and-identity)) |
|
|
265
267
|
| `POST /__admin/users`, `POST /__admin/users/bulk` | Insert a user (or `{ "users": [...] }`, all or none) with a chosen `user_id`; 409 on a duplicate id or `client_user_id` |
|
|
266
268
|
| `DELETE /__admin/users/{id}` | Remove a user outright, leaving no deletion tombstone |
|
|
@@ -322,6 +324,16 @@ loudly instead of silently disagreeing with production. A malformed ZIP still ge
|
|
|
322
324
|
instead — right for property tests that walk random ZIPs, wrong for a suite standing in for
|
|
323
325
|
production.
|
|
324
326
|
|
|
327
|
+
A covered ZIP is answered from its recordings, never next to them. The corpus records `area/info`
|
|
328
|
+
and `psc/info` at the default radius (25 miles, so `radius=25` and no `radius` are the same read)
|
|
329
|
+
and at 100 miles. A `psc/info` read at another radius is the smallest wider recording filtered to
|
|
330
|
+
`distance < radius`: the sandbox lists the nearest sites (at most 30), and `distance` is rounded, so
|
|
331
|
+
this reproduces every recorded 25-mile answer from its 100-mile one exactly. An `area/info` read at
|
|
332
|
+
a radius with no recording, or any read with parameters the corpus did not record (for example
|
|
333
|
+
`capabilities`), gets the same 424, because `within_radius` counts cannot be derived. In
|
|
334
|
+
`--geo synthetic` the derivation still applies, and only reads with nothing to derive from are
|
|
335
|
+
invented.
|
|
336
|
+
|
|
325
337
|
## Lab accounts
|
|
326
338
|
|
|
327
339
|
`create_order` routes, accepts and rejects `lab_account_id` against the team's lab accounts,
|
|
@@ -330,6 +342,10 @@ the ordered lab and be active; with no id, one active account for the lab is sel
|
|
|
330
342
|
an error, and a lab with none falls back to Junction's platform account. The billing type must be
|
|
331
343
|
allowed in the patient's state by the account's `allowed_billing`.
|
|
332
344
|
|
|
345
|
+
Junction's order (`ClientFacingOrder`) has no `lab_account_id`, so neither does the mock's: not on
|
|
346
|
+
the create response, `GET /v3/order/{id}`, `GET /v3/orders` or webhooks. The account an order was
|
|
347
|
+
placed with is reported only by `GET /__admin/orders` and `instance().orderLabAccount(orderId)`.
|
|
348
|
+
|
|
333
349
|
The accounts come from, in order: `labAccounts` / `--lab-accounts`, else the corpus (a pulled
|
|
334
350
|
corpus carries your team's real accounts), else built-in fixtures. Configure them as
|
|
335
351
|
`ClientFacingLabAccount`s, with `states` as shorthand for client-bill states:
|
|
@@ -357,6 +373,28 @@ Rejections keep Junction's body shape, `400 {"detail": "Lab account is not assoc
|
|
|
357
373
|
Availability reads that take a `lab_account_id` (`GET /v3/order/area/info`) check it against the
|
|
358
374
|
same live list, so an id `create_order` accepts is never a 404 there.
|
|
359
375
|
|
|
376
|
+
### Default billing type
|
|
377
|
+
|
|
378
|
+
An order that omits `billing_type` is evaluated as `client_bill`, Junction's documented default.
|
|
379
|
+
A sandbox team has been observed evaluating BioReference orders as `patient_bill_passthrough`
|
|
380
|
+
instead (whether that is a team, lab or account setting is not visible from outside), so the
|
|
381
|
+
default is configurable per lab slug:
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
import { createRuntime } from "@crvouga/mockingbird-service-junction"
|
|
385
|
+
|
|
386
|
+
const junction = createRuntime({ defaultBillingTypes: { bioreference: "patient_bill_passthrough" } })
|
|
387
|
+
console.log(junction.instance().defaultBillingTypes) // { bioreference: "patient_bill_passthrough" }
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
`--default-billing-type bioreference=patient_bill_passthrough` and
|
|
391
|
+
`PUT /__admin/default-billing-types` (per namespace, kept across its reset) do the same. When the
|
|
392
|
+
default resolves to a billing type the account lacks, the rejection is worded as the sandbox words
|
|
393
|
+
it, `400 {"detail": "Lab 13 does not support billing type patient_bill_passthrough"}`; an explicit
|
|
394
|
+
`billing_type` the account lacks keeps `Billing type … is not supported by the lab account used for
|
|
395
|
+
this order`. `verify --orders` orders through each active account with `billing_type` omitted, so a
|
|
396
|
+
run against your team shows which default it uses.
|
|
397
|
+
|
|
360
398
|
### Changing accounts at runtime
|
|
361
399
|
|
|
362
400
|
Each namespace has its own layout, kept across its own reset. Change one account at a time, or
|
|
@@ -381,7 +419,8 @@ declare it at boot with no admin call:
|
|
|
381
419
|
### Lab-account presets
|
|
382
420
|
|
|
383
421
|
Named after what they model; ids are `deterministicUuid("junction:lab-account:<name>")` unless
|
|
384
|
-
given.
|
|
422
|
+
given. `*_platform` presets are Junction's own accounts (`org_id: null`, `team_id_allowlist: []`);
|
|
423
|
+
the other active presets are customer-owned (an Org's account linked to the team). Where a preset copies an account recorded from a real BioReference-linked sandbox team
|
|
385
424
|
(2026-09-21), its billing states are the recording's. `PLATFORM_ACCOUNT_STATES` is every state
|
|
386
425
|
but NJ, NY and RI (47).
|
|
387
426
|
|
|
@@ -389,13 +428,16 @@ but NJ, NY and RI (47).
|
|
|
389
428
|
| --- | --- | --- | --- | --- | --- |
|
|
390
429
|
| `bioreference_ny_nj_delegated` | `bioreference` | `order_delegated` | `client_bill`: NY, NJ | the team | requested shape, **not** a recorded account |
|
|
391
430
|
| `bioreference_delegated_multi_state` | `bioreference` | `order_delegated` | `client_bill`: every state but NY, NJ (48) | the team | recorded |
|
|
392
|
-
| `
|
|
431
|
+
| `bioreference_platform` | `bioreference` | `not_delegated` | `client_bill`: `PLATFORM_ACCOUNT_STATES` | `[]` | recorded ("Junction BioReference Account") |
|
|
393
432
|
| `bioreference_patient_bill_passthrough` | `bioreference` | `not_delegated` | `patient_bill_passthrough`: NJ, NY | the team | recorded |
|
|
394
433
|
| `quest_platform` / `labcorp_platform` | `quest` / `labcorp` | `not_delegated` | `client_bill`: `PLATFORM_ACCOUNT_STATES` | `[]` | recorded ("Junction Quest/Labcorp Account") |
|
|
395
434
|
| `suspended_bioreference` / `suspended_quest` / `suspended_labcorp` | as above | `not_delegated` | as the platform accounts | `[]` | the platform shapes with `status: "suspended"` |
|
|
396
435
|
|
|
397
436
|
Every preset is `status: "active"` unless named `suspended_*`. `GET /__admin/lab-accounts/presets`
|
|
398
|
-
returns the full records
|
|
437
|
+
returns the full records, the deprecated `aliases`, and the `synthetic` presets (not a recorded
|
|
438
|
+
account). `bioreference_customer_multi_state` is a deprecated alias of `bioreference_platform`: it
|
|
439
|
+
was misnamed, since the record is Junction's platform account, not a customer-owned one. It still
|
|
440
|
+
works and keeps its own default id (`presetAccountId("bioreference_customer_multi_state")`).
|
|
399
441
|
|
|
400
442
|
### Team identity
|
|
401
443
|
|
|
@@ -416,6 +458,15 @@ active accounts for a lab with the id omitted — behaves the same live is **not
|
|
|
416
458
|
`verify --orders` checks it whenever the corpus has such an account; until a run records the
|
|
417
459
|
answer, a suite that depends on it should run against the sandbox too.
|
|
418
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).
|
|
469
|
+
|
|
419
470
|
## Sandbox limits
|
|
420
471
|
|
|
421
472
|
The mock exists to escape the sandbox's restrictions, so it enforces none of them unless a test
|
|
@@ -674,7 +725,7 @@ Main entry (`@crvouga/mockingbird-service-junction`, runtime-neutral):
|
|
|
674
725
|
|
|
675
726
|
| Export | Description |
|
|
676
727
|
| --- | --- |
|
|
677
|
-
| `createRuntime` | `(options?: JunctionRuntimeOptions) => JunctionRuntime` — the served mock (health, admin, namespaces, clock, faults, metrics, journal, webhooks) as one `fetch`. Options: `corpus`, `geo`, `labAccounts`, `teamId`, `limits`, `identity`, `fixtures`, `journalSize`, `webhooks`, `onWebhook`, `sqlite`, `clock`, `seed`, `adminKey`, `onLog`. |
|
|
728
|
+
| `createRuntime` | `(options?: JunctionRuntimeOptions) => JunctionRuntime` — the served mock (health, admin, namespaces, clock, faults, metrics, journal, webhooks) as one `fetch`. Options: `corpus`, `geo`, `labAccounts`, `teamId`, `limits`, `identity`, `defaultBillingTypes`, `fixtures`, `journalSize`, `webhooks`, `onWebhook`, `sqlite`, `clock`, `seed`, `adminKey`, `onLog`. |
|
|
678
729
|
| `JunctionAPI` | Class. `new JunctionAPI(options?)`: one namespace's mock, implementing `fetch(request: Request): Promise<Response>`. |
|
|
679
730
|
| `JUNCTION_NAMESPACE` | `"junction"` — the default namespace's storage key when sharing a `sqlite` client. |
|
|
680
731
|
| `document` | The vendored Junction OpenAPI document (Mockingbird subset) that drives routing. |
|
|
@@ -691,6 +742,9 @@ Main entry (`@crvouga/mockingbird-service-junction`, runtime-neutral):
|
|
|
691
742
|
| `OTHER_TEAM_ID` | A team that is not the mock's, for fixtures modelling another team's account. |
|
|
692
743
|
| `isLinkedToTeam` | `(account, teamId) => boolean` — the allowlist rule (`[]` counts as linked). |
|
|
693
744
|
| `LAB_ACCOUNT_PRESETS` | The [lab-account presets](#lab-account-presets), without ids. |
|
|
745
|
+
| `LAB_ACCOUNT_PRESET_ALIASES` | Deprecated preset names → the preset each stands for (`bioreference_customer_multi_state` → `bioreference_platform`). |
|
|
746
|
+
| `SYNTHETIC_LAB_ACCOUNT_PRESETS` | Presets that model a requested shape, not a recorded account. |
|
|
747
|
+
| `BILLING_TYPES` | The `billing_type` values `create_order` accepts. |
|
|
694
748
|
| `presetAccountId` | `(name) => string` — a preset's default id. |
|
|
695
749
|
| `PLATFORM_ACCOUNT_STATES` / `DELEGATED_ACCOUNT_STATES` | The recorded 47- and 48-state client-bill lists. |
|
|
696
750
|
| `LabAccountConflict` | Thrown by `addLabAccount` for an id that exists. |
|
|
@@ -748,8 +802,10 @@ Main entry (`@crvouga/mockingbird-service-junction`, runtime-neutral):
|
|
|
748
802
|
| `teamId` | The team this namespace answers as. |
|
|
749
803
|
| `limits` / `configureLimits(input)` | Read / change the sandbox limits; kept across `reset()`. |
|
|
750
804
|
| `identity` | `"strict" \| "adopt-users"`, settable. |
|
|
805
|
+
| `defaultBillingTypes` | Lab slug → the `billing_type` an order gets when it omits one ([default billing type](#default-billing-type)); settable, kept across `reset()`. |
|
|
751
806
|
| `insertUsers(users)` / `insertOrders(orders, { emitWebhooks? })` / `importFixtures(fixtures, options?)` / `hardDeleteUser(id)` | The [fixture backdoor](#fixtures-and-identity). |
|
|
752
807
|
| `order(id)` / `orders()` | Read orders. |
|
|
808
|
+
| `orderLabAccount(id)` | The lab account id an order was placed with (`null`: Junction's platform account; `undefined`: unknown order). |
|
|
753
809
|
| `transitionOrder(id, status, { now, flags? })` | Move an order to a full status and publish its webhook. |
|
|
754
810
|
| `installResultFixture(orderId, fixture)` / `resultFixture(orderId)` | Serve an exact result payload / PDF for an order. |
|
|
755
811
|
| `seedFrom(source, observations?)` | `Promise<SeedReport>` — copy users, orders, appointments, catalog and labs from a real Junction environment. |
|
|
@@ -772,6 +828,7 @@ type JunctionAPIOptions = {
|
|
|
772
828
|
teamId?: string // default: the corpus's recorded team, else MOCK_TEAM_ID
|
|
773
829
|
limits?: { maxUsers?: number | null; simulateRequiresSandbox?: boolean; rateLimitPerSecond?: number | null }
|
|
774
830
|
identity?: "strict" | "adopt-users" // default "strict"
|
|
831
|
+
defaultBillingTypes?: Record<string, BillingType> // lab slug → billing_type when omitted; default client_bill
|
|
775
832
|
fixtures?: { users?: UserFixture[]; orders?: OrderFixture[] } // loaded now and on every reset()
|
|
776
833
|
onWebhook?: WebhookPublisher // called with every recorded event
|
|
777
834
|
webhook?: JunctionWebhookOptions
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
IDENTITY_MODES,
|
|
3
3
|
createRuntime,
|
|
4
|
+
defaultBillingTypesFrom,
|
|
4
5
|
pullCorpus
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-KEP42KKR.js";
|
|
6
7
|
import {
|
|
7
8
|
defaultCorpus
|
|
8
9
|
} from "./chunk-C2UGIPPM.js";
|
|
@@ -354,6 +355,18 @@ var retryDelays = (value) => {
|
|
|
354
355
|
}
|
|
355
356
|
return delays;
|
|
356
357
|
};
|
|
358
|
+
var billingDefaults = (value) => {
|
|
359
|
+
if (value === void 0) return void 0;
|
|
360
|
+
const entries = value.split(",").map((part) => part.split("=").map((side) => side.trim()));
|
|
361
|
+
if (entries.some((entry) => entry.length !== 2 || !entry[0] || !entry[1])) {
|
|
362
|
+
throw new Error(`--default-billing-type must be lab=billing_type pairs (got ${value})`);
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
return defaultBillingTypesFrom(Object.fromEntries(entries));
|
|
366
|
+
} catch (error) {
|
|
367
|
+
throw new Error(`--default-billing-type: ${error.message}`);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
357
370
|
var serveTarget = {
|
|
358
371
|
name: "junction",
|
|
359
372
|
defaultPort: DEFAULT_PORT,
|
|
@@ -390,6 +403,11 @@ var serveTarget = {
|
|
|
390
403
|
description: "Unknown user ids: 404 (strict) or create on first use",
|
|
391
404
|
default: "strict"
|
|
392
405
|
},
|
|
406
|
+
"default-billing-type": {
|
|
407
|
+
type: "string",
|
|
408
|
+
value: "<lab=type,\u2026>",
|
|
409
|
+
description: "billing_type an order for that lab gets when it omits one (default: client_bill), e.g. bioreference=patient_bill_passthrough"
|
|
410
|
+
},
|
|
393
411
|
fixtures: {
|
|
394
412
|
type: "string",
|
|
395
413
|
value: "<file>",
|
|
@@ -459,6 +477,7 @@ var serveTarget = {
|
|
|
459
477
|
if (identity !== void 0 && !IDENTITY_MODES.includes(identity)) {
|
|
460
478
|
throw new Error(`--identity must be one of ${IDENTITY_MODES.join(", ")} (got ${identity})`);
|
|
461
479
|
}
|
|
480
|
+
const defaultBillingTypes = billingDefaults(asString2(values["default-billing-type"]));
|
|
462
481
|
const webhookUrl = asString2(values["webhook-url"]);
|
|
463
482
|
const webhookSecret = asString2(values["webhook-secret"]) ?? process.env.MOCKINGBIRD_JUNCTION_WEBHOOK_SECRET;
|
|
464
483
|
if (webhookUrl !== void 0 && webhookSecret === void 0) {
|
|
@@ -474,6 +493,7 @@ var serveTarget = {
|
|
|
474
493
|
...teamId !== void 0 ? { teamId } : {},
|
|
475
494
|
...maxUsers !== void 0 ? { limits: { maxUsers } } : {},
|
|
476
495
|
...identity !== void 0 ? { identity } : {},
|
|
496
|
+
...defaultBillingTypes ? { defaultBillingTypes } : {},
|
|
477
497
|
...journalSize !== void 0 ? { journalSize } : {},
|
|
478
498
|
...webhookUrl !== void 0 && webhookSecret !== void 0 ? {
|
|
479
499
|
webhooks: {
|
|
@@ -513,4 +533,4 @@ export {
|
|
|
513
533
|
createServer2 as createServer,
|
|
514
534
|
serveTarget
|
|
515
535
|
};
|
|
516
|
-
//# sourceMappingURL=chunk-
|
|
536
|
+
//# sourceMappingURL=chunk-2YNUCD2S.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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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",
|
|
6
|
+
"names": ["readFile", "readFile", "createServer", "asString"]
|
|
7
|
+
}
|