@crvouga/mockingbird-service-junction 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.d.ts CHANGED
@@ -170,82 +170,6 @@ type FaultRegistry$1 = {
170
170
  take(candidate: FaultCandidate$1): Promise<FaultHit$1[]>;
171
171
  };
172
172
 
173
- /** One handled request, as the structured log sees it. */
174
- type RequestLog$1 = {
175
- service: string;
176
- namespace: string;
177
- operationId: string | undefined;
178
- method: string;
179
- path: string;
180
- status: number;
181
- durationMs: number;
182
- /** True when the path matched no operation in the contract. */
183
- unmatched: boolean;
184
- /** Set when a fault rule produced the response. */
185
- faultId?: string;
186
- /** Resource ids the handler touched (`userId`, `orderId`, …), when the service reports them. */
187
- ids?: Record<string, string>;
188
- /** Set when the service created a resource the request referred to but that did not exist. */
189
- adopted?: boolean;
190
- };
191
- type MetricsReport$1 = {
192
- requests: number;
193
- /** Counts keyed `<operationId> <status>`. */
194
- byOperation: Record<string, number>;
195
- /**
196
- * Paths that matched no operation, most frequent first.
197
- *
198
- * This is the early-warning signal: a consumer calling something the mock does
199
- * not implement shows up here as a count, before it fails a suite as a 404.
200
- */
201
- unmatched: {
202
- method: string;
203
- path: string;
204
- count: number;
205
- }[];
206
- faults: number;
207
- totalDurationMs: number;
208
- };
209
- type Metrics$1 = {
210
- record(entry: RequestLog$1): void;
211
- report(): MetricsReport$1;
212
- reset(): void;
213
- };
214
-
215
- /** One journal entry: a request log stamped with when (on the mock clock) it was handled. */
216
- type JournalEntry$1 = RequestLog$1 & {
217
- at: string;
218
- };
219
- type JournalQuery$1 = {
220
- /** Only this namespace. Omit for every namespace, oldest first across all of them. */
221
- namespace?: string;
222
- operationId?: string;
223
- status?: number;
224
- /** Only entries at or after this instant (epoch ms). */
225
- since?: number;
226
- /** At most this many, the most recent kept. */
227
- limit?: number;
228
- };
229
- type Journal$1 = {
230
- readonly size: number;
231
- record(entry: JournalEntry$1): void;
232
- list(query?: JournalQuery$1): JournalEntry$1[];
233
- /** Forget one namespace's entries, or every namespace's. */
234
- clear(namespace?: string): void;
235
- };
236
-
237
- /** Credential → namespace mapping behind `PUT /__admin/credentials`. */
238
- type CredentialRegistry$1 = {
239
- set(credential: string, namespace: string): void;
240
- get(credential: string): string | undefined;
241
- remove(credential: string): boolean;
242
- clear(): void;
243
- entries(): {
244
- credential: string;
245
- namespace: string;
246
- }[];
247
- };
248
-
249
173
  /** A stable identifier for a point in a {@link Timeline}. */
250
174
  type CheckpointId$1 = string;
251
175
  /** An immutable node in a timeline's checkpoint DAG. */
@@ -336,6 +260,110 @@ interface FetchAPI$2 {
336
260
  fetch(request: Request): Promise<Response>;
337
261
  }
338
262
 
