@schmock/core 2.3.0 → 2.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.
Files changed (93) 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/builder.d.ts +31 -10
  7. package/dist/builder.d.ts.map +1 -1
  8. package/dist/builder.js +760 -190
  9. package/dist/constants.d.ts +38 -0
  10. package/dist/constants.d.ts.map +1 -1
  11. package/dist/constants.js +123 -2
  12. package/dist/errors.d.ts +14 -3
  13. package/dist/errors.d.ts.map +1 -1
  14. package/dist/errors.js +41 -6
  15. package/dist/helpers.d.ts +9 -0
  16. package/dist/helpers.d.ts.map +1 -1
  17. package/dist/helpers.js +18 -2
  18. package/dist/http-helpers.d.ts +39 -4
  19. package/dist/http-helpers.d.ts.map +1 -1
  20. package/dist/http-helpers.js +147 -49
  21. package/dist/index.d.ts +292 -34
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +8 -3
  24. package/dist/interceptor.d.ts +11 -1
  25. package/dist/interceptor.d.ts.map +1 -1
  26. package/dist/interceptor.js +316 -169
  27. package/dist/parser.d.ts.map +1 -1
  28. package/dist/parser.js +12 -3
  29. package/dist/plugin-pipeline.d.ts +3 -3
  30. package/dist/plugin-pipeline.d.ts.map +1 -1
  31. package/dist/plugin-pipeline.js +29 -14
  32. package/dist/response-normalizer.d.ts +16 -0
  33. package/dist/response-normalizer.d.ts.map +1 -0
  34. package/dist/response-normalizer.js +316 -0
  35. package/dist/response-parser.d.ts.map +1 -1
  36. package/dist/response-parser.js +39 -2
  37. package/dist/route-matcher.d.ts +3 -0
  38. package/dist/route-matcher.d.ts.map +1 -1
  39. package/dist/route-matcher.js +12 -6
  40. package/dist/types.d.ts +7 -2
  41. package/dist/types.d.ts.map +1 -1
  42. package/package.json +18 -6
  43. package/src/audit-core-builder.test.ts +0 -218
  44. package/src/audit-response-guard.test.ts +0 -38
  45. package/src/binary.test.ts +0 -126
  46. package/src/binary.ts +0 -11
  47. package/src/builder.test.ts +0 -289
  48. package/src/builder.ts +0 -748
  49. package/src/constants.test.ts +0 -99
  50. package/src/constants.ts +0 -73
  51. package/src/debug.test.ts +0 -241
  52. package/src/delay.test.ts +0 -319
  53. package/src/dist-shape.test.ts +0 -35
  54. package/src/errors.test.ts +0 -223
  55. package/src/errors.ts +0 -130
  56. package/src/factory.test.ts +0 -133
  57. package/src/helpers.test.ts +0 -147
  58. package/src/helpers.ts +0 -58
  59. package/src/http-helpers.test.ts +0 -39
  60. package/src/http-helpers.ts +0 -149
  61. package/src/index.ts +0 -152
  62. package/src/interceptor.test.ts +0 -488
  63. package/src/interceptor.ts +0 -407
  64. package/src/namespace.test.ts +0 -274
  65. package/src/parser.property.test.ts +0 -630
  66. package/src/parser.test.ts +0 -148
  67. package/src/parser.ts +0 -64
  68. package/src/plugin-pipeline.ts +0 -208
  69. package/src/plugin-system.test.ts +0 -697
  70. package/src/response-parser.ts +0 -114
  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 -234
  75. package/src/smart-defaults.test.ts +0 -361
  76. package/src/steps/async-support.steps.ts +0 -421
  77. package/src/steps/audit-core-builder.steps.ts +0 -188
  78. package/src/steps/audit-onerror-tuple.steps.ts +0 -83
  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 -555
  82. package/src/steps/fetch-interceptor.steps.ts +0 -457
  83. package/src/steps/fluent-api.steps.ts +0 -255
  84. package/src/steps/http-methods.steps.ts +0 -342
  85. package/src/steps/lifecycle-events.steps.ts +0 -149
  86. package/src/steps/performance-reliability.steps.ts +0 -90
  87. package/src/steps/plugin-integration.steps.ts +0 -334
  88. package/src/steps/request-history.steps.ts +0 -458
  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 -227
  92. package/src/steps/state-concurrency.steps.ts +0 -167
  93. 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,12 +100,17 @@ 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}`;
113
+ type RouteKey = `${HttpMethod} /${string}`;
31
114
 
32
115
  /**
33
116
  * Plugin interface for extending Schmock functionality
@@ -40,11 +123,19 @@ 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
+
48
139
  /**
49
140
  * Inspect or transform a request before its route generator executes.
50
141
  * Returning a response short-circuits the generator, while returning only
@@ -118,6 +209,8 @@ namespace Schmock {
118
209
  requestShortCircuited?: boolean;
119
210
  /** Route-specific state */
120
211
  routeState?: Record<string, unknown>;
212
+ /** Abort signal associated with the admitted request */
213
+ readonly signal?: AbortSignal;
121
214
  }
122
215
 
123
216
  // ===== Callable API Types =====
@@ -137,6 +230,10 @@ namespace Schmock {
137
230
  /**
138
231
  * Maximum number of requests retained in history (FIFO eviction).
139
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.
140
237
  */
141
238
  maxHistorySize?: number;
142
239
  }
@@ -153,8 +250,10 @@ namespace Schmock {
153
250
  * Extension point for plugin-specific metadata.
154
251
  *
155
252
  * Intentionally open: `@schmock/openapi` stores "openapi:*" keys here
156
- * (e.g. `"openapi:operationId"`, `"openapi:tags"`), and third-party plugins
157
- * 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.
158
257
  *
159
258
  * **Known tradeoff:** typos in known keys (e.g. `{ contenType: "…" }`) compile
160
259
  * silently. Prefer using the explicitly typed properties above when possible.
@@ -163,9 +262,15 @@ namespace Schmock {
163
262
  }
164
263
 
165
264
  /**
166
- * 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 }))`.
167
272
  */
