@nifrajs/mock 1.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.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `@nifrajs/mock` — contract-based mock server.
3
+ *
4
+ * Reads a Nifra app's registered routes and their Standard Schema / JSON Schema
5
+ * response definitions, then generates fake responses from supported JSON Schema keywords. Use it
6
+ * for frontend-first development, agent testing, or CI smoke tests.
7
+ */
8
+ export declare class UnsupportedMockSchemaError extends Error {
9
+ readonly keyword: string;
10
+ constructor(keyword: string, message: string);
11
+ }
12
+ /**
13
+ * Generate a mock value from a schema object. Inspects JSON Schema properties
14
+ * (`type`, `properties`, `items`, `enum`, `format`) that TypeBox / NifraSchema
15
+ * objects carry directly. Unsupported constraints fail closed with
16
+ * {@link UnsupportedMockSchemaError} rather than returning a known-invalid response.
17
+ *
18
+ * @param schema A JSON-Schema-shaped object (TypeBox, NifraSchema, or raw JSON Schema)
19
+ * @param fieldName Optional field name hint for generating contextual string values
20
+ * @param rng Optional seeded random function for deterministic output
21
+ */
22
+ export declare function generateMockValue(schema: unknown, fieldName?: string | undefined, rng?: (() => number) | undefined): unknown;
23
+ /** Minimal route shape returned by `app.routes()`. */
24
+ export interface MockableRoute {
25
+ readonly method: string;
26
+ readonly path: string;
27
+ readonly schema?: {
28
+ readonly response?: unknown;
29
+ } | undefined;
30
+ }
31
+ /** App shape — anything with a `routes()` method. */
32
+ export interface MockableApp {
33
+ routes(): readonly MockableRoute[];
34
+ }
35
+ export interface MockServerOptions {
36
+ /** Seed for deterministic mock generation. */
37
+ readonly seed?: number | undefined;
38
+ /** Log mock requests to console. */
39
+ readonly verbose?: boolean | undefined;
40
+ }
41
+ export interface MockServer {
42
+ /** Handle a request against the mock routes. */
43
+ fetch(request: Request): Promise<Response>;
44
+ /** The generated mock routes for inspection. */
45
+ readonly mockRoutes: ReadonlyArray<{
46
+ method: string;
47
+ path: string;
48
+ }>;
49
+ }
50
+ /**
51
+ * Create a mock server from a Nifra app's route definitions. For each route
52
+ * with a `schema.response`, generates a handler returning fake data that
53
+ * matches the response schema structure. Routes without response schemas
54
+ * return `{}`.
55
+ */
56
+ export declare function createMockServer(app: MockableApp, options?: MockServerOptions | undefined): MockServer;
57
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAsBH,qBAAa,0BAA2B,SAAQ,KAAK;IAEjD,QAAQ,CAAC,OAAO,EAAE,MAAM;gBAAf,OAAO,EAAE,MAAM,EACxB,OAAO,EAAE,MAAM;CAKlB;AAwDD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,EACf,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,EAC9B,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,GAC/B,OAAO,CAmJT;AAMD,sDAAsD;AACtD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,CAAC,EACZ;QACE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAC5B,GACD,SAAS,CAAA;CACd;AAED,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,MAAM,IAAI,SAAS,aAAa,EAAE,CAAA;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,oCAAoC;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CACvC;AAED,MAAM,WAAW,UAAU;IACzB,gDAAgD;IAChD,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC1C,gDAAgD;IAChD,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACrE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,WAAW,EAChB,OAAO,CAAC,EAAE,iBAAiB,GAAG,SAAS,GACtC,UAAU,CAgEZ"}
package/dist/index.js ADDED
@@ -0,0 +1,303 @@
1
+ /**
2
+ * `@nifrajs/mock` — contract-based mock server.
3
+ *
4
+ * Reads a Nifra app's registered routes and their Standard Schema / JSON Schema
5
+ * response definitions, then generates fake responses from supported JSON Schema keywords. Use it
6
+ * for frontend-first development, agent testing, or CI smoke tests.
7
+ */
8
+ import { reflectRoutes, reflectSchema } from "@nifrajs/core/reflection";
9
+ // ---------------------------------------------------------------------------
10
+ // Seeded PRNG (deterministic mocks)
11
+ // ---------------------------------------------------------------------------
12
+ function seededRandom(seed) {
13
+ let s = seed;
14
+ return () => {
15
+ s = (s * 1664525 + 1013904223) & 0x7fffffff;
16
+ // Divide by 2^31 (not 2^31 - 1) so the result stays strictly below 1 and never
17
+ // produces an out-of-range index when scaled by an array length.
18
+ return s / 0x80000000;
19
+ };
20
+ }
21
+ /** Clamp a `rand() * length` index so an inclusive-range rng cannot index past the end. */
22
+ const randomIndex = (rand, length) => Math.min(Math.floor(rand() * length), length - 1);
23
+ export class UnsupportedMockSchemaError extends Error {
24
+ keyword;
25
+ constructor(keyword, message) {
26
+ super(message);
27
+ this.keyword = keyword;
28
+ this.name = "UnsupportedMockSchemaError";
29
+ }
30
+ }
31
+ function numericBounds(schema) {
32
+ const minimum = typeof schema.minimum === "number" ? schema.minimum : 0;
33
+ const maximum = typeof schema.maximum === "number" ? schema.maximum : 100;
34
+ const exclusiveMinimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : undefined;
35
+ const exclusiveMaximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : undefined;
36
+ const min = exclusiveMinimum === undefined ? minimum : Math.max(minimum, exclusiveMinimum + Number.EPSILON);
37
+ const max = exclusiveMaximum === undefined ? maximum : Math.min(maximum, exclusiveMaximum - Number.EPSILON);
38
+ if (min > max)
39
+ throw new UnsupportedMockSchemaError("minimum", "Numeric schema has no satisfiable range");
40
+ return { min, max };
41
+ }
42
+ function constrainedString(schema, initial, fieldName) {
43
+ const minLength = typeof schema.minLength === "number" ? schema.minLength : 0;
44
+ const maxLength = typeof schema.maxLength === "number" ? schema.maxLength : Number.POSITIVE_INFINITY;
45
+ if (minLength > maxLength) {
46
+ throw new UnsupportedMockSchemaError("minLength", "String schema has no satisfiable length");
47
+ }
48
+ const candidates = [
49
+ initial,
50
+ fieldName ?? "mock",
51
+ "mock_value",
52
+ "test",
53
+ "abc",
54
+ "123",
55
+ "A1",
56
+ "https://example.com",
57
+ "test@example.com",
58
+ ];
59
+ const pattern = typeof schema.pattern === "string" ? new RegExp(schema.pattern) : undefined;
60
+ for (const candidate of candidates) {
61
+ const padded = candidate.length < minLength ? candidate.padEnd(minLength, "a") : candidate;
62
+ const value = padded.slice(0, maxLength);
63
+ if (value.length >= minLength && (pattern === undefined || pattern.test(value)))
64
+ return value;
65
+ }
66
+ throw new UnsupportedMockSchemaError("pattern", `Cannot generate a value for pattern ${JSON.stringify(schema.pattern)}`);
67
+ }
68
+ // ---------------------------------------------------------------------------
69
+ // Schema introspection → mock value generation
70
+ // ---------------------------------------------------------------------------
71
+ /**
72
+ * Generate a mock value from a schema object. Inspects JSON Schema properties
73
+ * (`type`, `properties`, `items`, `enum`, `format`) that TypeBox / NifraSchema
74
+ * objects carry directly. Unsupported constraints fail closed with
75
+ * {@link UnsupportedMockSchemaError} rather than returning a known-invalid response.
76
+ *
77
+ * @param schema A JSON-Schema-shaped object (TypeBox, NifraSchema, or raw JSON Schema)
78
+ * @param fieldName Optional field name hint for generating contextual string values
79
+ * @param rng Optional seeded random function for deterministic output
80
+ */
81
+ export function generateMockValue(schema, fieldName, rng) {
82
+ const rand = rng ?? Math.random;
83
+ const reflected = reflectSchema(schema).jsonSchema;
84
+ if (reflected === true || reflected === undefined)
85
+ return {};
86
+ if (reflected === false) {
87
+ throw new UnsupportedMockSchemaError("false", "Boolean false schema has no valid value");
88
+ }
89
+ const raw = reflected;
90
+ if ("const" in raw)
91
+ return raw.const;
92
+ if (raw.$ref !== undefined) {
93
+ throw new UnsupportedMockSchemaError("$ref", "Resolve JSON Schema references before generating mocks");
94
+ }
95
+ if (raw.not !== undefined) {
96
+ throw new UnsupportedMockSchemaError("not", "JSON Schema `not` generation is unsupported");
97
+ }
98
+ if (raw.oneOf !== undefined) {
99
+ throw new UnsupportedMockSchemaError("oneOf", "Exclusive union generation requires a schema validator");
100
+ }
101
+ const alternatives = raw.anyOf;
102
+ if (Array.isArray(alternatives) && alternatives.length > 0) {
103
+ return generateMockValue(alternatives[randomIndex(rand, alternatives.length)], fieldName, rand);
104
+ }
105
+ const intersections = raw.allOf;
106
+ if (Array.isArray(intersections) && intersections.length > 0) {
107
+ const values = intersections.map((part) => generateMockValue(part, fieldName, rand));
108
+ if (values.every((value) => value !== null && typeof value === "object" && !Array.isArray(value))) {
109
+ return Object.assign({}, ...values);
110
+ }
111
+ const first = values[0];
112
+ if (values.every((value) => Object.is(value, first)))
113
+ return first;
114
+ throw new UnsupportedMockSchemaError("allOf", "Could not prove that a scalar satisfies every intersection branch");
115
+ }
116
+ // Enum — pick the first value (deterministic) or random
117
+ const enumValues = raw.enum;
118
+ if (enumValues && Array.isArray(enumValues) && enumValues.length > 0) {
119
+ return enumValues[randomIndex(rand, enumValues.length)];
120
+ }
121
+ const declaredType = raw.type;
122
+ const type = Array.isArray(declaredType)
123
+ ? (declaredType.find((candidate) => candidate !== "null") ?? "null")
124
+ : declaredType;
125
+ switch (type) {
126
+ case "string": {
127
+ const format = raw.format;
128
+ switch (format) {
129
+ case "email":
130
+ return constrainedString(raw, `mock@${fieldName ?? "example"}.com`, fieldName);
131
+ case "uri":
132
+ case "url":
133
+ return constrainedString(raw, `https://${fieldName ?? "example"}.mock.dev`, fieldName);
134
+ case "date":
135
+ return constrainedString(raw, "2025-01-15", fieldName);
136
+ case "date-time":
137
+ return constrainedString(raw, "2025-01-15T12:00:00.000Z", fieldName);
138
+ case "uuid":
139
+ return constrainedString(raw, "00000000-0000-4000-8000-000000000001", fieldName);
140
+ default:
141
+ return constrainedString(raw, `mock_${fieldName ?? "value"}`, fieldName);
142
+ }
143
+ }
144
+ case "number": {
145
+ const { min, max } = numericBounds(raw);
146
+ const multipleOf = typeof raw.multipleOf === "number" ? raw.multipleOf : undefined;
147
+ if (multipleOf !== undefined && multipleOf <= 0) {
148
+ throw new UnsupportedMockSchemaError("multipleOf", "multipleOf must be positive");
149
+ }
150
+ if (multipleOf !== undefined) {
151
+ const first = Math.ceil(min / multipleOf) * multipleOf;
152
+ const last = Math.floor(max / multipleOf) * multipleOf;
153
+ if (first > last)
154
+ throw new UnsupportedMockSchemaError("multipleOf", "No multiple exists in range");
155
+ const steps = Math.floor((last - first) / multipleOf);
156
+ return first + Math.floor(rand() * (steps + 1)) * multipleOf;
157
+ }
158
+ return Math.round((min + rand() * (max - min)) * 100) / 100;
159
+ }
160
+ case "integer": {
161
+ const { min, max } = numericBounds(raw);
162
+ const multipleOf = typeof raw.multipleOf === "number" ? raw.multipleOf : 1;
163
+ const first = Math.ceil(min / multipleOf) * multipleOf;
164
+ const last = Math.floor(max / multipleOf) * multipleOf;
165
+ if (first > last)
166
+ throw new UnsupportedMockSchemaError("integer", "No integer exists in range");
167
+ const steps = Math.floor((last - first) / multipleOf);
168
+ return first + Math.floor(rand() * (steps + 1)) * multipleOf;
169
+ }
170
+ case "boolean":
171
+ return true;
172
+ case "null":
173
+ return null;
174
+ case "array": {
175
+ const items = raw.items;
176
+ if (!items)
177
+ return [];
178
+ const minItems = typeof raw.minItems === "number" ? raw.minItems : 1;
179
+ const maxItems = typeof raw.maxItems === "number" ? raw.maxItems : Math.max(minItems, 3);
180
+ if (minItems > maxItems) {
181
+ throw new UnsupportedMockSchemaError("minItems", "Array schema has no satisfiable length");
182
+ }
183
+ const count = minItems + Math.floor(rand() * (maxItems - minItems + 1));
184
+ const result = [];
185
+ for (let i = 0; i < count; i++) {
186
+ result.push(generateMockValue(items, `${fieldName ?? "item"}_${i}`, rand));
187
+ }
188
+ if (raw.uniqueItems === true &&
189
+ new Set(result.map((value) => JSON.stringify(value))).size !== result.length) {
190
+ throw new UnsupportedMockSchemaError("uniqueItems", "Could not generate enough unique items");
191
+ }
192
+ return result;
193
+ }
194
+ default: {
195
+ const properties = raw.properties;
196
+ if (!properties || typeof properties !== "object")
197
+ return {};
198
+ const result = {};
199
+ for (const [key, propSchema] of Object.entries(properties)) {
200
+ result[key] = generateMockValue(propSchema, key, rand);
201
+ }
202
+ return result;
203
+ }
204
+ }
205
+ }
206
+ /**
207
+ * Create a mock server from a Nifra app's route definitions. For each route
208
+ * with a `schema.response`, generates a handler returning fake data that
209
+ * matches the response schema structure. Routes without response schemas
210
+ * return `{}`.
211
+ */
212
+ export function createMockServer(app, options) {
213
+ const seed = options?.seed ?? 42;
214
+ const verbose = options?.verbose ?? false;
215
+ const routes = reflectRoutes(app);
216
+ // Pre-generate mock responses for each route
217
+ const mockMap = new Map();
218
+ const mockRoutes = [];
219
+ for (const route of routes) {
220
+ const key = `${route.method.toUpperCase()} ${route.path}`;
221
+ const rng = seededRandom(seed);
222
+ const responseSchema = route.schema?.response?.jsonSchema;
223
+ const mockValue = responseSchema ? generateMockValue(responseSchema, undefined, rng) : {};
224
+ mockMap.set(key, mockValue);
225
+ mockRoutes.push({ method: route.method.toUpperCase(), path: route.path });
226
+ }
227
+ return {
228
+ mockRoutes,
229
+ async fetch(request) {
230
+ const url = new URL(request.url);
231
+ const method = request.method.toUpperCase();
232
+ const pathname = url.pathname;
233
+ // Exact match first
234
+ const exactKey = `${method} ${pathname}`;
235
+ if (mockMap.has(exactKey)) {
236
+ if (verbose)
237
+ console.log(`[mock] ${exactKey} → 200`);
238
+ return new Response(JSON.stringify(mockMap.get(exactKey)), {
239
+ status: 200,
240
+ headers: {
241
+ "content-type": "application/json",
242
+ "x-nifra-mock": "true",
243
+ },
244
+ });
245
+ }
246
+ // Try matching parameterized routes (e.g. /users/:id → /users/123)
247
+ for (const [key, value] of mockMap) {
248
+ const [routeMethod, routePath] = key.split(" ", 2);
249
+ if (routeMethod !== method || !routePath)
250
+ continue;
251
+ if (matchParameterizedPath(routePath, pathname)) {
252
+ if (verbose)
253
+ console.log(`[mock] ${method} ${pathname} → ${routePath} → 200`);
254
+ return new Response(JSON.stringify(value), {
255
+ status: 200,
256
+ headers: {
257
+ "content-type": "application/json",
258
+ "x-nifra-mock": "true",
259
+ },
260
+ });
261
+ }
262
+ }
263
+ if (verbose)
264
+ console.log(`[mock] ${method} ${pathname} → 404`);
265
+ return new Response(JSON.stringify({ error: "Not Found", mock: true }), {
266
+ status: 404,
267
+ headers: { "content-type": "application/json", "x-nifra-mock": "true" },
268
+ });
269
+ },
270
+ };
271
+ }
272
+ /**
273
+ * Match a parameterized route pattern (e.g. `/users/:id`) against a concrete pathname.
274
+ * Supports non-empty `:param` segments and Nifra's trailing `*wildcard` segment.
275
+ */
276
+ function matchParameterizedPath(pattern, pathname) {
277
+ const patternParts = pattern.split("/");
278
+ const pathParts = pathname.split("/");
279
+ const wildcardIndex = patternParts.findIndex((part) => part.startsWith("*"));
280
+ if (wildcardIndex === -1 && patternParts.length !== pathParts.length)
281
+ return false;
282
+ if (wildcardIndex !== -1 && wildcardIndex !== patternParts.length - 1)
283
+ return false;
284
+ if (wildcardIndex !== -1 && pathParts.length < wildcardIndex + 1)
285
+ return false;
286
+ for (let i = 0; i < patternParts.length; i++) {
287
+ const pp = patternParts[i];
288
+ const actual = pathParts[i];
289
+ if (pp === undefined || actual === undefined)
290
+ return false;
291
+ if (pp.startsWith("*"))
292
+ return true;
293
+ if (pp.startsWith(":")) {
294
+ if (actual.length === 0)
295
+ return false;
296
+ continue;
297
+ }
298
+ if (pp !== actual)
299
+ return false;
300
+ }
301
+ return true;
302
+ }
303
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@nifrajs/mock",
3
+ "version": "1.4.0",
4
+ "description": "Contract-based mock server — reads route schemas and generates fake responses.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "bun": "./src/index.ts",
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "src"
19
+ ],
20
+ "sideEffects": false,
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.build.json && bun run ../../scripts/fix-dts.ts dist"
23
+ },
24
+ "license": "MIT",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "dependencies": {
29
+ "@nifrajs/core": "1.4.0"
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,391 @@
1
+ /**
2
+ * `@nifrajs/mock` — contract-based mock server.
3
+ *
4
+ * Reads a Nifra app's registered routes and their Standard Schema / JSON Schema
5
+ * response definitions, then generates fake responses from supported JSON Schema keywords. Use it
6
+ * for frontend-first development, agent testing, or CI smoke tests.
7
+ */
8
+
9
+ import { reflectRoutes, reflectSchema } from "@nifrajs/core/reflection"
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Seeded PRNG (deterministic mocks)
13
+ // ---------------------------------------------------------------------------
14
+
15
+ function seededRandom(seed: number): () => number {
16
+ let s = seed
17
+ return () => {
18
+ s = (s * 1664525 + 1013904223) & 0x7fffffff
19
+ // Divide by 2^31 (not 2^31 - 1) so the result stays strictly below 1 and never
20
+ // produces an out-of-range index when scaled by an array length.
21
+ return s / 0x80000000
22
+ }
23
+ }
24
+
25
+ /** Clamp a `rand() * length` index so an inclusive-range rng cannot index past the end. */
26
+ const randomIndex = (rand: () => number, length: number): number =>
27
+ Math.min(Math.floor(rand() * length), length - 1)
28
+
29
+ export class UnsupportedMockSchemaError extends Error {
30
+ constructor(
31
+ readonly keyword: string,
32
+ message: string,
33
+ ) {
34
+ super(message)
35
+ this.name = "UnsupportedMockSchemaError"
36
+ }
37
+ }
38
+
39
+ function numericBounds(schema: Record<string, unknown>): { min: number; max: number } {
40
+ const minimum = typeof schema.minimum === "number" ? schema.minimum : 0
41
+ const maximum = typeof schema.maximum === "number" ? schema.maximum : 100
42
+ const exclusiveMinimum =
43
+ typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : undefined
44
+ const exclusiveMaximum =
45
+ typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : undefined
46
+ const min =
47
+ exclusiveMinimum === undefined ? minimum : Math.max(minimum, exclusiveMinimum + Number.EPSILON)
48
+ const max =
49
+ exclusiveMaximum === undefined ? maximum : Math.min(maximum, exclusiveMaximum - Number.EPSILON)
50
+ if (min > max)
51
+ throw new UnsupportedMockSchemaError("minimum", "Numeric schema has no satisfiable range")
52
+ return { min, max }
53
+ }
54
+
55
+ function constrainedString(
56
+ schema: Record<string, unknown>,
57
+ initial: string,
58
+ fieldName: string | undefined,
59
+ ): string {
60
+ const minLength = typeof schema.minLength === "number" ? schema.minLength : 0
61
+ const maxLength =
62
+ typeof schema.maxLength === "number" ? schema.maxLength : Number.POSITIVE_INFINITY
63
+ if (minLength > maxLength) {
64
+ throw new UnsupportedMockSchemaError("minLength", "String schema has no satisfiable length")
65
+ }
66
+ const candidates = [
67
+ initial,
68
+ fieldName ?? "mock",
69
+ "mock_value",
70
+ "test",
71
+ "abc",
72
+ "123",
73
+ "A1",
74
+ "https://example.com",
75
+ "test@example.com",
76
+ ]
77
+ const pattern = typeof schema.pattern === "string" ? new RegExp(schema.pattern) : undefined
78
+ for (const candidate of candidates) {
79
+ const padded = candidate.length < minLength ? candidate.padEnd(minLength, "a") : candidate
80
+ const value = padded.slice(0, maxLength)
81
+ if (value.length >= minLength && (pattern === undefined || pattern.test(value))) return value
82
+ }
83
+ throw new UnsupportedMockSchemaError(
84
+ "pattern",
85
+ `Cannot generate a value for pattern ${JSON.stringify(schema.pattern)}`,
86
+ )
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Schema introspection → mock value generation
91
+ // ---------------------------------------------------------------------------
92
+
93
+ /**
94
+ * Generate a mock value from a schema object. Inspects JSON Schema properties
95
+ * (`type`, `properties`, `items`, `enum`, `format`) that TypeBox / NifraSchema
96
+ * objects carry directly. Unsupported constraints fail closed with
97
+ * {@link UnsupportedMockSchemaError} rather than returning a known-invalid response.
98
+ *
99
+ * @param schema A JSON-Schema-shaped object (TypeBox, NifraSchema, or raw JSON Schema)
100
+ * @param fieldName Optional field name hint for generating contextual string values
101
+ * @param rng Optional seeded random function for deterministic output
102
+ */
103
+ export function generateMockValue(
104
+ schema: unknown,
105
+ fieldName?: string | undefined,
106
+ rng?: (() => number) | undefined,
107
+ ): unknown {
108
+ const rand = rng ?? Math.random
109
+
110
+ const reflected = reflectSchema(schema).jsonSchema
111
+ if (reflected === true || reflected === undefined) return {}
112
+ if (reflected === false) {
113
+ throw new UnsupportedMockSchemaError("false", "Boolean false schema has no valid value")
114
+ }
115
+ const raw = reflected
116
+
117
+ if ("const" in raw) return raw.const
118
+ if (raw.$ref !== undefined) {
119
+ throw new UnsupportedMockSchemaError(
120
+ "$ref",
121
+ "Resolve JSON Schema references before generating mocks",
122
+ )
123
+ }
124
+ if (raw.not !== undefined) {
125
+ throw new UnsupportedMockSchemaError("not", "JSON Schema `not` generation is unsupported")
126
+ }
127
+ if (raw.oneOf !== undefined) {
128
+ throw new UnsupportedMockSchemaError(
129
+ "oneOf",
130
+ "Exclusive union generation requires a schema validator",
131
+ )
132
+ }
133
+ const alternatives = raw.anyOf as readonly unknown[] | undefined
134
+ if (Array.isArray(alternatives) && alternatives.length > 0) {
135
+ return generateMockValue(alternatives[randomIndex(rand, alternatives.length)], fieldName, rand)
136
+ }
137
+ const intersections = raw.allOf as readonly unknown[] | undefined
138
+ if (Array.isArray(intersections) && intersections.length > 0) {
139
+ const values = intersections.map((part) => generateMockValue(part, fieldName, rand))
140
+ if (
141
+ values.every((value) => value !== null && typeof value === "object" && !Array.isArray(value))
142
+ ) {
143
+ return Object.assign({}, ...values)
144
+ }
145
+ const first = values[0]
146
+ if (values.every((value) => Object.is(value, first))) return first
147
+ throw new UnsupportedMockSchemaError(
148
+ "allOf",
149
+ "Could not prove that a scalar satisfies every intersection branch",
150
+ )
151
+ }
152
+
153
+ // Enum — pick the first value (deterministic) or random
154
+ const enumValues = raw.enum as readonly unknown[] | undefined
155
+ if (enumValues && Array.isArray(enumValues) && enumValues.length > 0) {
156
+ return enumValues[randomIndex(rand, enumValues.length)]
157
+ }
158
+
159
+ const declaredType = raw.type as string | readonly string[] | undefined
160
+ const type = Array.isArray(declaredType)
161
+ ? (declaredType.find((candidate) => candidate !== "null") ?? "null")
162
+ : declaredType
163
+
164
+ switch (type) {
165
+ case "string": {
166
+ const format = raw.format as string | undefined
167
+ switch (format) {
168
+ case "email":
169
+ return constrainedString(raw, `mock@${fieldName ?? "example"}.com`, fieldName)
170
+ case "uri":
171
+ case "url":
172
+ return constrainedString(raw, `https://${fieldName ?? "example"}.mock.dev`, fieldName)
173
+ case "date":
174
+ return constrainedString(raw, "2025-01-15", fieldName)
175
+ case "date-time":
176
+ return constrainedString(raw, "2025-01-15T12:00:00.000Z", fieldName)
177
+ case "uuid":
178
+ return constrainedString(raw, "00000000-0000-4000-8000-000000000001", fieldName)
179
+ default:
180
+ return constrainedString(raw, `mock_${fieldName ?? "value"}`, fieldName)
181
+ }
182
+ }
183
+
184
+ case "number": {
185
+ const { min, max } = numericBounds(raw)
186
+ const multipleOf = typeof raw.multipleOf === "number" ? raw.multipleOf : undefined
187
+ if (multipleOf !== undefined && multipleOf <= 0) {
188
+ throw new UnsupportedMockSchemaError("multipleOf", "multipleOf must be positive")
189
+ }
190
+ if (multipleOf !== undefined) {
191
+ const first = Math.ceil(min / multipleOf) * multipleOf
192
+ const last = Math.floor(max / multipleOf) * multipleOf
193
+ if (first > last)
194
+ throw new UnsupportedMockSchemaError("multipleOf", "No multiple exists in range")
195
+ const steps = Math.floor((last - first) / multipleOf)
196
+ return first + Math.floor(rand() * (steps + 1)) * multipleOf
197
+ }
198
+ return Math.round((min + rand() * (max - min)) * 100) / 100
199
+ }
200
+
201
+ case "integer": {
202
+ const { min, max } = numericBounds(raw)
203
+ const multipleOf = typeof raw.multipleOf === "number" ? raw.multipleOf : 1
204
+ const first = Math.ceil(min / multipleOf) * multipleOf
205
+ const last = Math.floor(max / multipleOf) * multipleOf
206
+ if (first > last)
207
+ throw new UnsupportedMockSchemaError("integer", "No integer exists in range")
208
+ const steps = Math.floor((last - first) / multipleOf)
209
+ return first + Math.floor(rand() * (steps + 1)) * multipleOf
210
+ }
211
+
212
+ case "boolean":
213
+ return true
214
+
215
+ case "null":
216
+ return null
217
+
218
+ case "array": {
219
+ const items = raw.items as Record<string, unknown> | undefined
220
+ if (!items) return []
221
+ const minItems = typeof raw.minItems === "number" ? raw.minItems : 1
222
+ const maxItems = typeof raw.maxItems === "number" ? raw.maxItems : Math.max(minItems, 3)
223
+ if (minItems > maxItems) {
224
+ throw new UnsupportedMockSchemaError("minItems", "Array schema has no satisfiable length")
225
+ }
226
+ const count = minItems + Math.floor(rand() * (maxItems - minItems + 1))
227
+ const result: unknown[] = []
228
+ for (let i = 0; i < count; i++) {
229
+ result.push(generateMockValue(items, `${fieldName ?? "item"}_${i}`, rand))
230
+ }
231
+ if (
232
+ raw.uniqueItems === true &&
233
+ new Set(result.map((value) => JSON.stringify(value))).size !== result.length
234
+ ) {
235
+ throw new UnsupportedMockSchemaError(
236
+ "uniqueItems",
237
+ "Could not generate enough unique items",
238
+ )
239
+ }
240
+ return result
241
+ }
242
+
243
+ default: {
244
+ const properties = raw.properties as Record<string, Record<string, unknown>> | undefined
245
+ if (!properties || typeof properties !== "object") return {}
246
+
247
+ const result: Record<string, unknown> = {}
248
+ for (const [key, propSchema] of Object.entries(properties)) {
249
+ result[key] = generateMockValue(propSchema, key, rand)
250
+ }
251
+ return result
252
+ }
253
+ }
254
+ }
255
+
256
+ // ---------------------------------------------------------------------------
257
+ // Mock server
258
+ // ---------------------------------------------------------------------------
259
+
260
+ /** Minimal route shape returned by `app.routes()`. */
261
+ export interface MockableRoute {
262
+ readonly method: string
263
+ readonly path: string
264
+ readonly schema?:
265
+ | {
266
+ readonly response?: unknown
267
+ }
268
+ | undefined
269
+ }
270
+
271
+ /** App shape — anything with a `routes()` method. */
272
+ export interface MockableApp {
273
+ routes(): readonly MockableRoute[]
274
+ }
275
+
276
+ export interface MockServerOptions {
277
+ /** Seed for deterministic mock generation. */
278
+ readonly seed?: number | undefined
279
+ /** Log mock requests to console. */
280
+ readonly verbose?: boolean | undefined
281
+ }
282
+
283
+ export interface MockServer {
284
+ /** Handle a request against the mock routes. */
285
+ fetch(request: Request): Promise<Response>
286
+ /** The generated mock routes for inspection. */
287
+ readonly mockRoutes: ReadonlyArray<{ method: string; path: string }>
288
+ }
289
+
290
+ /**
291
+ * Create a mock server from a Nifra app's route definitions. For each route
292
+ * with a `schema.response`, generates a handler returning fake data that
293
+ * matches the response schema structure. Routes without response schemas
294
+ * return `{}`.
295
+ */
296
+ export function createMockServer(
297
+ app: MockableApp,
298
+ options?: MockServerOptions | undefined,
299
+ ): MockServer {
300
+ const seed = options?.seed ?? 42
301
+ const verbose = options?.verbose ?? false
302
+
303
+ const routes = reflectRoutes(app)
304
+
305
+ // Pre-generate mock responses for each route
306
+ const mockMap = new Map<string, unknown>()
307
+ const mockRoutes: Array<{ method: string; path: string }> = []
308
+
309
+ for (const route of routes) {
310
+ const key = `${route.method.toUpperCase()} ${route.path}`
311
+ const rng = seededRandom(seed)
312
+ const responseSchema = route.schema?.response?.jsonSchema
313
+ const mockValue = responseSchema ? generateMockValue(responseSchema, undefined, rng) : {}
314
+ mockMap.set(key, mockValue)
315
+ mockRoutes.push({ method: route.method.toUpperCase(), path: route.path })
316
+ }
317
+
318
+ return {
319
+ mockRoutes,
320
+
321
+ async fetch(request: Request): Promise<Response> {
322
+ const url = new URL(request.url)
323
+ const method = request.method.toUpperCase()
324
+ const pathname = url.pathname
325
+
326
+ // Exact match first
327
+ const exactKey = `${method} ${pathname}`
328
+ if (mockMap.has(exactKey)) {
329
+ if (verbose) console.log(`[mock] ${exactKey} → 200`)
330
+ return new Response(JSON.stringify(mockMap.get(exactKey)), {
331
+ status: 200,
332
+ headers: {
333
+ "content-type": "application/json",
334
+ "x-nifra-mock": "true",
335
+ },
336
+ })
337
+ }
338
+
339
+ // Try matching parameterized routes (e.g. /users/:id → /users/123)
340
+ for (const [key, value] of mockMap) {
341
+ const [routeMethod, routePath] = key.split(" ", 2)
342
+ if (routeMethod !== method || !routePath) continue
343
+
344
+ if (matchParameterizedPath(routePath, pathname)) {
345
+ if (verbose) console.log(`[mock] ${method} ${pathname} → ${routePath} → 200`)
346
+ return new Response(JSON.stringify(value), {
347
+ status: 200,
348
+ headers: {
349
+ "content-type": "application/json",
350
+ "x-nifra-mock": "true",
351
+ },
352
+ })
353
+ }
354
+ }
355
+
356
+ if (verbose) console.log(`[mock] ${method} ${pathname} → 404`)
357
+ return new Response(JSON.stringify({ error: "Not Found", mock: true }), {
358
+ status: 404,
359
+ headers: { "content-type": "application/json", "x-nifra-mock": "true" },
360
+ })
361
+ },
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Match a parameterized route pattern (e.g. `/users/:id`) against a concrete pathname.
367
+ * Supports non-empty `:param` segments and Nifra's trailing `*wildcard` segment.
368
+ */
369
+ function matchParameterizedPath(pattern: string, pathname: string): boolean {
370
+ const patternParts = pattern.split("/")
371
+ const pathParts = pathname.split("/")
372
+
373
+ const wildcardIndex = patternParts.findIndex((part) => part.startsWith("*"))
374
+ if (wildcardIndex === -1 && patternParts.length !== pathParts.length) return false
375
+ if (wildcardIndex !== -1 && wildcardIndex !== patternParts.length - 1) return false
376
+ if (wildcardIndex !== -1 && pathParts.length < wildcardIndex + 1) return false
377
+
378
+ for (let i = 0; i < patternParts.length; i++) {
379
+ const pp = patternParts[i]
380
+ const actual = pathParts[i]
381
+ if (pp === undefined || actual === undefined) return false
382
+ if (pp.startsWith("*")) return true
383
+ if (pp.startsWith(":")) {
384
+ if (actual.length === 0) return false
385
+ continue
386
+ }
387
+ if (pp !== actual) return false
388
+ }
389
+
390
+ return true
391
+ }