263
+ /**
264
+ * One problem with a request body, at a dotted path (`patient.address.city`, `items.0.sku`).
265
+ * `kind` is `media_type` when the body is present but its media type is not one the operation
266
+ * accepts (a `415` on most vendors), `syntax` when it could not be decoded, `required` when
267
+ * the operation needs a body and none arrived, and `schema` for a contract violation.
268
+ */
269
+ type BodyIssue$1 = {
270
+ path: string;
271
+ message: string;
272
+ kind?: "media_type" | "syntax" | "required" | "schema";
273
+ };
274
+
275
+ /**
276
+ * What the mock received from a request it rejected, so a 4xx can be attributed after the
277
+ * fact (an empty body, a missing `content-type`, a schema violation) without logging the body.
278
+ */
279
+ type RejectedRequest$1 = {
280
+ /** The raw `content-type` header, or `null` when the request sent none. */
281
+ contentType: string | null;
282
+ /** Bytes of body received from `content-length` (`0` for none), or `null` when the client sent no length (a streamed / chunked body). */
283
+ bodyBytes: number | null;
284
+ /** The raw `transfer-encoding` header (`chunked`), or `null`. */
285
+ transferEncoding: string | null;
286
+ };
287
+ /** One handled request, as the structured log sees it. */
288
+ type RequestLog$1 = {
289
+ service: string;
290
+ namespace: string;
291
+ operationId: string | undefined;
292
+ method: string;
293
+ path: string;
294
+ status: number;
295
+ durationMs: number;
296
+ /** True when the path matched no operation in the contract. */
297
+ unmatched: boolean;
298
+ /** Set when a fault rule produced the response. */
299
+ faultId?: string;
300
+ /** Resource ids the handler touched (`userId`, `orderId`, …), when the service reports them. */
301
+ ids?: Record<string, string>;
302
+ /** Set when the service created a resource the request referred to but that did not exist. */
303
+ adopted?: boolean;
304
+ /** Set on a rejection (status ≥ 400) the mock produced itself, never on a scripted fault. */
305
+ request?: RejectedRequest$1;
306
+ /** The validation issues behind a rejection, when the service found any. */
307
+ issues?: BodyIssue$1[];
308
+ };
309
+ type MetricsReport$1 = {
310
+ requests: number;
311
+ /** Counts keyed `<operationId> <status>`. */
312
+ byOperation: Record<string, number>;
313
+ /**
314
+ * Paths that matched no operation, most frequent first.
315
+ *
316
+ * This is the early-warning signal: a consumer calling something the mock does
317
+ * not implement shows up here as a count, before it fails a suite as a 404.
318
+ */
319
+ unmatched: {
320
+ method: string;
321
+ path: string;
322
+ count: number;
323
+ }[];
324
+ faults: number;
325
+ totalDurationMs: number;
326
+ };
327
+ type Metrics$1 = {
328
+ record(entry: RequestLog$1): void;
329
+ report(): MetricsReport$1;
330
+ reset(): void;
331
+ };
332
+
333
+ /** One journal entry: a request log stamped with when (on the mock clock) it was handled. */
334
+ type JournalEntry$1 = RequestLog$1 & {
335
+ at: string;
336
+ };
337
+ type JournalQuery$1 = {
338
+ /** Only this namespace. Omit for every namespace, oldest first across all of them. */
339
+ namespace?: string;
340
+ operationId?: string;
341
+ status?: number;
342
+ /** Only entries at or after this instant (epoch ms). */
343
+ since?: number;
344
+ /** At most this many, the most recent kept. */
345
+ limit?: number;
346
+ };
347
+ type Journal$1 = {
348
+ readonly size: number;
349
+ record(entry: JournalEntry$1): void;
350
+ list(query?: JournalQuery$1): JournalEntry$1[];
351
+ /** Forget one namespace's entries, or every namespace's. */
352
+ clear(namespace?: string): void;
353
+ };
354
+
355
+ /** Credential → namespace mapping behind `PUT /__admin/credentials`. */
356
+ type CredentialRegistry$1 = {
357
+ set(credential: string, namespace: string): void;
358
+ get(credential: string): string | undefined;
359
+ remove(credential: string): boolean;
360
+ clear(): void;
361
+ entries(): {
362
+ credential: string;
363
+ namespace: string;
364
+ }[];
365
+ };
366
+
339
367
  /**
340
368
  * A point-in-time copy of everything a service namespace holds.
341
369
  *
@@ -692,82 +720,6 @@ type FaultRegistry = {
692
720
  take(candidate: FaultCandidate): Promise<FaultHit[]>;
693
721
  };
694
722
 
695
- /** One handled request, as the structured log sees it. */
696
- type RequestLog = {
697
- service: string;
698
- namespace: string;
699
- operationId: string | undefined;
700
- method: string;
701
- path: string;
702
- status: number;
703
- durationMs: number;
704
- /** True when the path matched no operation in the contract. */
705
- unmatched: boolean;
706
- /** Set when a fault rule produced the response. */
707
- faultId?: string;
708
- /** Resource ids the handler touched (`userId`, `orderId`, …), when the service reports them. */
709
- ids?: Record<string, string>;
710
- /** Set when the service created a resource the request referred to but that did not exist. */
711
- adopted?: boolean;
712
- };
713
- type MetricsReport = {
714
- requests: number;
715
- /** Counts keyed `<operationId> <status>`. */
716
- byOperation: Record<string, number>;
717
- /**
718
- * Paths that matched no operation, most frequent first.
719
- *
720
- * This is the early-warning signal: a consumer calling something the mock does
721
- * not implement shows up here as a count, before it fails a suite as a 404.
722
- */
723
- unmatched: {
724
- method: string;
725
- path: string;
726
- count: number;
727
- }[];
728
- faults: number;
729
- totalDurationMs: number;
730
- };
731
- type Metrics = {
732
- record(entry: RequestLog): void;
733
- report(): MetricsReport;
734
- reset(): void;
735
- };
736
-
737
- /** One journal entry: a request log stamped with when (on the mock clock) it was handled. */
738
- type JournalEntry = RequestLog & {
739
- at: string;
740
- };
741
- type JournalQuery = {
742
- /** Only this namespace. Omit for every namespace, oldest first across all of them. */
743
- namespace?: string;
744
- operationId?: string;
745
- status?: number;
746
- /** Only entries at or after this instant (epoch ms). */
747
- since?: number;
748
- /** At most this many, the most recent kept. */
749
- limit?: number;
750
- };
751
- type Journal = {
752
- readonly size: number;
753
- record(entry: JournalEntry): void;
754
- list(query?: JournalQuery): JournalEntry[];
755
- /** Forget one namespace's entries, or every namespace's. */
756
- clear(namespace?: string): void;
757
- };
758
-
759
- /** Credential → namespace mapping behind `PUT /__admin/credentials`. */
760
- type CredentialRegistry = {
761
- set(credential: string, namespace: string): void;
762
- get(credential: string): string | undefined;
763
- remove(credential: string): boolean;
764
- clear(): void;
765
- entries(): {
766
- credential: string;
767
- namespace: string;
768
- }[];
769
- };
770
-
771
723
  /** A stable identifier for a point in a {@link Timeline}. */
