@schmock/core 2.2.3 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -0
  3. package/dist/abort.d.ts +3 -0
  4. package/dist/abort.d.ts.map +1 -0
  5. package/dist/abort.js +30 -0
  6. package/dist/binary.d.ts +8 -0
  7. package/dist/binary.d.ts.map +1 -0
  8. package/dist/binary.js +9 -0
  9. package/dist/builder.d.ts +31 -10
  10. package/dist/builder.d.ts.map +1 -1
  11. package/dist/builder.js +804 -203
  12. package/dist/constants.d.ts +38 -0
  13. package/dist/constants.d.ts.map +1 -1
  14. package/dist/constants.js +123 -2
  15. package/dist/errors.d.ts +14 -3
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/errors.js +41 -6
  18. package/dist/helpers.d.ts +9 -0
  19. package/dist/helpers.d.ts.map +1 -1
  20. package/dist/helpers.js +18 -2
  21. package/dist/http-helpers.d.ts +52 -5
  22. package/dist/http-helpers.d.ts.map +1 -1
  23. package/dist/http-helpers.js +153 -37
  24. package/dist/index.d.ts +381 -60
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +9 -3
  27. package/dist/interceptor.d.ts +11 -1
  28. package/dist/interceptor.d.ts.map +1 -1
  29. package/dist/interceptor.js +354 -156
  30. package/dist/parser.d.ts.map +1 -1
  31. package/dist/parser.js +12 -3
  32. package/dist/plugin-pipeline.d.ts +11 -4
  33. package/dist/plugin-pipeline.d.ts.map +1 -1
  34. package/dist/plugin-pipeline.js +117 -39
  35. package/dist/response-normalizer.d.ts +16 -0
  36. package/dist/response-normalizer.d.ts.map +1 -0
  37. package/dist/response-normalizer.js +316 -0
  38. package/dist/response-parser.d.ts.map +1 -1
  39. package/dist/response-parser.js +85 -18
  40. package/dist/route-matcher.d.ts +3 -0
  41. package/dist/route-matcher.d.ts.map +1 -1
  42. package/dist/route-matcher.js +12 -6
  43. package/dist/types.d.ts +7 -2
  44. package/dist/types.d.ts.map +1 -1
  45. package/package.json +18 -8
  46. package/src/audit-core-builder.test.ts +0 -218
  47. package/src/audit-response-guard.test.ts +0 -38
  48. package/src/builder.test.ts +0 -289
  49. package/src/builder.ts +0 -701
  50. package/src/constants.test.ts +0 -99
  51. package/src/constants.ts +0 -73
  52. package/src/debug.test.ts +0 -241
  53. package/src/delay.test.ts +0 -319
  54. package/src/dist-shape.test.ts +0 -35
  55. package/src/errors.test.ts +0 -223
  56. package/src/errors.ts +0 -130
  57. package/src/factory.test.ts +0 -133
  58. package/src/helpers.test.ts +0 -147
  59. package/src/helpers.ts +0 -58
  60. package/src/http-helpers.ts +0 -113
  61. package/src/index.ts +0 -151
  62. package/src/interceptor.test.ts +0 -291
  63. package/src/interceptor.ts +0 -320
  64. package/src/namespace.test.ts +0 -274
  65. package/src/parser.property.test.ts +0 -594
  66. package/src/parser.test.ts +0 -148
  67. package/src/parser.ts +0 -64
  68. package/src/plugin-pipeline.ts +0 -103
  69. package/src/plugin-system.test.ts +0 -602
  70. package/src/response-parser.ts +0 -75
  71. package/src/response-parsing.test.ts +0 -333
  72. package/src/route-matcher.ts +0 -69
  73. package/src/route-matching.test.ts +0 -394
  74. package/src/server.test.ts +0 -165
  75. package/src/smart-defaults.test.ts +0 -361
  76. package/src/steps/async-support.steps.ts +0 -400
  77. package/src/steps/audit-core-builder.steps.ts +0 -188
  78. package/src/steps/audit-onerror-tuple.steps.ts +0 -99
  79. package/src/steps/basic-usage.steps.ts +0 -245
  80. package/src/steps/developer-experience.steps.ts +0 -204
  81. package/src/steps/error-handling.steps.ts +0 -345
  82. package/src/steps/fluent-api.steps.ts +0 -255
  83. package/src/steps/http-methods.steps.ts +0 -331
  84. package/src/steps/interceptor.steps.ts +0 -260
  85. package/src/steps/lifecycle-events.steps.ts +0 -142
  86. package/src/steps/performance-reliability.steps.ts +0 -423
  87. package/src/steps/plugin-integration.steps.ts +0 -280
  88. package/src/steps/request-history.steps.ts +0 -276
  89. package/src/steps/response-delay.steps.ts +0 -88
  90. package/src/steps/route-key-format.steps.ts +0 -99
  91. package/src/steps/standalone-server.steps.ts +0 -233
  92. package/src/steps/state-concurrency.steps.ts +0 -739
  93. package/src/steps/stateful-workflows.steps.ts +0 -353
  94. package/src/types.ts +0 -36