168
- type Generator = GeneratorFunction | StaticData | JSONSchema7;
273
+ type Generator = GeneratorFunction | StaticData;
169
274
 
170
275
  /**
171
276
  * Function that generates responses
@@ -175,7 +280,11 @@ namespace Schmock {
175
280
  ) => ResponseResult | Promise<ResponseResult>;
176
281
 
177
282
  /**
178
- * 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.
179
288
  */
180
289
  type StaticData =
181
290
  | string
@@ -206,6 +315,16 @@ namespace Schmock {
206
315
  body?: unknown;
207
316
  /** Shared mutable state */
208
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;
209
328
  }
210
329
 
211
330
  /**
@@ -213,11 +332,22 @@ namespace Schmock {
213
332
  * - Any value: returns as 200 OK
214
333
  * - [status, body]: custom status with body
215
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.
216
345
  */
217
346
  type ResponseResult =
218
347
  | ResponseBody
219
348
  | [number, unknown]
220
- | [number, unknown, Record<string, string>];
349
+ | [number, unknown, Record<string, string>]
350
+ | { status: number; body: unknown; headers?: Record<string, string> };
221
351
 
222
352
  /**
223
353
  * Response object returned by handle method
@@ -235,6 +365,7 @@ namespace Schmock {
235
365
  headers?: Record<string, string>;
236
366
  body?: unknown;
237
367
  query?: Record<string, string>;
368
+ signal?: AbortSignal;
238
369
  }
239
370
 
240
371
  /**
@@ -267,7 +398,9 @@ namespace Schmock {
267
398
  * Define a route by calling the instance directly
268
399
  *
269
400
  * @param route - Route pattern in format 'METHOD /path'
270
- * @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.
271
404
  * @param config - Route-specific configuration
272
405
  * @returns The same instance for method chaining
273
406
  *
@@ -360,7 +493,9 @@ namespace Schmock {
360
493
  // ===== Reset / Lifecycle =====
361
494
 
362
495
  /**
363
- * 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.
364
499
  */
365
500
  reset(): void;
366
501
 
@@ -427,9 +562,14 @@ namespace Schmock {
427
562
  * Intercept globalThis.fetch and route requests through this mock.
428
563
  * Client-side equivalent of listen().
429
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
+ *
430
570
  * @param options - Intercept configuration
431
- * @returns Handle with restore() to stop intercepting
432
- * @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
433
573
  */
434
574
  intercept(options?: InterceptOptions): InterceptHandle;
435
575
  }
@@ -476,6 +616,7 @@ namespace Schmock {
476
616
  headers: Record<string, string>;
477
617
  body?: unknown;
478
618
  query: Record<string, string>;
619
+ readonly signal?: AbortSignal;
479
620
  }
480
621
 
