@schmock/schema 1.0.2 → 1.2.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/dist/index.d.ts.map +1 -1
- package/dist/index.js +59 -59
- package/dist/test-utils.d.ts +5 -7
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/test-utils.js +1 -15
- package/package.json +5 -5
- package/src/data-quality.test.ts +1 -1
- package/src/index.ts +90 -86
- package/src/performance.test.ts +14 -8
- package/src/steps/schema-plugin.steps.ts +2 -2
- package/src/test-utils.ts +14 -26
package/dist/test-utils.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { JSONSchema7 } from "json-schema";
|
|
2
|
+
interface FakerSchema extends JSONSchema7 {
|
|
3
|
+
faker?: string;
|
|
4
|
+
}
|
|
2
5
|
export declare const schemas: {
|
|
3
6
|
simple: {
|
|
4
7
|
string: () => JSONSchema7;
|
|
@@ -9,7 +12,7 @@ export declare const schemas: {
|
|
|
9
12
|
maxItems?: number;
|
|
10
13
|
}) => JSONSchema7;
|
|
11
14
|
};
|
|
12
|
-
withFaker: (type: JSONSchema7["type"], fakerMethod: string) =>
|
|
15
|
+
withFaker: (type: JSONSchema7["type"], fakerMethod: string) => FakerSchema;
|
|
13
16
|
nested: {
|
|
14
17
|
deep: (depth: number, leafSchema?: JSONSchema7) => JSONSchema7;
|
|
15
18
|
wide: (width: number, propertySchema?: JSONSchema7) => JSONSchema7;
|
|
@@ -50,10 +53,5 @@ export declare const schemaTests: {
|
|
|
50
53
|
expectInvalid: (schema: any, errorMessage?: string | RegExp) => void;
|
|
51
54
|
expectSchemaError: (schema: any, path: string, issue?: string) => void;
|
|
52
55
|
};
|
|
53
|
-
export
|
|
54
|
-
trackFakerCalls: () => {
|
|
55
|
-
calls: string[];
|
|
56
|
-
reset: () => void;
|
|
57
|
-
};
|
|
58
|
-
};
|
|
56
|
+
export {};
|
|
59
57
|
//# sourceMappingURL=test-utils.d.ts.map
|
package/dist/test-utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,UAAU,WAAY,SAAQ,WAAW;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAGD,eAAO,MAAM,OAAO;;sBAEJ,WAAW;sBAIX,WAAW;8BAIF,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,KAAQ,WAAW;uBAM1D,WAAW,gBACJ;YAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,KACrD,WAAW;;sBAOE,WAAW,CAAC,MAAM,CAAC,eAAe,MAAM,KAAG,WAAW;;sBAO7D,MAAM,eACD,WAAW,KACtB,WAAW;sBAWL,MAAM,mBACG,WAAW,KAC1B,WAAW;;;oBASJ,WAAW;2BAYJ,WAAW;;CAkB/B,CAAC;AAGF,eAAO,MAAM,UAAU;+BAGR,MAAM,cACN,WAAW,CAAC,MAAM,CAAC,KAC7B,OAAO,CAAC,OAAO,CAAC;+BAoCQ,GAAG,EAAE,KAAG,MAAM;wBAMrB,GAAG,EAAE,aAAa,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,KAAG,OAAO;uCAM7D,MAAM,EAAE,YACP,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,KACjE,OAAO;CAgDX,CAAC;AAGF,eAAO,MAAM,WAAW;cACN,CAAC,MACX,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,KACvB,OAAO,CAAC;QAAE,MAAM,EAAE,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;wBAOvB,MAAM,IAAI,KAAG,MAAM;uBAW9B,MAAM,MACT,MAAM,GAAG,0BAEZ,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;CAgBvD,CAAC;AAGF,eAAO,MAAM,QAAQ;cACT,CAAC,UAAU,WAAW,4BAAwB,GAAG,KAAG,CAAC,EAAE;uBAM9C,WAAW,UAAU,MAAM,KAAG,GAAG;CAKrD,CAAC;AAGF,eAAO,MAAM,KAAK;4BACQ,GAAG,EAAE,KAAG,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC;uBAS7B,GAAG,EAAE,KAAG,MAAM;CAclC,CAAC;AAGF,eAAO,MAAM,WAAW;0BACA,WAAW,KAAG,IAAI;4BAIhB,GAAG,iBAAiB,MAAM,GAAG,MAAM,KAAG,IAAI;gCAQtC,GAAG,QAAQ,MAAM,UAAU,MAAM,KAAG,IAAI;CAerE,CAAC"}
|
package/dist/test-utils.js
CHANGED
|
@@ -20,7 +20,7 @@ export const schemas = {
|
|
|
20
20
|
}),
|
|
21
21
|
},
|
|
22
22
|
withFaker: (type, fakerMethod) => ({
|
|
23
|
-
type
|
|
23
|
+
type,
|
|
24
24
|
faker: fakerMethod,
|
|
25
25
|
}),
|
|
26
26
|
nested: {
|
|
@@ -253,17 +253,3 @@ function analyzeDataCharacteristics(samples) {
|
|
|
253
253
|
characteristics.push("low-entropy");
|
|
254
254
|
return characteristics.join("-");
|
|
255
255
|
}
|
|
256
|
-
// Mock/Spy utilities for testing faker integration
|
|
257
|
-
export const mocks = {
|
|
258
|
-
trackFakerCalls: () => {
|
|
259
|
-
const calls = [];
|
|
260
|
-
// This would need actual implementation with faker.js internals
|
|
261
|
-
// For now, it's a placeholder for the concept
|
|
262
|
-
return {
|
|
263
|
-
calls,
|
|
264
|
-
reset: () => {
|
|
265
|
-
calls.length = 0;
|
|
266
|
-
},
|
|
267
|
-
};
|
|
268
|
-
},
|
|
269
|
-
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schmock/schema",
|
|
3
3
|
"description": "JSON Schema-based automatic data generation for Schmock",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"
|
|
34
|
-
"
|
|
33
|
+
"@faker-js/faker": "^10.3.0",
|
|
34
|
+
"json-schema-faker": "^0.5.6"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@schmock/core": "^1.0.0"
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@amiceli/vitest-cucumber": "^6.2.0",
|
|
41
41
|
"@types/json-schema": "^7.0.15",
|
|
42
|
-
"@types/node": "^25.1
|
|
43
|
-
"vitest": "^4.0.
|
|
42
|
+
"@types/node": "^25.2.1",
|
|
43
|
+
"vitest": "^4.0.18"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/src/data-quality.test.ts
CHANGED
|
@@ -273,7 +273,7 @@ describe("Data Quality and Statistical Properties", () => {
|
|
|
273
273
|
// Last names should be single words, may have special chars
|
|
274
274
|
expect(sample.lastName).toMatch(/^[A-Z]/);
|
|
275
275
|
expect(sample.lastName.length).toBeGreaterThanOrEqual(2);
|
|
276
|
-
expect(sample.lastName.length).
|
|
276
|
+
expect(sample.lastName.length).toBeLessThanOrEqual(30); // Generous for longer generated names
|
|
277
277
|
|
|
278
278
|
// Full names should have multiple parts
|
|
279
279
|
const nameParts = sample.fullName.split(" ");
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,15 @@ import {
|
|
|
8
8
|
import type { JSONSchema7 } from "json-schema";
|
|
9
9
|
import jsf from "json-schema-faker";
|
|
10
10
|
|
|
11
|
+
function isJSONSchema7(value: unknown): value is JSONSchema7 {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** JSONSchema7 extended with json-schema-faker's `faker` property */
|
|
16
|
+
interface FakerSchema extends JSONSchema7 {
|
|
17
|
+
faker?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
/**
|
|
12
21
|
* Create isolated faker instance to avoid race conditions
|
|
13
22
|
* Each generation gets its own faker instance to ensure thread-safety
|
|
@@ -122,22 +131,22 @@ export function generateFromSchema(options: SchemaGenerationContext): any {
|
|
|
122
131
|
throw new ResourceLimitError("array_size", MAX_ARRAY_SIZE, itemCount);
|
|
123
132
|
}
|
|
124
133
|
|
|
125
|
-
const
|
|
134
|
+
const rawItemSchema = Array.isArray(schema.items)
|
|
126
135
|
? schema.items[0]
|
|
127
136
|
: schema.items;
|
|
128
137
|
|
|
129
|
-
if (!
|
|
138
|
+
if (!rawItemSchema || typeof rawItemSchema === "boolean") {
|
|
130
139
|
throw new SchemaValidationError(
|
|
131
140
|
"$.items",
|
|
132
141
|
"Array schema must have valid items definition",
|
|
133
142
|
);
|
|
134
143
|
}
|
|
135
144
|
|
|
145
|
+
const itemSchema = rawItemSchema;
|
|
146
|
+
|
|
136
147
|
generated = [];
|
|
137
148
|
for (let i = 0; i < itemCount; i++) {
|
|
138
|
-
let item = getJsf().generate(
|
|
139
|
-
enhanceSchemaWithSmartMapping(itemSchema as JSONSchema7),
|
|
140
|
-
);
|
|
149
|
+
let item = getJsf().generate(enhanceSchemaWithSmartMapping(itemSchema));
|
|
141
150
|
item = applyOverrides(item, overrides, params, state, query);
|
|
142
151
|
generated.push(item);
|
|
143
152
|
}
|
|
@@ -173,17 +182,19 @@ function validateSchema(schema: JSONSchema7, path = "$"): void {
|
|
|
173
182
|
}
|
|
174
183
|
|
|
175
184
|
// Check for invalid schema types
|
|
185
|
+
const validTypes = [
|
|
186
|
+
"object",
|
|
187
|
+
"array",
|
|
188
|
+
"string",
|
|
189
|
+
"number",
|
|
190
|
+
"integer",
|
|
191
|
+
"boolean",
|
|
192
|
+
"null",
|
|
193
|
+
];
|
|
176
194
|
if (
|
|
177
195
|
schema.type &&
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
"array",
|
|
181
|
-
"string",
|
|
182
|
-
"number",
|
|
183
|
-
"integer",
|
|
184
|
-
"boolean",
|
|
185
|
-
"null",
|
|
186
|
-
].includes(schema.type as string)
|
|
196
|
+
typeof schema.type === "string" &&
|
|
197
|
+
!validTypes.includes(schema.type)
|
|
187
198
|
) {
|
|
188
199
|
throw new SchemaValidationError(
|
|
189
200
|
path,
|
|
@@ -209,28 +220,34 @@ function validateSchema(schema: JSONSchema7, path = "$"): void {
|
|
|
209
220
|
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
210
221
|
if (typeof propSchema === "object" && propSchema !== null) {
|
|
211
222
|
// Check for invalid faker methods in property schemas
|
|
212
|
-
|
|
223
|
+
const fakerProp =
|
|
224
|
+
"faker" in propSchema ? String(propSchema.faker) : undefined;
|
|
225
|
+
if (fakerProp) {
|
|
213
226
|
try {
|
|
214
|
-
validateFakerMethod(
|
|
227
|
+
validateFakerMethod(fakerProp);
|
|
215
228
|
} catch (error: unknown) {
|
|
216
229
|
// Re-throw with proper path context
|
|
217
230
|
if (error instanceof SchemaValidationError) {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
231
|
+
const ctx = error.context;
|
|
232
|
+
let issue = "Invalid faker method";
|
|
233
|
+
let suggestion: string | undefined;
|
|
234
|
+
if (ctx && typeof ctx === "object") {
|
|
235
|
+
if ("issue" in ctx && typeof ctx.issue === "string")
|
|
236
|
+
issue = ctx.issue;
|
|
237
|
+
if ("suggestion" in ctx && typeof ctx.suggestion === "string")
|
|
238
|
+
suggestion = ctx.suggestion;
|
|
239
|
+
}
|
|
221
240
|
throw new SchemaValidationError(
|
|
222
241
|
`${path}.properties.${propName}.faker`,
|
|
223
|
-
|
|
224
|
-
|
|
242
|
+
issue,
|
|
243
|
+
suggestion,
|
|
225
244
|
);
|
|
226
245
|
}
|
|
227
|
-
|
|
246
|
+
if (error instanceof Error) throw error;
|
|
247
|
+
throw new Error(String(error));
|
|
228
248
|
}
|
|
229
249
|
}
|
|
230
|
-
validateSchema(
|
|
231
|
-
propSchema as JSONSchema7,
|
|
232
|
-
`${path}.properties.${propName}`,
|
|
233
|
-
);
|
|
250
|
+
validateSchema(propSchema, `${path}.properties.${propName}`);
|
|
234
251
|
}
|
|
235
252
|
}
|
|
236
253
|
}
|
|
@@ -256,11 +273,11 @@ function validateSchema(schema: JSONSchema7, path = "$"): void {
|
|
|
256
273
|
}
|
|
257
274
|
schema.items.forEach((item, index) => {
|
|
258
275
|
if (typeof item === "object" && item !== null) {
|
|
259
|
-
validateSchema(item
|
|
276
|
+
validateSchema(item, `${path}.items[${index}]`);
|
|
260
277
|
}
|
|
261
278
|
});
|
|
262
279
|
} else if (typeof schema.items === "object" && schema.items !== null) {
|
|
263
|
-
validateSchema(schema.items
|
|
280
|
+
validateSchema(schema.items, `${path}.items`);
|
|
264
281
|
}
|
|
265
282
|
}
|
|
266
283
|
|
|
@@ -327,8 +344,8 @@ function hasCircularReference(
|
|
|
327
344
|
|
|
328
345
|
if (schema.type === "object" && schema.properties) {
|
|
329
346
|
for (const prop of Object.values(schema.properties)) {
|
|
330
|
-
if (
|
|
331
|
-
if (hasCircularReference(prop
|
|
347
|
+
if (isJSONSchema7(prop)) {
|
|
348
|
+
if (hasCircularReference(prop, currentPath)) {
|
|
332
349
|
return true;
|
|
333
350
|
}
|
|
334
351
|
}
|
|
@@ -338,8 +355,8 @@ function hasCircularReference(
|
|
|
338
355
|
if (schema.type === "array" && schema.items) {
|
|
339
356
|
const items = Array.isArray(schema.items) ? schema.items : [schema.items];
|
|
340
357
|
for (const item of items) {
|
|
341
|
-
if (
|
|
342
|
-
if (hasCircularReference(item
|
|
358
|
+
if (isJSONSchema7(item)) {
|
|
359
|
+
if (hasCircularReference(item, currentPath)) {
|
|
343
360
|
return true;
|
|
344
361
|
}
|
|
345
362
|
}
|
|
@@ -368,11 +385,8 @@ function calculateNestingDepth(schema: JSONSchema7, depth = 0): number {
|
|
|
368
385
|
|
|
369
386
|
if (schema.type === "object" && schema.properties) {
|
|
370
387
|
for (const prop of Object.values(schema.properties)) {
|
|
371
|
-
if (
|
|
372
|
-
maxDepth = Math.max(
|
|
373
|
-
maxDepth,
|
|
374
|
-
calculateNestingDepth(prop as JSONSchema7, depth + 1),
|
|
375
|
-
);
|
|
388
|
+
if (isJSONSchema7(prop)) {
|
|
389
|
+
maxDepth = Math.max(maxDepth, calculateNestingDepth(prop, depth + 1));
|
|
376
390
|
}
|
|
377
391
|
}
|
|
378
392
|
}
|
|
@@ -380,11 +394,8 @@ function calculateNestingDepth(schema: JSONSchema7, depth = 0): number {
|
|
|
380
394
|
if (schema.type === "array" && schema.items) {
|
|
381
395
|
const items = Array.isArray(schema.items) ? schema.items : [schema.items];
|
|
382
396
|
for (const item of items) {
|
|
383
|
-
if (
|
|
384
|
-
maxDepth = Math.max(
|
|
385
|
-
maxDepth,
|
|
386
|
-
calculateNestingDepth(item as JSONSchema7, depth + 1),
|
|
387
|
-
);
|
|
397
|
+
if (isJSONSchema7(item)) {
|
|
398
|
+
maxDepth = Math.max(maxDepth, calculateNestingDepth(item, depth + 1));
|
|
388
399
|
}
|
|
389
400
|
}
|
|
390
401
|
}
|
|
@@ -405,16 +416,16 @@ function checkForDeepNestingWithArrays(
|
|
|
405
416
|
): void {
|
|
406
417
|
// Look for arrays in deeply nested structures that could cause memory issues
|
|
407
418
|
function findArraysInDeepNesting(
|
|
408
|
-
|
|
419
|
+
node: JSONSchema7,
|
|
409
420
|
currentDepth: number,
|
|
410
421
|
): boolean {
|
|
411
|
-
const schemaType =
|
|
422
|
+
const schemaType = node.type;
|
|
412
423
|
const isArray = Array.isArray(schemaType)
|
|
413
424
|
? schemaType.includes("array")
|
|
414
425
|
: schemaType === "array";
|
|
415
426
|
|
|
416
427
|
if (isArray) {
|
|
417
|
-
const maxItems =
|
|
428
|
+
const maxItems = node.maxItems || DEFAULT_ARRAY_COUNT;
|
|
418
429
|
// Be more aggressive about deep nesting detection
|
|
419
430
|
if (
|
|
420
431
|
currentDepth >= DEEP_NESTING_THRESHOLD &&
|
|
@@ -428,15 +439,11 @@ function checkForDeepNestingWithArrays(
|
|
|
428
439
|
}
|
|
429
440
|
|
|
430
441
|
// Check items if they exist
|
|
431
|
-
if (
|
|
432
|
-
const items = Array.isArray(
|
|
433
|
-
? schema.items
|
|
434
|
-
: [schema.items];
|
|
442
|
+
if (node.items) {
|
|
443
|
+
const items = Array.isArray(node.items) ? node.items : [node.items];
|
|
435
444
|
for (const item of items) {
|
|
436
|
-
if (
|
|
437
|
-
if (
|
|
438
|
-
findArraysInDeepNesting(item as JSONSchema7, currentDepth + 1)
|
|
439
|
-
) {
|
|
445
|
+
if (isJSONSchema7(item)) {
|
|
446
|
+
if (findArraysInDeepNesting(item, currentDepth + 1)) {
|
|
440
447
|
return true;
|
|
441
448
|
}
|
|
442
449
|
}
|
|
@@ -446,10 +453,10 @@ function checkForDeepNestingWithArrays(
|
|
|
446
453
|
return true;
|
|
447
454
|
}
|
|
448
455
|
|
|
449
|
-
if (schemaType === "object" &&
|
|
450
|
-
for (const prop of Object.values(
|
|
451
|
-
if (
|
|
452
|
-
if (findArraysInDeepNesting(prop
|
|
456
|
+
if (schemaType === "object" && node.properties) {
|
|
457
|
+
for (const prop of Object.values(node.properties)) {
|
|
458
|
+
if (isJSONSchema7(prop)) {
|
|
459
|
+
if (findArraysInDeepNesting(prop, currentDepth + 1)) {
|
|
453
460
|
return true;
|
|
454
461
|
}
|
|
455
462
|
}
|
|
@@ -495,11 +502,8 @@ function checkArraySizeLimits(schema: JSONSchema7, path: string): void {
|
|
|
495
502
|
// Recursively check nested schemas
|
|
496
503
|
if (schema.type === "object" && schema.properties) {
|
|
497
504
|
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
498
|
-
if (
|
|
499
|
-
checkArraySizeLimits(
|
|
500
|
-
propSchema as JSONSchema7,
|
|
501
|
-
`${path}.properties.${propName}`,
|
|
502
|
-
);
|
|
505
|
+
if (isJSONSchema7(propSchema)) {
|
|
506
|
+
checkArraySizeLimits(propSchema, `${path}.properties.${propName}`);
|
|
503
507
|
}
|
|
504
508
|
}
|
|
505
509
|
}
|
|
@@ -507,12 +511,12 @@ function checkArraySizeLimits(schema: JSONSchema7, path: string): void {
|
|
|
507
511
|
if (schema.type === "array" && schema.items) {
|
|
508
512
|
if (Array.isArray(schema.items)) {
|
|
509
513
|
schema.items.forEach((item, index) => {
|
|
510
|
-
if (
|
|
511
|
-
checkArraySizeLimits(item
|
|
514
|
+
if (isJSONSchema7(item)) {
|
|
515
|
+
checkArraySizeLimits(item, `${path}.items[${index}]`);
|
|
512
516
|
}
|
|
513
517
|
});
|
|
514
|
-
} else if (
|
|
515
|
-
checkArraySizeLimits(schema.items
|
|
518
|
+
} else if (isJSONSchema7(schema.items)) {
|
|
519
|
+
checkArraySizeLimits(schema.items, `${path}.items`);
|
|
516
520
|
}
|
|
517
521
|
}
|
|
518
522
|
}
|
|
@@ -573,7 +577,7 @@ function applyOverrides(
|
|
|
573
577
|
): any {
|
|
574
578
|
if (!overrides) return data;
|
|
575
579
|
|
|
576
|
-
const result =
|
|
580
|
+
const result = structuredClone(data);
|
|
577
581
|
|
|
578
582
|
for (const [key, value] of Object.entries(overrides)) {
|
|
579
583
|
// Handle nested paths like "data.id" or "pagination.page"
|
|
@@ -748,10 +752,10 @@ function enhanceSchemaWithSmartMapping(schema: JSONSchema7): JSONSchema7 {
|
|
|
748
752
|
for (const [fieldName, fieldSchema] of Object.entries(
|
|
749
753
|
enhanced.properties,
|
|
750
754
|
)) {
|
|
751
|
-
if (
|
|
755
|
+
if (isJSONSchema7(fieldSchema)) {
|
|
752
756
|
enhanced.properties[fieldName] = enhanceFieldSchema(
|
|
753
757
|
fieldName,
|
|
754
|
-
fieldSchema
|
|
758
|
+
fieldSchema,
|
|
755
759
|
);
|
|
756
760
|
}
|
|
757
761
|
}
|
|
@@ -763,12 +767,12 @@ function enhanceSchemaWithSmartMapping(schema: JSONSchema7): JSONSchema7 {
|
|
|
763
767
|
function enhanceFieldSchema(
|
|
764
768
|
fieldName: string,
|
|
765
769
|
fieldSchema: JSONSchema7,
|
|
766
|
-
):
|
|
767
|
-
const enhanced = { ...fieldSchema };
|
|
770
|
+
): FakerSchema {
|
|
771
|
+
const enhanced: FakerSchema = { ...fieldSchema };
|
|
768
772
|
|
|
769
773
|
// If already has faker extension, validate it and don't override
|
|
770
|
-
if (
|
|
771
|
-
validateFakerMethod(
|
|
774
|
+
if (enhanced.faker) {
|
|
775
|
+
validateFakerMethod(enhanced.faker);
|
|
772
776
|
return enhanced;
|
|
773
777
|
}
|
|
774
778
|
|
|
@@ -778,34 +782,34 @@ function enhanceFieldSchema(
|
|
|
778
782
|
// Email fields
|
|
779
783
|
if (lowerFieldName.includes("email")) {
|
|
780
784
|
enhanced.format = "email";
|
|
781
|
-
|
|
785
|
+
enhanced.faker = "internet.email";
|
|
782
786
|
}
|
|
783
787
|
// Name fields
|
|
784
788
|
else if (lowerFieldName === "firstname" || lowerFieldName === "first_name") {
|
|
785
|
-
|
|
789
|
+
enhanced.faker = "person.firstName";
|
|
786
790
|
} else if (lowerFieldName === "lastname" || lowerFieldName === "last_name") {
|
|
787
|
-
|
|
791
|
+
enhanced.faker = "person.lastName";
|
|
788
792
|
} else if (lowerFieldName === "name" || lowerFieldName === "fullname") {
|
|
789
|
-
|
|
793
|
+
enhanced.faker = "person.fullName";
|
|
790
794
|
}
|
|
791
795
|
// Phone fields
|
|
792
796
|
else if (lowerFieldName.includes("phone") || lowerFieldName === "mobile") {
|
|
793
|
-
|
|
797
|
+
enhanced.faker = "phone.number";
|
|
794
798
|
}
|
|
795
799
|
// Address fields
|
|
796
800
|
else if (lowerFieldName === "street" || lowerFieldName === "address") {
|
|
797
|
-
|
|
801
|
+
enhanced.faker = "location.streetAddress";
|
|
798
802
|
} else if (lowerFieldName === "city") {
|
|
799
|
-
|
|
803
|
+
enhanced.faker = "location.city";
|
|
800
804
|
} else if (lowerFieldName === "zipcode" || lowerFieldName === "zip") {
|
|
801
|
-
|
|
805
|
+
enhanced.faker = "location.zipCode";
|
|
802
806
|
}
|
|
803
807
|
// UUID fields
|
|
804
808
|
else if (
|
|
805
809
|
lowerFieldName === "uuid" ||
|
|
806
810
|
(lowerFieldName === "id" && enhanced.format === "uuid")
|
|
807
811
|
) {
|
|
808
|
-
|
|
812
|
+
enhanced.faker = "string.uuid";
|
|
809
813
|
}
|
|
810
814
|
// Date fields
|
|
811
815
|
else if (
|
|
@@ -815,17 +819,17 @@ function enhanceFieldSchema(
|
|
|
815
819
|
lowerFieldName.includes("updated_at")
|
|
816
820
|
) {
|
|
817
821
|
enhanced.format = "date-time";
|
|
818
|
-
|
|
822
|
+
enhanced.faker = "date.recent";
|
|
819
823
|
}
|
|
820
824
|
// Company fields
|
|
821
825
|
else if (lowerFieldName.includes("company")) {
|
|
822
|
-
|
|
826
|
+
enhanced.faker = "company.name";
|
|
823
827
|
} else if (lowerFieldName === "position" || lowerFieldName === "jobtitle") {
|
|
824
|
-
|
|
828
|
+
enhanced.faker = "person.jobTitle";
|
|
825
829
|
}
|
|
826
830
|
// Price/money fields
|
|
827
831
|
else if (lowerFieldName === "price" || lowerFieldName === "amount") {
|
|
828
|
-
|
|
832
|
+
enhanced.faker = "commerce.price";
|
|
829
833
|
}
|
|
830
834
|
|
|
831
835
|
return enhanced;
|
package/src/performance.test.ts
CHANGED
|
@@ -152,11 +152,11 @@ describe("Performance and Memory", () => {
|
|
|
152
152
|
generateFromSchema({ schema: wideSchema });
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
// Measure with multiple runs
|
|
155
|
+
// Measure with multiple runs, dropping outliers
|
|
156
156
|
const narrowTimes: number[] = [];
|
|
157
157
|
const wideTimes: number[] = [];
|
|
158
158
|
|
|
159
|
-
for (let i = 0; i <
|
|
159
|
+
for (let i = 0; i < 10; i++) {
|
|
160
160
|
const start1 = performance.now();
|
|
161
161
|
generateFromSchema({ schema: narrowSchema });
|
|
162
162
|
narrowTimes.push(performance.now() - start1);
|
|
@@ -166,13 +166,19 @@ describe("Performance and Memory", () => {
|
|
|
166
166
|
wideTimes.push(performance.now() - start2);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
// Use median instead of average to reduce sensitivity to GC pauses
|
|
170
|
+
const median = (arr: number[]) => {
|
|
171
|
+
const sorted = [...arr].sort((a, b) => a - b);
|
|
172
|
+
const mid = Math.floor(sorted.length / 2);
|
|
173
|
+
return sorted.length % 2
|
|
174
|
+
? sorted[mid]
|
|
175
|
+
: (sorted[mid - 1] + sorted[mid]) / 2;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const medWide = median(wideTimes);
|
|
172
179
|
|
|
173
|
-
//
|
|
174
|
-
expect(
|
|
175
|
-
expect(avgWide).toBeLessThan(avgNarrow * 15); // Linear-ish scaling, not exponential
|
|
180
|
+
// 100-property object should still generate quickly (under 50ms)
|
|
181
|
+
expect(medWide).toBeLessThan(50);
|
|
176
182
|
});
|
|
177
183
|
});
|
|
178
184
|
|
|
@@ -131,7 +131,7 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
131
131
|
|
|
132
132
|
Then("it should throw a SchemaValidationError", () => {
|
|
133
133
|
expect(error).not.toBeNull();
|
|
134
|
-
expect(error!.
|
|
134
|
+
expect(error!.name).toBe("SchemaValidationError");
|
|
135
135
|
});
|
|
136
136
|
});
|
|
137
137
|
|
|
@@ -153,7 +153,7 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
153
153
|
|
|
154
154
|
Then("it should throw a SchemaValidationError with message {string}", (_, message: string) => {
|
|
155
155
|
expect(error).not.toBeNull();
|
|
156
|
-
expect(error!.
|
|
156
|
+
expect(error!.name).toBe("SchemaValidationError");
|
|
157
157
|
expect(error!.message).toContain(message);
|
|
158
158
|
});
|
|
159
159
|
});
|
package/src/test-utils.ts
CHANGED
|
@@ -2,6 +2,10 @@ import type { JSONSchema7 } from "json-schema";
|
|
|
2
2
|
import { expect } from "vitest";
|
|
3
3
|
import { generateFromSchema } from "./index";
|
|
4
4
|
|
|
5
|
+
interface FakerSchema extends JSONSchema7 {
|
|
6
|
+
faker?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
// Schema Factory Functions
|
|
6
10
|
export const schemas = {
|
|
7
11
|
simple: {
|
|
@@ -28,11 +32,10 @@ export const schemas = {
|
|
|
28
32
|
}),
|
|
29
33
|
},
|
|
30
34
|
|
|
31
|
-
withFaker: (type: JSONSchema7["type"], fakerMethod: string):
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}) as any,
|
|
35
|
+
withFaker: (type: JSONSchema7["type"], fakerMethod: string): FakerSchema => ({
|
|
36
|
+
type,
|
|
37
|
+
faker: fakerMethod,
|
|
38
|
+
}),
|
|
36
39
|
|
|
37
40
|
nested: {
|
|
38
41
|
deep: (
|
|
@@ -99,17 +102,17 @@ export const validators = {
|
|
|
99
102
|
fieldName: string,
|
|
100
103
|
fieldType: JSONSchema7["type"] = "string",
|
|
101
104
|
): Promise<boolean> => {
|
|
102
|
-
const mappedSchema = {
|
|
103
|
-
type: "object"
|
|
105
|
+
const mappedSchema: JSONSchema7 = {
|
|
106
|
+
type: "object",
|
|
104
107
|
properties: {
|
|
105
|
-
[fieldName]: { type: fieldType
|
|
108
|
+
[fieldName]: { type: fieldType },
|
|
106
109
|
},
|
|
107
110
|
};
|
|
108
111
|
|
|
109
|
-
const unmappedSchema = {
|
|
110
|
-
type: "object"
|
|
112
|
+
const unmappedSchema: JSONSchema7 = {
|
|
113
|
+
type: "object",
|
|
111
114
|
properties: {
|
|
112
|
-
unmappedRandomField12345: { type: fieldType
|
|
115
|
+
unmappedRandomField12345: { type: fieldType },
|
|
113
116
|
},
|
|
114
117
|
};
|
|
115
118
|
|
|
@@ -340,18 +343,3 @@ function analyzeDataCharacteristics(samples: any[]): string {
|
|
|
340
343
|
|
|
341
344
|
return characteristics.join("-");
|
|
342
345
|
}
|
|
343
|
-
|
|
344
|
-
// Mock/Spy utilities for testing faker integration
|
|
345
|
-
export const mocks = {
|
|
346
|
-
trackFakerCalls: () => {
|
|
347
|
-
const calls: string[] = [];
|
|
348
|
-
// This would need actual implementation with faker.js internals
|
|
349
|
-
// For now, it's a placeholder for the concept
|
|
350
|
-
return {
|
|
351
|
-
calls,
|
|
352
|
-
reset: () => {
|
|
353
|
-
calls.length = 0;
|
|
354
|
-
},
|
|
355
|
-
};
|
|
356
|
-
},
|
|
357
|
-
};
|