package/dist/index.d.ts CHANGED
@@ -7,6 +7,84 @@ declare global {
7
7
 
8
8
  namespace Schmock {
9
9
  type JSONSchema7 = import("json-schema").JSONSchema7;
10
+
11
+ /**
12
+ * A `Schema` or the boolean shorthand, wherever draft-07 allows a subschema.
13
+ */
14
+ type SchemaDefinition = Schema | boolean;
15
+
16
+ /**
17
+ * JSON Schema draft-07 plus the keywords Schmock's own tooling understands,
18
+ * applied recursively so nested subschemas accept them too.
19
+ *
20
+ * `JSONSchema7` rejects these keywords in an object literal, which is why
21
+ * schemas carrying them otherwise need a cast at every level that uses one.
22
+ * A `Schema` is assignable to `JSONSchema7`, so it can be handed to
23
+ * `fakerPlugin`, `validationPlugin` or any other schema-typed option.
24
+ *
25
+ * Only `@schmock/*` packages know these keywords. `@schmock/validation`
26
+ * registers them on its own Ajv instance; a strict Ajv of your own throws
27
+ * `strict mode: unknown keyword` until you do the same.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const schema: Schmock.Schema = {
32
+ * type: 'object',
33
+ * properties: {
34
+ * name: { type: 'string', faker: 'person.fullName' },
35
+ * nickname: { type: ['string', 'null'], schmockNullable: true },
36
+ * active: { type: 'boolean', schmockTrueProbability: 0.8 },
37
+ * },
38
+ * }
39
+ * ```
40
+ */
41
+ interface Schema
42
+ extends Omit<
43
+ JSONSchema7,
44
+ | "properties"
45
+ | "patternProperties"
46
+ | "additionalProperties"
47
+ | "items"
48
+ | "additionalItems"
49
+ | "contains"
50
+ | "propertyNames"
51
+ | "allOf"
52
+ | "anyOf"
53
+ | "oneOf"
54
+ | "not"
55
+ | "if"
56
+ | "then"
57
+ | "else"
58
+ | "$defs"
59
+ | "definitions"
60
+ | "dependencies"
61
+ > {
62
+ /** json-schema-faker method path, e.g. `"person.fullName"`. */
63
+ faker?: string | Record<string, unknown>;
64
+ /** Marks a null-permitting field: ~5% of generated values are `null`. */
65
+ schmockNullable?: boolean;
66
+ /** Probability (0–1) that a generated boolean is `true`. */
67
+ schmockTrueProbability?: number;
68
+
69
+ properties?: Record<string, SchemaDefinition>;
70
+ patternProperties?: Record<string, SchemaDefinition>;
71
+ additionalProperties?: SchemaDefinition;
72
+ items?: SchemaDefinition | SchemaDefinition[];
73
+ additionalItems?: SchemaDefinition;
74
+ contains?: SchemaDefinition;
75
+ propertyNames?: SchemaDefinition;
76
+ allOf?: SchemaDefinition[];
77
+ anyOf?: SchemaDefinition[];
78
+ oneOf?: SchemaDefinition[];
79
+ not?: SchemaDefinition;
80
+ if?: SchemaDefinition;
81
+ then?: SchemaDefinition;
82
+ else?: SchemaDefinition;
83
+ $defs?: Record<string, SchemaDefinition>;
84
+ definitions?: Record<string, SchemaDefinition>;
85
+ dependencies?: Record<string, SchemaDefinition | string[]>;
86
+ }
87
+
10
88
  /**
11
89
  * HTTP methods supported by Schmock
12
90
  */
@@ -22,13 +100,18 @@ namespace Schmock {
22
100
  /**
23
101
  * Route key format: 'METHOD /path'
24
102
  *
103
+ * The path must start with '/': transports always deliver a leading-slash
104
+ * pathname, so a slash-less key would register a route no request can reach.
105
+ * `parseRouteKey` rejects it at runtime; the template type surfaces the same
106
+ * mistake at compile time.
107
+ *
25
108
  * @example
26
109
  * 'GET /users'
27
110
  * 'POST /users/:id'
28
111
  * 'DELETE /api/posts/:postId/comments/:commentId'
29
112
  */
30
- type RouteKey = `${HttpMethod} ${string}`;
31
-
113
+ type RouteKey = `${HttpMethod} /${string}`;
114
+
32
115
  /**
33
116
  * Plugin interface for extending Schmock functionality
34
117
  */
@@ -40,11 +123,28 @@ namespace Schmock {
40
123
 
41
124
  /**
42
125
  * Called once when the plugin is added via .pipe()
43
- * Use this to register routes or configure the instance at setup time
44
- * @param instance - The callable mock instance
126
+ * Route registrations are committed atomically only when this hook returns
127
+ * synchronously. The scoped instance must not be retained or used later.
128
+ * Returning a Promise is unsupported and leaves the plugin inactive.
129
+ * @param instance - A synchronous, installation-scoped callable instance
45
130
  */
46
131
  install?(instance: CallableMockInstance): void;
47
132
 
133
+ /**
134
+ * Called during reset after every request admitted with this plugin settles.
135
+ * Cleanup runs in reverse registration order and must complete synchronously.
136
+ */
137
+ uninstall?(instance: CallableMockInstance): void;
138
+
139
+ /**
140
+ * Inspect or transform a request before its route generator executes.
141
+ * Returning a response short-circuits the generator, while returning only
142
+ * a context allows request changes to flow into the generator.
143
+ */
144
+ beforeRequest?(
145
+ context: PluginContext,
146
+ ): PluginResult | void | Promise<PluginResult | void>;
147
+
48
148
  /**
49
149
  * Process the request through this plugin
50
150
  * First plugin to set response becomes the generator, others transform
@@ -52,16 +152,22 @@ namespace Schmock {
52
152
  * @param response - Response from previous plugin (if any)
53
153
  * @returns Updated context and response
54
154
  */
55
- process(context: PluginContext, response?: unknown): PluginResult | Promise<PluginResult>;
155
+ process(
156
+ context: PluginContext,
157
+ response?: unknown,
158
+ ): PluginResult | Promise<PluginResult>;
56
159
 
57
160
  /**
58
- * Called when an error occurs
59
- * Can handle, transform, or suppress errors
161
+ * Called when this plugin or an earlier pipeline stage fails. If this hook
162
+ * does not recover, downstream handlers are tried in registration order.
60
163
  * @param error - The error that occurred
61
164
  * @param context - Plugin context
62
165
  * @returns Modified error, response data, or void to continue error propagation
63
166
  */
64
- onError?(error: Error, context: PluginContext): Error | ResponseResult | void | Promise<Error | ResponseResult | void>;
167
+ onError?(
168
+ error: Error,
169
+ context: PluginContext,
170
+ ): Error | ResponseResult | void | Promise<Error | ResponseResult | void>;
65
171
  }
66
172
 
67
173
  /**
@@ -99,8 +205,12 @@ namespace Schmock {
99
205
  body?: unknown;
100
206
  /** Shared state between plugins for this request */
101
207
  state: Map<string, unknown>;
208
+ /** True when a beforeRequest hook supplied the response instead of the route generator. */
209
+ requestShortCircuited?: boolean;
102
210
  /** Route-specific state */
103
211
  routeState?: Record<string, unknown>;
212
+ /** Abort signal associated with the admitted request */
213
+ readonly signal?: AbortSignal;
104
214
  }
105
215
 
106
216
  // ===== Callable API Types =====
@@ -120,6 +230,10 @@ namespace Schmock {
120
230
  /**
121
231
  * Maximum number of requests retained in history (FIFO eviction).
122
232
  * Defaults to unbounded; set this to cap memory growth in long-running servers.
233
+ *
234
+ * Must be a non-negative integer — `0` disables history entirely, and any
235
+ * other value (negative, fractional, NaN, Infinity) is rejected with a
236
+ * `SchmockError` (`INVALID_CONFIG`) when the mock is created.
123
237
  */
124
238
  maxHistorySize?: number;
125
239
  }
@@ -136,8 +250,10 @@ namespace Schmock {
136
250
  * Extension point for plugin-specific metadata.
137
251
  *
138
252
  * Intentionally open: `@schmock/openapi` stores "openapi:*" keys here
139
- * (e.g. `"openapi:operationId"`, `"openapi:tags"`), and third-party plugins
140
- * may do the same. Removing this signature would be a breaking change.
253
+ * (e.g. `"openapi:operationId"`, `"openapi:tags"`, `"openapi:owner"`,
254
+ * `"openapi:requestContent"`), and
255
+ * third-party plugins may do the same. Removing this signature would be a
256
+ * breaking change.
141
257
  *
142
258
  * **Known tradeoff:** typos in known keys (e.g. `{ contenType: "…" }`) compile
143
259
  * silently. Prefer using the explicitly typed properties above when possible.
@@ -146,22 +262,40 @@ namespace Schmock {
146
262
  }
147
263
 
148
264
  /**
149
- * Generator types that can be passed to route definitions
265
+ * Generator types that can be passed to route definitions.
266
+ *
267
+ * Core dispatches on exactly two shapes: a function is called per request,
268
+ * and anything else is returned verbatim as static data. There is no
269
+ * schema-generation arm — a JSON Schema handed to a route is serialized back
270
+ * to the client as a literal schema document. Schema-driven responses come
271
+ * from a plugin: `.pipe(fakerPlugin({ schema }))`.
150
272
  */
151
- type Generator =
152
- | GeneratorFunction
153
- | StaticData
154
- | JSONSchema7;
273
+ type Generator = GeneratorFunction | StaticData;
155
274
 
156
275
  /**
157
276
  * Function that generates responses
158
277
  */
159
- type GeneratorFunction = (context: RequestContext) => ResponseResult | Promise<ResponseResult>;
278
+ type GeneratorFunction = (
279
+ context: RequestContext,
280
+ ) => ResponseResult | Promise<ResponseResult>;
160
281
 
161
282
  /**
162
- * Static data (non-function) that gets returned as-is
283
+ * Static data (non-function) that gets returned as-is.
284
+ *
285
+ * `Record<string, unknown>` accepts object literals but not a value whose
286
+ * declared type is an interface without an index signature (e.g. a variable
287
+ * typed `JSONSchema7`); pass such values as literals or widen them.
163
288
  */
164
- type StaticData = string | number | boolean | null | undefined | Record<string, unknown> | unknown[];
289
+ type StaticData =
290
+ | string
291
+ | number
292
+ | boolean
293
+ | null
294
+ | undefined
295
+ | Record<string, unknown>
296
+ | unknown[]
297
+ | ArrayBuffer
298
+ | ArrayBufferView;
165
299
 
166
300
  /**
167
301
  * Context passed to generator functions
@@ -181,6 +315,16 @@ namespace Schmock {
181
315
  body?: unknown;
182
316
  /** Shared mutable state */
183
317
  state: Record<string, unknown>;
318
+ /**
319
+ * Per-request plugin state — the same `Map` as `PluginContext.state`.
320
+ *
321
+ * Lets a generator hand request-scoped data to the plugins that post-process
322
+ * its response (e.g. mutations staged until the final status is known).
323
+ * Absent when a generator is invoked outside the request pipeline.
324
+ */
325
+ pluginState?: Map<string, unknown>;
326
+ /** Abort signal associated with the request */
327
+ readonly signal?: AbortSignal;
184
328
  }
185
329
 
186
330
  /**
@@ -188,11 +332,22 @@ namespace Schmock {
188
332
  * - Any value: returns as 200 OK
189
333
  * - [status, body]: custom status with body
190
334
  * - [status, body, headers]: custom status, body, and headers
335
+ * - { status, body, headers? }: object envelope, equivalent to the tuple
336
+ * forms and produced by plugin error recovery
337
+ *
338
+ * The object envelope is detected by shape, so any returned object carrying a
339
+ * numeric `status` alongside a `body` is unwrapped rather than delivered as
340
+ * the payload — a domain object such as `{ status: 200, body: "draft" }` is
341
+ * indistinguishable from an envelope. An object whose `headers` is present
342
+ * but not a string record is *not* an envelope and is delivered whole. To
343
+ * return such a shape as data, nest it (`{ value: { status, body } }`) or use
344
+ * an explicit `[status, body]` tuple for the envelope instead.
191
345
  */
192
346
  type ResponseResult =
193
347
  | ResponseBody
194
348
  | [number, unknown]
195
- | [number, unknown, Record<string, string>];
349
+ | [number, unknown, Record<string, string>]
350
+ | { status: number; body: unknown; headers?: Record<string, string> };
196
351
 
197
352
  /**
198
353
  * Response object returned by handle method
@@ -210,6 +365,7 @@ namespace Schmock {
210
365
  headers?: Record<string, string>;
211
366
  body?: unknown;
212
367
  query?: Record<string, string>;
368
+ signal?: AbortSignal;
213
369
  }
214
370
 
215
371
  /**
@@ -240,12 +396,14 @@ namespace Schmock {
240
396
  interface CallableMockInstance {
241
397
  /**
242
398
  * Define a route by calling the instance directly
243
- *
399
+ *
244
400
  * @param route - Route pattern in format 'METHOD /path'
245
- * @param generator - Response generator (function, static data, or schema)
401
+ * @param generator - Response generator: a function called per request, or
402
+ * static data returned verbatim. There is no schema arm — use
403
+ * `.pipe(fakerPlugin({ schema }))` for schema-driven responses.
246
404
  * @param config - Route-specific configuration
247
405
  * @returns The same instance for method chaining
248
- *
406
+ *
249
407
  * @example
250
408
  * ```typescript
251
409
  * const mock = schmock()
@@ -253,19 +411,22 @@ namespace Schmock {
253
411
  * mock('POST /users', userData, { contentType: 'application/json' })
254
412
  * ```
255
413
  */
256
- (route: RouteKey, generator: Generator, config?: RouteConfig): CallableMockInstance;
414
+ (
415
+ route: RouteKey,
416
+ generator: Generator,
417
+ config?: RouteConfig,
418
+ ): CallableMockInstance;
257
419
 
258
420
  /**
259
421
  * Add a plugin to the pipeline
260
- *
422
+ *
261
423
  * @param plugin - Plugin to add to the pipeline
262
424
  * @returns The same instance for method chaining
263
- *
425
+ *
264
426
  * @example
265
427
  * ```typescript
428
+ * mock.pipe(authPlugin()).pipe(corsPlugin())
266
429
  * mock('GET /users', generator, config)
267
- * .pipe(authPlugin())
268
- * .pipe(corsPlugin())
269
430
  * ```
270
431
  */
271
432
  pipe(plugin: Plugin): CallableMockInstance;
@@ -285,7 +446,11 @@ namespace Schmock {
285
446
  * })
286
447
  * ```
287
448
  */
288
- handle(method: HttpMethod, path: string, options?: RequestOptions): Promise<Response>;
449
+ handle(
450
+ method: HttpMethod,
451
+ path: string,
452
+ options?: RequestOptions,
453
+ ): Promise<Response>;
289
454
 
290
455
  // ===== Request Spy / History API =====
291
456
 
@@ -328,7 +493,9 @@ namespace Schmock {
328
493
  // ===== Reset / Lifecycle =====
329
494
 
330
495
  /**
331
- * Clear all routes, state, plugins, and history
496
+ * Clear routes, state, plugins, listeners, and history, and stop the Node
497
+ * server. An explicitly acquired fetch interception remains active until
498
+ * its InterceptHandle is restored.
332
499
  */
333
500
  reset(): void;
334
501
 
@@ -347,12 +514,18 @@ namespace Schmock {
347
514
  /**
348
515
  * Register an event listener
349
516
  */
350
- on<E extends SchmockEvent>(event: E, listener: (data: SchmockEventMap[E]) => void): CallableMockInstance;
517
+ on<E extends SchmockEvent>(
518
+ event: E,
519
+ listener: (data: SchmockEventMap[E]) => void,
520
+ ): CallableMockInstance;
351
521
 
352
522
  /**
353
523
  * Remove an event listener
354
524
  */
355
- off<E extends SchmockEvent>(event: E, listener: (data: SchmockEventMap[E]) => void): CallableMockInstance;
525
+ off<E extends SchmockEvent>(
526
+ event: E,
527
+ listener: (data: SchmockEventMap[E]) => void,
528
+ ): CallableMockInstance;
356
529
 
357
530
  // ===== Introspection =====
358
531
 
@@ -389,9 +562,14 @@ namespace Schmock {
389
562
  * Intercept globalThis.fetch and route requests through this mock.
390
563
  * Client-side equivalent of listen().
391
564
  *
565
+ * A mock may hold any number of concurrent leases — nested providers,
566
+ * separate roots, or an adapter alongside a manual call. Each lease owns
567
+ * its own options and is released independently; the newest lease is
568
+ * consulted first.
569
+ *
392
570
  * @param options - Intercept configuration
393
- * @returns Handle with restore() to stop intercepting
394
- * @throws If already intercepting (call restore() first)
571
+ * @returns Handle with restore() to release this lease and update() to
572
+ * change its options without changing its position in the stack
395
573
  */
396
574
  intercept(options?: InterceptOptions): InterceptHandle;
397
575
  }
@@ -438,6 +616,7 @@ namespace Schmock {
438
616
  headers: Record<string, string>;
439
617
  body?: unknown;
440
618
  query: Record<string, string>;
619
+ readonly signal?: AbortSignal;
441
620
  }
442
621
 
443
622
  interface AdapterResponse {
@@ -477,37 +656,52 @@ namespace Schmock {
477
656
  }
478
657
 
479
658
  interface InterceptHandle {
480
- /** Stop intercepting and restore original fetch */
659
+ /** Release this lease; restores original fetch once the last lease goes */
481
660
  restore(): void;
661
+ /**
662
+ * Reconfigure this lease in place, keeping its position in the
663
+ * interception stack. Options are replaced wholesale — omitted fields fall
664
+ * back to their defaults, so `update({})` restores `passthrough: true`.
665
+ * No-op once the lease has been restored.
666
+ */
667
+ update(options?: InterceptOptions): void;
482
668
  /** Whether this interceptor is currently active */
483
669
  readonly active: boolean;
484
670
  }
485
671
 
486
672
  // ===== Lifecycle Events =====
487
673
 
674
+ /**
675
+ * Every lifecycle event carries `path`: the ORIGINAL request path in its
676
+ * canonical percent-encoded form, namespace prefix included. The
677
+ * namespace-stripped route form is available only as `routePath` on
678
+ * `request:match`.
679
+ */
488
680
  interface RequestStartEvent {
489
- method: HttpMethod;
490
- path: string;
491
- headers: Record<string, string>;
681
+ readonly method: HttpMethod;
682
+ readonly path: string;
683
+ readonly headers: Readonly<Record<string, string>>;
492
684
  }
493
685
 
494
686
  interface RequestMatchEvent {
495
- method: HttpMethod;
496
- path: string;
497
- routePath: string;
498
- params: Record<string, string>;
687
+ readonly method: HttpMethod;
688
+ /** Original request path, namespace prefix included */
689
+ readonly path: string;
690
+ /** Registered route path, namespace prefix stripped */
691
+ readonly routePath: string;
692
+ readonly params: Readonly<Record<string, string>>;
499
693
  }
500
694
 
501
695
  interface RequestNotFoundEvent {
502
- method: HttpMethod;
503
- path: string;
696
+ readonly method: HttpMethod;
697
+ readonly path: string;
504
698
  }
505
699
 
506
700
  interface RequestEndEvent {
507
- method: HttpMethod;
508
- path: string;
509
- status: number;
510
- duration: number;
701
+ readonly method: HttpMethod;
702
+ readonly path: string;
703
+ readonly status: number;
704
+ readonly duration: number;
511
705
  }
512
706
 
513
707
  type SchmockEventMap = {
@@ -555,10 +749,20 @@ namespace Schmock {
555
749
  interface CrudOperationMeta {
556
750
  /** Full success response schema (wrapper + items) */
557
751
  responseSchema?: JSONSchema7;
752
+ /** Concrete success status selected from the operation contract. */
753
+ responseStatus?: number;
558
754
  /** Response headers from spec */
559
755
  responseHeaders?: Record<string, ResponseHeaderDef>;
560
756
  /** Error response schemas keyed by status code */
561
757
  errorSchemas?: Map<number, JSONSchema7>;
758
+ /** Declared media types for the success response, in spec order. */
759
+ responseContentTypes?: string[];
760
+ /**
761
+ * Success response schemas keyed by declared media type (OAS3 `content`).
762
+ * When present it takes precedence over `responseSchema`, so anything that
763
+ * replaces `responseSchema` must clear this map too.
764
+ */
765
+ responseSchemasByMediaType?: Map<string, JSONSchema7>;
562
766
  }
563
767
 
564
768
  // ===== Faker Plugin Types =====
@@ -627,22 +831,72 @@ namespace Schmock {
627
831
  passthrough?: boolean;
628
832
  errorFormatter?: (error: Error, request: unknown) => unknown;
629
833
  transformRequest?: (request: unknown) => AdapterRequestOverride;
630
- transformResponse?: (
631
- response: Response,
632
- request: unknown,
633
- ) => Response;
834
+ transformResponse?: (response: Response, request: unknown) => Response;
634
835
  }
635
836
 
636
837
  // ===== OpenAPI Plugin Options =====
637
838
 
839
+ /** A callback request resolved from an OpenAPI callback expression. */
840
+ interface OpenApiCallbackRequest {
841
+ url: string;
842
+ method: HttpMethod;
843
+ headers: Record<string, string>;
844
+ body?: unknown;
845
+ }
846
+
847
+ /** Explicit application-owned delivery for OpenAPI callbacks. */
848
+ interface OpenApiCallbackOptions {
849
+ dispatch(request: OpenApiCallbackRequest): void | Promise<void>;
850
+ }
851
+
852
+ /**
853
+ * Policy governing `$ref`s that leave the root spec document.
854
+ *
855
+ * A spec is untrusted input — on the CLI it is a path a caller hands over —
856
+ * and `$ref` is a file-read and network primitive, so nothing outside the
857
+ * root document resolves unless it is opted into here.
858
+ */
859
+ interface OpenApiRefPolicy {
860
+ /** Allow any `$ref` that leaves the root document. Default `false`. */
861
+ external?: boolean;
862
+ /** Allow `http(s)` `$ref`s. Requires `external`. Default `false`. */
863
+ allowHttp?: boolean;
864
+ /**
865
+ * Hostnames an `http(s)` `$ref` may target. Empty or omitted means any
866
+ * host, still minus loopback, link-local and private ranges.
867
+ */
868
+ allowedHosts?: string[];
869
+ /** Per-request timeout for http `$ref`s, in ms. Default 5000. */
870
+ timeoutMs?: number;
871
+ /**
872
+ * Redirects to follow for an http `$ref`. Default 0.
873
+ *
874
+ * `fetch` exposes no numeric redirect cap, so this behaves as a boolean:
875
+ * `0` refuses redirects, any positive value follows up to the platform
876
+ * default. Use `allowedHosts` when the exact destination matters.
877
+ */
878
+ redirects?: number;
879
+ /** Maximum size of a single http `$ref` document, in bytes. Default 1 MB. */
880
+ maxBytes?: number;
881
+ }
882
+
638
883
  /**
639
884
  * Options for the OpenAPI plugin
640
885
  */
641
886
  interface OpenApiOptions {
642
887
  spec: string | object;
643
888
  seed?: SeedConfig;
889
+ /**
890
+ * Validate the spec against the OpenAPI schema and specification when it is
891
+ * loaded. Default `false`: incomplete specs are deliberately tolerated, and
892
+ * validation is expensive on large documents.
893
+ */
894
+ strict?: boolean;
895
+ /** External `$ref` resolution policy. External refs are off by default. */
896
+ refs?: OpenApiRefPolicy;
644
897
  validateRequests?: boolean;
645
898
  validateResponses?: boolean;
899
+ /** @deprecated Unsupported. Supplying this option throws OPENAPI_UNSUPPORTED_OPTION. */
646
900
  queryFeatures?: {
647
901
  pagination?: boolean;
648
902
  sorting?: boolean;
@@ -652,12 +906,24 @@ namespace Schmock {
652
906
  debug?: boolean;
653
907
  fakerSeed?: number;
654
908
  security?: boolean;
909
+ /**
910
+ * Enable callback delivery through an application-supplied dispatcher.
911
+ * Callbacks are disabled when this option is omitted; Schmock never
912
+ * performs callback network requests implicitly.
913
+ */
914
+ callbacks?: OpenApiCallbackOptions;
655
915
  /** Replace response schemas for specific routes. Key format: "METHOD /path" or "METHOD /path STATUS" */
656
916
  schemas?: Record<string, import("json-schema").JSONSchema7>;
657
917
  /** Called before generating a response body. Return a schema to replace the original, or void to keep it. */
658
918
  onSchema?: (
659
919
  schema: import("json-schema").JSONSchema7,
660
- context: { method: string; path: string; params: Record<string, string>; query: Record<string, string>; headers: Record<string, string> },
920
+ context: {
921
+ method: string;
922
+ path: string;
923
+ params: Record<string, string>;
924
+ query: Record<string, string>;
925
+ headers: Record<string, string>;
926
+ },
661
927
  ) => import("json-schema").JSONSchema7 | undefined;
662
928
  }
663
929
 
@@ -687,18 +953,70 @@ namespace Schmock {
687
953
  errors?: boolean;
688
954
  watch?: boolean;
689
955
  admin?: boolean;
956
+ /**
957
+ * Bearer token required by every `/schmock-admin/*` request
958
+ * (`--admin-token`). When `admin` is on and this is omitted, a random
959
+ * token is minted once and surfaced on {@link CliServer.adminToken}.
960
+ */
961
+ adminToken?: string;
962
+ /**
963
+ * How many requests the mock retains for `GET /schmock-admin/history`
964
+ * (`--admin-history-limit`, default 500). Ignored — history is disabled
965
+ * entirely — when `admin` is off.
966
+ */
967
+ adminHistoryLimit?: number;
968
+ /** Validate the spec against the OpenAPI schema at startup (`--strict`). */
969
+ strict?: boolean;
970
+ /** Resolve `$ref`s outside the spec document (`--refs-external`). */
971
+ refsExternal?: boolean;
972
+ /**
973
+ * Hosts an `http(s)` `$ref` may target (`--refs-allow-http`). Supplying
974
+ * this also enables http resolution, which still requires `refsExternal`.
975
+ */
976
+ refsAllowHttp?: string[];
977
+ /**
978
+ * How long {@link CliServer.close} waits for in-flight requests before
979
+ * the remaining sockets are destroyed (default 5000 ms). A half-sent
980
+ * request never completes on its own, so without a bound the close would
981
+ * hang.
982
+ */
983
+ shutdownGraceMs?: number;
984
+ }
985
+
986
+ /** Browser-safe subset of the Node server exposed by a CLI instance. */
987
+ interface CliHttpServer {
988
+ readonly listening: boolean;
989
+ address():
990
+ | string
991
+ | { address: string; family: string; port: number }
992
+ | null;
993
+ close(callback?: (error?: Error) => void): this;
994
+ closeAllConnections(): void;
995
+ closeIdleConnections(): void;
996
+ ref(): this;
997
+ unref(): this;
690
998
  }
