@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.
@@ -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) => JSONSchema7;
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 declare const mocks: {
54
- trackFakerCalls: () => {
55
- calls: string[];
56
- reset: () => void;
57
- };
58
- };
56
+ export {};
59
57
  //# sourceMappingURL=test-utils.d.ts.map
@@ -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;AAK/C,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;;sBAQ7D,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;AA8BF,eAAO,MAAM,KAAK;;;;;CAYjB,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"}
@@ -20,7 +20,7 @@ export const schemas = {
20
20
  }),
21
21
  },
22
22
  withFaker: (type, fakerMethod) => ({
23
- type: 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.0.2",
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
- "json-schema-faker": "^0.5.6",
34
- "@faker-js/faker": "^10.2.0"
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.0",
43
- "vitest": "^4.0.15"
42
+ "@types/node": "^25.2.1",
43
+ "vitest": "^4.0.18"
44
44
  }
45
45
  }
@@ -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).toBeLessThan(25); // More generous for longer names
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 itemSchema = Array.isArray(schema.items)
134
+ const rawItemSchema = Array.isArray(schema.items)
126
135
  ? schema.items[0]
127
136
  : schema.items;
128
137
 
129
- if (!itemSchema) {
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
- "object",
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
- if ((propSchema as any).faker) {
223
+ const fakerProp =
224
+ "faker" in propSchema ? String(propSchema.faker) : undefined;
225
+ if (fakerProp) {
213
226
  try {
214
- validateFakerMethod((propSchema as any).faker);
227
+ validateFakerMethod(fakerProp);
215
228
  } catch (error: unknown) {
216
229
  // Re-throw with proper path context
217
230
  if (error instanceof SchemaValidationError) {
218
- const context = error.context as
219
- | { issue?: string; suggestion?: string }
220
- | undefined;
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
- context?.issue || "Invalid faker method",
224
- context?.suggestion,
242
+ issue,
243
+ suggestion,
225
244
  );
226
245
  }
227
- throw error as Error;
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 as JSONSchema7, `${path}.items[${index}]`);
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 as JSONSchema7, `${path}.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 (typeof prop === "object" && prop !== null) {
331
- if (hasCircularReference(prop as JSONSchema7, currentPath)) {
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 (typeof item === "object" && item !== null) {
342
- if (hasCircularReference(item as JSONSchema7, currentPath)) {
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 (typeof prop === "object" && prop !== null) {
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 (typeof item === "object" && item !== null) {
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
- schema: JSONSchema7,
419
+ node: JSONSchema7,
409
420
  currentDepth: number,
410
421
  ): boolean {
411
- const schemaType = schema.type;
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 = schema.maxItems || DEFAULT_ARRAY_COUNT;
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 (schema.items) {
432
- const items = Array.isArray(schema.items)
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 (typeof item === "object" && item !== null) {
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" && schema.properties) {
450
- for (const prop of Object.values(schema.properties)) {
451
- if (typeof prop === "object" && prop !== null) {
452
- if (findArraysInDeepNesting(prop as JSONSchema7, currentDepth + 1)) {
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 (typeof propSchema === "object" && propSchema !== null) {
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 (typeof item === "object" && item !== null) {
511
- checkArraySizeLimits(item as JSONSchema7, `${path}.items[${index}]`);
514
+ if (isJSONSchema7(item)) {
515
+ checkArraySizeLimits(item, `${path}.items[${index}]`);
512
516
  }
513
517
  });
514
- } else if (typeof schema.items === "object" && schema.items !== null) {
515
- checkArraySizeLimits(schema.items as JSONSchema7, `${path}.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 = JSON.parse(JSON.stringify(data)); // Deep clone
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 (typeof fieldSchema === "object" && fieldSchema !== null) {
755
+ if (isJSONSchema7(fieldSchema)) {
752
756
  enhanced.properties[fieldName] = enhanceFieldSchema(
753
757
  fieldName,
754
- fieldSchema as JSONSchema7,
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
- ): JSONSchema7 {
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 ((enhanced as any).faker) {
771
- validateFakerMethod((enhanced as any).faker);
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
- (enhanced as any).faker = "internet.email";
785
+ enhanced.faker = "internet.email";
782
786
  }
783
787
  // Name fields
784
788
  else if (lowerFieldName === "firstname" || lowerFieldName === "first_name") {
785
- (enhanced as any).faker = "person.firstName";
789
+ enhanced.faker = "person.firstName";
786
790
  } else if (lowerFieldName === "lastname" || lowerFieldName === "last_name") {
787
- (enhanced as any).faker = "person.lastName";
791
+ enhanced.faker = "person.lastName";
788
792
  } else if (lowerFieldName === "name" || lowerFieldName === "fullname") {
789
- (enhanced as any).faker = "person.fullName";
793
+ enhanced.faker = "person.fullName";
790
794
  }
791
795
  // Phone fields
792
796
  else if (lowerFieldName.includes("phone") || lowerFieldName === "mobile") {
793
- (enhanced as any).faker = "phone.number";
797
+ enhanced.faker = "phone.number";
794
798
  }
795
799
  // Address fields
796
800
  else if (lowerFieldName === "street" || lowerFieldName === "address") {
797
- (enhanced as any).faker = "location.streetAddress";
801
+ enhanced.faker = "location.streetAddress";
798
802
  } else if (lowerFieldName === "city") {
799
- (enhanced as any).faker = "location.city";
803
+ enhanced.faker = "location.city";
800
804
  } else if (lowerFieldName === "zipcode" || lowerFieldName === "zip") {
801
- (enhanced as any).faker = "location.zipCode";
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
- (enhanced as any).faker = "string.uuid";
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
- (enhanced as any).faker = "date.recent";
822
+ enhanced.faker = "date.recent";
819
823
  }
820
824
  // Company fields
821
825
  else if (lowerFieldName.includes("company")) {
822
- (enhanced as any).faker = "company.name";
826
+ enhanced.faker = "company.name";
823
827
  } else if (lowerFieldName === "position" || lowerFieldName === "jobtitle") {
824
- (enhanced as any).faker = "person.jobTitle";
828
+ enhanced.faker = "person.jobTitle";
825
829
  }
826
830
  // Price/money fields
827
831
  else if (lowerFieldName === "price" || lowerFieldName === "amount") {
828
- (enhanced as any).faker = "commerce.price";
832
+ enhanced.faker = "commerce.price";
829
833
  }
830
834
 
831
835
  return enhanced;
@@ -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 < 8; 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
- const avgNarrow =
170
- narrowTimes.reduce((a, b) => a + b, 0) / narrowTimes.length;
171
- const avgWide = wideTimes.reduce((a, b) => a + b, 0) / wideTimes.length;
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
- // 5x more properties should take more time but scale reasonably
174
- expect(avgWide).toBeGreaterThan(avgNarrow);
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!.constructor.name).toBe("SchemaValidationError");
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!.constructor.name).toBe("SchemaValidationError");
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): JSONSchema7 =>
32
- ({
33
- type: type as any,
34
- faker: fakerMethod,
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" as const,
105
+ const mappedSchema: JSONSchema7 = {
106
+ type: "object",
104
107
  properties: {
105
- [fieldName]: { type: fieldType as any },
108
+ [fieldName]: { type: fieldType },
106
109
  },
107
110
  };
108
111
 
109
- const unmappedSchema = {
110
- type: "object" as const,
112
+ const unmappedSchema: JSONSchema7 = {
113
+ type: "object",
111
114
  properties: {
112
- unmappedRandomField12345: { type: fieldType as any },
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
- };