@crvouga/mockingbird-service-junction 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -5
- package/README.md +222 -21
- package/dist/{chunk-MD4KYEIM.js → chunk-C2UGIPPM.js} +2 -2
- package/dist/{chunk-4C6VYRJN.js → chunk-ISDNA2TU.js} +8 -3
- package/dist/chunk-ISDNA2TU.js.map +7 -0
- package/dist/{chunk-KKNULV7X.js → chunk-KLBGX23P.js} +76 -8
- package/dist/chunk-KLBGX23P.js.map +7 -0
- package/dist/{chunk-MA3HSUVD.js → chunk-NFWUEPW4.js} +1856 -778
- package/dist/chunk-NFWUEPW4.js.map +7 -0
- package/dist/cli.js +4 -4
- package/dist/corpus.d.ts +10 -2
- package/dist/corpus.js +2 -2
- package/dist/index.d.ts +322 -49
- package/dist/index.js +34 -2
- package/dist/server.d.ts +242 -10
- package/dist/server.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-4C6VYRJN.js.map +0 -7
- package/dist/chunk-KKNULV7X.js.map +0 -7
- package/dist/chunk-MA3HSUVD.js.map +0 -7
- /package/dist/{chunk-MD4KYEIM.js.map → chunk-C2UGIPPM.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -3,11 +3,23 @@ import {
|
|
|
3
3
|
AVAILABILITY_START_DATE,
|
|
4
4
|
COVERAGE_ZIPS,
|
|
5
5
|
DEFAULT_JUNCTION_BASE_URL,
|
|
6
|
+
DEFAULT_LIMITS,
|
|
7
|
+
DELEGATED_ACCOUNT_STATES,
|
|
6
8
|
FAULT_PRESETS,
|
|
9
|
+
FixtureError,
|
|
10
|
+
IDENTITY_MODES,
|
|
7
11
|
JUNCTION_NAMESPACE,
|
|
8
12
|
JunctionAPI,
|
|
13
|
+
KNOWN_HEADER,
|
|
14
|
+
LAB_ACCOUNT_PRESETS,
|
|
15
|
+
LabAccountConflict,
|
|
16
|
+
MISS_HEADER,
|
|
17
|
+
MOCK_TEAM_ID,
|
|
18
|
+
ORDER_NOT_FOUND,
|
|
9
19
|
ORDER_STATUSES_BY_METHOD,
|
|
20
|
+
OTHER_TEAM_ID,
|
|
10
21
|
PHLEBOTOMY_AVAILABILITY_ZIPS,
|
|
22
|
+
PLATFORM_ACCOUNT_STATES,
|
|
11
23
|
PSC_AVAILABILITY_ZIPS,
|
|
12
24
|
PSC_LAB_IDS,
|
|
13
25
|
RESULT_FIXTURES,
|
|
@@ -23,38 +35,55 @@ import {
|
|
|
23
35
|
diffCorpus,
|
|
24
36
|
document,
|
|
25
37
|
fingerprintCorpus,
|
|
38
|
+
isLinkedToTeam,
|
|
26
39
|
isSandboxKey,
|
|
27
40
|
labAccountFromInput,
|
|
28
41
|
observationCacheKey,
|
|
29
42
|
operationIds,
|
|
30
43
|
prefetchCoverageObservations,
|
|
44
|
+
presetAccountId,
|
|
31
45
|
pullCorpus,
|
|
32
46
|
reshapeCoverageGeoCommand,
|
|
33
47
|
resolveTransition,
|
|
48
|
+
sandboxUserQuotaBody,
|
|
34
49
|
signSvix,
|
|
35
50
|
supportedOperationIds,
|
|
36
51
|
verifyAgainstReal,
|
|
37
52
|
verifySvix
|
|
38
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-NFWUEPW4.js";
|
|
39
54
|
import {
|
|
40
55
|
SEALED_CORPUS_VERSION,
|
|
56
|
+
SUPPORTED_SEALED_CORPUS_VERSIONS,
|
|
41
57
|
parseSealedCorpus
|
|
42
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-ISDNA2TU.js";
|
|
43
59
|
export {
|
|
44
60
|
AVAILABILITY_ADDRESS,
|
|
45
61
|
AVAILABILITY_START_DATE,
|
|
46
62
|
COVERAGE_ZIPS,
|
|
47
63
|
DEFAULT_JUNCTION_BASE_URL,
|
|
64
|
+
DEFAULT_LIMITS,
|
|
65
|
+
DELEGATED_ACCOUNT_STATES,
|
|
48
66
|
FAULT_PRESETS,
|
|
67
|
+
FixtureError,
|
|
68
|
+
IDENTITY_MODES,
|
|
49
69
|
JUNCTION_NAMESPACE,
|
|
50
70
|
JunctionAPI,
|
|
71
|
+
KNOWN_HEADER,
|
|
72
|
+
LAB_ACCOUNT_PRESETS,
|
|
73
|
+
LabAccountConflict,
|
|
74
|
+
MISS_HEADER,
|
|
75
|
+
MOCK_TEAM_ID,
|
|
76
|
+
ORDER_NOT_FOUND,
|
|
51
77
|
ORDER_STATUSES_BY_METHOD,
|
|
78
|
+
OTHER_TEAM_ID,
|
|
52
79
|
PHLEBOTOMY_AVAILABILITY_ZIPS,
|
|
80
|
+
PLATFORM_ACCOUNT_STATES,
|
|
53
81
|
PSC_AVAILABILITY_ZIPS,
|
|
54
82
|
PSC_LAB_IDS,
|
|
55
83
|
RESULT_FIXTURES,
|
|
56
84
|
SANDBOX_KEY_PREFIXES,
|
|
57
85
|
SEALED_CORPUS_VERSION,
|
|
86
|
+
SUPPORTED_SEALED_CORPUS_VERSIONS,
|
|
58
87
|
TEAM_LAB_ACCOUNTS,
|
|
59
88
|
TRANSITION_ALIASES,
|
|
60
89
|
UNKNOWN_ZIP_ERROR_TYPE,
|
|
@@ -66,15 +95,18 @@ export {
|
|
|
66
95
|
diffCorpus,
|
|
67
96
|
document,
|
|
68
97
|
fingerprintCorpus,
|
|
98
|
+
isLinkedToTeam,
|
|
69
99
|
isSandboxKey,
|
|
70
100
|
labAccountFromInput,
|
|
71
101
|
observationCacheKey,
|
|
72
102
|
operationIds,
|
|
73
103
|
parseSealedCorpus,
|
|
74
104
|
prefetchCoverageObservations,
|
|
105
|
+
presetAccountId,
|
|
75
106
|
pullCorpus,
|
|
76
107
|
reshapeCoverageGeoCommand,
|
|
77
108
|
resolveTransition,
|
|
109
|
+
sandboxUserQuotaBody,
|
|
78
110
|
signSvix,
|
|
79
111
|
supportedOperationIds,
|
|
80
112
|
verifyAgainstReal,
|
package/dist/server.d.ts
CHANGED
|
@@ -148,6 +148,10 @@ type RequestLog$1 = {
|
|
|
148
148
|
unmatched: boolean;
|
|
149
149
|
/** Set when a fault rule produced the response. */
|
|
150
150
|
faultId?: string;
|
|
151
|
+
/** Resource ids the handler touched (`userId`, `orderId`, …), when the service reports them. */
|
|
152
|
+
ids?: Record<string, string>;
|
|
153
|
+
/** Set when the service created a resource the request referred to but that did not exist. */
|
|
154
|
+
adopted?: boolean;
|
|
151
155
|
};
|
|
152
156
|
type MetricsReport$1 = {
|
|
153
157
|
requests: number;
|
|
@@ -173,6 +177,28 @@ type Metrics$1 = {
|
|
|
173
177
|
reset(): void;
|
|
174
178
|
};
|
|
175
179
|
|
|
180
|
+
/** One journal entry: a request log stamped with when (on the mock clock) it was handled. */
|
|
181
|
+
type JournalEntry$1 = RequestLog$1 & {
|
|
182
|
+
at: string;
|
|
183
|
+
};
|
|
184
|
+
type JournalQuery$1 = {
|
|
185
|
+
/** Only this namespace. Omit for every namespace, oldest first across all of them. */
|
|
186
|
+
namespace?: string;
|
|
187
|
+
operationId?: string;
|
|
188
|
+
status?: number;
|
|
189
|
+
/** Only entries at or after this instant (epoch ms). */
|
|
190
|
+
since?: number;
|
|
191
|
+
/** At most this many, the most recent kept. */
|
|
192
|
+
limit?: number;
|
|
193
|
+
};
|
|
194
|
+
type Journal$1 = {
|
|
195
|
+
readonly size: number;
|
|
196
|
+
record(entry: JournalEntry$1): void;
|
|
197
|
+
list(query?: JournalQuery$1): JournalEntry$1[];
|
|
198
|
+
/** Forget one namespace's entries, or every namespace's. */
|
|
199
|
+
clear(namespace?: string): void;
|
|
200
|
+
};
|
|
201
|
+
|
|
176
202
|
/**
|
|
177
203
|
* A point-in-time copy of everything a service namespace holds.
|
|
178
204
|
*
|
|
@@ -215,6 +241,7 @@ type ServiceRuntime$1<T extends ServiceInstance$1> = FetchAPI$2 & {
|
|
|
215
241
|
readonly clock: Clock$1;
|
|
216
242
|
readonly faults: FaultRegistry$1;
|
|
217
243
|
readonly metrics: Metrics$1;
|
|
244
|
+
readonly journal: Journal$1;
|
|
218
245
|
readonly rng: Rng$1;
|
|
219
246
|
/** The instance behind `namespace` (the default one when omitted), created on first use. */
|
|
220
247
|
instance(namespace?: string): T;
|
|
@@ -410,6 +437,10 @@ type RequestLog = {
|
|
|
410
437
|
unmatched: boolean;
|
|
411
438
|
/** Set when a fault rule produced the response. */
|
|
412
439
|
faultId?: string;
|
|
440
|
+
/** Resource ids the handler touched (`userId`, `orderId`, …), when the service reports them. */
|
|
441
|
+
ids?: Record<string, string>;
|
|
442
|
+
/** Set when the service created a resource the request referred to but that did not exist. */
|
|
443
|
+
adopted?: boolean;
|
|
413
444
|
};
|
|
414
445
|
type MetricsReport = {
|
|
415
446
|
requests: number;
|
|
@@ -435,6 +466,28 @@ type Metrics = {
|
|
|
435
466
|
reset(): void;
|
|
436
467
|
};
|
|
437
468
|
|
|
469
|
+
/** One journal entry: a request log stamped with when (on the mock clock) it was handled. */
|
|
470
|
+
type JournalEntry = RequestLog & {
|
|
471
|
+
at: string;
|
|
472
|
+
};
|
|
473
|
+
type JournalQuery = {
|
|
474
|
+
/** Only this namespace. Omit for every namespace, oldest first across all of them. */
|
|
475
|
+
namespace?: string;
|
|
476
|
+
operationId?: string;
|
|
477
|
+
status?: number;
|
|
478
|
+
/** Only entries at or after this instant (epoch ms). */
|
|
479
|
+
since?: number;
|
|
480
|
+
/** At most this many, the most recent kept. */
|
|
481
|
+
limit?: number;
|
|
482
|
+
};
|
|
483
|
+
type Journal = {
|
|
484
|
+
readonly size: number;
|
|
485
|
+
record(entry: JournalEntry): void;
|
|
486
|
+
list(query?: JournalQuery): JournalEntry[];
|
|
487
|
+
/** Forget one namespace's entries, or every namespace's. */
|
|
488
|
+
clear(namespace?: string): void;
|
|
489
|
+
};
|
|
490
|
+
|
|
438
491
|
/**
|
|
439
492
|
* A point-in-time copy of everything a service namespace holds.
|
|
440
493
|
*
|
|
@@ -477,6 +530,7 @@ type ServiceRuntime<T extends ServiceInstance> = FetchAPI$1 & {
|
|
|
477
530
|
readonly clock: Clock;
|
|
478
531
|
readonly faults: FaultRegistry;
|
|
479
532
|
readonly metrics: Metrics;
|
|
533
|
+
readonly journal: Journal;
|
|
480
534
|
readonly rng: Rng;
|
|
481
535
|
/** The instance behind `namespace` (the default one when omitted), created on first use. */
|
|
482
536
|
instance(namespace?: string): T;
|
|
@@ -582,6 +636,21 @@ type LabTestRecord = {
|
|
|
582
636
|
worst_case_tat_days: number | null;
|
|
583
637
|
};
|
|
584
638
|
|
|
639
|
+
type JunctionLimits = {
|
|
640
|
+
/**
|
|
641
|
+
* Live users a team may hold; creating one more answers the sandbox's
|
|
642
|
+
* "maximum of 50 Sandbox users" 400. Deleted users free their slot. `null`: unlimited.
|
|
643
|
+
*/
|
|
644
|
+
maxUsers: number | null;
|
|
645
|
+
/** Refuse `POST /v3/order/{id}/test` unless the API key is a sandbox key (`sk_us_…`/`sk_eu_…`). */
|
|
646
|
+
simulateRequiresSandbox: boolean;
|
|
647
|
+
/** Vendor requests per second per namespace before a 429. `null`: unlimited. */
|
|
648
|
+
rateLimitPerSecond: number | null;
|
|
649
|
+
};
|
|
650
|
+
type JunctionLimitsInput = {
|
|
651
|
+
[K in keyof JunctionLimits]?: JunctionLimits[K] | undefined;
|
|
652
|
+
};
|
|
653
|
+
|
|
585
654
|
type GetCacheEntry = {
|
|
586
655
|
status: number;
|
|
587
656
|
headers: Record<string, string>;
|
|
@@ -602,6 +671,25 @@ type WebhookDeliveryAttempt = {
|
|
|
602
671
|
timeout_ms: number;
|
|
603
672
|
acknowledged: boolean;
|
|
604
673
|
};
|
|
674
|
+
type UserRecord = {
|
|
675
|
+
user_id: string;
|
|
676
|
+
team_id: string;
|
|
677
|
+
client_user_id: string;
|
|
678
|
+
created_on: string;
|
|
679
|
+
connected_sources: unknown[];
|
|
680
|
+
fallback_time_zone: {
|
|
681
|
+
id: string;
|
|
682
|
+
source_slug: string;
|
|
683
|
+
updated_at: string;
|
|
684
|
+
} | null;
|
|
685
|
+
fallback_birth_date: {
|
|
686
|
+
value: string;
|
|
687
|
+
source_slug: string;
|
|
688
|
+
updated_at: string;
|
|
689
|
+
} | null;
|
|
690
|
+
ingestion_start: string | null;
|
|
691
|
+
ingestion_end: string | null;
|
|
692
|
+
};
|
|
605
693
|
type OrderEventRecord = {
|
|
606
694
|
id: number;
|
|
607
695
|
created_at: string;
|
|
@@ -707,6 +795,62 @@ type JunctionWebhookOptions = {
|
|
|
707
795
|
timeoutMs?: number;
|
|
708
796
|
};
|
|
709
797
|
|
|
798
|
+
/**
|
|
799
|
+
* The test backdoor: users and orders put straight into a namespace, with ids a suite
|
|
800
|
+
* chooses — the Junction `user_id`s a database fixture already carries, or an order
|
|
801
|
+
* already in some status — plus the lenient identity mode that adopts unknown users.
|
|
802
|
+
*
|
|
803
|
+
* Everything inserted here goes through the records `create_user` and `create_order`
|
|
804
|
+
* build, so no read path can tell a fixture from an API-created resource.
|
|
805
|
+
*/
|
|
806
|
+
|
|
807
|
+
/** How requests referring to a user the namespace does not hold are answered. */
|
|
808
|
+
type IdentityMode = "strict" | "adopt-users";
|
|
809
|
+
type UserFixture = {
|
|
810
|
+
/** Default: a fresh id, as `create_user` would mint. */
|
|
811
|
+
user_id?: string;
|
|
812
|
+
client_user_id: string;
|
|
813
|
+
/** ISO-8601. Default: the mock clock's now. */
|
|
814
|
+
created_on?: string;
|
|
815
|
+
/** An IANA zone, or the full `{ id, source_slug, updated_at }` object. */
|
|
816
|
+
fallback_time_zone?: string | UserRecord["fallback_time_zone"];
|
|
817
|
+
/** `YYYY-MM-DD`, or the full `{ value, source_slug, updated_at }` object. */
|
|
818
|
+
fallback_birth_date?: string | UserRecord["fallback_birth_date"];
|
|
819
|
+
ingestion_start?: string | null;
|
|
820
|
+
ingestion_end?: string | null;
|
|
821
|
+
};
|
|
822
|
+
type OrderFixture = {
|
|
823
|
+
/** Default: a fresh id, as `create_order` would mint. */
|
|
824
|
+
order_id?: string;
|
|
825
|
+
user_id: string;
|
|
826
|
+
lab_test_id: string;
|
|
827
|
+
lab_account_id?: string;
|
|
828
|
+
/** A transition target (`completed`, `at_lab`, or a full status). Default: just ordered. */
|
|
829
|
+
status?: string;
|
|
830
|
+
/** Default: the lab test's own method. */
|
|
831
|
+
collection_method?: string;
|
|
832
|
+
patient_details?: Record<string, unknown>;
|
|
833
|
+
patient_address?: Record<string, unknown>;
|
|
834
|
+
billing_type?: string;
|
|
835
|
+
icd_codes?: string[] | null;
|
|
836
|
+
passthrough?: string | null;
|
|
837
|
+
clinical_notes?: string | null;
|
|
838
|
+
/** ISO-8601. Default: the mock clock's now. */
|
|
839
|
+
created_at?: string;
|
|
840
|
+
/** Results served for the order, as `PUT /__admin/results/{id}` takes them. */
|
|
841
|
+
result_fixture?: Omit<ResultFixture, "name"> & {
|
|
842
|
+
name?: string;
|
|
843
|
+
};
|
|
844
|
+
};
|
|
845
|
+
type JunctionFixtures = {
|
|
846
|
+
users?: readonly UserFixture[];
|
|
847
|
+
orders?: readonly OrderFixture[];
|
|
848
|
+
};
|
|
849
|
+
type InsertOptions = {
|
|
850
|
+
/** Publish the webhooks the equivalent API calls would. Default `false`. */
|
|
851
|
+
emitWebhooks?: boolean;
|
|
852
|
+
};
|
|
853
|
+
|
|
710
854
|
/**
|
|
711
855
|
* Anything that can answer a Fetch `Request` with a `Response`.
|
|
712
856
|
*
|
|
@@ -739,15 +883,39 @@ type LabAccountInput = {
|
|
|
739
883
|
allowed_billing?: Record<string, readonly string[]>;
|
|
740
884
|
/** Shorthand for `allowed_billing: { client_bill: states }`. Default: every state. */
|
|
741
885
|
states?: readonly string[];
|
|
742
|
-
/** Teams the account is linked to. Default: the
|
|
886
|
+
/** Teams the account is linked to. Default: the configured team, so it is selectable. */
|
|
743
887
|
team_id_allowlist?: readonly string[];
|
|
744
888
|
};
|
|
745
889
|
|
|
890
|
+
/**
|
|
891
|
+
* Named lab accounts a suite can add in one call, modelled on a real team's recorded
|
|
892
|
+
* listing (a BioReference-linked team, recorded 2026-09-21 from the Junction sandbox).
|
|
893
|
+
*
|
|
894
|
+
* Where a preset copies a recorded account, its states and billing types are the
|
|
895
|
+
* recording's. `bioreference_ny_nj_delegated` is not a recorded account: it is the
|
|
896
|
+
* NY/NJ client-bill shape a consumer asked for, kept because suites depend on it.
|
|
897
|
+
*/
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* A lab-account layout as a file or option declares it: a bare list of accounts, or
|
|
901
|
+
* presets (by name, or `{ name, id }`) plus accounts, added in that order.
|
|
902
|
+
*/
|
|
903
|
+
type LabAccountLayout = readonly LabAccountInput[] | {
|
|
904
|
+
presets?: readonly (string | {
|
|
905
|
+
name: string;
|
|
906
|
+
id?: string;
|
|
907
|
+
})[];
|
|
908
|
+
accounts?: readonly LabAccountInput[];
|
|
909
|
+
};
|
|
910
|
+
|
|
746
911
|
/**
|
|
747
912
|
* Version of the sealed-corpus on-disk format. Bumped whenever the shape changes so a
|
|
748
913
|
* stale recording is rejected instead of half-applied.
|
|
914
|
+
*
|
|
915
|
+
* - 1: observations, catalog and lab accounts.
|
|
916
|
+
* - 2: adds `teamId`, the recording team, so lab-account allowlists load verbatim.
|
|
749
917
|
*/
|
|
750
|
-
declare const SEALED_CORPUS_VERSION =
|
|
918
|
+
declare const SEALED_CORPUS_VERSION = 2;
|
|
751
919
|
/**
|
|
752
920
|
* Exact sandbox recording of the provider-owned reads a drop-in consumer depends on:
|
|
753
921
|
* parameter-stable GET observations (area/PSC inventory), the lab catalog, team labs, and
|
|
@@ -755,9 +923,14 @@ declare const SEALED_CORPUS_VERSION = 1;
|
|
|
755
923
|
* `booking_key`s can never match a live cache key, so it stays with the generator.
|
|
756
924
|
*/
|
|
757
925
|
type SealedCorpus = {
|
|
758
|
-
version: typeof SEALED_CORPUS_VERSION;
|
|
926
|
+
version: 1 | typeof SEALED_CORPUS_VERSION;
|
|
759
927
|
recordedAt: string;
|
|
760
928
|
source: string;
|
|
929
|
+
/**
|
|
930
|
+
* The recording team's id (version 2+). When present it is the mock's default team,
|
|
931
|
+
* and each lab account's `team_id_allowlist` is kept exactly as recorded.
|
|
932
|
+
*/
|
|
933
|
+
teamId?: string;
|
|
761
934
|
/** SHA-256 of the recording's content, written by `corpus pull`. Identifies it in logs. */
|
|
762
935
|
fingerprint?: string;
|
|
763
936
|
observations: Record<string, GetCacheEntry>;
|
|
@@ -856,10 +1029,22 @@ type JunctionAPIOptions = APIOptions & {
|
|
|
856
1029
|
*/
|
|
857
1030
|
geo?: GeoMode;
|
|
858
1031
|
/**
|
|
859
|
-
* The team's lab accounts, replacing the built-in fixtures (and any from the corpus)
|
|
860
|
-
* `
|
|
1032
|
+
* The team's lab accounts, replacing the built-in fixtures (and any from the corpus):
|
|
1033
|
+
* a list of accounts, or `{ presets: [...], accounts: [...] }`. `create_order` routes,
|
|
1034
|
+
* accepts and rejects `lab_account_id` against these.
|
|
1035
|
+
*/
|
|
1036
|
+
labAccounts?: LabAccountLayout;
|
|
1037
|
+
/**
|
|
1038
|
+
* The team the mock answers as: `team_id` on users and orders, and the team lab-account
|
|
1039
|
+
* allowlists are checked against. Default: the corpus's recorded team, else a fixed id.
|
|
861
1040
|
*/
|
|
862
|
-
|
|
1041
|
+
teamId?: string;
|
|
1042
|
+
/** Sandbox-only restrictions to enforce. Every one is off unless set here. */
|
|
1043
|
+
limits?: JunctionLimitsInput;
|
|
1044
|
+
/** `adopt-users` creates an unknown (well-formed) `user_id` on first use. Default `strict`. */
|
|
1045
|
+
identity?: IdentityMode;
|
|
1046
|
+
/** Users and orders to load into every namespace, re-applied on every `reset()`. */
|
|
1047
|
+
fixtures?: JunctionFixtures;
|
|
863
1048
|
};
|
|
864
1049
|
/** What a loaded corpus covers, for `/health` and startup logs. */
|
|
865
1050
|
type CorpusInfo = {
|
|
@@ -870,12 +1055,18 @@ type CorpusInfo = {
|
|
|
870
1055
|
labTests: number;
|
|
871
1056
|
labAccounts: number;
|
|
872
1057
|
zips: number;
|
|
1058
|
+
/** The recording team, when the corpus recorded one (version 2+). */
|
|
1059
|
+
teamId?: string;
|
|
873
1060
|
};
|
|
874
1061
|
declare class JunctionAPI implements FetchAPI {
|
|
875
1062
|
readonly app: Hono;
|
|
876
1063
|
readonly sqlite: SqliteClient;
|
|
877
1064
|
private readonly service;
|
|
878
1065
|
private readonly state;
|
|
1066
|
+
private readonly now;
|
|
1067
|
+
private readonly explicitTeamId;
|
|
1068
|
+
private readonly fixtures;
|
|
1069
|
+
private readonly rateWindow;
|
|
879
1070
|
private corpus;
|
|
880
1071
|
private labAccountConfig;
|
|
881
1072
|
constructor(options?: JunctionAPIOptions);
|
|
@@ -892,9 +1083,40 @@ declare class JunctionAPI implements FetchAPI {
|
|
|
892
1083
|
* recorded from a team with no accounts leaves in place).
|
|
893
1084
|
*/
|
|
894
1085
|
private seedLabAccounts;
|
|
895
|
-
/**
|
|
896
|
-
|
|
1086
|
+
/** The team this namespace answers as. */
|
|
1087
|
+
get teamId(): string;
|
|
1088
|
+
/**
|
|
1089
|
+
* Replace the team's lab accounts (a list, or `{ presets, accounts }`); `undefined`
|
|
1090
|
+
* restores the default. Kept across `reset()`, per namespace.
|
|
1091
|
+
*/
|
|
1092
|
+
configureLabAccounts(accounts: LabAccountLayout | undefined): StoredLabAccount[];
|
|
897
1093
|
labAccounts(): StoredLabAccount[];
|
|
1094
|
+
/** Apply one change to the effective account list and keep it as this namespace's config. */
|
|
1095
|
+
private editLabAccounts;
|
|
1096
|
+
/** Add one account. Throws `LabAccountConflict` when the id is taken. Kept across resets. */
|
|
1097
|
+
addLabAccount(input: LabAccountInput | StoredLabAccount): StoredLabAccount;
|
|
1098
|
+
/** Add a named preset (see `LAB_ACCOUNT_PRESETS`); `undefined` for an unknown name. */
|
|
1099
|
+
addLabAccountPreset(name: string, id?: string): StoredLabAccount | undefined;
|
|
1100
|
+
/** Merge fields into an account; `undefined` when there is none with that id. */
|
|
1101
|
+
patchLabAccount(id: string, patch: Partial<LabAccountInput>): StoredLabAccount | undefined;
|
|
1102
|
+
removeLabAccount(id: string): boolean;
|
|
1103
|
+
/** The sandbox limits this namespace enforces. Every one is off unless configured. */
|
|
1104
|
+
get limits(): JunctionLimits;
|
|
1105
|
+
/** Change some limits (`null` lifts a numeric one); kept across `reset()`. */
|
|
1106
|
+
configureLimits(input: JunctionLimitsInput): JunctionLimits;
|
|
1107
|
+
get identity(): IdentityMode;
|
|
1108
|
+
set identity(mode: IdentityMode);
|
|
1109
|
+
/** Insert users with chosen ids, all or none. Throws `FixtureError` (400/404/409). */
|
|
1110
|
+
insertUsers(users: readonly UserFixture[]): UserRecord[];
|
|
1111
|
+
/** Remove a user outright, leaving no deletion tombstone. */
|
|
1112
|
+
hardDeleteUser(userId: string): boolean;
|
|
1113
|
+
/** Insert orders, optionally already in a status. No webhook fires unless asked. */
|
|
1114
|
+
insertOrders(orders: readonly OrderFixture[], options?: InsertOptions): OrderRecord[];
|
|
1115
|
+
/** Load users, then orders, in one call. */
|
|
1116
|
+
importFixtures(fixtures: JunctionFixtures, options?: InsertOptions): {
|
|
1117
|
+
users: UserRecord[];
|
|
1118
|
+
orders: OrderRecord[];
|
|
1119
|
+
};
|
|
898
1120
|
corpusInfo(): CorpusInfo | undefined;
|
|
899
1121
|
get geoMode(): GeoMode;
|
|
900
1122
|
set geoMode(mode: GeoMode);
|
|
@@ -928,8 +1150,18 @@ type JunctionRuntimeOptions = {
|
|
|
928
1150
|
corpus?: SealedCorpus;
|
|
929
1151
|
/** Default: `corpus` when a corpus is loaded, otherwise `synthetic`. */
|
|
930
1152
|
geo?: GeoMode;
|
|
931
|
-
/** The team's lab accounts
|
|
932
|
-
labAccounts?:
|
|
1153
|
+
/** The team's lab accounts, or `{ presets, accounts }`; see {@link JunctionAPIOptions.labAccounts}. */
|
|
1154
|
+
labAccounts?: LabAccountLayout;
|
|
1155
|
+
/** The team the mock answers as. Default: the corpus's recorded team, else a fixed id. */
|
|
1156
|
+
teamId?: string;
|
|
1157
|
+
/** Sandbox-only restrictions, every one off by default; per namespace via `PUT /__admin/limits`. */
|
|
1158
|
+
limits?: JunctionLimitsInput;
|
|
1159
|
+
/** `adopt-users` creates unknown user ids on first use. Default `strict`. */
|
|
1160
|
+
identity?: IdentityMode;
|
|
1161
|
+
/** Users and orders every namespace starts with, re-applied on each reset. */
|
|
1162
|
+
fixtures?: JunctionFixtures;
|
|
1163
|
+
/** Requests each namespace's journal keeps (`GET /__admin/requests`). Default 1000. */
|
|
1164
|
+
journalSize?: number;
|
|
933
1165
|
/** Deliver signed webhooks here, with Svix's retry schedule. */
|
|
934
1166
|
webhooks?: WebhookEndpoint;
|
|
935
1167
|
/** Called in-process for every webhook event, delivered or not. */
|
package/dist/server.js
CHANGED
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
createServer,
|
|
4
4
|
loadCorpus,
|
|
5
5
|
serveTarget
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-KLBGX23P.js";
|
|
7
|
+
import "./chunk-NFWUEPW4.js";
|
|
8
|
+
import "./chunk-C2UGIPPM.js";
|
|
9
|
+
import "./chunk-ISDNA2TU.js";
|
|
10
10
|
export {
|
|
11
11
|
DEFAULT_PORT,
|
|
12
12
|
createServer,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvouga/mockingbird-service-junction",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Stateful mock of the Junction (Vital) user API driven by its OpenAPI contract and verified by differential property tests against the Junction sandbox.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/sealed-corpus.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ExpectedResult, LabTestRecord } from \"./catalog.js\"\nimport type { GetCacheEntry } from \"./state.js\"\n\n/**\n * Version of the sealed-corpus on-disk format. Bumped whenever the shape changes so a\n * stale recording is rejected instead of half-applied.\n */\nexport const SEALED_CORPUS_VERSION = 1\n\n/**\n * Exact sandbox recording of the provider-owned reads a drop-in consumer depends on:\n * parameter-stable GET observations (area/PSC inventory), the lab catalog, team labs, and\n * lab accounts. Availability is intentionally absent \u2014 its slot dates and single-use\n * `booking_key`s can never match a live cache key, so it stays with the generator.\n */\nexport type SealedCorpus = {\n version: typeof SEALED_CORPUS_VERSION\n recordedAt: string\n source: string\n /** SHA-256 of the recording's content, written by `corpus pull`. Identifies it in logs. */\n fingerprint?: string\n observations: Record<string, GetCacheEntry>\n catalog: {\n labTests: LabTestRecord[]\n labs: Record<string, unknown>[]\n expectedResults: Record<string, ExpectedResult[]>\n }\n labAccounts: Record<string, unknown>[]\n}\n\n/** Throws on a non-object or an unrecognized `version`. */\nexport const parseSealedCorpus = (value: unknown): SealedCorpus => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(\"sealed corpus must be an object\")\n }\n const record = value as Record<string, unknown>\n if (record.version !== SEALED_CORPUS_VERSION) {\n throw new Error(`unsupported sealed corpus version: ${String(record.version)}`)\n }\n return record as unknown as SealedCorpus\n}\n"],
|
|
5
|
-
"mappings": ";AAOO,IAAM,wBAAwB;AAwB9B,IAAM,oBAAoB,CAAC,UAAiC;AACjE,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GAAG;AACvE,UAAM,IAAI,MAAM,iCAAiC;AAAA,EACnD;AACA,QAAM,SAAS;AACf,MAAI,OAAO,YAAY,uBAAuB;AAC5C,UAAM,IAAI,MAAM,sCAAsC,OAAO,OAAO,OAAO,CAAC,EAAE;AAAA,EAChF;AACA,SAAO;AACT;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
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 type { LabAccountInput } 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 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/** 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: \"JSON array of lab accounts to route orders through\",\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\n ? undefined\n : (JSON.parse(await readFile(accountsFile, \"utf8\")) as LabAccountInput[])\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 ...(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 `lab accounts: ${api.labAccounts().length}`,\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 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 console.log(\n `${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}`,\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 = (asString(values.log) ?? \"pretty\") 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 request = new Request(url, init)\n const response = await api.fetch(request)\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 res.writeHead(response.status, headers)\n res.end(Buffer.from(await response.arrayBuffer()))\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
|
-
}
|