772
724
  type CheckpointId = string;
773
725
  /** An immutable node in a timeline's checkpoint DAG. */
@@ -858,6 +810,123 @@ interface FetchAPI$1 {
858
810
  fetch(request: Request): Promise<Response>;
859
811
  }
860
812
 
813
+ /** Options every provider constructor accepts. */
814
+ type APIOptions = {
815
+ /** Sync SQLite client. Defaults to `@crvouga/mockingbird-service-sqlite`. */
816
+ sqlite?: SqliteClient$1;
817
+ /** Clock used for `created`-style fields. Default `Date.now`. */
818
+ now?: () => number;
819
+ /**
820
+ * Storage namespace for this instance's records. Instances sharing one SQLite
821
+ * client stay isolated when their namespaces differ. Defaults to the service name.
822
+ */
823
+ namespace?: string;
824
+ };
825
+
826
+ /**
827
+ * One problem with a request body, at a dotted path (`patient.address.city`, `items.0.sku`).
828
+ * `kind` is `media_type` when the body is present but its media type is not one the operation
829
+ * accepts (a `415` on most vendors), `syntax` when it could not be decoded, `required` when
830
+ * the operation needs a body and none arrived, and `schema` for a contract violation.
831
+ */
832
+ type BodyIssue = {
833
+ path: string;
834
+ message: string;
835
+ kind?: "media_type" | "syntax" | "required" | "schema";
836
+ };
837
+
838
+ /**
839
+ * What the mock received from a request it rejected, so a 4xx can be attributed after the
840
+ * fact (an empty body, a missing `content-type`, a schema violation) without logging the body.
841
+ */
842
+ type RejectedRequest = {
843
+ /** The raw `content-type` header, or `null` when the request sent none. */
844
+ contentType: string | null;
845
+ /** Bytes of body received from `content-length` (`0` for none), or `null` when the client sent no length (a streamed / chunked body). */
846
+ bodyBytes: number | null;
847
+ /** The raw `transfer-encoding` header (`chunked`), or `null`. */
848
+ transferEncoding: string | null;
849
+ };
850
+ /** One handled request, as the structured log sees it. */
851
+ type RequestLog = {
852
+ service: string;
853
+ namespace: string;
854
+ operationId: string | undefined;
855
+ method: string;
856
+ path: string;
857
+ status: number;
858
+ durationMs: number;
859
+ /** True when the path matched no operation in the contract. */
860
+ unmatched: boolean;
861
+ /** Set when a fault rule produced the response. */
862
+ faultId?: string;
863
+ /** Resource ids the handler touched (`userId`, `orderId`, …), when the service reports them. */
864
+ ids?: Record<string, string>;
865
+ /** Set when the service created a resource the request referred to but that did not exist. */
866
+ adopted?: boolean;
867
+ /** Set on a rejection (status ≥ 400) the mock produced itself, never on a scripted fault. */
868
+ request?: RejectedRequest;
869
+ /** The validation issues behind a rejection, when the service found any. */
870
+ issues?: BodyIssue[];
871
+ };
872
+ type MetricsReport = {
873
+ requests: number;
874
+ /** Counts keyed `<operationId> <status>`. */
875
+ byOperation: Record<string, number>;
876
+ /**
877
+ * Paths that matched no operation, most frequent first.
878
+ *
879
+ * This is the early-warning signal: a consumer calling something the mock does
880
+ * not implement shows up here as a count, before it fails a suite as a 404.
881
+ */
882
+ unmatched: {
883
+ method: string;
884
+ path: string;
885
+ count: number;
886
+ }[];
887
+ faults: number;
888
+ totalDurationMs: number;
889
+ };
890
+ type Metrics = {
891
+ record(entry: RequestLog): void;
892
+ report(): MetricsReport;
893
+ reset(): void;
894
+ };
895
+
896
+ /** One journal entry: a request log stamped with when (on the mock clock) it was handled. */
897
+ type JournalEntry = RequestLog & {
898
+ at: string;
899
+ };
900
+ type JournalQuery = {
901
+ /** Only this namespace. Omit for every namespace, oldest first across all of them. */
902
+ namespace?: string;
903
+ operationId?: string;
904
+ status?: number;
905
+ /** Only entries at or after this instant (epoch ms). */
906
+ since?: number;
907
+ /** At most this many, the most recent kept. */
908
+ limit?: number;
909
+ };
910
+ type Journal = {
911
+ readonly size: number;
912
+ record(entry: JournalEntry): void;
913
+ list(query?: JournalQuery): JournalEntry[];
914
+ /** Forget one namespace's entries, or every namespace's. */
915
+ clear(namespace?: string): void;
916
+ };
917
+
918
+ /** Credential → namespace mapping behind `PUT /__admin/credentials`. */
919
+ type CredentialRegistry = {
920
+ set(credential: string, namespace: string): void;
921
+ get(credential: string): string | undefined;
922
+ remove(credential: string): boolean;
923
+ clear(): void;
924
+ entries(): {
925
+ credential: string;
926
+ namespace: string;
927
+ }[];
928
+ };
929
+
861
930
  /**
862
931
  * A point-in-time copy of everything a service namespace holds.
863
932
  *
@@ -1008,19 +1077,6 @@ type ServiceRuntime<T extends ServiceInstance> = FetchAPI$1 & {
1008
1077
  timeline(namespace?: string): Timeline<ServiceTimelineState>;
1009
1078
  };
1010
1079
 
1011
- /** Options every provider constructor accepts. */
1012
- type APIOptions = {
1013
- /** Sync SQLite client. Defaults to `@crvouga/mockingbird-service-sqlite`. */
1014
- sqlite?: SqliteClient$1;
1015
- /** Clock used for `created`-style fields. Default `Date.now`. */
1016
- now?: () => number;
1017
- /**
1018
- * Storage namespace for this instance's records. Instances sharing one SQLite
1019
- * client stay isolated when their namespaces differ. Defaults to the service name.
1020
- */
1021
- namespace?: string;
1022
- };
1023
-
1024
1080
  /** Bind values accepted by Mockingbird's SQLite port (matches sqlite-mem / better-sqlite3). */