691
999
 
692
1000
  /**
693
- * Running CLI server instance
1001
+ * Running CLI server instance. Import `CliServer` from `@schmock/cli` for
1002
+ * the exact Node.js server type.
694
1003
  */
695
1004
  interface CliServer {
696
- server: import("node:http").Server;
1005
+ server: CliHttpServer;
697
1006
  port: number;
698
1007
  hostname: string;
699
- close(): void;
1008
+ /**
1009
+ * The bearer token this server requires on `/schmock-admin/*`. Present
1010
+ * only when admin is enabled.
1011
+ */
1012
+ adminToken?: string;
1013
+ /**
1014
+ * Stop watching, stop accepting, and settle once the socket is released —
1015
+ * within {@link CliOptions.shutdownGraceMs}. Memoized: closing twice is
1016
+ * safe and resolves twice.
1017
+ */
1018
+ close(): Promise<void>;
700
1019
  }
701
-
702
1020
  }
703
1021
  }
704
1022
  // >>> schmock ambient namespace <<<
@@ -726,10 +1044,13 @@ namespace Schmock {
726
1044
  * @returns A callable mock instance
727
1045
  */
728
1046
  export declare function schmock(config?: Schmock.GlobalConfig): Schmock.CallableMockInstance;
729
- export { HTTP_METHODS, isHttpMethod, isRouteNotFound, isStatusTuple, ROUTE_NOT_FOUND_CODE, toHttpMethod, toRouteKey, } from "./constants.js";
730
- export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.js";
1047
+ export { isBinaryBody } from "./binary.js";
1048
+ export { getResponseException, HTTP_METHODS, isHttpMethod, isRouteNotFound, isStatusTuple, ROUTE_NOT_FOUND_CODE, toHttpMethod, toRouteKey, } from "./constants.js";
1049
+ export { InvalidResponseError, PluginError, ResourceLimitError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.js";
731
1050
  export { badRequest, created, forbidden, noContent, notFound, paginate, serverError, unauthorized, } from "./helpers.js";
732
- export { collectBody, parseNodeHeaders, parseNodeQuery, writeSchmockResponse, } from "./http-helpers.js";
1051
+ export type { HttpIngressErrorCode } from "./http-helpers.js";
1052
+ export { collectBody, HttpIngressError, parseNodeHeaders, parseNodeQuery, writeRejectedSchmockResponse, writeSchmockResponse, } from "./http-helpers.js";
733
1053
  export { createFetchInterceptor } from "./interceptor.js";
734
- export type { AdapterRequest, AdapterResponse, CallableMockInstance, Generator, GeneratorFunction, GlobalConfig, HttpMethod, InterceptHandle, InterceptOptions, Plugin, PluginContext, PluginResult, RequestContext, RequestOptions, RequestRecord, Response, ResponseBody, ResponseResult, RouteConfig, RouteInfo, RouteKey, ServerInfo, StaticData, } from "./types.js";
1054
+ export { normalizeResponse, serializeResponseBody, } from "./response-normalizer.js";
1055
+ export type { AdapterRequest, AdapterRequestOverride, AdapterResponse, AngularAdapterOptions, CallableMockInstance, CrudOperationMeta, ExpressAdapterOptions, FakerPluginOptions, Generator, GeneratorFunction, GlobalConfig, HttpMethod, InterceptHandle, InterceptOptions, OpenApiCallbackOptions, OpenApiCallbackRequest, OpenApiOptions, Plugin, PluginContext, PluginResult, RequestContext, RequestOptions, RequestRecord, ResourceOverride, Response, ResponseBody, ResponseHeaderDef, ResponseResult, RouteConfig, RouteInfo, RouteKey, Schema, SchemaDefinition, SchemaGenerationContext, SeedConfig, SeedSource, ServerInfo, StaticData, } from "./types.js";
735
1056
  //# sourceMappingURL=index.d.ts.map