@crvouga/mockingbird-service-junction 0.2.0 → 0.4.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 +13 -5
- package/README.md +222 -21
- package/dist/{chunk-MD4KYEIM.js → chunk-C2UGIPPM.js} +2 -2
- package/dist/{chunk-KKNULV7X.js → chunk-CR35KYXY.js} +120 -10
- package/dist/chunk-CR35KYXY.js.map +7 -0
- package/dist/{chunk-MA3HSUVD.js → chunk-ICINEQ7D.js} +2181 -821
- package/dist/chunk-ICINEQ7D.js.map +7 -0
- package/dist/{chunk-4C6VYRJN.js → chunk-ISDNA2TU.js} +8 -3
- package/dist/chunk-ISDNA2TU.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 +453 -56
- package/dist/index.js +34 -2
- package/dist/server.d.ts +504 -24
- 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/server.d.ts
CHANGED
|
@@ -99,7 +99,12 @@ type FaultRule$1 = {
|
|
|
99
99
|
* rules added through `POST /__admin/faults` default to the calling namespace.
|
|
100
100
|
*/
|
|
101
101
|
namespace?: string;
|
|
102
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Status of the injected response. Omit for a rule that only delays (`delayMs` /
|
|
104
|
+
* `latencyMs`), only drops the connection (`drop`), or only switches on an `effect`:
|
|
105
|
+
* the request then still reaches the service.
|
|
106
|
+
*/
|
|
107
|
+
status?: number;
|
|
103
108
|
/** Response body, serialized as JSON. A string is sent as-is. */
|
|
104
109
|
body?: unknown;
|
|
105
110
|
headers?: Record<string, string>;
|
|
@@ -109,6 +114,34 @@ type FaultRule$1 = {
|
|
|
109
114
|
rate?: number;
|
|
110
115
|
/** Hold the response back this long, to exercise timeouts. */
|
|
111
116
|
delayMs?: number;
|
|
117
|
+
/** Alias of `delayMs`. */
|
|
118
|
+
latencyMs?: number;
|
|
119
|
+
/**
|
|
120
|
+
* Drop the connection instead of answering: an in-process `fetch` rejects with a
|
|
121
|
+
* `TypeError`, and a served mock destroys the socket. Models "unknown outcome" failures.
|
|
122
|
+
*/
|
|
123
|
+
drop?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* A named service behaviour to switch on for the matching request instead of (or
|
|
126
|
+
* before) a canned response, e.g. `created_but_500` or `numeric_tracking_id`. Services
|
|
127
|
+
* read it with `faultEffects(request)`.
|
|
128
|
+
*/
|
|
129
|
+
effect?: string;
|
|
130
|
+
/** Parameters for `effect`. */
|
|
131
|
+
params?: Record<string, unknown>;
|
|
132
|
+
/** From the preset this rule was expanded from, if any. */
|
|
133
|
+
preset?: string;
|
|
134
|
+
};
|
|
135
|
+
/** A fault that fired for one request. */
|
|
136
|
+
type FaultHit$1 = {
|
|
137
|
+
id: string;
|
|
138
|
+
/** The injected response; absent when the rule only delays, drops, or sets an effect. */
|
|
139
|
+
response?: Response;
|
|
140
|
+
drop?: boolean;
|
|
141
|
+
effect?: {
|
|
142
|
+
name: string;
|
|
143
|
+
params: Record<string, unknown>;
|
|
144
|
+
};
|
|
112
145
|
};
|
|
113
146
|
/** What a request looks like to the fault matcher. */
|
|
114
147
|
type FaultCandidate$1 = {
|
|
@@ -126,13 +159,11 @@ type FaultRegistry$1 = {
|
|
|
126
159
|
remove(id: string): boolean;
|
|
127
160
|
clear(): void;
|
|
128
161
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
162
|
+
* Every fault this request should get, in rule order, stopping at the first that answers
|
|
163
|
+
* or drops (effect-only and delay-only rules let later rules match too). Consumes one of
|
|
164
|
+
* each matching rule's remaining uses.
|
|
131
165
|
*/
|
|
132
|
-
take(candidate: FaultCandidate$1): Promise<
|
|
133
|
-
id: string;
|
|
134
|
-
response: Response;
|
|
135
|
-
} | undefined>;
|
|
166
|
+
take(candidate: FaultCandidate$1): Promise<FaultHit$1[]>;
|
|
136
167
|
};
|
|
137
168
|
|
|
138
169
|
/** One handled request, as the structured log sees it. */
|
|
@@ -148,6 +179,10 @@ type RequestLog$1 = {
|
|
|
148
179
|
unmatched: boolean;
|
|
149
180
|
/** Set when a fault rule produced the response. */
|
|
150
181
|
faultId?: string;
|
|
182
|
+
/** Resource ids the handler touched (`userId`, `orderId`, …), when the service reports them. */
|
|
183
|
+
ids?: Record<string, string>;
|
|
184
|
+
/** Set when the service created a resource the request referred to but that did not exist. */
|
|
185
|
+
adopted?: boolean;
|
|
151
186
|
};
|
|
152
187
|
type MetricsReport$1 = {
|
|
153
188
|
requests: number;
|
|
@@ -173,6 +208,28 @@ type Metrics$1 = {
|
|
|
173
208
|
reset(): void;
|
|
174
209
|
};
|
|
175
210
|
|
|
211
|
+
/** One journal entry: a request log stamped with when (on the mock clock) it was handled. */
|
|
212
|
+
type JournalEntry$1 = RequestLog$1 & {
|
|
213
|
+
at: string;
|
|
214
|
+
};
|
|
215
|
+
type JournalQuery$1 = {
|
|
216
|
+
/** Only this namespace. Omit for every namespace, oldest first across all of them. */
|
|
217
|
+
namespace?: string;
|
|
218
|
+
operationId?: string;
|
|
219
|
+
status?: number;
|
|
220
|
+
/** Only entries at or after this instant (epoch ms). */
|
|
221
|
+
since?: number;
|
|
222
|
+
/** At most this many, the most recent kept. */
|
|
223
|
+
limit?: number;
|
|
224
|
+
};
|
|
225
|
+
type Journal$1 = {
|
|
226
|
+
readonly size: number;
|
|
227
|
+
record(entry: JournalEntry$1): void;
|
|
228
|
+
list(query?: JournalQuery$1): JournalEntry$1[];
|
|
229
|
+
/** Forget one namespace's entries, or every namespace's. */
|
|
230
|
+
clear(namespace?: string): void;
|
|
231
|
+
};
|
|
232
|
+
|
|
176
233
|
/**
|
|
177
234
|
* A point-in-time copy of everything a service namespace holds.
|
|
178
235
|
*
|
|
@@ -195,6 +252,18 @@ type NamespaceSnapshot$1 = {
|
|
|
195
252
|
}[];
|
|
196
253
|
};
|
|
197
254
|
|
|
255
|
+
/** Credential → namespace mapping behind `PUT /__admin/credentials`. */
|
|
256
|
+
type CredentialRegistry$1 = {
|
|
257
|
+
set(credential: string, namespace: string): void;
|
|
258
|
+
get(credential: string): string | undefined;
|
|
259
|
+
remove(credential: string): boolean;
|
|
260
|
+
clear(): void;
|
|
261
|
+
entries(): {
|
|
262
|
+
credential: string;
|
|
263
|
+
namespace: string;
|
|
264
|
+
}[];
|
|
265
|
+
};
|
|
266
|
+
|
|
198
267
|
/**
|
|
199
268
|
* Anything that can answer a Fetch `Request` with a `Response`.
|
|
200
269
|
*
|
|
@@ -205,6 +274,82 @@ interface FetchAPI$2 {
|
|
|
205
274
|
fetch(request: Request): Promise<Response>;
|
|
206
275
|
}
|
|
207
276
|
|
|
277
|
+
type WebhookEndpoint$2 = {
|
|
278
|
+
/** Stable id; generated when omitted. */
|
|
279
|
+
id?: string;
|
|
280
|
+
url: string;
|
|
281
|
+
secret?: string;
|
|
282
|
+
/** Event types to deliver; omit or include `"*"` for every type. */
|
|
283
|
+
events?: string[];
|
|
284
|
+
/** Deliver only messages whose tags include all of these (e.g. `{ account: "mso" }`). */
|
|
285
|
+
tags?: Record<string, string>;
|
|
286
|
+
/** The public URL the receiver verifies signatures against (Twilio), when it differs. */
|
|
287
|
+
signUrl?: string;
|
|
288
|
+
headers?: Record<string, string>;
|
|
289
|
+
};
|
|
290
|
+
type WebhookMessage$1 = {
|
|
291
|
+
id: string;
|
|
292
|
+
namespace: string;
|
|
293
|
+
type: string;
|
|
294
|
+
body: string;
|
|
295
|
+
contentType: string;
|
|
296
|
+
tags: Record<string, string>;
|
|
297
|
+
/** Wall-clock ISO-8601 time of publication. */
|
|
298
|
+
publishedAt: string;
|
|
299
|
+
};
|
|
300
|
+
type WebhookAttempt$2 = {
|
|
301
|
+
attempt: number;
|
|
302
|
+
at: string;
|
|
303
|
+
status: number | null;
|
|
304
|
+
error: string | null;
|
|
305
|
+
durationMs: number;
|
|
306
|
+
};
|
|
307
|
+
type WebhookDelivery$2 = {
|
|
308
|
+
id: string;
|
|
309
|
+
messageId: string;
|
|
310
|
+
namespace: string;
|
|
311
|
+
type: string;
|
|
312
|
+
endpointId: string;
|
|
313
|
+
url: string;
|
|
314
|
+
state: "pending" | "delivered" | "failed" | "dropped";
|
|
315
|
+
attempts: WebhookAttempt$2[];
|
|
316
|
+
};
|
|
317
|
+
/** A delivery-level fault: what happens to the next `count` messages in a namespace. */
|
|
318
|
+
type WebhookFault$1 = {
|
|
319
|
+
mode: "duplicate" | "reorder" | "drop";
|
|
320
|
+
/** Messages affected; default 1. */
|
|
321
|
+
count?: number;
|
|
322
|
+
};
|
|
323
|
+
type PublishInput$1 = {
|
|
324
|
+
namespace: string;
|
|
325
|
+
type: string;
|
|
326
|
+
/** Exact body; objects are JSON-encoded. */
|
|
327
|
+
body: string | Record<string, unknown> | unknown[];
|
|
328
|
+
/** Default `application/json`, or form-encoded when `form` is given. */
|
|
329
|
+
contentType?: string;
|
|
330
|
+
/** Form parameters, when the vendor posts `application/x-www-form-urlencoded`. */
|
|
331
|
+
form?: Record<string, string>;
|
|
332
|
+
tags?: Record<string, string>;
|
|
333
|
+
/** Message id; generated when omitted. */
|
|
334
|
+
id?: string;
|
|
335
|
+
};
|
|
336
|
+
type WebhookHub$1 = {
|
|
337
|
+
publish(input: PublishInput$1): WebhookMessage$1;
|
|
338
|
+
/** Replace a namespace's own endpoints (`PUT /__admin/webhook-endpoints`). */
|
|
339
|
+
setEndpoints(namespace: string, endpoints: WebhookEndpoint$2[]): WebhookEndpoint$2[];
|
|
340
|
+
/** The endpoints a namespace delivers to: its own, plus the global ones. */
|
|
341
|
+
endpoints(namespace: string): WebhookEndpoint$2[];
|
|
342
|
+
messages(namespace?: string): WebhookMessage$1[];
|
|
343
|
+
deliveries(namespace?: string): WebhookDelivery$2[];
|
|
344
|
+
replay(deliveryId: string): Promise<WebhookDelivery$2 | undefined>;
|
|
345
|
+
/** Run every pending retry (and release held reordered messages) now. */
|
|
346
|
+
flush(): Promise<void>;
|
|
347
|
+
/** Resolve once nothing is in flight. */
|
|
348
|
+
idle(): Promise<void>;
|
|
349
|
+
fault(namespace: string, fault: WebhookFault$1): void;
|
|
350
|
+
clear(namespace?: string): void;
|
|
351
|
+
};
|
|
352
|
+
|
|
208
353
|
/** What the runtime needs from a service: a Fetch handler it can reset. */
|
|
209
354
|
type ServiceInstance$1 = FetchAPI$2 & {
|
|
210
355
|
reset(): Promise<void>;
|
|
@@ -215,7 +360,13 @@ type ServiceRuntime$1<T extends ServiceInstance$1> = FetchAPI$2 & {
|
|
|
215
360
|
readonly clock: Clock$1;
|
|
216
361
|
readonly faults: FaultRegistry$1;
|
|
217
362
|
readonly metrics: Metrics$1;
|
|
363
|
+
readonly journal: Journal$1;
|
|
218
364
|
readonly rng: Rng$1;
|
|
365
|
+
readonly credentials: CredentialRegistry$1;
|
|
366
|
+
/** The webhook hub, when the service has outbound webhooks. */
|
|
367
|
+
readonly webhooks: WebhookHub$1 | undefined;
|
|
368
|
+
/** Expand a named preset into fault rules (and webhook faults) for `namespace`. */
|
|
369
|
+
applyPreset(name: string, namespace?: string, overrides?: Partial<FaultRule$1>): FaultRule$1[];
|
|
219
370
|
/** The instance behind `namespace` (the default one when omitted), created on first use. */
|
|
220
371
|
instance(namespace?: string): T;
|
|
221
372
|
/** Public names of every namespace created so far. */
|
|
@@ -361,7 +512,12 @@ type FaultRule = {
|
|
|
361
512
|
* rules added through `POST /__admin/faults` default to the calling namespace.
|
|
362
513
|
*/
|
|
363
514
|
namespace?: string;
|
|
364
|
-
|
|
515
|
+
/**
|
|
516
|
+
* Status of the injected response. Omit for a rule that only delays (`delayMs` /
|
|
517
|
+
* `latencyMs`), only drops the connection (`drop`), or only switches on an `effect`:
|
|
518
|
+
* the request then still reaches the service.
|
|
519
|
+
*/
|
|
520
|
+
status?: number;
|
|
365
521
|
/** Response body, serialized as JSON. A string is sent as-is. */
|
|
366
522
|
body?: unknown;
|
|
367
523
|
headers?: Record<string, string>;
|
|
@@ -371,6 +527,34 @@ type FaultRule = {
|
|
|
371
527
|
rate?: number;
|
|
372
528
|
/** Hold the response back this long, to exercise timeouts. */
|
|
373
529
|
delayMs?: number;
|
|
530
|
+
/** Alias of `delayMs`. */
|
|
531
|
+
latencyMs?: number;
|
|
532
|
+
/**
|
|
533
|
+
* Drop the connection instead of answering: an in-process `fetch` rejects with a
|
|
534
|
+
* `TypeError`, and a served mock destroys the socket. Models "unknown outcome" failures.
|
|
535
|
+
*/
|
|
536
|
+
drop?: boolean;
|
|
537
|
+
/**
|
|
538
|
+
* A named service behaviour to switch on for the matching request instead of (or
|
|
539
|
+
* before) a canned response, e.g. `created_but_500` or `numeric_tracking_id`. Services
|
|
540
|
+
* read it with `faultEffects(request)`.
|
|
541
|
+
*/
|
|
542
|
+
effect?: string;
|
|
543
|
+
/** Parameters for `effect`. */
|
|
544
|
+
params?: Record<string, unknown>;
|
|
545
|
+
/** From the preset this rule was expanded from, if any. */
|
|
546
|
+
preset?: string;
|
|
547
|
+
};
|
|
548
|
+
/** A fault that fired for one request. */
|
|
549
|
+
type FaultHit = {
|
|
550
|
+
id: string;
|
|
551
|
+
/** The injected response; absent when the rule only delays, drops, or sets an effect. */
|
|
552
|
+
response?: Response;
|
|
553
|
+
drop?: boolean;
|
|
554
|
+
effect?: {
|
|
555
|
+
name: string;
|
|
556
|
+
params: Record<string, unknown>;
|
|
557
|
+
};
|
|
374
558
|
};
|
|
375
559
|
/** What a request looks like to the fault matcher. */
|
|
376
560
|
type FaultCandidate = {
|
|
@@ -388,13 +572,11 @@ type FaultRegistry = {
|
|
|
388
572
|
remove(id: string): boolean;
|
|
389
573
|
clear(): void;
|
|
390
574
|
/**
|
|
391
|
-
*
|
|
392
|
-
*
|
|
575
|
+
* Every fault this request should get, in rule order, stopping at the first that answers
|
|
576
|
+
* or drops (effect-only and delay-only rules let later rules match too). Consumes one of
|
|
577
|
+
* each matching rule's remaining uses.
|
|
393
578
|
*/
|
|
394
|
-
take(candidate: FaultCandidate): Promise<
|
|
395
|
-
id: string;
|
|
396
|
-
response: Response;
|
|
397
|
-
} | undefined>;
|
|
579
|
+
take(candidate: FaultCandidate): Promise<FaultHit[]>;
|
|
398
580
|
};
|
|
399
581
|
|
|
400
582
|
/** One handled request, as the structured log sees it. */
|
|
@@ -410,6 +592,10 @@ type RequestLog = {
|
|
|
410
592
|
unmatched: boolean;
|
|
411
593
|
/** Set when a fault rule produced the response. */
|
|
412
594
|
faultId?: string;
|
|
595
|
+
/** Resource ids the handler touched (`userId`, `orderId`, …), when the service reports them. */
|
|
596
|
+
ids?: Record<string, string>;
|
|
597
|
+
/** Set when the service created a resource the request referred to but that did not exist. */
|
|
598
|
+
adopted?: boolean;
|
|
413
599
|
};
|
|
414
600
|
type MetricsReport = {
|
|
415
601
|
requests: number;
|
|
@@ -435,6 +621,28 @@ type Metrics = {
|
|
|
435
621
|
reset(): void;
|
|
436
622
|
};
|
|
437
623
|
|
|
624
|
+
/** One journal entry: a request log stamped with when (on the mock clock) it was handled. */
|
|
625
|
+
type JournalEntry = RequestLog & {
|
|
626
|
+
at: string;
|
|
627
|
+
};
|
|
628
|
+
type JournalQuery = {
|
|
629
|
+
/** Only this namespace. Omit for every namespace, oldest first across all of them. */
|
|
630
|
+
namespace?: string;
|
|
631
|
+
operationId?: string;
|
|
632
|
+
status?: number;
|
|
633
|
+
/** Only entries at or after this instant (epoch ms). */
|
|
634
|
+
since?: number;
|
|
635
|
+
/** At most this many, the most recent kept. */
|
|
636
|
+
limit?: number;
|
|
637
|
+
};
|
|
638
|
+
type Journal = {
|
|
639
|
+
readonly size: number;
|
|
640
|
+
record(entry: JournalEntry): void;
|
|
641
|
+
list(query?: JournalQuery): JournalEntry[];
|
|
642
|
+
/** Forget one namespace's entries, or every namespace's. */
|
|
643
|
+
clear(namespace?: string): void;
|
|
644
|
+
};
|
|
645
|
+
|
|
438
646
|
/**
|
|
439
647
|
* A point-in-time copy of everything a service namespace holds.
|
|
440
648
|
*
|
|
@@ -457,6 +665,18 @@ type NamespaceSnapshot = {
|
|
|
457
665
|
}[];
|
|
458
666
|
};
|
|
459
667
|
|
|
668
|
+
/** Credential → namespace mapping behind `PUT /__admin/credentials`. */
|
|
669
|
+
type CredentialRegistry = {
|
|
670
|
+
set(credential: string, namespace: string): void;
|
|
671
|
+
get(credential: string): string | undefined;
|
|
672
|
+
remove(credential: string): boolean;
|
|
673
|
+
clear(): void;
|
|
674
|
+
entries(): {
|
|
675
|
+
credential: string;
|
|
676
|
+
namespace: string;
|
|
677
|
+
}[];
|
|
678
|
+
};
|
|
679
|
+
|
|
460
680
|
/**
|
|
461
681
|
* Anything that can answer a Fetch `Request` with a `Response`.
|
|
462
682
|
*
|
|
@@ -467,6 +687,82 @@ interface FetchAPI$1 {
|
|
|
467
687
|
fetch(request: Request): Promise<Response>;
|
|
468
688
|
}
|
|
469
689
|
|
|
690
|
+
type WebhookEndpoint$1 = {
|
|
691
|
+
/** Stable id; generated when omitted. */
|
|
692
|
+
id?: string;
|
|
693
|
+
url: string;
|
|
694
|
+
secret?: string;
|
|
695
|
+
/** Event types to deliver; omit or include `"*"` for every type. */
|
|
696
|
+
events?: string[];
|
|
697
|
+
/** Deliver only messages whose tags include all of these (e.g. `{ account: "mso" }`). */
|
|
698
|
+
tags?: Record<string, string>;
|
|
699
|
+
/** The public URL the receiver verifies signatures against (Twilio), when it differs. */
|
|
700
|
+
signUrl?: string;
|
|
701
|
+
headers?: Record<string, string>;
|
|
702
|
+
};
|
|
703
|
+
type WebhookMessage = {
|
|
704
|
+
id: string;
|
|
705
|
+
namespace: string;
|
|
706
|
+
type: string;
|
|
707
|
+
body: string;
|
|
708
|
+
contentType: string;
|
|
709
|
+
tags: Record<string, string>;
|
|
710
|
+
/** Wall-clock ISO-8601 time of publication. */
|
|
711
|
+
publishedAt: string;
|
|
712
|
+
};
|
|
713
|
+
type WebhookAttempt$1 = {
|
|
714
|
+
attempt: number;
|
|
715
|
+
at: string;
|
|
716
|
+
status: number | null;
|
|
717
|
+
error: string | null;
|
|
718
|
+
durationMs: number;
|
|
719
|
+
};
|
|
720
|
+
type WebhookDelivery$1 = {
|
|
721
|
+
id: string;
|
|
722
|
+
messageId: string;
|
|
723
|
+
namespace: string;
|
|
724
|
+
type: string;
|
|
725
|
+
endpointId: string;
|
|
726
|
+
url: string;
|
|
727
|
+
state: "pending" | "delivered" | "failed" | "dropped";
|
|
728
|
+
attempts: WebhookAttempt$1[];
|
|
729
|
+
};
|
|
730
|
+
/** A delivery-level fault: what happens to the next `count` messages in a namespace. */
|
|
731
|
+
type WebhookFault = {
|
|
732
|
+
mode: "duplicate" | "reorder" | "drop";
|
|
733
|
+
/** Messages affected; default 1. */
|
|
734
|
+
count?: number;
|
|
735
|
+
};
|
|
736
|
+
type PublishInput = {
|
|
737
|
+
namespace: string;
|
|
738
|
+
type: string;
|
|
739
|
+
/** Exact body; objects are JSON-encoded. */
|
|
740
|
+
body: string | Record<string, unknown> | unknown[];
|
|
741
|
+
/** Default `application/json`, or form-encoded when `form` is given. */
|
|
742
|
+
contentType?: string;
|
|
743
|
+
/** Form parameters, when the vendor posts `application/x-www-form-urlencoded`. */
|
|
744
|
+
form?: Record<string, string>;
|
|
745
|
+
tags?: Record<string, string>;
|
|
746
|
+
/** Message id; generated when omitted. */
|
|
747
|
+
id?: string;
|
|
748
|
+
};
|
|
749
|
+
type WebhookHub = {
|
|
750
|
+
publish(input: PublishInput): WebhookMessage;
|
|
751
|
+
/** Replace a namespace's own endpoints (`PUT /__admin/webhook-endpoints`). */
|
|
752
|
+
setEndpoints(namespace: string, endpoints: WebhookEndpoint$1[]): WebhookEndpoint$1[];
|
|
753
|
+
/** The endpoints a namespace delivers to: its own, plus the global ones. */
|
|
754
|
+
endpoints(namespace: string): WebhookEndpoint$1[];
|
|
755
|
+
messages(namespace?: string): WebhookMessage[];
|
|
756
|
+
deliveries(namespace?: string): WebhookDelivery$1[];
|
|
757
|
+
replay(deliveryId: string): Promise<WebhookDelivery$1 | undefined>;
|
|
758
|
+
/** Run every pending retry (and release held reordered messages) now. */
|
|
759
|
+
flush(): Promise<void>;
|
|
760
|
+
/** Resolve once nothing is in flight. */
|
|
761
|
+
idle(): Promise<void>;
|
|
762
|
+
fault(namespace: string, fault: WebhookFault): void;
|
|
763
|
+
clear(namespace?: string): void;
|
|
764
|
+
};
|
|
765
|
+
|
|
470
766
|
/** What the runtime needs from a service: a Fetch handler it can reset. */
|
|
471
767
|
type ServiceInstance = FetchAPI$1 & {
|
|
472
768
|
reset(): Promise<void>;
|
|
@@ -477,7 +773,13 @@ type ServiceRuntime<T extends ServiceInstance> = FetchAPI$1 & {
|
|
|
477
773
|
readonly clock: Clock;
|
|
478
774
|
readonly faults: FaultRegistry;
|
|
479
775
|
readonly metrics: Metrics;
|
|
776
|
+
readonly journal: Journal;
|
|
480
777
|
readonly rng: Rng;
|
|
778
|
+
readonly credentials: CredentialRegistry;
|
|
779
|
+
/** The webhook hub, when the service has outbound webhooks. */
|
|
780
|
+
readonly webhooks: WebhookHub | undefined;
|
|
781
|
+
/** Expand a named preset into fault rules (and webhook faults) for `namespace`. */
|
|
782
|
+
applyPreset(name: string, namespace?: string, overrides?: Partial<FaultRule>): FaultRule[];
|
|
481
783
|
/** The instance behind `namespace` (the default one when omitted), created on first use. */
|
|
482
784
|
instance(namespace?: string): T;
|
|
483
785
|
/** Public names of every namespace created so far. */
|
|
@@ -582,6 +884,21 @@ type LabTestRecord = {
|
|
|
582
884
|
worst_case_tat_days: number | null;
|
|
583
885
|
};
|
|
584
886
|
|
|
887
|
+
type JunctionLimits = {
|
|
888
|
+
/**
|
|
889
|
+
* Live users a team may hold; creating one more answers the sandbox's
|
|
890
|
+
* "maximum of 50 Sandbox users" 400. Deleted users free their slot. `null`: unlimited.
|
|
891
|
+
*/
|
|
892
|
+
maxUsers: number | null;
|
|
893
|
+
/** Refuse `POST /v3/order/{id}/test` unless the API key is a sandbox key (`sk_us_…`/`sk_eu_…`). */
|
|
894
|
+
simulateRequiresSandbox: boolean;
|
|
895
|
+
/** Vendor requests per second per namespace before a 429. `null`: unlimited. */
|
|
896
|
+
rateLimitPerSecond: number | null;
|
|
897
|
+
};
|
|
898
|
+
type JunctionLimitsInput = {
|
|
899
|
+
[K in keyof JunctionLimits]?: JunctionLimits[K] | undefined;
|
|
900
|
+
};
|
|
901
|
+
|
|
585
902
|
type GetCacheEntry = {
|
|
586
903
|
status: number;
|
|
587
904
|
headers: Record<string, string>;
|
|
@@ -602,6 +919,25 @@ type WebhookDeliveryAttempt = {
|
|
|
602
919
|
timeout_ms: number;
|
|
603
920
|
acknowledged: boolean;
|
|
604
921
|
};
|
|
922
|
+
type UserRecord = {
|
|
923
|
+
user_id: string;
|
|
924
|
+
team_id: string;
|
|
925
|
+
client_user_id: string;
|
|
926
|
+
created_on: string;
|
|
927
|
+
connected_sources: unknown[];
|
|
928
|
+
fallback_time_zone: {
|
|
929
|
+
id: string;
|
|
930
|
+
source_slug: string;
|
|
931
|
+
updated_at: string;
|
|
932
|
+
} | null;
|
|
933
|
+
fallback_birth_date: {
|
|
934
|
+
value: string;
|
|
935
|
+
source_slug: string;
|
|
936
|
+
updated_at: string;
|
|
937
|
+
} | null;
|
|
938
|
+
ingestion_start: string | null;
|
|
939
|
+
ingestion_end: string | null;
|
|
940
|
+
};
|
|
605
941
|
type OrderEventRecord = {
|
|
606
942
|
id: number;
|
|
607
943
|
created_at: string;
|
|
@@ -707,6 +1043,62 @@ type JunctionWebhookOptions = {
|
|
|
707
1043
|
timeoutMs?: number;
|
|
708
1044
|
};
|
|
709
1045
|
|
|
1046
|
+
/**
|
|
1047
|
+
* The test backdoor: users and orders put straight into a namespace, with ids a suite
|
|
1048
|
+
* chooses — the Junction `user_id`s a database fixture already carries, or an order
|
|
1049
|
+
* already in some status — plus the lenient identity mode that adopts unknown users.
|
|
1050
|
+
*
|
|
1051
|
+
* Everything inserted here goes through the records `create_user` and `create_order`
|
|
1052
|
+
* build, so no read path can tell a fixture from an API-created resource.
|
|
1053
|
+
*/
|
|
1054
|
+
|
|
1055
|
+
/** How requests referring to a user the namespace does not hold are answered. */
|
|
1056
|
+
type IdentityMode = "strict" | "adopt-users";
|
|
1057
|
+
type UserFixture = {
|
|
1058
|
+
/** Default: a fresh id, as `create_user` would mint. */
|
|
1059
|
+
user_id?: string;
|
|
1060
|
+
client_user_id: string;
|
|
1061
|
+
/** ISO-8601. Default: the mock clock's now. */
|
|
1062
|
+
created_on?: string;
|
|
1063
|
+
/** An IANA zone, or the full `{ id, source_slug, updated_at }` object. */
|
|
1064
|
+
fallback_time_zone?: string | UserRecord["fallback_time_zone"];
|
|
1065
|
+
/** `YYYY-MM-DD`, or the full `{ value, source_slug, updated_at }` object. */
|
|
1066
|
+
fallback_birth_date?: string | UserRecord["fallback_birth_date"];
|
|
1067
|
+
ingestion_start?: string | null;
|
|
1068
|
+
ingestion_end?: string | null;
|
|
1069
|
+
};
|
|
1070
|
+
type OrderFixture = {
|
|
1071
|
+
/** Default: a fresh id, as `create_order` would mint. */
|
|
1072
|
+
order_id?: string;
|
|
1073
|
+
user_id: string;
|
|
1074
|
+
lab_test_id: string;
|
|
1075
|
+
lab_account_id?: string;
|
|
1076
|
+
/** A transition target (`completed`, `at_lab`, or a full status). Default: just ordered. */
|
|
1077
|
+
status?: string;
|
|
1078
|
+
/** Default: the lab test's own method. */
|
|
1079
|
+
collection_method?: string;
|
|
1080
|
+
patient_details?: Record<string, unknown>;
|
|
1081
|
+
patient_address?: Record<string, unknown>;
|
|
1082
|
+
billing_type?: string;
|
|
1083
|
+
icd_codes?: string[] | null;
|
|
1084
|
+
passthrough?: string | null;
|
|
1085
|
+
clinical_notes?: string | null;
|
|
1086
|
+
/** ISO-8601. Default: the mock clock's now. */
|
|
1087
|
+
created_at?: string;
|
|
1088
|
+
/** Results served for the order, as `PUT /__admin/results/{id}` takes them. */
|
|
1089
|
+
result_fixture?: Omit<ResultFixture, "name"> & {
|
|
1090
|
+
name?: string;
|
|
1091
|
+
};
|
|
1092
|
+
};
|
|
1093
|
+
type JunctionFixtures = {
|
|
1094
|
+
users?: readonly UserFixture[];
|
|
1095
|
+
orders?: readonly OrderFixture[];
|
|
1096
|
+
};
|
|
1097
|
+
type InsertOptions = {
|
|
1098
|
+
/** Publish the webhooks the equivalent API calls would. Default `false`. */
|
|
1099
|
+
emitWebhooks?: boolean;
|
|
1100
|
+
};
|
|
1101
|
+
|
|
710
1102
|
/**
|
|
711
1103
|
* Anything that can answer a Fetch `Request` with a `Response`.
|
|
712
1104
|
*
|
|
@@ -739,15 +1131,39 @@ type LabAccountInput = {
|
|
|
739
1131
|
allowed_billing?: Record<string, readonly string[]>;
|
|
740
1132
|
/** Shorthand for `allowed_billing: { client_bill: states }`. Default: every state. */
|
|
741
1133
|
states?: readonly string[];
|
|
742
|
-
/** Teams the account is linked to. Default: the
|
|
1134
|
+
/** Teams the account is linked to. Default: the configured team, so it is selectable. */
|
|
743
1135
|
team_id_allowlist?: readonly string[];
|
|
744
1136
|
};
|
|
745
1137
|
|
|
1138
|
+
/**
|
|
1139
|
+
* Named lab accounts a suite can add in one call, modelled on a real team's recorded
|
|
1140
|
+
* listing (a BioReference-linked team, recorded 2026-09-21 from the Junction sandbox).
|
|
1141
|
+
*
|
|
1142
|
+
* Where a preset copies a recorded account, its states and billing types are the
|
|
1143
|
+
* recording's. `bioreference_ny_nj_delegated` is not a recorded account: it is the
|
|
1144
|
+
* NY/NJ client-bill shape a consumer asked for, kept because suites depend on it.
|
|
1145
|
+
*/
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* A lab-account layout as a file or option declares it: a bare list of accounts, or
|
|
1149
|
+
* presets (by name, or `{ name, id }`) plus accounts, added in that order.
|
|
1150
|
+
*/
|
|
1151
|
+
type LabAccountLayout = readonly LabAccountInput[] | {
|
|
1152
|
+
presets?: readonly (string | {
|
|
1153
|
+
name: string;
|
|
1154
|
+
id?: string;
|
|
1155
|
+
})[];
|
|
1156
|
+
accounts?: readonly LabAccountInput[];
|
|
1157
|
+
};
|
|
1158
|
+
|
|
746
1159
|
/**
|
|
747
1160
|
* Version of the sealed-corpus on-disk format. Bumped whenever the shape changes so a
|
|
748
1161
|
* stale recording is rejected instead of half-applied.
|
|
1162
|
+
*
|
|
1163
|
+
* - 1: observations, catalog and lab accounts.
|
|
1164
|
+
* - 2: adds `teamId`, the recording team, so lab-account allowlists load verbatim.
|
|
749
1165
|
*/
|
|
750
|
-
declare const SEALED_CORPUS_VERSION =
|
|
1166
|
+
declare const SEALED_CORPUS_VERSION = 2;
|
|
751
1167
|
/**
|
|
752
1168
|
* Exact sandbox recording of the provider-owned reads a drop-in consumer depends on:
|
|
753
1169
|
* parameter-stable GET observations (area/PSC inventory), the lab catalog, team labs, and
|
|
@@ -755,9 +1171,14 @@ declare const SEALED_CORPUS_VERSION = 1;
|
|
|
755
1171
|
* `booking_key`s can never match a live cache key, so it stays with the generator.
|
|
756
1172
|
*/
|
|
757
1173
|
type SealedCorpus = {
|
|
758
|
-
version: typeof SEALED_CORPUS_VERSION;
|
|
1174
|
+
version: 1 | typeof SEALED_CORPUS_VERSION;
|
|
759
1175
|
recordedAt: string;
|
|
760
1176
|
source: string;
|
|
1177
|
+
/**
|
|
1178
|
+
* The recording team's id (version 2+). When present it is the mock's default team,
|
|
1179
|
+
* and each lab account's `team_id_allowlist` is kept exactly as recorded.
|
|
1180
|
+
*/
|
|
1181
|
+
teamId?: string;
|
|
761
1182
|
/** SHA-256 of the recording's content, written by `corpus pull`. Identifies it in logs. */
|
|
762
1183
|
fingerprint?: string;
|
|
763
1184
|
observations: Record<string, GetCacheEntry>;
|
|
@@ -856,10 +1277,22 @@ type JunctionAPIOptions = APIOptions & {
|
|
|
856
1277
|
*/
|
|
857
1278
|
geo?: GeoMode;
|
|
858
1279
|
/**
|
|
859
|
-
* The team's lab accounts, replacing the built-in fixtures (and any from the corpus)
|
|
860
|
-
* `
|
|
1280
|
+
* The team's lab accounts, replacing the built-in fixtures (and any from the corpus):
|
|
1281
|
+
* a list of accounts, or `{ presets: [...], accounts: [...] }`. `create_order` routes,
|
|
1282
|
+
* accepts and rejects `lab_account_id` against these.
|
|
861
1283
|
*/
|
|
862
|
-
labAccounts?:
|
|
1284
|
+
labAccounts?: LabAccountLayout;
|
|
1285
|
+
/**
|
|
1286
|
+
* The team the mock answers as: `team_id` on users and orders, and the team lab-account
|
|
1287
|
+
* allowlists are checked against. Default: the corpus's recorded team, else a fixed id.
|
|
1288
|
+
*/
|
|
1289
|
+
teamId?: string;
|
|
1290
|
+
/** Sandbox-only restrictions to enforce. Every one is off unless set here. */
|
|
1291
|
+
limits?: JunctionLimitsInput;
|
|
1292
|
+
/** `adopt-users` creates an unknown (well-formed) `user_id` on first use. Default `strict`. */
|
|
1293
|
+
identity?: IdentityMode;
|
|
1294
|
+
/** Users and orders to load into every namespace, re-applied on every `reset()`. */
|
|
1295
|
+
fixtures?: JunctionFixtures;
|
|
863
1296
|
};
|
|
864
1297
|
/** What a loaded corpus covers, for `/health` and startup logs. */
|
|
865
1298
|
type CorpusInfo = {
|
|
@@ -870,12 +1303,18 @@ type CorpusInfo = {
|
|
|
870
1303
|
labTests: number;
|
|
871
1304
|
labAccounts: number;
|
|
872
1305
|
zips: number;
|
|
1306
|
+
/** The recording team, when the corpus recorded one (version 2+). */
|
|
1307
|
+
teamId?: string;
|
|
873
1308
|
};
|
|
874
1309
|
declare class JunctionAPI implements FetchAPI {
|
|
875
1310
|
readonly app: Hono;
|
|
876
1311
|
readonly sqlite: SqliteClient;
|
|
877
1312
|
private readonly service;
|
|
878
1313
|
private readonly state;
|
|
1314
|
+
private readonly now;
|
|
1315
|
+
private readonly explicitTeamId;
|
|
1316
|
+
private readonly fixtures;
|
|
1317
|
+
private readonly rateWindow;
|
|
879
1318
|
private corpus;
|
|
880
1319
|
private labAccountConfig;
|
|
881
1320
|
constructor(options?: JunctionAPIOptions);
|
|
@@ -892,9 +1331,40 @@ declare class JunctionAPI implements FetchAPI {
|
|
|
892
1331
|
* recorded from a team with no accounts leaves in place).
|
|
893
1332
|
*/
|
|
894
1333
|
private seedLabAccounts;
|
|
895
|
-
/**
|
|
896
|
-
|
|
1334
|
+
/** The team this namespace answers as. */
|
|
1335
|
+
get teamId(): string;
|
|
1336
|
+
/**
|
|
1337
|
+
* Replace the team's lab accounts (a list, or `{ presets, accounts }`); `undefined`
|
|
1338
|
+
* restores the default. Kept across `reset()`, per namespace.
|
|
1339
|
+
*/
|
|
1340
|
+
configureLabAccounts(accounts: LabAccountLayout | undefined): StoredLabAccount[];
|
|
897
1341
|
labAccounts(): StoredLabAccount[];
|
|
1342
|
+
/** Apply one change to the effective account list and keep it as this namespace's config. */
|
|
1343
|
+
private editLabAccounts;
|
|
1344
|
+
/** Add one account. Throws `LabAccountConflict` when the id is taken. Kept across resets. */
|
|
1345
|
+
addLabAccount(input: LabAccountInput | StoredLabAccount): StoredLabAccount;
|
|
1346
|
+
/** Add a named preset (see `LAB_ACCOUNT_PRESETS`); `undefined` for an unknown name. */
|
|
1347
|
+
addLabAccountPreset(name: string, id?: string): StoredLabAccount | undefined;
|
|
1348
|
+
/** Merge fields into an account; `undefined` when there is none with that id. */
|
|
1349
|
+
patchLabAccount(id: string, patch: Partial<LabAccountInput>): StoredLabAccount | undefined;
|
|
1350
|
+
removeLabAccount(id: string): boolean;
|
|
1351
|
+
/** The sandbox limits this namespace enforces. Every one is off unless configured. */
|
|
1352
|
+
get limits(): JunctionLimits;
|
|
1353
|
+
/** Change some limits (`null` lifts a numeric one); kept across `reset()`. */
|
|
1354
|
+
configureLimits(input: JunctionLimitsInput): JunctionLimits;
|
|
1355
|
+
get identity(): IdentityMode;
|
|
1356
|
+
set identity(mode: IdentityMode);
|
|
1357
|
+
/** Insert users with chosen ids, all or none. Throws `FixtureError` (400/404/409). */
|
|
1358
|
+
insertUsers(users: readonly UserFixture[]): UserRecord[];
|
|
1359
|
+
/** Remove a user outright, leaving no deletion tombstone. */
|
|
1360
|
+
hardDeleteUser(userId: string): boolean;
|
|
1361
|
+
/** Insert orders, optionally already in a status. No webhook fires unless asked. */
|
|
1362
|
+
insertOrders(orders: readonly OrderFixture[], options?: InsertOptions): OrderRecord[];
|
|
1363
|
+
/** Load users, then orders, in one call. */
|
|
1364
|
+
importFixtures(fixtures: JunctionFixtures, options?: InsertOptions): {
|
|
1365
|
+
users: UserRecord[];
|
|
1366
|
+
orders: OrderRecord[];
|
|
1367
|
+
};
|
|
898
1368
|
corpusInfo(): CorpusInfo | undefined;
|
|
899
1369
|
get geoMode(): GeoMode;
|
|
900
1370
|
set geoMode(mode: GeoMode);
|
|
@@ -928,8 +1398,18 @@ type JunctionRuntimeOptions = {
|
|
|
928
1398
|
corpus?: SealedCorpus;
|
|
929
1399
|
/** Default: `corpus` when a corpus is loaded, otherwise `synthetic`. */
|
|
930
1400
|
geo?: GeoMode;
|
|
931
|
-
/** The team's lab accounts
|
|
932
|
-
labAccounts?:
|
|
1401
|
+
/** The team's lab accounts, or `{ presets, accounts }`; see {@link JunctionAPIOptions.labAccounts}. */
|
|
1402
|
+
labAccounts?: LabAccountLayout;
|
|
1403
|
+
/** The team the mock answers as. Default: the corpus's recorded team, else a fixed id. */
|
|
1404
|
+
teamId?: string;
|
|
1405
|
+
/** Sandbox-only restrictions, every one off by default; per namespace via `PUT /__admin/limits`. */
|
|
1406
|
+
limits?: JunctionLimitsInput;
|
|
1407
|
+
/** `adopt-users` creates unknown user ids on first use. Default `strict`. */
|
|
1408
|
+
identity?: IdentityMode;
|
|
1409
|
+
/** Users and orders every namespace starts with, re-applied on each reset. */
|
|
1410
|
+
fixtures?: JunctionFixtures;
|
|
1411
|
+
/** Requests each namespace's journal keeps (`GET /__admin/requests`). Default 1000. */
|
|
1412
|
+
journalSize?: number;
|
|
933
1413
|
/** Deliver signed webhooks here, with Svix's retry schedule. */
|
|
934
1414
|
webhooks?: WebhookEndpoint;
|
|
935
1415
|
/** Called in-process for every webhook event, delivered or not. */
|