1025
1081
  type SqliteValue = null | number | bigint | string | Uint8Array | boolean;
1026
1082
  /** Mutation counters returned by {@link SqliteStatement.run}. */
@@ -1187,8 +1243,6 @@ type OrderRecord = {
1187
1243
  notes: null;
1188
1244
  clinical_notes: string | null;
1189
1245
  passthrough: string | null;
1190
- /** Echoed from the request's opaque provider account id; absent when the order had none. */
1191
- lab_account_id?: string;
1192
1246
  created_at: string;
1193
1247
  updated_at: string;
1194
1248
  events: OrderEventRecord[];
@@ -1329,6 +1383,9 @@ interface FetchAPI {
1329
1383
 
1330
1384
  type LabAccountStatus = "active" | "pending" | "suspended" | "ready_to_launch";
1331
1385
  type LabAccountDelegatedFlow = "order_delegated" | "result_delegated" | "fully_delegated" | "not_delegated";
1386
+ /** `billing_type` values the vendored create-order body accepts. */
1387
+ declare const BILLING_TYPES: readonly ["client_bill", "commercial_insurance", "patient_bill_passthrough", "patient_bill", "upfront_payment"];
1388
+ type BillingType = (typeof BILLING_TYPES)[number];
1332
1389
  /**
1333
1390
  * A lab account as a consumer configures it. Mirrors `ClientFacingLabAccount`, with
1334
1391
  * every field but `id` and `lab` optional, plus a `states` shorthand for the common
@@ -1359,7 +1416,11 @@ type LabAccountInput = {
1359
1416
  *
1360
1417
  * Where a preset copies a recorded account, its states and billing types are the
1361
1418
  * recording's. `bioreference_ny_nj_delegated` is not a recorded account: it is the
1362
- * NY/NJ client-bill shape a consumer asked for, kept because suites depend on it.
1419
+ * NY/NJ client-bill shape a consumer asked for, kept because suites depend on it, and
1420
+ * listed in `SYNTHETIC_LAB_ACCOUNT_PRESETS`.
1421
+ *
1422
+ * `*_platform` presets are Junction's own accounts (`org_id: null`, allowlist `[]`); every
1423
+ * other active preset is customer-owned (it belongs to an Org and is linked to the team).
1363
1424
  */
1364
1425
 
1365
1426
  /**
@@ -1514,6 +1575,12 @@ type JunctionAPIOptions = APIOptions & {
1514
1575
  limits?: JunctionLimitsInput;
1515
1576
  /** `adopt-users` creates an unknown (well-formed) `user_id` on first use. Default `strict`. */
1516
1577
  identity?: IdentityMode;
1578
+ /**
1579
+ * Lab slug → the `billing_type` `create_order` uses when the request omits it, e.g.
1580
+ * `{ bioreference: "patient_bill_passthrough" }`. Unlisted labs use Junction's documented
1581
+ * default, `client_bill`.
1582
+ */
1583
+ defaultBillingTypes?: Partial<Record<string, BillingType>>;
1517
1584
  /** Users and orders to load into every namespace, re-applied on every `reset()`. */
1518
1585
  fixtures?: JunctionFixtures;
1519
1586
  };
@@ -1575,6 +1642,10 @@ declare class JunctionAPI implements FetchAPI {
1575
1642
  get limits(): JunctionLimits;
1576
1643
  /** Change some limits (`null` lifts a numeric one); kept across `reset()`. */
1577
1644
  configureLimits(input: JunctionLimitsInput): JunctionLimits;
1645
+ /** Lab slug → the `billing_type` an order gets when it omits one. Kept across `reset()`. */
1646
+ get defaultBillingTypes(): Record<string, BillingType>;
1647
+ /** Replace the per-lab default billing types (`{}` restores `client_bill` everywhere). */
1648
+ set defaultBillingTypes(input: Partial<Record<string, BillingType>>);
1578
1649
  get identity(): IdentityMode;
1579
1650
  set identity(mode: IdentityMode);
1580
1651
  /** Insert users with chosen ids, all or none. Throws `FixtureError` (400/404/409). */
@@ -1593,6 +1664,12 @@ declare class JunctionAPI implements FetchAPI {
1593
1664
  set geoMode(mode: GeoMode);
1594
1665
  order(id: string): OrderRecord | undefined;
1595
1666
  orders(): OrderRecord[];
1667
+ /**
1668
+ * The lab account an order was placed with: its id, `null` for Junction's platform account,
1669
+ * or `undefined` for an unknown order. Junction's order has no such field, so this is the
1670
+ * only place the mock reports it.
1671
+ */
1672
+ orderLabAccount(id: string): string | null | undefined;
1596
1673
  /**
1597
1674
  * Put an order straight into a status (a full `<status>.<method>.<event>` value) and
1598
1675
  * publish the webhook a real transition would. `flags` takes the same keys as
@@ -1629,6 +1706,11 @@ type JunctionRuntimeOptions = {
1629
1706
  limits?: JunctionLimitsInput;
1630
1707
  /** `adopt-users` creates unknown user ids on first use. Default `strict`. */
1631
1708
  identity?: IdentityMode;
1709
+ /**
1710
+ * Lab slug → `billing_type` for orders that omit it; see
1711
+ * {@link JunctionAPIOptions.defaultBillingTypes}. Per namespace via `PUT /__admin/default-billing-types`.
1712
+ */
1713
+ defaultBillingTypes?: Partial<Record<string, BillingType>>;
1632
1714
  /** Users and orders every namespace starts with, re-applied on each reset. */
1633
1715
  fixtures?: JunctionFixtures;
1634
1716
  /** Requests each namespace's journal keeps (`GET /__admin/requests`). Default 1000. */
package/dist/server.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  createServer,
4
4
  loadCorpus,
5
5
  serveTarget
6
- } from "./chunk-TG2EOH3J.js";
7
- import "./chunk-MS774AJA.js";
6
+ } from "./chunk-A4KJJ5V3.js";
7
+ import "./chunk-GWXEPT6A.js";
8
8
  import "./chunk-C2UGIPPM.js";
9
9
  import "./chunk-ISDNA2TU.js";
10
10
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crvouga/mockingbird-service-junction",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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",