@molecule/api-mock-server 1.0.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 (67) hide show
  1. package/LICENSE +115 -0
  2. package/README.md +853 -0
  3. package/dist/browser-guard.d.ts +2 -0
  4. package/dist/browser-guard.d.ts.map +1 -0
  5. package/dist/browser-guard.js +19 -0
  6. package/dist/browser-guard.js.map +1 -0
  7. package/dist/cli.d.ts +11 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +152 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/fixtures/app-fixtures.d.ts +54 -0
  12. package/dist/fixtures/app-fixtures.d.ts.map +1 -0
  13. package/dist/fixtures/app-fixtures.js +601 -0
  14. package/dist/fixtures/app-fixtures.js.map +1 -0
  15. package/dist/fixtures/index.d.ts +9 -0
  16. package/dist/fixtures/index.d.ts.map +1 -0
  17. package/dist/fixtures/index.js +9 -0
  18. package/dist/fixtures/index.js.map +1 -0
  19. package/dist/fixtures/seed.d.ts +74 -0
  20. package/dist/fixtures/seed.d.ts.map +1 -0
  21. package/dist/fixtures/seed.js +112 -0
  22. package/dist/fixtures/seed.js.map +1 -0
  23. package/dist/fixtures/semantic-generator.d.ts +20 -0
  24. package/dist/fixtures/semantic-generator.d.ts.map +1 -0
  25. package/dist/fixtures/semantic-generator.js +539 -0
  26. package/dist/fixtures/semantic-generator.js.map +1 -0
  27. package/dist/fixtures/zod-walker.d.ts +34 -0
  28. package/dist/fixtures/zod-walker.d.ts.map +1 -0
  29. package/dist/fixtures/zod-walker.js +183 -0
  30. package/dist/fixtures/zod-walker.js.map +1 -0
  31. package/dist/index.d.ts +78 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +78 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/scanner/index.d.ts +6 -0
  36. package/dist/scanner/index.d.ts.map +1 -0
  37. package/dist/scanner/index.js +6 -0
  38. package/dist/scanner/index.js.map +1 -0
  39. package/dist/scanner/scanner.d.ts +21 -0
  40. package/dist/scanner/scanner.d.ts.map +1 -0
  41. package/dist/scanner/scanner.js +463 -0
  42. package/dist/scanner/scanner.js.map +1 -0
  43. package/dist/server/index.d.ts +7 -0
  44. package/dist/server/index.d.ts.map +1 -0
  45. package/dist/server/index.js +7 -0
  46. package/dist/server/index.js.map +1 -0
  47. package/dist/server/middleware.d.ts +51 -0
  48. package/dist/server/middleware.d.ts.map +1 -0
  49. package/dist/server/middleware.js +124 -0
  50. package/dist/server/middleware.js.map +1 -0
  51. package/dist/server/server.d.ts +29 -0
  52. package/dist/server/server.d.ts.map +1 -0
  53. package/dist/server/server.js +314 -0
  54. package/dist/server/server.js.map +1 -0
  55. package/dist/states/index.d.ts +6 -0
  56. package/dist/states/index.d.ts.map +1 -0
  57. package/dist/states/index.js +6 -0
  58. package/dist/states/index.js.map +1 -0
  59. package/dist/states/states.d.ts +57 -0
  60. package/dist/states/states.d.ts.map +1 -0
  61. package/dist/states/states.js +89 -0
  62. package/dist/states/states.js.map +1 -0
  63. package/dist/types.d.ts +214 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/types.js +6 -0
  66. package/dist/types.js.map +1 -0
  67. package/package.json +66 -0
