@wylan24/mockgen 0.0.0-stage → 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +177 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +226 -0
- package/dist/cli.js.map +1 -0
- package/dist/collections.d.ts +45 -0
- package/dist/collections.js +215 -0
- package/dist/collections.js.map +1 -0
- package/dist/generate.d.ts +13 -0
- package/dist/generate.js +377 -0
- package/dist/generate.js.map +1 -0
- package/dist/heuristics.d.ts +28 -0
- package/dist/heuristics.js +139 -0
- package/dist/heuristics.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/infer.d.ts +10 -0
- package/dist/infer.js +166 -0
- package/dist/infer.js.map +1 -0
- package/dist/load.d.ts +3 -0
- package/dist/load.js +15 -0
- package/dist/load.js.map +1 -0
- package/dist/model.d.ts +34 -0
- package/dist/model.js +4 -0
- package/dist/model.js.map +1 -0
- package/dist/openapi.d.ts +17 -0
- package/dist/openapi.js +209 -0
- package/dist/openapi.js.map +1 -0
- package/dist/samples.d.ts +14 -0
- package/dist/samples.js +115 -0
- package/dist/samples.js.map +1 -0
- package/dist/schema.d.ts +38 -0
- package/dist/schema.js +321 -0
- package/dist/schema.js.map +1 -0
- package/dist/server.d.ts +41 -0
- package/dist/server.js +171 -0
- package/dist/server.js.map +1 -0
- package/dist/validate.d.ts +21 -0
- package/dist/validate.js +164 -0
- package/dist/validate.js.map +1 -0
- package/package.json +68 -4
package/dist/schema.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { isIPv4, isIPv6 } from 'node:net';
|
|
2
|
+
// Small schema helpers shared by the generator, server and collections.
|
|
3
|
+
export function typeOf(schema) {
|
|
4
|
+
let t = schema.type;
|
|
5
|
+
if (Array.isArray(t)) {
|
|
6
|
+
// 3.1 nullable: prefer the non-null type so data looks real.
|
|
7
|
+
const nonNull = t.filter((x) => x !== 'null');
|
|
8
|
+
t = nonNull[0] ?? 'null';
|
|
9
|
+
}
|
|
10
|
+
if (t)
|
|
11
|
+
return t;
|
|
12
|
+
if (schema.properties || schema.additionalProperties)
|
|
13
|
+
return 'object';
|
|
14
|
+
if (schema.items)
|
|
15
|
+
return 'array';
|
|
16
|
+
if (schema.format || schema.pattern || schema.minLength !== undefined)
|
|
17
|
+
return 'string';
|
|
18
|
+
if (schema.minimum !== undefined || schema.maximum !== undefined)
|
|
19
|
+
return 'number';
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* OpenAPI `discriminator`s, resolved at load time. Plain JSON Schema can't tell
|
|
24
|
+
* `oneOf` branches apart when they differ only by a discriminator value, so the
|
|
25
|
+
* generator, `fits` and the validator all pick the branch from this mapping.
|
|
26
|
+
*/
|
|
27
|
+
export const DISCRIMINATOR = Symbol.for('mockgen.discriminator');
|
|
28
|
+
export function discriminatorOf(schema) {
|
|
29
|
+
return schema[DISCRIMINATOR];
|
|
30
|
+
}
|
|
31
|
+
const LOWER_BOUNDS = new Set(['minimum', 'minLength', 'minItems', 'minProperties']);
|
|
32
|
+
const UPPER_BOUNDS = new Set(['maximum', 'maxLength', 'maxItems', 'maxProperties']);
|
|
33
|
+
/**
|
|
34
|
+
* Merge `allOf` members into one schema: properties and required lists combine.
|
|
35
|
+
* A member with `additionalProperties: false` only allows its own properties, so
|
|
36
|
+
* optional properties from other members are dropped (that's what a validator
|
|
37
|
+
* enforces). Required ones are kept: such a spec can't be satisfied anyway, and
|
|
38
|
+
* a realistic value beats a placeholder.
|
|
39
|
+
*
|
|
40
|
+
* Schemas are never mutated, so results are cached by identity: generation merges
|
|
41
|
+
* the same schemas for every record.
|
|
42
|
+
*/
|
|
43
|
+
export function mergeAllOf(schema) {
|
|
44
|
+
let merged = mergeCache.get(schema);
|
|
45
|
+
if (!merged)
|
|
46
|
+
mergeCache.set(schema, (merged = mergeUncached(schema)));
|
|
47
|
+
return merged;
|
|
48
|
+
}
|
|
49
|
+
const mergeCache = new WeakMap();
|
|
50
|
+
/** `mergeAllOf({allOf: [base, branch]})`, cached per (base, branch) pair. */
|
|
51
|
+
export function mergeBranch(base, branch) {
|
|
52
|
+
let byBranch = branchCache.get(base);
|
|
53
|
+
if (!byBranch)
|
|
54
|
+
branchCache.set(base, (byBranch = new WeakMap()));
|
|
55
|
+
let merged = byBranch.get(branch);
|
|
56
|
+
if (!merged)
|
|
57
|
+
byBranch.set(branch, (merged = mergeUncached({ allOf: [base, branch] })));
|
|
58
|
+
return merged;
|
|
59
|
+
}
|
|
60
|
+
const branchCache = new WeakMap();
|
|
61
|
+
function mergeUncached(schema) {
|
|
62
|
+
const { allOf, ...rest } = schema;
|
|
63
|
+
const merged = { ...rest };
|
|
64
|
+
const closed = [];
|
|
65
|
+
if (rest.additionalProperties === false && rest.properties)
|
|
66
|
+
closed.push(new Set(Object.keys(rest.properties)));
|
|
67
|
+
for (const part of allOf) {
|
|
68
|
+
if (part.additionalProperties === false)
|
|
69
|
+
closed.push(new Set(Object.keys(part.properties ?? {})));
|
|
70
|
+
const flat = part.allOf ? mergeAllOf(part) : part;
|
|
71
|
+
for (const [k, v] of Object.entries(flat)) {
|
|
72
|
+
if (k === 'properties')
|
|
73
|
+
merged.properties = mergeProperties(merged.properties, v);
|
|
74
|
+
else if (k === 'required')
|
|
75
|
+
merged.required = [...new Set([...(merged.required ?? []), ...v])];
|
|
76
|
+
else if (!(k in merged))
|
|
77
|
+
merged[k] = v;
|
|
78
|
+
// Every member applies, so constraints narrow rather than first-one-wins.
|
|
79
|
+
else if (k === 'enum' && Array.isArray(v))
|
|
80
|
+
merged.enum = merged.enum.filter((e) => v.some((x) => JSON.stringify(x) === JSON.stringify(e)));
|
|
81
|
+
else if (LOWER_BOUNDS.has(k) && typeof v === 'number')
|
|
82
|
+
merged[k] = Math.max(merged[k], v);
|
|
83
|
+
else if (UPPER_BOUNDS.has(k) && typeof v === 'number')
|
|
84
|
+
merged[k] = Math.min(merged[k], v);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (closed.length && merged.properties) {
|
|
88
|
+
const required = new Set(merged.required ?? []);
|
|
89
|
+
merged.properties = Object.fromEntries(Object.entries(merged.properties).filter(([k]) => required.has(k) || closed.every((allowed) => allowed.has(k))));
|
|
90
|
+
}
|
|
91
|
+
return merged;
|
|
92
|
+
}
|
|
93
|
+
/** A property defined by two members must satisfy both definitions. */
|
|
94
|
+
function mergeProperties(a, b) {
|
|
95
|
+
const out = { ...a };
|
|
96
|
+
for (const [key, schema] of Object.entries(b)) {
|
|
97
|
+
const existing = out[key];
|
|
98
|
+
out[key] = existing && existing !== schema ? { allOf: [existing, schema] } : schema;
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Whether `value` validates against `schema` — a compact validator covering the
|
|
104
|
+
* keywords OpenAPI uses, used to reject bad spec examples and to keep `oneOf`
|
|
105
|
+
* branches from overlapping. Returns false when there is no schema at all.
|
|
106
|
+
*/
|
|
107
|
+
export function fits(value, schema) {
|
|
108
|
+
return schema !== undefined && check(value, schema, 0);
|
|
109
|
+
}
|
|
110
|
+
const MAX_DEPTH = 64;
|
|
111
|
+
function check(value, schema, depth) {
|
|
112
|
+
if (schema === undefined || schema === true)
|
|
113
|
+
return true;
|
|
114
|
+
if (schema === false)
|
|
115
|
+
return false;
|
|
116
|
+
if (depth > MAX_DEPTH)
|
|
117
|
+
return true;
|
|
118
|
+
const s = schema;
|
|
119
|
+
const d = depth + 1;
|
|
120
|
+
// OpenAPI 3.0.3: `nullable` widens `type` (and `enum`), but null must still pass
|
|
121
|
+
// oneOf/allOf and the rest. Mirrors how validate.ts converts it.
|
|
122
|
+
const nullable = value === null && s.nullable === true;
|
|
123
|
+
if (nullable && !s.type)
|
|
124
|
+
return true;
|
|
125
|
+
if (Array.isArray(s.enum) && !nullable && !s.enum.some((e) => equal(e, value)))
|
|
126
|
+
return false;
|
|
127
|
+
if ('const' in s && !equal(s.const, value))
|
|
128
|
+
return false;
|
|
129
|
+
if (Array.isArray(s.allOf) && !s.allOf.every((p) => check(value, p, d)))
|
|
130
|
+
return false;
|
|
131
|
+
const disc = s.oneOf || s.anyOf ? discriminatorOf(s) : undefined;
|
|
132
|
+
if (disc) {
|
|
133
|
+
const branch = value && typeof value === 'object' ? disc.mapping.find((m) => m.value === value[disc.property]) : undefined;
|
|
134
|
+
if (!branch || !check(value, branch.schema, d))
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
if (Array.isArray(s.anyOf) && !s.anyOf.some((p) => check(value, p, d)))
|
|
139
|
+
return false;
|
|
140
|
+
if (Array.isArray(s.oneOf) && countMatches(value, s.oneOf, d, 2) !== 1)
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
if (s.not !== undefined && check(value, s.not, d))
|
|
144
|
+
return false;
|
|
145
|
+
const types = Array.isArray(s.type) ? s.type : s.type ? [s.type] : [];
|
|
146
|
+
if (types.length && !nullable && !types.some((t) => isType(value, t)))
|
|
147
|
+
return false;
|
|
148
|
+
if (typeof value === 'number')
|
|
149
|
+
return checkNumber(value, s);
|
|
150
|
+
if (typeof value === 'string')
|
|
151
|
+
return checkString(value, s);
|
|
152
|
+
if (Array.isArray(value))
|
|
153
|
+
return checkArray(value, s, d);
|
|
154
|
+
if (value && typeof value === 'object')
|
|
155
|
+
return checkObject(value, s, d);
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
/** How many of `schemas` the value matches, stopping early at `stopAt`. */
|
|
159
|
+
export function countMatches(value, schemas, depth = 0, stopAt = Infinity) {
|
|
160
|
+
let n = 0;
|
|
161
|
+
for (const s of schemas)
|
|
162
|
+
if (check(value, s, depth) && ++n >= stopAt)
|
|
163
|
+
break;
|
|
164
|
+
return n;
|
|
165
|
+
}
|
|
166
|
+
function isType(v, t) {
|
|
167
|
+
switch (t) {
|
|
168
|
+
case 'null':
|
|
169
|
+
return v === null;
|
|
170
|
+
case 'integer':
|
|
171
|
+
return Number.isInteger(v);
|
|
172
|
+
case 'number':
|
|
173
|
+
return typeof v === 'number' && Number.isFinite(v);
|
|
174
|
+
case 'string':
|
|
175
|
+
return typeof v === 'string';
|
|
176
|
+
case 'boolean':
|
|
177
|
+
return typeof v === 'boolean';
|
|
178
|
+
case 'array':
|
|
179
|
+
return Array.isArray(v);
|
|
180
|
+
case 'object':
|
|
181
|
+
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
182
|
+
default:
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function checkNumber(n, s) {
|
|
187
|
+
if (s.minimum !== undefined && (s.exclusiveMinimum === true ? n <= s.minimum : n < s.minimum))
|
|
188
|
+
return false;
|
|
189
|
+
if (s.maximum !== undefined && (s.exclusiveMaximum === true ? n >= s.maximum : n > s.maximum))
|
|
190
|
+
return false;
|
|
191
|
+
if (typeof s.exclusiveMinimum === 'number' && n <= s.exclusiveMinimum)
|
|
192
|
+
return false;
|
|
193
|
+
if (typeof s.exclusiveMaximum === 'number' && n >= s.exclusiveMaximum)
|
|
194
|
+
return false;
|
|
195
|
+
if (s.multipleOf) {
|
|
196
|
+
const q = n / s.multipleOf;
|
|
197
|
+
if (Math.abs(q - Math.round(q)) > 1e-9)
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
if (s.format === 'int32')
|
|
201
|
+
return Number.isInteger(n) && n >= -(2 ** 31) && n < 2 ** 31;
|
|
202
|
+
if (s.format === 'int64')
|
|
203
|
+
return Number.isInteger(n);
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
function checkString(v, s) {
|
|
207
|
+
const length = [...v].length;
|
|
208
|
+
if (s.minLength !== undefined && length < s.minLength)
|
|
209
|
+
return false;
|
|
210
|
+
if (s.maxLength !== undefined && length > s.maxLength)
|
|
211
|
+
return false;
|
|
212
|
+
if (s.pattern && !safeTest(s.pattern, v))
|
|
213
|
+
return false;
|
|
214
|
+
const format = s.format && FORMATS[s.format];
|
|
215
|
+
return !format || format(v);
|
|
216
|
+
}
|
|
217
|
+
function checkArray(v, s, d) {
|
|
218
|
+
if (s.minItems !== undefined && v.length < s.minItems)
|
|
219
|
+
return false;
|
|
220
|
+
if (s.maxItems !== undefined && v.length > s.maxItems)
|
|
221
|
+
return false;
|
|
222
|
+
if (s.uniqueItems && new Set(v.map((x) => JSON.stringify(x))).size !== v.length)
|
|
223
|
+
return false;
|
|
224
|
+
const prefix = s.prefixItems ?? [];
|
|
225
|
+
return v.every((item, i) => check(item, i < prefix.length ? prefix[i] : s.items, d));
|
|
226
|
+
}
|
|
227
|
+
function checkObject(v, s, d) {
|
|
228
|
+
const props = s.properties ?? {};
|
|
229
|
+
for (const key of s.required ?? []) {
|
|
230
|
+
// Responses never carry writeOnly fields, even required ones.
|
|
231
|
+
if (!(key in v) && !props[key]?.writeOnly)
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
const keys = Object.keys(v);
|
|
235
|
+
if (s.minProperties !== undefined && keys.length < s.minProperties)
|
|
236
|
+
return false;
|
|
237
|
+
if (s.maxProperties !== undefined && keys.length > s.maxProperties)
|
|
238
|
+
return false;
|
|
239
|
+
const patterns = Object.entries(s.patternProperties ?? {});
|
|
240
|
+
for (const key of keys) {
|
|
241
|
+
if (key in props) {
|
|
242
|
+
if (!check(v[key], props[key], d))
|
|
243
|
+
return false;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const matched = patterns.filter(([p]) => safeTest(p, key));
|
|
247
|
+
if (matched.length) {
|
|
248
|
+
if (!matched.every(([, ps]) => check(v[key], ps, d)))
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
else if (s.additionalProperties !== undefined && !check(v[key], s.additionalProperties, d)) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
/** Test against a spec pattern, compiled once; an invalid pattern matches nothing. */
|
|
258
|
+
function safeTest(pattern, value) {
|
|
259
|
+
let re = regexCache.get(pattern);
|
|
260
|
+
if (re === undefined) {
|
|
261
|
+
try {
|
|
262
|
+
re = new RegExp(pattern, 'u');
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
re = null;
|
|
266
|
+
}
|
|
267
|
+
regexCache.set(pattern, re);
|
|
268
|
+
}
|
|
269
|
+
return re !== null && re.test(value);
|
|
270
|
+
}
|
|
271
|
+
const regexCache = new Map();
|
|
272
|
+
function equal(a, b) {
|
|
273
|
+
return a === b || JSON.stringify(a) === JSON.stringify(b);
|
|
274
|
+
}
|
|
275
|
+
/** Characters RFC 3986 never allows unescaped — `{id}` placeholders in examples trip validators. */
|
|
276
|
+
function uriChars(v) {
|
|
277
|
+
if (/^(https?|ftp):/i.test(v)) {
|
|
278
|
+
// WHATWG URL parsing accepts hosts like `***`; RFC 3986 validators don't.
|
|
279
|
+
const host = /^[a-z]+:\/\/(?:[^@/?#]*@)?([^/?#:]*)/i.exec(v)?.[1] ?? '';
|
|
280
|
+
if (!/^(\[[0-9a-f:.]+\]|[a-z0-9.-]+)$/i.test(host))
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
return !/[\s{}<>"\^`|]/.test(v) && !/%(?![0-9a-f]{2})/i.test(v);
|
|
284
|
+
}
|
|
285
|
+
const DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
286
|
+
const TIME = /^\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$/;
|
|
287
|
+
function isDate(v) {
|
|
288
|
+
const m = DATE.exec(v);
|
|
289
|
+
if (!m)
|
|
290
|
+
return false;
|
|
291
|
+
const d = new Date(`${v}T00:00:00Z`);
|
|
292
|
+
return !Number.isNaN(d.getTime()) && d.getUTCDate() === Number(m[3]);
|
|
293
|
+
}
|
|
294
|
+
/** Formats checked as strictly as Ajv's `ajv-formats`, so passing here means passing there. */
|
|
295
|
+
const FORMATS = {
|
|
296
|
+
date: isDate,
|
|
297
|
+
time: (v) => TIME.test(v),
|
|
298
|
+
'date-time': (v) => {
|
|
299
|
+
const i = v.search(/[Tt ]/);
|
|
300
|
+
return i === 10 && isDate(v.slice(0, 10)) && TIME.test(v.slice(11));
|
|
301
|
+
},
|
|
302
|
+
uuid: (v) => /^(urn:uuid:)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v),
|
|
303
|
+
email: (v) => /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i.test(v),
|
|
304
|
+
uri: (v) => /^[a-z][a-z0-9+.-]*:/i.test(v) && uriChars(v) && URL.canParse(v),
|
|
305
|
+
url: (v) => /^(https?|ftp):\/\//i.test(v) && uriChars(v) && URL.canParse(v),
|
|
306
|
+
'uri-reference': uriChars,
|
|
307
|
+
hostname: (v) => /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i.test(v),
|
|
308
|
+
ipv4: (v) => isIPv4(v),
|
|
309
|
+
ipv6: (v) => isIPv6(v),
|
|
310
|
+
'json-pointer': (v) => /^(?:\/(?:[^~/]|~0|~1)*)*$/.test(v),
|
|
311
|
+
regex: (v) => {
|
|
312
|
+
try {
|
|
313
|
+
new RegExp(v, 'u');
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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","sourcesContent":["import { isIPv4, isIPv6 } from 'node:net';\nimport type { Schema } from './model.js';\n\n// Small schema helpers shared by the generator, server and collections.\n\nexport function typeOf(schema: Schema): string | undefined {\n let t = schema.type;\n if (Array.isArray(t)) {\n // 3.1 nullable: prefer the non-null type so data looks real.\n const nonNull = t.filter((x: string) => x !== 'null');\n t = nonNull[0] ?? 'null';\n }\n if (t) return t;\n if (schema.properties || schema.additionalProperties) return 'object';\n if (schema.items) return 'array';\n if (schema.format || schema.pattern || schema.minLength !== undefined) return 'string';\n if (schema.minimum !== undefined || schema.maximum !== undefined) return 'number';\n return undefined;\n}\n\n/**\n * OpenAPI `discriminator`s, resolved at load time. Plain JSON Schema can't tell\n * `oneOf` branches apart when they differ only by a discriminator value, so the\n * generator, `fits` and the validator all pick the branch from this mapping.\n */\nexport const DISCRIMINATOR = Symbol.for('mockgen.discriminator');\n\nexport interface Discriminator {\n property: string;\n /** One entry per `oneOf`/`anyOf` branch. */\n mapping: { value: string; schema: Schema }[];\n}\n\nexport function discriminatorOf(schema: Schema): Discriminator | undefined {\n return (schema as Record<symbol, Discriminator | undefined>)[DISCRIMINATOR];\n}\n\nconst LOWER_BOUNDS = new Set(['minimum', 'minLength', 'minItems', 'minProperties']);\nconst UPPER_BOUNDS = new Set(['maximum', 'maxLength', 'maxItems', 'maxProperties']);\n\n/**\n * Merge `allOf` members into one schema: properties and required lists combine.\n * A member with `additionalProperties: false` only allows its own properties, so\n * optional properties from other members are dropped (that's what a validator\n * enforces). Required ones are kept: such a spec can't be satisfied anyway, and\n * a realistic value beats a placeholder.\n *\n * Schemas are never mutated, so results are cached by identity: generation merges\n * the same schemas for every record.\n */\nexport function mergeAllOf(schema: Schema): Schema {\n let merged = mergeCache.get(schema);\n if (!merged) mergeCache.set(schema, (merged = mergeUncached(schema)));\n return merged;\n}\n\nconst mergeCache = new WeakMap<Schema, Schema>();\n\n/** `mergeAllOf({allOf: [base, branch]})`, cached per (base, branch) pair. */\nexport function mergeBranch(base: Schema, branch: Schema): Schema {\n let byBranch = branchCache.get(base);\n if (!byBranch) branchCache.set(base, (byBranch = new WeakMap()));\n let merged = byBranch.get(branch);\n if (!merged) byBranch.set(branch, (merged = mergeUncached({ allOf: [base, branch] })));\n return merged;\n}\n\nconst branchCache = new WeakMap<Schema, WeakMap<Schema, Schema>>();\n\nfunction mergeUncached(schema: Schema): Schema {\n const { allOf, ...rest } = schema;\n const merged: Schema = { ...rest };\n const closed: Set<string>[] = [];\n if (rest.additionalProperties === false && rest.properties) closed.push(new Set(Object.keys(rest.properties)));\n for (const part of allOf as Schema[]) {\n if (part.additionalProperties === false) closed.push(new Set(Object.keys(part.properties ?? {})));\n const flat = part.allOf ? mergeAllOf(part) : part;\n for (const [k, v] of Object.entries(flat)) {\n if (k === 'properties') merged.properties = mergeProperties(merged.properties, v);\n else if (k === 'required') merged.required = [...new Set([...(merged.required ?? []), ...v])];\n else if (!(k in merged)) merged[k] = v;\n // Every member applies, so constraints narrow rather than first-one-wins.\n else if (k === 'enum' && Array.isArray(v)) merged.enum = merged.enum.filter((e: unknown) => v.some((x: unknown) => JSON.stringify(x) === JSON.stringify(e)));\n else if (LOWER_BOUNDS.has(k) && typeof v === 'number') merged[k] = Math.max(merged[k], v);\n else if (UPPER_BOUNDS.has(k) && typeof v === 'number') merged[k] = Math.min(merged[k], v);\n }\n }\n if (closed.length && merged.properties) {\n const required = new Set(merged.required ?? []);\n merged.properties = Object.fromEntries(\n Object.entries(merged.properties).filter(([k]) => required.has(k) || closed.every((allowed) => allowed.has(k))),\n );\n }\n return merged;\n}\n\n/** A property defined by two members must satisfy both definitions. */\nfunction mergeProperties(a: Record<string, Schema> | undefined, b: Record<string, Schema>): Record<string, Schema> {\n const out = { ...a };\n for (const [key, schema] of Object.entries(b)) {\n const existing = out[key];\n out[key] = existing && existing !== schema ? { allOf: [existing, schema] } : schema;\n }\n return out;\n}\n\n/**\n * Whether `value` validates against `schema` — a compact validator covering the\n * keywords OpenAPI uses, used to reject bad spec examples and to keep `oneOf`\n * branches from overlapping. Returns false when there is no schema at all.\n */\nexport function fits(value: unknown, schema: Schema | undefined): boolean {\n return schema !== undefined && check(value, schema, 0);\n}\n\nconst MAX_DEPTH = 64;\n\nfunction check(value: unknown, schema: Schema | boolean | undefined, depth: number): boolean {\n if (schema === undefined || schema === true) return true;\n if (schema === false) return false;\n if (depth > MAX_DEPTH) return true;\n const s = schema;\n const d = depth + 1;\n\n // OpenAPI 3.0.3: `nullable` widens `type` (and `enum`), but null must still pass\n // oneOf/allOf and the rest. Mirrors how validate.ts converts it.\n const nullable = value === null && s.nullable === true;\n if (nullable && !s.type) return true;\n if (Array.isArray(s.enum) && !nullable && !s.enum.some((e: unknown) => equal(e, value))) return false;\n if ('const' in s && !equal(s.const, value)) return false;\n if (Array.isArray(s.allOf) && !s.allOf.every((p: Schema) => check(value, p, d))) return false;\n const disc = s.oneOf || s.anyOf ? discriminatorOf(s) : undefined;\n if (disc) {\n const branch = value && typeof value === 'object' ? disc.mapping.find((m) => m.value === (value as Record<string, unknown>)[disc.property]) : undefined;\n if (!branch || !check(value, branch.schema, d)) return false;\n } else {\n if (Array.isArray(s.anyOf) && !s.anyOf.some((p: Schema) => check(value, p, d))) return false;\n if (Array.isArray(s.oneOf) && countMatches(value, s.oneOf, d, 2) !== 1) return false;\n }\n if (s.not !== undefined && check(value, s.not, d)) return false;\n\n const types: string[] = Array.isArray(s.type) ? s.type : s.type ? [s.type] : [];\n if (types.length && !nullable && !types.some((t) => isType(value, t))) return false;\n\n if (typeof value === 'number') return checkNumber(value, s);\n if (typeof value === 'string') return checkString(value, s);\n if (Array.isArray(value)) return checkArray(value, s, d);\n if (value && typeof value === 'object') return checkObject(value as Record<string, unknown>, s, d);\n return true;\n}\n\n/** How many of `schemas` the value matches, stopping early at `stopAt`. */\nexport function countMatches(value: unknown, schemas: Schema[], depth = 0, stopAt = Infinity): number {\n let n = 0;\n for (const s of schemas) if (check(value, s, depth) && ++n >= stopAt) break;\n return n;\n}\n\nfunction isType(v: unknown, t: string): boolean {\n switch (t) {\n case 'null':\n return v === null;\n case 'integer':\n return Number.isInteger(v);\n case 'number':\n return typeof v === 'number' && Number.isFinite(v);\n case 'string':\n return typeof v === 'string';\n case 'boolean':\n return typeof v === 'boolean';\n case 'array':\n return Array.isArray(v);\n case 'object':\n return !!v && typeof v === 'object' && !Array.isArray(v);\n default:\n return false;\n }\n}\n\nfunction checkNumber(n: number, s: Schema): boolean {\n if (s.minimum !== undefined && (s.exclusiveMinimum === true ? n <= s.minimum : n < s.minimum)) return false;\n if (s.maximum !== undefined && (s.exclusiveMaximum === true ? n >= s.maximum : n > s.maximum)) return false;\n if (typeof s.exclusiveMinimum === 'number' && n <= s.exclusiveMinimum) return false;\n if (typeof s.exclusiveMaximum === 'number' && n >= s.exclusiveMaximum) return false;\n if (s.multipleOf) {\n const q = n / s.multipleOf;\n if (Math.abs(q - Math.round(q)) > 1e-9) return false;\n }\n if (s.format === 'int32') return Number.isInteger(n) && n >= -(2 ** 31) && n < 2 ** 31;\n if (s.format === 'int64') return Number.isInteger(n);\n return true;\n}\n\nfunction checkString(v: string, s: Schema): boolean {\n const length = [...v].length;\n if (s.minLength !== undefined && length < s.minLength) return false;\n if (s.maxLength !== undefined && length > s.maxLength) return false;\n if (s.pattern && !safeTest(s.pattern, v)) return false;\n const format = s.format && FORMATS[s.format];\n return !format || format(v);\n}\n\nfunction checkArray(v: unknown[], s: Schema, d: number): boolean {\n if (s.minItems !== undefined && v.length < s.minItems) return false;\n if (s.maxItems !== undefined && v.length > s.maxItems) return false;\n if (s.uniqueItems && new Set(v.map((x) => JSON.stringify(x))).size !== v.length) return false;\n const prefix: Schema[] = s.prefixItems ?? [];\n return v.every((item, i) => check(item, i < prefix.length ? prefix[i] : s.items, d));\n}\n\nfunction checkObject(v: Record<string, unknown>, s: Schema, d: number): boolean {\n const props: Record<string, Schema> = s.properties ?? {};\n for (const key of s.required ?? []) {\n // Responses never carry writeOnly fields, even required ones.\n if (!(key in v) && !props[key]?.writeOnly) return false;\n }\n const keys = Object.keys(v);\n if (s.minProperties !== undefined && keys.length < s.minProperties) return false;\n if (s.maxProperties !== undefined && keys.length > s.maxProperties) return false;\n const patterns = Object.entries<Schema>(s.patternProperties ?? {});\n for (const key of keys) {\n if (key in props) {\n if (!check(v[key], props[key], d)) return false;\n continue;\n }\n const matched = patterns.filter(([p]) => safeTest(p, key));\n if (matched.length) {\n if (!matched.every(([, ps]) => check(v[key], ps, d))) return false;\n } else if (s.additionalProperties !== undefined && !check(v[key], s.additionalProperties, d)) {\n return false;\n }\n }\n return true;\n}\n\n/** Test against a spec pattern, compiled once; an invalid pattern matches nothing. */\nfunction safeTest(pattern: string, value: string): boolean {\n let re = regexCache.get(pattern);\n if (re === undefined) {\n try {\n re = new RegExp(pattern, 'u');\n } catch {\n re = null;\n }\n regexCache.set(pattern, re);\n }\n return re !== null && re.test(value);\n}\n\nconst regexCache = new Map<string, RegExp | null>();\n\nfunction equal(a: unknown, b: unknown): boolean {\n return a === b || JSON.stringify(a) === JSON.stringify(b);\n}\n\n/** Characters RFC 3986 never allows unescaped — `{id}` placeholders in examples trip validators. */\nfunction uriChars(v: string): boolean {\n if (/^(https?|ftp):/i.test(v)) {\n // WHATWG URL parsing accepts hosts like `***`; RFC 3986 validators don't.\n const host = /^[a-z]+:\\/\\/(?:[^@/?#]*@)?([^/?#:]*)/i.exec(v)?.[1] ?? '';\n if (!/^(\\[[0-9a-f:.]+\\]|[a-z0-9.-]+)$/i.test(host)) return false;\n }\n return !/[\\s{}<>\"\\^`|]/.test(v) && !/%(?![0-9a-f]{2})/i.test(v);\n}\n\nconst DATE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst TIME = /^\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?([Zz]|[+-]\\d{2}:\\d{2})$/;\n\nfunction isDate(v: string): boolean {\n const m = DATE.exec(v);\n if (!m) return false;\n const d = new Date(`${v}T00:00:00Z`);\n return !Number.isNaN(d.getTime()) && d.getUTCDate() === Number(m[3]);\n}\n\n/** Formats checked as strictly as Ajv's `ajv-formats`, so passing here means passing there. */\nconst FORMATS: Record<string, (v: string) => boolean> = {\n date: isDate,\n time: (v) => TIME.test(v),\n 'date-time': (v) => {\n const i = v.search(/[Tt ]/);\n return i === 10 && isDate(v.slice(0, 10)) && TIME.test(v.slice(11));\n },\n uuid: (v) => /^(urn:uuid:)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v),\n email: (v) => /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i.test(v),\n uri: (v) => /^[a-z][a-z0-9+.-]*:/i.test(v) && uriChars(v) && URL.canParse(v),\n url: (v) => /^(https?|ftp):\\/\\//i.test(v) && uriChars(v) && URL.canParse(v),\n 'uri-reference': uriChars,\n hostname: (v) => /^(?=.{1,253}\\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\\.?$/i.test(v),\n ipv4: (v) => isIPv4(v),\n ipv6: (v) => isIPv6(v),\n 'json-pointer': (v) => /^(?:\\/(?:[^~/]|~0|~1)*)*$/.test(v),\n regex: (v) => {\n try {\n new RegExp(v, 'u');\n return true;\n } catch {\n return false;\n }\n },\n};\n"]}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import type { MockModel } from './model.js';
|
|
3
|
+
export interface MockServerOptions {
|
|
4
|
+
model: MockModel;
|
|
5
|
+
/** Seed for generated data; same seed → same responses. Default 1. */
|
|
6
|
+
seed?: number;
|
|
7
|
+
/** Delay every response by this many milliseconds. Overridden per request by `X-Mock-Delay`. */
|
|
8
|
+
delay?: number;
|
|
9
|
+
/** Size of every list endpoint's collection, paged through with `?page=`/`?limit=`. Default 50. */
|
|
10
|
+
records?: number;
|
|
11
|
+
/** Called once per request with a one-line summary. */
|
|
12
|
+
log?: (line: string) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface MockServer {
|
|
15
|
+
server: http.Server;
|
|
16
|
+
listen(port?: number, host?: string): Promise<string>;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
/** Swap the model in place, e.g. after the spec changed on disk. */
|
|
19
|
+
setModel(model: MockModel): void;
|
|
20
|
+
}
|
|
21
|
+
export interface MockResult {
|
|
22
|
+
status: number;
|
|
23
|
+
body: unknown;
|
|
24
|
+
headers: Record<string, string>;
|
|
25
|
+
}
|
|
26
|
+
export declare function createMockServer(options: MockServerOptions): MockServer;
|
|
27
|
+
export interface RespondInput {
|
|
28
|
+
method: string;
|
|
29
|
+
path: string;
|
|
30
|
+
seed?: number;
|
|
31
|
+
/** Force a status code, e.g. from `X-Mock-Status`. */
|
|
32
|
+
status?: string | number;
|
|
33
|
+
/** Query string; drives pagination on list endpoints. */
|
|
34
|
+
query?: URLSearchParams | string;
|
|
35
|
+
/** `http://host:port`, used to build absolute next/prev links. */
|
|
36
|
+
origin?: string;
|
|
37
|
+
/** Collection size for list endpoints. Default 50. */
|
|
38
|
+
records?: number;
|
|
39
|
+
}
|
|
40
|
+
/** Produce the mock response for one request. Pure apart from the generator's seed. */
|
|
41
|
+
export declare function respond(model: MockModel, input: RespondInput): MockResult;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import { buildPage, findDetailRoute, findListShape } from './collections.js';
|
|
3
|
+
import { generate } from './generate.js';
|
|
4
|
+
import { fits, mergeAllOf } from './schema.js';
|
|
5
|
+
const CORS_HEADERS = {
|
|
6
|
+
'access-control-allow-origin': '*',
|
|
7
|
+
'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS,HEAD',
|
|
8
|
+
'access-control-allow-headers': '*',
|
|
9
|
+
'access-control-expose-headers': '*',
|
|
10
|
+
};
|
|
11
|
+
export function createMockServer(options) {
|
|
12
|
+
let model = options.model;
|
|
13
|
+
const seed = options.seed ?? 1;
|
|
14
|
+
const server = http.createServer(async (req, res) => {
|
|
15
|
+
const started = performance.now();
|
|
16
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
17
|
+
const method = (req.method ?? 'GET').toUpperCase();
|
|
18
|
+
req.resume(); // Request bodies are not used yet; drain them.
|
|
19
|
+
let result;
|
|
20
|
+
if (method === 'OPTIONS' && !findRoute(model, 'OPTIONS', url.pathname).route) {
|
|
21
|
+
result = { status: 204, body: undefined, headers: {} };
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
result = respond(model, {
|
|
25
|
+
method,
|
|
26
|
+
path: url.pathname,
|
|
27
|
+
seed,
|
|
28
|
+
status: header(req, 'x-mock-status') ?? url.searchParams.get('__status') ?? undefined,
|
|
29
|
+
query: url.searchParams,
|
|
30
|
+
origin: req.headers.host ? `http://${req.headers.host}` : undefined,
|
|
31
|
+
records: options.records,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const delay = Number(header(req, 'x-mock-delay') ?? url.searchParams.get('__delay') ?? options.delay ?? 0);
|
|
35
|
+
if (delay > 0)
|
|
36
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
37
|
+
const hasBody = result.body !== undefined && method !== 'HEAD' && result.status !== 204 && result.status !== 304;
|
|
38
|
+
res.writeHead(result.status, {
|
|
39
|
+
...CORS_HEADERS,
|
|
40
|
+
...(hasBody ? { 'content-type': 'application/json; charset=utf-8' } : {}),
|
|
41
|
+
...result.headers,
|
|
42
|
+
});
|
|
43
|
+
res.end(hasBody ? JSON.stringify(result.body, null, 2) : undefined);
|
|
44
|
+
options.log?.(`${method} ${url.pathname}${url.search} → ${result.status} ${Math.round(performance.now() - started)}ms`);
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
server,
|
|
48
|
+
listen(port = 4010, host = '127.0.0.1') {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
server.once('error', reject);
|
|
51
|
+
server.listen(port, host, () => {
|
|
52
|
+
const addr = server.address();
|
|
53
|
+
resolve(`http://${host.includes(':') ? `[${host}]` : host}:${addr.port}`);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
close() {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
server.closeAllConnections();
|
|
60
|
+
server.close((err) => (err ? reject(err) : resolve()));
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
setModel(next) {
|
|
64
|
+
model = next;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Produce the mock response for one request. Pure apart from the generator's seed. */
|
|
69
|
+
export function respond(model, input) {
|
|
70
|
+
const { route, params, pathExists } = findRoute(model, input.method, input.path);
|
|
71
|
+
if (!route) {
|
|
72
|
+
return pathExists
|
|
73
|
+
? errorResult(405, `Method ${input.method} is not defined for ${input.path}`)
|
|
74
|
+
: errorResult(404, `No route in the spec matches ${input.method} ${input.path}`);
|
|
75
|
+
}
|
|
76
|
+
const forced = input.status !== undefined ? Number(input.status) : undefined;
|
|
77
|
+
if (forced !== undefined && !(forced >= 100 && forced <= 599)) {
|
|
78
|
+
return errorResult(400, `X-Mock-Status must be an HTTP status code, got "${input.status}"`);
|
|
79
|
+
}
|
|
80
|
+
const response = pickResponse(route.responses, forced);
|
|
81
|
+
const status = forced ?? response?.status ?? 200;
|
|
82
|
+
if (!response || (forced !== undefined && response.status !== forced && !response.isDefault)) {
|
|
83
|
+
// The spec doesn't describe this status; still honor it so error paths can be tested.
|
|
84
|
+
return status >= 400 ? errorResult(status, http.STATUS_CODES[status] ?? 'Error') : { status, body: undefined, headers: {} };
|
|
85
|
+
}
|
|
86
|
+
let body;
|
|
87
|
+
if (response.hasExample)
|
|
88
|
+
body = structuredClone(response.example);
|
|
89
|
+
else if (response.schema)
|
|
90
|
+
body = generate(response.schema, { seed: input.seed, key: `${route.method} ${input.path} ${status}` });
|
|
91
|
+
let headers = {};
|
|
92
|
+
// List endpoints (not `/users/{id}`) page through a stable collection.
|
|
93
|
+
const shape = route.method === 'GET' && !response.hasExample && status < 300 && !route.path.endsWith('}') ? findListShape(response.schema) : undefined;
|
|
94
|
+
if (shape) {
|
|
95
|
+
const requestPath = input.path.replace(/(.)\/$/, '$1');
|
|
96
|
+
const detailRoute = findDetailRoute(model.routes, route);
|
|
97
|
+
const detailResponse = detailRoute && pickResponse(detailRoute.responses);
|
|
98
|
+
const linked = !!detailResponse?.schema && !detailResponse.hasExample;
|
|
99
|
+
({ body, headers } = buildPage({
|
|
100
|
+
schema: response.schema,
|
|
101
|
+
requestPath,
|
|
102
|
+
query: new URLSearchParams(input.query ?? ''),
|
|
103
|
+
origin: input.origin,
|
|
104
|
+
seed: input.seed,
|
|
105
|
+
shape,
|
|
106
|
+
body,
|
|
107
|
+
total: input.records ?? 50,
|
|
108
|
+
idKey: detailRoute?.paramNames.at(-1),
|
|
109
|
+
detailSchema: linked ? detailResponse.schema : undefined,
|
|
110
|
+
detail: linked
|
|
111
|
+
? (id) => {
|
|
112
|
+
const r = respond(model, { method: 'GET', path: `${requestPath}/${encodeURIComponent(String(id))}`, seed: input.seed });
|
|
113
|
+
return r.status < 300 && isRecord(r.body) ? r.body : undefined;
|
|
114
|
+
}
|
|
115
|
+
: undefined,
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
if (isRecord(body) && response.schema)
|
|
119
|
+
applyPathParams(body, response.schema, route, params);
|
|
120
|
+
return { status, body, headers };
|
|
121
|
+
}
|
|
122
|
+
function findRoute(model, method, rawPath) {
|
|
123
|
+
let p = rawPath;
|
|
124
|
+
if (model.basePath && (p === model.basePath || p.startsWith(`${model.basePath}/`))) {
|
|
125
|
+
p = p.slice(model.basePath.length) || '/';
|
|
126
|
+
}
|
|
127
|
+
let pathExists = false;
|
|
128
|
+
for (const route of model.routes) {
|
|
129
|
+
const m = route.pattern.exec(p);
|
|
130
|
+
if (!m)
|
|
131
|
+
continue;
|
|
132
|
+
pathExists = true;
|
|
133
|
+
if (route.method !== method && !(method === 'HEAD' && route.method === 'GET'))
|
|
134
|
+
continue;
|
|
135
|
+
const params = {};
|
|
136
|
+
route.paramNames.forEach((name, i) => (params[name] = decodeURIComponent(m[i + 1])));
|
|
137
|
+
return { route, params, pathExists };
|
|
138
|
+
}
|
|
139
|
+
return { route: undefined, params: {}, pathExists };
|
|
140
|
+
}
|
|
141
|
+
function pickResponse(responses, forced) {
|
|
142
|
+
if (forced !== undefined) {
|
|
143
|
+
return responses.find((r) => r.status === forced && !r.isDefault) ?? responses.find((r) => r.isDefault);
|
|
144
|
+
}
|
|
145
|
+
const explicit = responses.filter((r) => !r.isDefault).sort((a, b) => a.status - b.status);
|
|
146
|
+
return explicit.find((r) => r.status >= 200 && r.status < 300) ?? responses.find((r) => r.isDefault) ?? explicit[0];
|
|
147
|
+
}
|
|
148
|
+
/** `GET /users/42` returns a user whose `id` is 42 — when 42 is a valid `id` for the schema. */
|
|
149
|
+
function applyPathParams(body, schema, route, params) {
|
|
150
|
+
const props = (schema.allOf ? mergeAllOf(schema) : schema).properties ?? {};
|
|
151
|
+
const last = route.paramNames.at(-1);
|
|
152
|
+
for (const [name, raw] of Object.entries(params)) {
|
|
153
|
+
const key = name in body ? name : name === last && 'id' in body ? 'id' : undefined;
|
|
154
|
+
if (!key)
|
|
155
|
+
continue;
|
|
156
|
+
const value = raw.trim() !== '' && typeof body[key] === 'number' ? Number(raw) : raw;
|
|
157
|
+
if (fits(value, props[key]))
|
|
158
|
+
body[key] = value;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function isRecord(v) {
|
|
162
|
+
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
163
|
+
}
|
|
164
|
+
function errorResult(status, message) {
|
|
165
|
+
return { status, body: { error: http.STATUS_CODES[status] ?? 'Error', message }, headers: {} };
|
|
166
|
+
}
|
|
167
|
+
function header(req, name) {
|
|
168
|
+
const v = req.headers[name];
|
|
169
|
+
return Array.isArray(v) ? v[0] : v;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import http from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { buildPage, findDetailRoute, findListShape } from './collections.js';\nimport { generate } from './generate.js';\nimport type { MockModel, MockResponse, MockRoute, Schema } from './model.js';\nimport { fits, mergeAllOf } from './schema.js';\n\nexport interface MockServerOptions {\n model: MockModel;\n /** Seed for generated data; same seed → same responses. Default 1. */\n seed?: number;\n /** Delay every response by this many milliseconds. Overridden per request by `X-Mock-Delay`. */\n delay?: number;\n /** Size of every list endpoint's collection, paged through with `?page=`/`?limit=`. Default 50. */\n records?: number;\n /** Called once per request with a one-line summary. */\n log?: (line: string) => void;\n}\n\nexport interface MockServer {\n server: http.Server;\n listen(port?: number, host?: string): Promise<string>;\n close(): Promise<void>;\n /** Swap the model in place, e.g. after the spec changed on disk. */\n setModel(model: MockModel): void;\n}\n\nexport interface MockResult {\n status: number;\n body: unknown;\n headers: Record<string, string>;\n}\n\nconst CORS_HEADERS = {\n 'access-control-allow-origin': '*',\n 'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS,HEAD',\n 'access-control-allow-headers': '*',\n 'access-control-expose-headers': '*',\n};\n\nexport function createMockServer(options: MockServerOptions): MockServer {\n let model = options.model;\n const seed = options.seed ?? 1;\n\n const server = http.createServer(async (req, res) => {\n const started = performance.now();\n const url = new URL(req.url ?? '/', 'http://localhost');\n const method = (req.method ?? 'GET').toUpperCase();\n req.resume(); // Request bodies are not used yet; drain them.\n\n let result: MockResult;\n if (method === 'OPTIONS' && !findRoute(model, 'OPTIONS', url.pathname).route) {\n result = { status: 204, body: undefined, headers: {} };\n } else {\n result = respond(model, {\n method,\n path: url.pathname,\n seed,\n status: header(req, 'x-mock-status') ?? url.searchParams.get('__status') ?? undefined,\n query: url.searchParams,\n origin: req.headers.host ? `http://${req.headers.host}` : undefined,\n records: options.records,\n });\n }\n\n const delay = Number(header(req, 'x-mock-delay') ?? url.searchParams.get('__delay') ?? options.delay ?? 0);\n if (delay > 0) await new Promise((r) => setTimeout(r, delay));\n\n const hasBody = result.body !== undefined && method !== 'HEAD' && result.status !== 204 && result.status !== 304;\n res.writeHead(result.status, {\n ...CORS_HEADERS,\n ...(hasBody ? { 'content-type': 'application/json; charset=utf-8' } : {}),\n ...result.headers,\n });\n res.end(hasBody ? JSON.stringify(result.body, null, 2) : undefined);\n\n options.log?.(`${method} ${url.pathname}${url.search} → ${result.status} ${Math.round(performance.now() - started)}ms`);\n });\n\n return {\n server,\n listen(port = 4010, host = '127.0.0.1') {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(port, host, () => {\n const addr = server.address() as AddressInfo;\n resolve(`http://${host.includes(':') ? `[${host}]` : host}:${addr.port}`);\n });\n });\n },\n close() {\n return new Promise((resolve, reject) => {\n server.closeAllConnections();\n server.close((err) => (err ? reject(err) : resolve()));\n });\n },\n setModel(next) {\n model = next;\n },\n };\n}\n\nexport interface RespondInput {\n method: string;\n path: string;\n seed?: number;\n /** Force a status code, e.g. from `X-Mock-Status`. */\n status?: string | number;\n /** Query string; drives pagination on list endpoints. */\n query?: URLSearchParams | string;\n /** `http://host:port`, used to build absolute next/prev links. */\n origin?: string;\n /** Collection size for list endpoints. Default 50. */\n records?: number;\n}\n\n/** Produce the mock response for one request. Pure apart from the generator's seed. */\nexport function respond(model: MockModel, input: RespondInput): MockResult {\n const { route, params, pathExists } = findRoute(model, input.method, input.path);\n if (!route) {\n return pathExists\n ? errorResult(405, `Method ${input.method} is not defined for ${input.path}`)\n : errorResult(404, `No route in the spec matches ${input.method} ${input.path}`);\n }\n\n const forced = input.status !== undefined ? Number(input.status) : undefined;\n if (forced !== undefined && !(forced >= 100 && forced <= 599)) {\n return errorResult(400, `X-Mock-Status must be an HTTP status code, got \"${input.status}\"`);\n }\n\n const response = pickResponse(route.responses, forced);\n const status = forced ?? response?.status ?? 200;\n if (!response || (forced !== undefined && response.status !== forced && !response.isDefault)) {\n // The spec doesn't describe this status; still honor it so error paths can be tested.\n return status >= 400 ? errorResult(status, http.STATUS_CODES[status] ?? 'Error') : { status, body: undefined, headers: {} };\n }\n\n let body: unknown;\n if (response.hasExample) body = structuredClone(response.example);\n else if (response.schema) body = generate(response.schema, { seed: input.seed, key: `${route.method} ${input.path} ${status}` });\n\n let headers: Record<string, string> = {};\n // List endpoints (not `/users/{id}`) page through a stable collection.\n const shape = route.method === 'GET' && !response.hasExample && status < 300 && !route.path.endsWith('}') ? findListShape(response.schema) : undefined;\n if (shape) {\n const requestPath = input.path.replace(/(.)\\/$/, '$1');\n const detailRoute = findDetailRoute(model.routes, route);\n const detailResponse = detailRoute && pickResponse(detailRoute.responses);\n const linked = !!detailResponse?.schema && !detailResponse.hasExample;\n ({ body, headers } = buildPage({\n schema: response.schema!,\n requestPath,\n query: new URLSearchParams(input.query ?? ''),\n origin: input.origin,\n seed: input.seed,\n shape,\n body,\n total: input.records ?? 50,\n idKey: detailRoute?.paramNames.at(-1),\n detailSchema: linked ? detailResponse.schema : undefined,\n detail: linked\n ? (id) => {\n const r = respond(model, { method: 'GET', path: `${requestPath}/${encodeURIComponent(String(id))}`, seed: input.seed });\n return r.status < 300 && isRecord(r.body) ? r.body : undefined;\n }\n : undefined,\n }));\n }\n\n if (isRecord(body) && response.schema) applyPathParams(body, response.schema, route, params);\n return { status, body, headers };\n}\n\nfunction findRoute(model: MockModel, method: string, rawPath: string) {\n let p = rawPath;\n if (model.basePath && (p === model.basePath || p.startsWith(`${model.basePath}/`))) {\n p = p.slice(model.basePath.length) || '/';\n }\n let pathExists = false;\n for (const route of model.routes) {\n const m = route.pattern.exec(p);\n if (!m) continue;\n pathExists = true;\n if (route.method !== method && !(method === 'HEAD' && route.method === 'GET')) continue;\n const params: Record<string, string> = {};\n route.paramNames.forEach((name, i) => (params[name] = decodeURIComponent(m[i + 1]!)));\n return { route, params, pathExists };\n }\n return { route: undefined, params: {}, pathExists };\n}\n\nfunction pickResponse(responses: MockResponse[], forced?: number): MockResponse | undefined {\n if (forced !== undefined) {\n return responses.find((r) => r.status === forced && !r.isDefault) ?? responses.find((r) => r.isDefault);\n }\n const explicit = responses.filter((r) => !r.isDefault).sort((a, b) => a.status - b.status);\n return explicit.find((r) => r.status >= 200 && r.status < 300) ?? responses.find((r) => r.isDefault) ?? explicit[0];\n}\n\n/** `GET /users/42` returns a user whose `id` is 42 — when 42 is a valid `id` for the schema. */\nfunction applyPathParams(body: Record<string, unknown>, schema: Schema, route: MockRoute, params: Record<string, string>) {\n const props: Record<string, Schema> = (schema.allOf ? mergeAllOf(schema) : schema).properties ?? {};\n const last = route.paramNames.at(-1);\n for (const [name, raw] of Object.entries(params)) {\n const key = name in body ? name : name === last && 'id' in body ? 'id' : undefined;\n if (!key) continue;\n const value = raw.trim() !== '' && typeof body[key] === 'number' ? Number(raw) : raw;\n if (fits(value, props[key])) body[key] = value;\n }\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return !!v && typeof v === 'object' && !Array.isArray(v);\n}\n\nfunction errorResult(status: number, message: string): MockResult {\n return { status, body: { error: http.STATUS_CODES[status] ?? 'Error', message }, headers: {} };\n}\n\nfunction header(req: http.IncomingMessage, name: string): string | undefined {\n const v = req.headers[name];\n return Array.isArray(v) ? v[0] : v;\n}\n"]}
|