@schmock/core 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.d.ts +696 -0
  2. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -1,3 +1,699 @@
1
+ // >>> schmock ambient namespace <<<
2
+ declare global {
3
+ /**
4
+ * Schmock - Schema-driven mock API generator with callable API
5
+ * @packageDocumentation
6
+ */
7
+
8
+ namespace Schmock {
9
+ type JSONSchema7 = import("json-schema").JSONSchema7;
10
+ /**
11
+ * HTTP methods supported by Schmock
12
+ */
13
+ type HttpMethod =
14
+ | "GET"
15
+ | "POST"
16
+ | "PUT"
17
+ | "DELETE"
18
+ | "PATCH"
19
+ | "HEAD"
20
+ | "OPTIONS";
21
+
22
+ /**
23
+ * Route key format: 'METHOD /path'
24
+ *
25
+ * @example
26
+ * 'GET /users'
27
+ * 'POST /users/:id'
28
+ * 'DELETE /api/posts/:postId/comments/:commentId'
29
+ */
30
+ type RouteKey = `${HttpMethod} ${string}`;
31
+
32
+ /**
33
+ * Plugin interface for extending Schmock functionality
34
+ */
35
+ interface Plugin {
36
+ /** Unique plugin identifier */
37
+ name: string;
38
+ /** Plugin version (semver) */
39
+ version?: string;
40
+
41
+ /**
42
+ * 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
45
+ */
46
+ install?(instance: CallableMockInstance): void;
47
+
48
+ /**
49
+ * Process the request through this plugin
50
+ * First plugin to set response becomes the generator, others transform
51
+ * @param context - Plugin context with request details
52
+ * @param response - Response from previous plugin (if any)
53
+ * @returns Updated context and response
54
+ */
55
+ process(context: PluginContext, response?: unknown): PluginResult | Promise<PluginResult>;
56
+
57
+ /**
58
+ * Called when an error occurs
59
+ * Can handle, transform, or suppress errors
60
+ * @param error - The error that occurred
61
+ * @param context - Plugin context
62
+ * @returns Modified error, response data, or void to continue error propagation
63
+ */
64
+ onError?(error: Error, context: PluginContext): Error | ResponseResult | void | Promise<Error | ResponseResult | void>;
65
+ }
66
+
67
+ /**
68
+ * Alias for response body type
69
+ */
70
+ type ResponseBody = unknown;
71
+
72
+ /**
73
+ * Result returned by plugin process method
74
+ */
75
+ interface PluginResult {
76
+ /** Updated context */
77
+ context: PluginContext;
78
+ /** Response data (if generated/modified) */
79
+ response?: unknown;
80
+ }
81
+
82
+ /**
83
+ * Context passed through plugin pipeline
84
+ */
85
+ interface PluginContext {
86
+ /** Request path */
87
+ path: string;
88
+ /** Matched route configuration */
89
+ route: RouteConfig;
90
+ /** HTTP method */
91
+ method: HttpMethod;
92
+ /** Route parameters */
93
+ params: Record<string, string>;
94
+ /** Query parameters */
95
+ query: Record<string, string>;
96
+ /** Request headers */
97
+ headers: Record<string, string>;
98
+ /** Request body */
99
+ body?: unknown;
100
+ /** Shared state between plugins for this request */
101
+ state: Map<string, unknown>;
102
+ /** Route-specific state */
103
+ routeState?: Record<string, unknown>;
104
+ }
105
+
106
+ // ===== Callable API Types =====
107
+
108
+ /**
109
+ * Global configuration options for the mock instance
110
+ */
111
+ interface GlobalConfig {
112
+ /** Base path prefix for all routes */
113
+ namespace?: string;
114
+ /** Response delay in ms, or [min, max] for random delay */
115
+ delay?: number | [number, number];
116
+ /** Enable debug mode for detailed logging */
117
+ debug?: boolean;
118
+ /** Initial shared state object */
119
+ state?: Record<string, unknown>;
120
+ /**
121
+ * Maximum number of requests retained in history (FIFO eviction).
122
+ * Defaults to unbounded; set this to cap memory growth in long-running servers.
123
+ */
124
+ maxHistorySize?: number;
125
+ }
126
+
127
+ /**
128
+ * Route-specific configuration options
129
+ */
130
+ interface RouteConfig {
131
+ /** MIME type for content type validation (auto-detected if not provided) */
132
+ contentType?: string;
133
+ /** Per-route response delay in ms, or [min, max] for random delay (overrides global) */
134
+ delay?: number | [number, number];
135
+ /** Additional route-specific options */
136
+ [key: string]: unknown;
137
+ }
138
+
139
+ /**
140
+ * Generator types that can be passed to route definitions
141
+ */
142
+ type Generator =
143
+ | GeneratorFunction
144
+ | StaticData
145
+ | JSONSchema7;
146
+
147
+ /**
148
+ * Function that generates responses
149
+ */
150
+ type GeneratorFunction = (context: RequestContext) => ResponseResult | Promise<ResponseResult>;
151
+
152
+ /**
153
+ * Static data (non-function) that gets returned as-is
154
+ */
155
+ type StaticData = string | number | boolean | null | undefined | Record<string, unknown> | unknown[];
156
+
157
+ /**
158
+ * Context passed to generator functions
159
+ */
160
+ interface RequestContext {
161
+ /** HTTP method */
162
+ method: HttpMethod;
163
+ /** Request path */
164
+ path: string;
165
+ /** Route parameters (e.g., :id) */
166
+ params: Record<string, string>;
167
+ /** Query string parameters */
168
+ query: Record<string, string>;
169
+ /** Request headers */
170
+ headers: Record<string, string>;
171
+ /** Request body (for POST, PUT, PATCH) */
172
+ body?: unknown;
173
+ /** Shared mutable state */
174
+ state: Record<string, unknown>;
175
+ }
176
+
177
+ /**
178
+ * Response result types:
179
+ * - Any value: returns as 200 OK
180
+ * - [status, body]: custom status with body
181
+ * - [status, body, headers]: custom status, body, and headers
182
+ */
183
+ type ResponseResult =
184
+ | ResponseBody
185
+ | [number, unknown]
186
+ | [number, unknown, Record<string, string>];
187
+
188
+ /**
189
+ * Response object returned by handle method
190
+ */
191
+ interface Response {
192
+ status: number;
193
+ body: unknown;
194
+ headers: Record<string, string>;
195
+ }
196
+
197
+ /**
198
+ * Options for handle method
199
+ */
200
+ interface RequestOptions {
201
+ headers?: Record<string, string>;
202
+ body?: unknown;
203
+ query?: Record<string, string>;
204
+ }
205
+
206
+ /**
207
+ * Record of a single request handled by the mock
208
+ */
209
+ interface RequestRecord {
210
+ /** HTTP method */
211
+ method: HttpMethod;
212
+ /** Request path (without namespace) */
213
+ path: string;
214
+ /** Extracted route parameters */
215
+ params: Record<string, string>;
216
+ /** Query parameters */
217
+ query: Record<string, string>;
218
+ /** Request headers */
219
+ headers: Record<string, string>;
220
+ /** Request body */
221
+ body: unknown;
222
+ /** Unix timestamp (ms) when request was handled */
223
+ timestamp: number;
224
+ /** Response returned for this request */
225
+ response: { status: number; body: unknown };
226
+ }
227
+
228
+ /**
229
+ * Main callable mock instance interface
230
+ */
231
+ interface CallableMockInstance {
232
+ /**
233
+ * Define a route by calling the instance directly
234
+ *
235
+ * @param route - Route pattern in format 'METHOD /path'
236
+ * @param generator - Response generator (function, static data, or schema)
237
+ * @param config - Route-specific configuration
238
+ * @returns The same instance for method chaining
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * const mock = schmock()
243
+ * mock('GET /users', () => [...users], { contentType: 'application/json' })
244
+ * mock('POST /users', userData, { contentType: 'application/json' })
245
+ * ```
246
+ */
247
+ (route: RouteKey, generator: Generator, config?: RouteConfig): CallableMockInstance;
248
+
249
+ /**
250
+ * Add a plugin to the pipeline
251
+ *
252
+ * @param plugin - Plugin to add to the pipeline
253
+ * @returns The same instance for method chaining
254
+ *
255
+ * @example
256
+ * ```typescript
257
+ * mock('GET /users', generator, config)
258
+ * .pipe(authPlugin())
259
+ * .pipe(corsPlugin())
260
+ * ```
261
+ */
262
+ pipe(plugin: Plugin): CallableMockInstance;
263
+
264
+ /**
265
+ * Handle a request and return a response
266
+ *
267
+ * @param method - HTTP method
268
+ * @param path - Request path
269
+ * @param options - Request options (headers, body, query)
270
+ * @returns Promise resolving to response object
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * const response = await mock.handle('GET', '/users', {
275
+ * headers: { 'Authorization': 'Bearer token' }
276
+ * })
277
+ * ```
278
+ */
279
+ handle(method: HttpMethod, path: string, options?: RequestOptions): Promise<Response>;
280
+
281
+ // ===== Request Spy / History API =====
282
+
283
+ /**
284
+ * Get all recorded requests, optionally filtered by method and path
285
+ *
286
+ * @param method - Filter by HTTP method
287
+ * @param path - Filter by request path
288
+ * @returns Array of request records
289
+ */
290
+ history(): RequestRecord[];
291
+ history(method: HttpMethod, path: string): RequestRecord[];
292
+
293
+ /**
294
+ * Check if any request was made, optionally for a specific route
295
+ *
296
+ * @param method - Filter by HTTP method
297
+ * @param path - Filter by request path
298
+ * @returns true if at least one matching request was recorded
299
+ */
300
+ called(): boolean;
301
+ called(method: HttpMethod, path: string): boolean;
302
+
303
+ /**
304
+ * Get the number of recorded requests, optionally for a specific route
305
+ *
306
+ * @param method - Filter by HTTP method
307
+ * @param path - Filter by request path
308
+ * @returns Number of matching requests
309
+ */
310
+ callCount(): number;
311
+ callCount(method: HttpMethod, path: string): number;
312
+
313
+ /**
314
+ * Get the most recent request, optionally for a specific route
315
+ *
316
+ * @param method - Filter by HTTP method
317
+ * @param path - Filter by request path
318
+ * @returns Most recent matching request record, or undefined
319
+ */
320
+ lastRequest(): RequestRecord | undefined;
321
+ lastRequest(method: HttpMethod, path: string): RequestRecord | undefined;
322
+
323
+ // ===== Reset / Lifecycle =====
324
+
325
+ /**
326
+ * Clear all routes, state, plugins, and history
327
+ */
328
+ reset(): void;
329
+
330
+ /**
331
+ * Clear only request history, keep routes and state
332
+ */
333
+ resetHistory(): void;
334
+
335
+ /**
336
+ * Clear only state, keep routes and history
337
+ */
338
+ resetState(): void;
339
+
340
+ // ===== Lifecycle Events =====
341
+
342
+ /**
343
+ * Register an event listener
344
+ */
345
+ on<E extends SchmockEvent>(event: E, listener: (data: SchmockEventMap[E]) => void): CallableMockInstance;
346
+
347
+ /**
348
+ * Remove an event listener
349
+ */
350
+ off<E extends SchmockEvent>(event: E, listener: (data: SchmockEventMap[E]) => void): CallableMockInstance;
351
+
352
+ // ===== Introspection =====
353
+
354
+ /**
355
+ * Get all registered routes as an array of route info objects
356
+ */
357
+ getRoutes(): RouteInfo[];
358
+
359
+ /**
360
+ * Get the current shared state object
361
+ */
362
+ getState(): Record<string, unknown>;
363
+
364
+ // ===== Standalone Server =====
365
+
366
+ /**
367
+ * Start a standalone HTTP server
368
+ *
369
+ * @param port - Port to listen on (0 for random)
370
+ * @param hostname - Hostname to bind to (default: "127.0.0.1")
371
+ * @returns Promise resolving to server info with actual port and hostname
372
+ * @throws If the server is already running
373
+ */
374
+ listen(port?: number, hostname?: string): Promise<ServerInfo>;
375
+
376
+ /**
377
+ * Stop the standalone server (idempotent, no-op if not running)
378
+ */
379
+ close(): void;
380
+
381
+ // ===== Fetch Interceptor =====
382
+
383
+ /**
384
+ * Intercept globalThis.fetch and route requests through this mock.
385
+ * Client-side equivalent of listen().
386
+ *
387
+ * @param options - Intercept configuration
388
+ * @returns Handle with restore() to stop intercepting
389
+ * @throws If already intercepting (call restore() first)
390
+ */
391
+ intercept(options?: InterceptOptions): InterceptHandle;
392
+ }
393
+
394
+ /**
395
+ * Information about a running standalone server
396
+ */
397
+ interface ServerInfo {
398
+ /** Port the server is listening on */
399
+ port: number;
400
+ /** Hostname the server is bound to */
401
+ hostname: string;
402
+ }
403
+
404
+ // ===== Response Helpers =====
405
+
406
+ interface PaginateOptions {
407
+ page?: number;
408
+ pageSize?: number;
409
+ }
410
+
411
+ interface PaginatedResponse<T> {
412
+ data: T[];
413
+ page: number;
414
+ pageSize: number;
415
+ total: number;
416
+ totalPages: number;
417
+ }
418
+
419
+ // ===== Adapter Types =====
420
+
421
+ interface AdapterRequest {
422
+ method: string;
423
+ path: string;
424
+ headers: Record<string, string>;
425
+ body?: unknown;
426
+ query: Record<string, string>;
427
+ }
428
+
429
+ interface AdapterResponse {
430
+ status: number;
431
+ body: unknown;
432
+ headers: Record<string, string>;
433
+ }
434
+
435
+ interface InterceptOptions {
436
+ /**
437
+ * Only intercept URLs matching this base.
438
+ *
439
+ * Two modes:
440
+ * - Path form ("/api"): match request pathnames whose prefix is the
441
+ * base path (with a segment-boundary check, so "/api" never
442
+ * matches "/apiv2").
443
+ * - Origin form ("https://api.example.com" or
444
+ * "https://api.example.com/v1"): require the request origin to
445
+ * match the base origin AND, if a base path is present, the
446
+ * request pathname to start with it. Relative-URL fetches
447
+ * (no origin) won't match an origin-form base.
448
+ */
449
+ baseUrl?: string;
450
+ /** Pass unmatched routes to real fetch (default: true) */
451
+ passthrough?: boolean;
452
+ /** Modify request before Schmock handles it */
453
+ beforeRequest?: (
454
+ request: AdapterRequest,
455
+ ) => AdapterRequest | void | Promise<AdapterRequest | void>;
456
+ /** Modify response before returning to caller */
457
+ beforeResponse?: (
458
+ response: AdapterResponse,
459
+ request: AdapterRequest,
460
+ ) => AdapterResponse | void | Promise<AdapterResponse | void>;
461
+ /** Format errors into custom response bodies */
462
+ errorFormatter?: (error: Error) => unknown;
463
+ }
464
+
465
+ interface InterceptHandle {
466
+ /** Stop intercepting and restore original fetch */
467
+ restore(): void;
468
+ /** Whether this interceptor is currently active */
469
+ readonly active: boolean;
470
+ }
471
+
472
+ // ===== Lifecycle Events =====
473
+
474
+ interface RequestStartEvent {
475
+ method: HttpMethod;
476
+ path: string;
477
+ headers: Record<string, string>;
478
+ }
479
+
480
+ interface RequestMatchEvent {
481
+ method: HttpMethod;
482
+ path: string;
483
+ routePath: string;
484
+ params: Record<string, string>;
485
+ }
486
+
487
+ interface RequestNotFoundEvent {
488
+ method: HttpMethod;
489
+ path: string;
490
+ }
491
+
492
+ interface RequestEndEvent {
493
+ method: HttpMethod;
494
+ path: string;
495
+ status: number;
496
+ duration: number;
497
+ }
498
+
499
+ type SchmockEventMap = {
500
+ "request:start": RequestStartEvent;
501
+ "request:match": RequestMatchEvent;
502
+ "request:notfound": RequestNotFoundEvent;
503
+ "request:end": RequestEndEvent;
504
+ };
505
+
506
+ type SchmockEvent = keyof SchmockEventMap;
507
+
508
+ // ===== Introspection Types =====
509
+
510
+ interface RouteInfo {
511
+ method: HttpMethod;
512
+ path: string;
513
+ hasParams: boolean;
514
+ }
515
+
516
+ // ===== OpenAPI Plugin Types =====
517
+
518
+ /**
519
+ * Per-resource configuration override for OpenAPI plugin
520
+ */
521
+ interface ResourceOverride {
522
+ /** Override: which property in list response holds the items array (e.g. "data") */
523
+ listWrapProperty?: string;
524
+ /** Override: force flat array for list (ignores any wrapper in the spec) */
525
+ listFlat?: boolean;
526
+ /** Override: JSON Schema for error responses (404, etc.) */
527
+ errorSchema?: JSONSchema7;
528
+ }
529
+
530
+ /**
531
+ * Response header definition from an OpenAPI spec
532
+ */
533
+ interface ResponseHeaderDef {
534
+ schema?: JSONSchema7;
535
+ description: string;
536
+ }
537
+
538
+ /**
539
+ * Per-operation metadata auto-detected from spec or set via overrides
540
+ */
541
+ interface CrudOperationMeta {
542
+ /** Full success response schema (wrapper + items) */
543
+ responseSchema?: JSONSchema7;
544
+ /** Response headers from spec */
545
+ responseHeaders?: Record<string, ResponseHeaderDef>;
546
+ /** Error response schemas keyed by status code */
547
+ errorSchemas?: Map<number, JSONSchema7>;
548
+ }
549
+
550
+ // ===== Faker Plugin Types =====
551
+
552
+ /**
553
+ * Context for schema-based data generation
554
+ */
555
+ interface SchemaGenerationContext {
556
+ schema: JSONSchema7;
557
+ count?: number;
558
+ overrides?: Record<string, unknown>;
559
+ params?: Record<string, string>;
560
+ state?: Record<string, unknown>;
561
+ query?: Record<string, string>;
562
+ seed?: number;
563
+ }
564
+
565
+ /**
566
+ * Options for the faker plugin
567
+ */
568
+ interface FakerPluginOptions {
569
+ schema: JSONSchema7;
570
+ count?: number;
571
+ overrides?: Record<string, unknown>;
572
+ seed?: number;
573
+ }
574
+
575
+ // ===== Express Adapter Types =====
576
+
577
+ /**
578
+ * Override parts of a request before Schmock handles it
579
+ */
580
+ interface AdapterRequestOverride {
581
+ method?: string;
582
+ path?: string;
583
+ headers?: Record<string, string>;
584
+ body?: unknown;
585
+ query?: Record<string, string>;
586
+ }
587
+
588
+ /**
589
+ * Configuration options for Express adapter
590
+ */
591
+ interface ExpressAdapterOptions {
592
+ errorFormatter?: (error: Error, req: unknown) => unknown;
593
+ passErrorsToNext?: boolean;
594
+ transformHeaders?: (
595
+ headers: Record<string, string | string[] | undefined>,
596
+ ) => Record<string, string>;
597
+ transformQuery?: (query: Record<string, unknown>) => Record<string, string>;
598
+ beforeRequest?: (
599
+ req: unknown,
600
+ res: unknown,
601
+ ) =>
602
+ | AdapterRequestOverride
603
+ | undefined
604
+ | Promise<AdapterRequestOverride | undefined>;
605
+ beforeResponse?: (
606
+ response: Response,
607
+ req: unknown,
608
+ res: unknown,
609
+ ) => Response | undefined | Promise<Response | undefined>;
610
+ }
611
+
612
+ // ===== Angular Adapter Types =====
613
+
614
+ /**
615
+ * Configuration options for Angular adapter
616
+ */
617
+ interface AngularAdapterOptions {
618
+ baseUrl?: string;
619
+ passthrough?: boolean;
620
+ errorFormatter?: (error: Error, request: unknown) => unknown;
621
+ transformRequest?: (request: unknown) => AdapterRequestOverride;
622
+ transformResponse?: (
623
+ response: Response,
624
+ request: unknown,
625
+ ) => Response;
626
+ }
627
+
628
+ // ===== OpenAPI Plugin Options =====
629
+
630
+ /**
631
+ * Options for the OpenAPI plugin
632
+ */
633
+ interface OpenApiOptions {
634
+ spec: string | object;
635
+ seed?: SeedConfig;
636
+ validateRequests?: boolean;
637
+ validateResponses?: boolean;
638
+ queryFeatures?: {
639
+ pagination?: boolean;
640
+ sorting?: boolean;
641
+ filtering?: boolean;
642
+ };
643
+ resources?: Record<string, ResourceOverride>;
644
+ debug?: boolean;
645
+ fakerSeed?: number;
646
+ security?: boolean;
647
+ /** Replace response schemas for specific routes. Key format: "METHOD /path" or "METHOD /path STATUS" */
648
+ schemas?: Record<string, import("json-schema").JSONSchema7>;
649
+ /** Called before generating a response body. Return a schema to replace the original, or void to keep it. */
650
+ onSchema?: (
651
+ schema: import("json-schema").JSONSchema7,
652
+ context: { method: string; path: string; params: Record<string, string>; query: Record<string, string>; headers: Record<string, string> },
653
+ ) => import("json-schema").JSONSchema7 | undefined;
654
+ }
655
+
656
+ /**
657
+ * Seed data source: inline array, file path, or auto-generate count
658
+ */
659
+ type SeedSource = unknown[] | string | { count: number };
660
+
661
+ /**
662
+ * Seed configuration mapping resource names to seed sources
663
+ */
664
+ type SeedConfig = Record<string, SeedSource>;
665
+
666
+ // ===== CLI Types =====
667
+
668
+ /**
669
+ * Options for the CLI server
670
+ */
671
+ interface CliOptions {
672
+ spec: string;
673
+ port?: number;
674
+ hostname?: string;
675
+ seed?: string;
676
+ cors?: boolean;
677
+ debug?: boolean;
678
+ fakerSeed?: number;
679
+ errors?: boolean;
680
+ watch?: boolean;
681
+ admin?: boolean;
682
+ }
683
+
684
+ /**
685
+ * Running CLI server instance
686
+ */
687
+ interface CliServer {
688
+ server: import("node:http").Server;
689
+ port: number;
690
+ hostname: string;
691
+ close(): void;
692
+ }
693
+
694
+ }
695
+ }
696
+ // >>> schmock ambient namespace <<<
1
697
  /**
2
698
  * Create a new Schmock mock instance with callable API.
3
699
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@schmock/core",
3
3
  "description": "Core functionality for Schmock",
4
- "version": "2.1.0",
4
+ "version": "2.1.2",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "build": "bun build:lib && bun build:types",
22
22
  "build:lib": "bun build --minify --target node --outdir=dist src/index.ts",
23
- "build:types": "rm -f tsconfig.tsbuildinfo && tsc --build",
23
+ "build:types": "rm -f tsconfig.tsbuildinfo && tsc --build && node scripts/embed-ambient-types.js",
24
24
  "pretest": "rm -f src/*.js src/*.d.ts || true",
25
25
  "test": "vitest",
26
26
  "test:watch": "vitest --watch",