package/README.md ADDED
@@ -0,0 +1,853 @@
1
+ <!--
2
+ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
+ Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
+ Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
+ To change this document, edit the module-level JSDoc in src/index.ts.
6
+ Generated: 2026-08-04T00:36:59.622Z
7
+ -->
8
+
9
+ # @molecule/api-mock-server
10
+
11
+ > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
12
+ > It is written to be read by coding agents as much as by people, and is generated from this
13
+ > package's source — edit `src/index.ts` JSDoc, not this file.
14
+
15
+ Mock API server with deterministic fixture data for testing, screenshots, and E2E.
16
+
17
+ Provides a lightweight Express server that serves realistic fixture responses
18
+ for any molecule app type. Supports per-request state control via query params
19
+ and headers for testing success, empty, error, and unauthorized states.
20
+
21
+ ## Quick Start
22
+
23
+ ```typescript
24
+ import { createMockServer } from '@molecule/api-mock-server'
25
+
26
+ const server = await createMockServer({
27
+ appType: 'personal-finance',
28
+ fixturesPath: './api/fixtures', // directory of *.json fixture files
29
+ port: 4000,
30
+ })
31
+
32
+ // Control state programmatically ('GET /accounts' and 'GET /api/accounts'
33
+ // are equivalent keys)
34
+ server.setState('GET /accounts', { state: 'error', statusCode: 500 })
35
+ server.setState('GET /transactions', { state: 'empty' })
36
+ server.setDefaultState('empty') // flips every endpoint at once
37
+
38
+ // Undo an endpoint override so ?_state / the default control it again —
39
+ // setState(key, { state: 'success' }) is NOT the same thing (see @remarks)
40
+ server.clearState('GET /accounts')
41
+
42
+ // Teardown
43
+ await server.close()
44
+ ```
45
+
46
+ ## Type
47
+
48
+ `utility`
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ npm install @molecule/api-mock-server express zod
54
+ npm install -D @types/express
55
+ ```
56
+
57
+ ## API
58
+
59
+ ### Interfaces
60
+
61
+ #### `AppDataPool`
62
+
63
+ App-specific fixture data pools
64
+
65
+ ```typescript
66
+ interface AppDataPool {
67
+ /** App type name */
68
+ appType: string
69
+ /** Resource-keyed fixture data: resource name -> array of records */
70
+ resources: Record<string, FixtureRecord[]>
71
+ /** Report endpoints: endpoint path suffix -> response data */
72
+ reports?: Record<string, unknown>
73
+ }
74
+ ```
75
+
76
+ #### `AppFixtureSet`
77
+
78
+ Fixture set for an entire app type
79
+
80
+ ```typescript
81
+ interface AppFixtureSet {
82
+ /** App type name */
83
+ appType: string
84
+ /** Endpoint key -> fixture data */
85
+ endpoints: Map<string, EndpointFixture>
86
+ }
87
+ ```
88
+
89
+ #### `EndpointDefinition`
90
+
91
+ A single discovered endpoint from handler scanning
92
+
93
+ ```typescript
94
+ interface EndpointDefinition {
95
+ /** HTTP method (GET, POST, PUT, DELETE) */
96
+ method: HttpMethod
97
+ /** Full route path (e.g. '/accounts', '/accounts/:id', '/reports/net-worth') */
98
+ path: string
99
+ /** Zod schema for request body (if any) */
100
+ bodySchema?: ZodSchemaDefinition
101
+ /** Whether the endpoint requires authentication */
102
+ requiresAuth: boolean
103
+ /** Expected response shape hints */
104
+ responseHints: ResponseHint
105
+ }
106
+ ```
107
+
108
+ #### `EndpointFixture`
109
+
110
+ Fixture data for a single endpoint
111
+
112
+ ```typescript
113
+ interface EndpointFixture {
114
+ /** The endpoint definition */
115
+ endpoint: EndpointDefinition
116
+ /** Success response body */
117
+ successResponse: unknown
118
+ /** Empty state response body */
119
+ emptyResponse: unknown
120
+ /** Error response */
121
+ errorResponse: { error: string }
122
+ }
123
+ ```
124
+
125
+ #### `FixtureConfig`
126
+
127
+ Configuration for fixture generation
128
+
129
+ ```typescript
130
+ interface FixtureConfig {
131
+ /** Number of items to generate for list endpoints */
132
+ listCount?: number
133
+ /** Seed override (default: derived from app type + path) */
134
+ seed?: number
135
+ /** App type for domain-specific data pools */
136
+ appType?: string
137
+ }
138
+ ```
139
+
140
+ #### `FixtureRecord`
141
+
142
+ Pre-built fixture record for a specific resource
143
+
144
+ ```typescript
145
+ interface FixtureRecord {
146
+ [key: string]: unknown
147
+ }
148
+ ```
149
+
150
+ #### `HandlerScanResult`
151
+
152
+ Result of scanning all handlers for an app type
153
+
154
+ ```typescript
155
+ interface HandlerScanResult {
156
+ /** App type (e.g. 'personal-finance', 'online-store') */
157
+ appType: string
158
+ /** All discovered endpoints */
159
+ endpoints: EndpointDefinition[]
160
+ /** Resource names discovered (accounts, transactions, etc.) */
161
+ resources: string[]
162
+ }
163
+ ```
164
+
165
+ #### `MockServer`
166
+
167
+ Running mock server instance with control methods
168
+
169
+ ```typescript
170
+ interface MockServer {
171
+ /** The port the server is listening on */
172
+ port: number
173
+ /** The app type being served */
174
+ appType: string
175
+ /**
176
+ * Set a persistent state override for one endpoint (key: `"METHOD /path"`,
177
+ * e.g. `'GET /accounts'` — the bare and `/api/`-prefixed forms are
178
+ * equivalent keys). This is the HIGHEST-priority source of state for that
179
+ * endpoint. Full per-request precedence (highest first):
180
+ *
181
+ * 1. `setState(endpointKey, ...)` (this method)
182
+ * 2. per-request `?_state` query param / `X-Mock-State` header
183
+ * 3. `setDefaultState(...)` / the server's configured `defaultState`
184
+ *
185
+ * So a forgotten `setState('GET /accounts', { state: 'error' })` left over
186
+ * from an earlier test SILENTLY beats every later `?_state=success` on
187
+ * that endpoint — the request looks like `?_state` is being ignored.
188
+ * **Calling `setState(key, { state: 'success' })` does NOT remove the
189
+ * override** — it replaces it with an override that happens to look like
190
+ * the default, but per-request `?_state`/`X-Mock-State` still can't reach
191
+ * that endpoint (the override still outranks them). Use
192
+ * {@link MockServer.clearState} to actually remove the override and hand
193
+ * control back to per-request/default state. `setDefaultState()` never
194
+ * clears endpoint overrides either — the default is only the fallback used
195
+ * when no endpoint override exists at all.
196
+ * @param endpointKey - `"METHOD /path"`, e.g. `'GET /accounts'`.
197
+ * @param state - The state this endpoint returns for every request until
198
+ * `setState` is called again, or `clearState`d, for the same key.
199
+ */
200
+ setState: (endpointKey: string, state: ResponseState) => void
201
+ /**
202
+ * Remove a persistent per-endpoint override previously set with
203
+ * {@link MockServer.setState}, restoring per-request `?_state`/
204
+ * `X-Mock-State` (and ultimately `setDefaultState()`) control over that
205
+ * endpoint. Pass the EXACT same key string used in the matching
206
+ * `setState()` call — keys are matched as opaque strings, not normalized,
207
+ * so `setState('GET /accounts', ...)` and `setState('GET /api/accounts',
208
+ * ...)` are independent overrides and each needs its own `clearState()`.
209
+ * Clearing a key with no active override is a harmless no-op.
210
+ * @param endpointKey - The same `"METHOD /path"` key passed to `setState`.
211
+ */
212
+ clearState: (endpointKey: string) => void
213
+ /**
214
+ * Set the fallback state used for any endpoint that has no per-endpoint
215
+ * `setState()` override — see {@link MockServer.setState} for the full
216
+ * precedence chain. A per-request `?_state`/`X-Mock-State` value still
217
+ * wins over this default; only an active `setState()` override outranks
218
+ * both.
219
+ * @param state - The fallback response state for endpoints with no override.
220
+ */
221
+ setDefaultState: (state: 'success' | 'empty' | 'error' | 'unauthorized') => void
222
+ /** Get the fixture set being served */
223
+ getFixtures: () => AppFixtureSet
224
+ /** Close the server */
225
+ close: () => Promise<void>
226
+ }
227
+ ```
228
+
229
+ #### `MockServerConfig`
230
+
231
+ Configuration for the mock HTTP server
232
+
233
+ ```typescript
234
+ interface MockServerConfig {
235
+ /** App type to serve fixtures for (used for display and handler resolution) */
236
+ appType: string
237
+ /** Path to a directory of JSON fixture files (takes priority over appType for data) */
238
+ fixturesPath?: string
239
+ /** Port to listen on (default 4000) */
240
+ port?: number
241
+ /** Default response delay in ms (default 0) */
242
+ defaultDelay?: number
243
+ /** Default state for all endpoints */
244
+ defaultState?: 'success' | 'empty' | 'error' | 'unauthorized'
245
+ /** Per-endpoint state overrides (key: "METHOD /path") */
246
+ endpointStates?: Record<string, ResponseState>
247
+ /** Path to handler templates directory (auto-detected from appType) */
248
+ handlersPath?: string
249
+ /** Custom fixture data to use instead of auto-generated */
250
+ customFixtures?: AppFixtureSet
251
+ /** Whether to log requests (default true) */
252
+ logging?: boolean
253
+ }
254
+ ```
255
+
256
+ #### `ResponseHint`
257
+
258
+ Hints about the response shape extracted from handler analysis
259
+
260
+ ```typescript
261
+ interface ResponseHint {
262
+ /** Whether the response is an array (list endpoint) */
263
+ isList: boolean
264
+ /** Whether the response is paginated ({ data, total, page, limit }) */
265
+ isPaginated: boolean
266
+ /** Whether the response includes nested resources */
267
+ hasNestedResources: boolean
268
+ /** Resource name (e.g. 'accounts', 'transactions') */
269
+ resourceName: string
270
+ /**
271
+ * Whether the success response is a single bare object literal
272
+ * (`res.json({ a, b, c })`) rather than a list or `{ data: [] }`
273
+ * envelope — e.g. `/analytics/summary`, `/profile/me`. When true,
274
+ * `responseFields` holds its top-level keys.
275
+ */
276
+ isSingleObject?: boolean
277
+ /** Top-level field names of the success `res.json({...})` object, if extracted */
278
+ responseFields?: string[]
279
+ }
280
+ ```
281
+
282
+ #### `ResponseState`
283
+
284
+ Response state for controlling mock behavior
285
+
286
+ ```typescript
287
+ interface ResponseState {
288
+ /** The state of the response */
289
+ state: 'success' | 'empty' | 'error' | 'unauthorized'
290
+ /**
291
+ * Additional delay in ms before responding. Clamped to `MAX_MOCK_DELAY_MS`
292
+ * (60s, see {@link applyDelay}) — an oversized value is capped and logged
293
+ * rather than honored verbatim, so it cannot hang a request indefinitely.
294
+ */
295
+ delay?: number
296
+ /** Custom status code override */
297
+ statusCode?: number
298
+ }
299
+ ```
300
+
301
+ #### `SemanticRule`
302
+
303
+ A semantic rule for generating realistic values based on field names
304
+
305
+ ```typescript
306
+ interface SemanticRule {
307
+ /** Field name pattern (regex) */
308
+ pattern: RegExp
309
+ /** Generator function that produces a realistic value */
310
+ generate: (rng: () => number, index: number) => unknown
311
+ }
312
+ ```
313
+
314
+ #### `ZodSchemaDefinition`
315
+
316
+ Serialized Zod schema definition for fixture generation
317
+
318
+ ```typescript
319
+ interface ZodSchemaDefinition {
320
+ /** Schema type name (e.g. 'ZodObject', 'ZodString') */
321
+ type: string
322
+ /** Schema shape for objects: field name -> nested schema def */
323
+ shape?: Record<string, ZodSchemaDefinition>
324
+ /** Enum values if applicable */
325
+ enumValues?: string[]
326
+ /** Default value if specified */
327
+ defaultValue?: unknown
328
+ /** Inner type for optional/nullable wrappers */
329
+ innerType?: ZodSchemaDefinition
330
+ /** Element type for arrays */
331
+ elementType?: ZodSchemaDefinition
332
+ /** Constraints (min, max, positive, int, etc.) */
333
+ constraints?: {
334
+ min?: number
335
+ max?: number
336
+ positive?: boolean
337
+ int?: boolean
338
+ minLength?: number
339
+ maxLength?: number
340
+ }
341
+ }
342
+ ```
343
+
344
+ ### Types
345
+
346
+ #### `HttpMethod`
347
+
348
+ HTTP methods supported by the mock server
349
+
350
+ ```typescript
351
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
352
+ ```
353
+
354
+ ### Functions
355
+
356
+ #### `applyDelay(state)`
357
+
358
+ Apply a delay if specified in the response state. The requested delay is
359
+ capped at {@link MAX_MOCK_DELAY_MS} — a value above the cap is clamped and
360
+ a warning is logged (via `console.warn`, immediately, before waiting —
361
+ not after — so the clamp is visible in server logs right when the
362
+ oversized delay is requested rather than a minute later).
363
+
364
+ ```typescript
365
+ function applyDelay(state: ResponseState): Promise<void>
366
+ ```
367
+
368
+ - `state` — The response state that may contain a delay
369
+
370
+ **Returns:** A promise that resolves after the (possibly clamped) delay, or immediately if no delay was requested
371
+
372
+ #### `applySemanticRules(fieldName, rng, index, rules)`
373
+
374
+ Apply semantic rules to generate a value for a given field name.
375
+
376
+ ```typescript
377
+ function applySemanticRules(
378
+ fieldName: string,
379
+ rng: () => number,
380
+ index: number,
381
+ rules?: SemanticRule[],
382
+ ): unknown
383
+ ```
384
+
385
+ - `fieldName` — The name of the field to generate a value for
386
+ - `rng` — The seeded random function
387
+ - `index` — The item index (for cycling through pools)
388
+ - `rules` — Custom rules to apply (defaults to defaultSemanticRules)
389
+
390
+ **Returns:** The generated value, or undefined if no rule matched
391
+
392
+ #### `buildFixtureSet(appType, endpoints, fixturesDir)`
393
+
394
+ Build a complete fixture set for an app type by combining
395
+ data pool records with endpoint definitions.
396
+
397
+ ```typescript
398
+ function buildFixtureSet(
399
+ appType: string,
400
+ endpoints: EndpointDefinition[],
401
+ fixturesDir?: string,
402
+ ): AppFixtureSet | undefined
403
+ ```
404
+
405
+ - `appType` — The app type
406
+ - `endpoints` — The discovered endpoints from scanning
407
+ - `fixturesDir` — Path to the fixtures directory
408
+
409
+ **Returns:** A complete fixture set, or undefined if no data available
410
+
411
+ #### `corsMiddleware()`
412
+
413
+ CORS middleware that sets permissive CORS headers for development.
414
+
415
+ ```typescript
416
+ function corsMiddleware(): (req: Request, res: Response, next: NextFunction) => void
417
+ ```
418
+
419
+ **Returns:** Express middleware function
420
+
421
+ #### `createMockServer(config)`
422
+
423
+ Create and start a mock API server for the given app type.
424
+ The server discovers endpoints by scanning handler templates and
425
+ serves deterministic fixture data for each discovered route.
426
+
427
+ ```typescript
428
+ function createMockServer(config: MockServerConfig): Promise<MockServer>
429
+ ```
430
+
431
+ - `config` — Server configuration
432
+
433
+ **Returns:** A running MockServer instance with control methods
434
+
435
+ #### `createSeededRandom(seed)`
436
+
437
+ Create a seeded random number generator using Mulberry32.
438
+ Returns a function that produces numbers in [0, 1).
439
+
440
+ ```typescript
441
+ function createSeededRandom(seed: number): () => number
442
+ ```
443
+
444
+ - `seed` — The seed value
445
+
446
+ **Returns:** A function that returns the next pseudo-random number
447
+
448
+ #### `futureDate(rng, daysAhead)`
449
+
450
+ Generate a "future" ISO date string within the next N days of the fixed
451
+ `FIXTURE_NOW` anchor (NOT the wall clock — see `FIXTURE_NOW`).
452
+
453
+ ```typescript
454
+ function futureDate(rng: () => number, daysAhead?: number): string
455
+ ```
456
+
457
+ - `rng` — The seeded random function
458
+ - `daysAhead` — Maximum days in the future (default 365)
459
+
460
+ **Returns:** An ISO date string
461
+
462
+ #### `generateFixtures(fixturesDir, appType)`
463
+
464
+ Generate fixtures from a directory of JSON files without scanning handlers.
465
+ Builds standard CRUD endpoints for each resource file and custom endpoints
466
+ for each sub-endpoint group.
467
+
468
+ ```typescript
469
+ function generateFixtures(fixturesDir: string, appType?: string): AppFixtureSet | undefined
470
+ ```
471
+
472
+ - `fixturesDir` — Absolute path to the fixtures directory
473
+ - `appType` — App type label (default: derived from directory name)
474
+
475
+ **Returns:** A fixture set with standard CRUD endpoints, or undefined if no data
476
+
477
+ #### `generateRecord(schema, rng, index)`
478
+
479
+ Generate a single conformant record from a schema, enriched with
480
+ standard fields (id, created_at, updated_at).
481
+
482
+ ```typescript
483
+ function generateRecord(
484
+ schema: ZodSchemaDefinition | undefined,
485
+ rng: () => number,
486
+ index: number,
487
+ ): Record<string, unknown>
488
+ ```
489
+
490
+ - `schema` — The serialized Zod schema definition
491
+ - `rng` — The seeded random function
492
+ - `index` — The item index
493
+
494
+ **Returns:** A record with all schema fields plus standard fields
495
+
496
+ #### `generateRecords(schema, count, appType, path, config)`
497
+
498
+ Generate multiple records from a schema definition.
499
+
500
+ ```typescript
501
+ function generateRecords(
502
+ schema: ZodSchemaDefinition | undefined,
503
+ count: number,
504
+ appType: string,
505
+ path: string,
506
+ config?: FixtureConfig,
507
+ ): Record<string, unknown>[]
508
+ ```
509
+
510
+ - `schema` — The serialized Zod schema definition
511
+ - `count` — Number of records to generate
512
+ - `appType` — App type for seed derivation
513
+ - `path` — Endpoint path for seed derivation
514
+ - `config` — Optional fixture configuration
515
+
516
+ **Returns:** An array of conformant records
517
+
518
+ #### `getAppDataPool(fixturesDir, appType)`
519
+
520
+ Get an AppDataPool for a fixtures directory, with fingerprint-validated
521
+ caching: editing/adding/removing a `*.json` fixture file (mtime or size
522
+ change) invalidates the cached pool, so a mock server re-created in the
523
+ same process serves the fresh data instead of the first load.
524
+
525
+ ```typescript
526
+ function getAppDataPool(fixturesDir: string, appType: string): AppDataPool | undefined
527
+ ```
528
+
529
+ - `fixturesDir` — Absolute path to the fixtures directory
530
+ - `appType` — App type label
531
+
532
+ **Returns:** The loaded pool, or undefined if directory missing/empty
533
+
534
+ #### `getResponseBody(state, method, fixture)`
535
+
536
+ Get the response body for a given state, using the endpoint fixture data.
537
+
538
+ ```typescript
539
+ function getResponseBody(
540
+ state: ResponseState,
541
+ method: HttpMethod,
542
+ fixture: { successResponse: unknown; emptyResponse: unknown; errorResponse: { error: string } },
543
+ ): unknown
544
+ ```
545
+
546
+ - `state` — The response state
547
+ - `method` — The HTTP method
548
+ - `fixture` — The fixture data containing success, empty, and error responses
549
+
550
+ **Returns:** The response body, or null for 204 responses
551
+
552
+ #### `getStatusCode(state, method)`
553
+
554
+ Get the HTTP status code for a given state and method.
555
+
556
+ ```typescript
557
+ function getStatusCode(state: ResponseState, method: HttpMethod): number
558
+ ```
559
+
560
+ - `state` — The response state
561
+ - `method` — The HTTP method
562
+
563
+ **Returns:** The appropriate HTTP status code
564
+
565
+ #### `loadFixturesFromDirectory(fixturesDir, appType)`
566
+
567
+ Load an AppDataPool from a directory of JSON fixture files.
568
+
569
+ File naming conventions:
570
+
571
+ - Array files (e.g. `products.json` containing `[...]`) become CRUD resources
572
+ - Object files (e.g. `reports.json` containing `{key: ...}`) become sub-endpoint groups
573
+ where each key maps to a GET endpoint
574
+
575
+ Special filenames are treated as report/sub-endpoint groups (object shape expected):
576
+ `reports.json`, `storefront.json`, `admin.json`
577
+
578
+ All other files are treated as array resources by default.
579
+
580
+ ```typescript
581
+ function loadFixturesFromDirectory(fixturesDir: string, appType: string): AppDataPool | undefined
582
+ ```
583
+
584
+ - `fixturesDir` — Absolute path to the fixtures directory
585
+ - `appType` — App type label (for the returned pool metadata)
586
+
587
+ **Returns:** An AppDataPool, or undefined if the directory doesn't exist or is empty
588
+
589
+ #### `loggingMiddleware()`
590
+
591
+ Request logging middleware for the mock server.
592
+
593
+ ```typescript
594
+ function loggingMiddleware(): (req: Request, res: Response, next: NextFunction) => void
595
+ ```
596
+
597
+ **Returns:** Express middleware function
598
+
599
+ #### `parseState(stateStr)`
600
+
601
+ Parse a state string into a ResponseState object.
602
+
603
+ ```typescript
604
+ function parseState(stateStr: string): ResponseState
605
+ ```
606
+
607
+ - `stateStr` — The state string (e.g. 'success', 'error', 'empty', 'unauthorized')
608
+
609
+ **Returns:** The parsed ResponseState. An unrecognized string falls back to `DEFAULT_STATES.success` — the same forgiving behavior as the per-request `?_state` middleware (which additionally labels the response with an `X-Mock-Invalid-State` header so typos are detectable).
610
+
611
+ #### `pick(rng, arr)`
612
+
613
+ Pick a random element from an array using the RNG.
614
+
615
+ ```typescript
616
+ function pick(rng: () => number, arr: readonly T[]): T
617
+ ```
618
+
619
+ - `rng` — The seeded random function
620
+ - `arr` — The array to pick from
621
+
622
+ **Returns:** A random element from the array
623
+
624
+ #### `randomDollars(rng, min, max)`
625
+
626
+ Generate a random dollar amount rounded to cents.
627
+
628
+ ```typescript
629
+ function randomDollars(rng: () => number, min: number, max: number): number
630
+ ```
631
+
632
+ - `rng` — The seeded random function
633
+ - `min` — Minimum value
634
+ - `max` — Maximum value
635
+
636
+ **Returns:** A number rounded to 2 decimal places
637
+
638
+ #### `randomInt(rng, min, max)`
639
+
640
+ Pick a random integer in [min, max] inclusive.
641
+
642
+ ```typescript
643
+ function randomInt(rng: () => number, min: number, max: number): number
644
+ ```
645
+
646
+ - `rng` — The seeded random function
647
+ - `min` — Minimum value (inclusive)
648
+ - `max` — Maximum value (inclusive)
649
+
650
+ **Returns:** A random integer in the given range
651
+
652
+ #### `recentDate(rng, daysBack)`
653
+
654
+ Generate a "recent" ISO date string within the last N days of the fixed
655
+ `FIXTURE_NOW` anchor (NOT the wall clock — see `FIXTURE_NOW`).
656
+
657
+ ```typescript
658
+ function recentDate(rng: () => number, daysBack?: number): string
659
+ ```
660
+
661
+ - `rng` — The seeded random function
662
+ - `daysBack` — Maximum days in the past (default 90)
663
+
664
+ **Returns:** An ISO date string
665
+
666
+ #### `resolveHandlersPath(appType, workspaceRoot)`
667
+
668
+ Resolve the handlers path for a given app type.
669
+ Searches standard locations in the mlcl templates directory.
670
+
671
+ ```typescript
672
+ function resolveHandlersPath(appType: string, workspaceRoot?: string): string | undefined
673
+ ```
674
+
675
+ - `appType` — The app type name
676
+ - `workspaceRoot` — The workspace root directory
677
+
678
+ **Returns:** The resolved handlers path, or undefined if not found
679
+
680
+ #### `scanHandlers(handlersPath, appType)`
681
+
682
+ Scan all handler files for a given app type and produce endpoint definitions.
683
+
684
+ ```typescript
685
+ function scanHandlers(handlersPath: string, appType: string): HandlerScanResult
686
+ ```
687
+
688
+ - `handlersPath` — Path to the handlers directory
689
+ - `appType` — The app type name
690
+
691
+ **Returns:** The scan result with all discovered endpoints
692
+
693
+ #### `seededUUID(rng)`
694
+
695
+ Generate a UUID-like string from the RNG (not cryptographically secure).
696
+
697
+ ```typescript
698
+ function seededUUID(rng: () => number): string
699
+ ```
700
+
701
+ - `rng` — The seeded random function
702
+
703
+ **Returns:** A UUID v4-like string
704
+
705
+ #### `seedFromPath(appType, path)`
706
+
707
+ Derive a deterministic seed from an app type and endpoint path.
708
+ The same app+path always produces the same seed, ensuring stable fixture output.
709
+
710
+ ```typescript
711
+ function seedFromPath(appType: string, path: string): number
712
+ ```
713
+
714
+ - `appType` — The application type (e.g. 'personal-finance')
715
+ - `path` — The endpoint path (e.g. '/accounts')
716
+
717
+ **Returns:** A numeric seed value
718
+
719
+ #### `stateControlMiddleware(defaultState)`
720
+
721
+ Express middleware that extracts state control signals from the request
722
+ and attaches them to res.locals for the route handler to use.
723
+
724
+ ```typescript
725
+ function stateControlMiddleware(
726
+ defaultState?: ResponseState | (() => ResponseState),
727
+ ): (req: Request, res: Response, next: NextFunction) => void
728
+ ```
729
+
730
+ - `defaultState` — The default state to use when no override is provided. Pass a function to have the default resolved per-request (a live getter) — required for `MockServer.setDefaultState()` to take effect after startup, since a plain object is captured once at middleware-creation time.
731
+
732
+ **Returns:** Express middleware function
733
+
734
+ #### `walkSchema(schema, fieldName, rng, index)`
735
+
736
+ Walk a serialized Zod schema definition and produce conformant data.
737
+
738
+ ```typescript
739
+ function walkSchema(
740
+ schema: ZodSchemaDefinition,
741
+ fieldName: string,
742
+ rng: () => number,
743
+ index: number,
744
+ ): unknown
745
+ ```
746
+
747
+ - `schema` — The serialized schema definition
748
+ - `fieldName` — The field name (for semantic heuristics)
749
+ - `rng` — The seeded random function
750
+ - `index` — The item index for list generation
751
+
752
+ **Returns:** A conformant value matching the schema
753
+
754
+ ### Constants
755
+
756
+ #### `DEFAULT_STATES`
757
+
758
+ Default response states for each scenario
759
+
760
+ ```typescript
761
+ const DEFAULT_STATES: {
762
+ readonly success: { readonly state: 'success' }
763
+ readonly empty: { readonly state: 'empty' }
764
+ readonly error: { readonly state: 'error'; readonly statusCode: 500 }
765
+ readonly unauthorized: { readonly state: 'unauthorized'; readonly statusCode: 401 }
766
+ }
767
+ ```
768
+
769
+ #### `defaultSemanticRules`
770
+
771
+ Default semantic rules that match field names to realistic generators.
772
+ Rules are checked in order; the first match wins.
773
+
774
+ ```typescript
775
+ const defaultSemanticRules: SemanticRule[]
776
+ ```
777
+
778
+ #### `FIXTURE_NOW`
779
+
780
+ Fixed "now" anchor (2026-04-01T12:00:00Z) for all generated dates.
781
+
782
+ Dates are deliberately anchored to a constant instead of the wall clock so
783
+ fixture output is byte-stable across runs (reliable screenshot diffs).
784
+ Consequence: "recent"/"future" are relative to this anchor, not the real
785
+ current time — never assert generated dates against `Date.now()`.
786
+
787
+ ```typescript
788
+ const FIXTURE_NOW: Date
789
+ ```
790
+
791
+ #### `MAX_MOCK_DELAY_MS`
792
+
793
+ Maximum delay, in ms, that {@link applyDelay} will actually wait — a
794
+ requested delay above this is clamped (and logged) rather than honored
795
+ verbatim. Guards against a stray oversized `?_delay` / `X-Mock-Delay` /
796
+ `defaultDelay` / `setState({ delay })` value (e.g. a units mistake
797
+ applying `*1000` twice) hanging a request until the CLIENT gives up —
798
+ which in an E2E harness presents as an inexplicable page timeout rather
799
+ than an obvious mock misconfiguration.
800
+
801
+ ```typescript
802
+ const MAX_MOCK_DELAY_MS: 60000
803
+ ```
804
+
805
+ ## Injection Notes
806
+
807
+ ### Requirements
808
+
809
+ Peer dependencies:
810
+
811
+ - `zod` >=4.0.0
812
+
813
+ ### Runtime Dependencies
814
+
815
+ - `express`
816
+ - `zod`
817
+
818
+ The server uses deterministic seeded PRNG for stable fixture data, making
819
+ screenshot comparisons reliable. Fixture data comes from the JSON files in
820
+ `fixturesPath` (array files become CRUD resources; `reports`/`storefront`/
821
+ `admin` object files become sub-endpoint groups).
822
+
823
+ Omitting `fixturesPath` makes the server resolve
824
+ `mlcl/templates/apps/<appType>/api/fixtures/` by walking up from `process.cwd()`
825
+ — that only works inside the molecule workspace. In a scaffolded project,
826
+ always pass `fixturesPath`.
827
+
828
+ Requests to `/api/*` paths with no matching fixture endpoint return an empty
829
+ success (`200 []` for GET) so pages still render — the response carries an
830
+ `X-Mock-Unmatched: true` header so a typo'd endpoint can be told apart from
831
+ an endpoint that legitimately returned empty data. Similarly, an invalid
832
+ `?_state`/`X-Mock-State` value is ignored (the default state is served) but
833
+ labeled with an `X-Mock-Invalid-State` response header, so a typo'd state
834
+ control is detectable instead of silently looking like "state applied".
835
+
836
+ State precedence, per request, highest first: (1) an endpoint-level
837
+ `server.setState(key, state)` override, (2) a per-request `?_state` query
838
+ param / `X-Mock-State` header, (3) `server.setDefaultState(...)` / the
839
+ configured `defaultState`. A `setState()` override is PERSISTENT — a
840
+ forgotten override from an earlier test silently beats every later
841
+ `?_state` on that same endpoint. Calling `setState(key, { state: 'success'
842
+ })` again does NOT remove the override (it replaces it with one that looks
843
+ like the default, still outranking `?_state`); call `server.clearState(key)`
844
+ to actually remove it and hand control back to per-request/default state.
845
+ `setDefaultState()` only changes the fallback and never clears endpoint
846
+ overrides.
847
+
848
+ Response delay (`defaultDelay`, `?_delay`/`X-Mock-Delay`, or a `delay` in
849
+ `setState`/`setDefaultState`) is capped at `MAX_MOCK_DELAY_MS` (60s) — an
850
+ oversized value (e.g. a units mistake applying `*1000` twice) is clamped
851
+ and logged with `console.warn` instead of hanging the request until the
852
+ client gives up, which in an E2E harness reads as an inexplicable page
853
+ timeout rather than a mock misconfiguration.