481
622
  interface AdapterResponse {
@@ -515,37 +656,52 @@ namespace Schmock {
515
656
  }
516
657
 
517
658
  interface InterceptHandle {
518
- /** Stop intercepting and restore original fetch */
659
+ /** Release this lease; restores original fetch once the last lease goes */
519
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;
520
668
  /** Whether this interceptor is currently active */
521
669
  readonly active: boolean;
522
670
  }
523
671
 
524
672
  // ===== Lifecycle Events =====
525
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
+ */
526
680
  interface RequestStartEvent {
527
- method: HttpMethod;
528
- path: string;
529
- headers: Record<string, string>;
681
+ readonly method: HttpMethod;
682
+ readonly path: string;
683
+ readonly headers: Readonly<Record<string, string>>;
530
684
  }
531
685
 
532
686
  interface RequestMatchEvent {
533
- method: HttpMethod;
534
- path: string;
535
- routePath: string;
536
- 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>>;
537
693
  }
538
694
 
539
695
  interface RequestNotFoundEvent {
540
- method: HttpMethod;
541
- path: string;
696
+ readonly method: HttpMethod;
697
+ readonly path: string;
542
698
  }
543
699
 
544
700
  interface RequestEndEvent {
545
- method: HttpMethod;
546
- path: string;
547
- status: number;
548
- duration: number;
701
+ readonly method: HttpMethod;
702
+ readonly path: string;
703
+ readonly status: number;
704
+ readonly duration: number;
549
705
  }
550
706
 
551
707
  type SchmockEventMap = {
@@ -599,6 +755,14 @@ namespace Schmock {
599
755
  responseHeaders?: Record<string, ResponseHeaderDef>;
600
756
  /** Error response schemas keyed by status code */
601
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>;
602
766
  }
603
767
 
604
768
  // ===== Faker Plugin Types =====
@@ -685,12 +849,51 @@ namespace Schmock {
685
849
  dispatch(request: OpenApiCallbackRequest): void | Promise<void>;
686
850
  }
687
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
+
688
883
  /**
689
884
  * Options for the OpenAPI plugin
690
885
  */
691
886
  interface OpenApiOptions {
692
887
  spec: string | object;
693
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;
694
897
  validateRequests?: boolean;
695
898
  validateResponses?: boolean;
696
899
  /** @deprecated Unsupported. Supplying this option throws OPENAPI_UNSUPPORTED_OPTION. */
@@ -750,16 +953,69 @@ namespace Schmock {
750
953
  errors?: boolean;
751
954
  watch?: boolean;
752
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;
753
998
  }
754
999
 
755
1000
  /**
756
- * Running CLI server instance
1001
+ * Running CLI server instance. Import `CliServer` from `@schmock/cli` for
1002
+ * the exact Node.js server type.
757
1003
  */
758
1004
  interface CliServer {
759
- server: import("node:http").Server;
1005
+ server: CliHttpServer;
760
1006
  port: number;
761
1007
  hostname: string;
762
- 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>;
763
1019
  }
764
1020
  }
765
1021
  }
@@ -789,10 +1045,12 @@ namespace Schmock {
789
1045
  */
790
1046
  export declare function schmock(config?: Schmock.GlobalConfig): Schmock.CallableMockInstance;
791
1047
  export { isBinaryBody } from "./binary.js";
792
- export { HTTP_METHODS, isHttpMethod, isRouteNotFound, isStatusTuple, ROUTE_NOT_FOUND_CODE, toHttpMethod, toRouteKey, } from "./constants.js";
793
- export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.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";
794
1050
  export { badRequest, created, forbidden, noContent, notFound, paginate, serverError, unauthorized, } from "./helpers.js";
795
- 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";
796
1053
  export { createFetchInterceptor } from "./interceptor.js";
797
- 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";
798
1056
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CACrB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,GAC5B,OAAO,CAAC,oBAAoB,CAqD9B;AAED,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,UAAU,GACX,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,GACb,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,UAAU,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,YAAY,GACb,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,YAAY,EACV,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,MAAM,EACN,aAAa,EACb,YAAY,EACZ,cAAc,EACd,cAAc,EACd,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,WAAW,EACX,SAAS,EACT,QAAQ,EACR,UAAU,EACV,UAAU,GACX,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CACrB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,GAC5B,OAAO,CAAC,oBAAoB,CAyD9B;AAED,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,UAAU,GACX,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,GACb,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,UAAU,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,YAAY,GACb,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,4BAA4B,EAC5B,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EACL,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAElC,YAAY,EACV,cAAc,EACd,sBAAsB,EACtB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,EACtB,cAAc,EACd,MAAM,EACN,aAAa,EACb,YAAY,EACZ,cAAc,EACd,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,SAAS,EACT,QAAQ,EACR,MAAM,EACN,gBAAgB,EAChB,uBAAuB,EACvB,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,GACX,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CallableMockInstance } from "./builder.js";
2
+ const REQUEST_ADMISSION = Symbol.for("@schmock/core.request-admission");
2
3
  /**
3
4
  * Create a new Schmock mock instance with callable API.
4
5
  *
@@ -56,18 +57,22 @@ export function schmock(config) {
56
57
  close: instance.close.bind(instance),
57
58
  intercept: (options) => instance.intercept(options),
58
59
  });
60
+ Object.defineProperty(callableInstance, REQUEST_ADMISSION, {
61
+ value: () => instance.createRequestAdmission(),
62
+ });
59
63
  instance.setCallableRef(callableInstance);
60
64
  return callableInstance;
61
65
  }
62
66
  export { isBinaryBody } from "./binary.js";
63
67
  // Re-export constants and utilities
64
- export { HTTP_METHODS, isHttpMethod, isRouteNotFound, isStatusTuple, ROUTE_NOT_FOUND_CODE, toHttpMethod, toRouteKey, } from "./constants.js";
68
+ export { getResponseException, HTTP_METHODS, isHttpMethod, isRouteNotFound, isStatusTuple, ROUTE_NOT_FOUND_CODE, toHttpMethod, toRouteKey, } from "./constants.js";
65
69
  // Re-export errors
66
- export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.js";
70
+ export { InvalidResponseError, PluginError, ResourceLimitError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.js";
67
71
  // Re-export response helpers
68
72
  export { badRequest, created, forbidden, noContent, notFound, paginate, serverError, unauthorized, } from "./helpers.js";
69
73
  // Re-export HTTP server helpers
70
- export { collectBody, parseNodeHeaders, parseNodeQuery, writeSchmockResponse, } from "./http-helpers.js";
74
+ export { collectBody, HttpIngressError, parseNodeHeaders, parseNodeQuery, writeRejectedSchmockResponse, writeSchmockResponse, } from "./http-helpers.js";
71
75
  // Re-export types
72
76
  // Re-export interceptor
73
77
  export { createFetchInterceptor } from "./interceptor.js";
78
+ export { normalizeResponse, serializeResponseBody, } from "./response-normalizer.js";
@@ -1,5 +1,15 @@
1
+ type InterceptRequestHandler = (method: Schmock.HttpMethod, path: string, requestOptions?: Schmock.RequestOptions) => Promise<Schmock.Response>;
2
+ interface InterceptRequestAdmission {
3
+ handle: InterceptRequestHandler;
4
+ release(): void;
5
+ }
1
6
  /**
2
7
  * Create a fetch interceptor that routes requests through mock.handle().
8
+ *
9
+ * `owner` identifies the mock behind the lease. Leases sharing an owner are
10
+ * consulted at most once per request, so a mock held by several leases runs
11
+ * its handler — and emits its lifecycle events — once per network request.
3
12
  */
4
- export declare function createFetchInterceptor(handle: (method: Schmock.HttpMethod, path: string, requestOptions?: Schmock.RequestOptions) => Promise<Schmock.Response>, options?: Schmock.InterceptOptions): Schmock.InterceptHandle;
13
+ export declare function createFetchInterceptor(handle: InterceptRequestHandler, options?: Schmock.InterceptOptions, admitRequest?: () => InterceptRequestAdmission, owner?: symbol): Schmock.InterceptHandle;
14
+ export {};
5
15
  //# sourceMappingURL=interceptor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAwPA;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,CACN,MAAM,EAAE,OAAO,CAAC,UAAU,EAC1B,IAAI,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,OAAO,CAAC,cAAc,KACpC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAC9B,OAAO,GAAE,OAAO,CAAC,gBAAqB,GACrC,OAAO,CAAC,eAAe,CAoJzB"}
1
+ {"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAkCA,KAAK,uBAAuB,GAAG,CAC7B,MAAM,EAAE,OAAO,CAAC,UAAU,EAC1B,IAAI,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,OAAO,CAAC,cAAc,KACpC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AAU/B,UAAU,yBAAyB;IACjC,MAAM,EAAE,uBAAuB,CAAC;IAChC,OAAO,IAAI,IAAI,CAAC;CACjB;AAqYD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,uBAAuB,EAC/B,OAAO,GAAE,OAAO,CAAC,gBAAqB,EACtC,YAAY,CAAC,EAAE,MAAM,yBAAyB,EAC9C,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,CAqKzB"}