@schmock/faker 2.3.0 → 2.3.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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -0
  3. package/dist/constants.d.ts +82 -1
  4. package/dist/constants.d.ts.map +1 -1
  5. package/dist/field-name-matcher.d.ts +19 -0
  6. package/dist/field-name-matcher.d.ts.map +1 -1
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +4 -1
  10. package/dist/jsf-config.d.ts +19 -4
  11. package/dist/jsf-config.d.ts.map +1 -1
  12. package/dist/output-limits.d.ts +3 -0
  13. package/dist/output-limits.d.ts.map +1 -0
  14. package/dist/overrides.d.ts +2 -1
  15. package/dist/overrides.d.ts.map +1 -1
  16. package/dist/schema-children.d.ts +14 -0
  17. package/dist/schema-children.d.ts.map +1 -0
  18. package/dist/schema-enhancement.d.ts +1 -0
  19. package/dist/schema-enhancement.d.ts.map +1 -1
  20. package/dist/validation.d.ts +24 -5
  21. package/dist/validation.d.ts.map +1 -1
  22. package/package.json +17 -7
  23. package/dist/constants.js +0 -8
  24. package/dist/field-mappings.js +0 -1114
  25. package/dist/field-name-matcher.js +0 -148
  26. package/dist/jsf-config.js +0 -117
  27. package/dist/overrides.js +0 -137
  28. package/dist/schema-enhancement.js +0 -104
  29. package/dist/test-utils.d.ts +0 -57
  30. package/dist/test-utils.d.ts.map +0 -1
  31. package/dist/test-utils.js +0 -284
  32. package/dist/validation.js +0 -300
  33. package/src/advanced-features.test.ts +0 -912
  34. package/src/audit-faker-args.test.ts +0 -97
  35. package/src/constants.d.ts.map +0 -1
  36. package/src/constants.ts +0 -9
  37. package/src/data-quality.test.ts +0 -415
  38. package/src/error-handling.test.ts +0 -504
  39. package/src/field-mappings.test.ts +0 -351
  40. package/src/field-mappings.ts +0 -1148
  41. package/src/field-name-matcher.test.ts +0 -292
  42. package/src/field-name-matcher.ts +0 -187
  43. package/src/index.d.ts.map +0 -1
  44. package/src/index.test.ts +0 -1320
  45. package/src/index.ts +0 -185
  46. package/src/integration.test.ts +0 -634
  47. package/src/jsf-config.d.ts.map +0 -1
  48. package/src/jsf-config.test.ts +0 -65
  49. package/src/jsf-config.ts +0 -156
  50. package/src/overrides.d.ts.map +0 -1
  51. package/src/overrides.property.test.ts +0 -172
  52. package/src/overrides.test.ts +0 -38
  53. package/src/overrides.ts +0 -179
  54. package/src/performance.test.ts +0 -441
  55. package/src/plugin-integration.test.ts +0 -575
  56. package/src/post-process.test.ts +0 -177
  57. package/src/real-world.test.ts +0 -636
  58. package/src/schema-enhancement.d.ts.map +0 -1
  59. package/src/schema-enhancement.test.ts +0 -222
  60. package/src/schema-enhancement.ts +0 -144
  61. package/src/steps/deterministic-seeds.steps.ts +0 -112
  62. package/src/steps/faker-plugin.steps.ts +0 -301
  63. package/src/test-utils.ts +0 -385
  64. package/src/validation.d.ts.map +0 -1
  65. package/src/validation.ts +0 -417
package/src/validation.ts DELETED
@@ -1,417 +0,0 @@
1
- import type { Faker } from "@faker-js/faker";
2
- import { ResourceLimitError, SchemaValidationError } from "@schmock/core";
3
- import type { JSONSchema7 } from "json-schema";
4
- import {
5
- DEEP_NESTING_THRESHOLD,
6
- DEFAULT_ARRAY_COUNT,
7
- LARGE_ARRAY_THRESHOLD,
8
- MAX_ARRAY_SIZE,
9
- MAX_NESTING_DEPTH,
10
- } from "./constants.js";
11
- import { createFakerInstance } from "./jsf-config.js";
12
-
13
- let validationFaker: Faker | undefined;
14
-
15
- export function isJSONSchema7(value: unknown): value is JSONSchema7 {
16
- return typeof value === "object" && value !== null && !Array.isArray(value);
17
- }
18
-
19
- /**
20
- * Validate JSON Schema structure and enforce resource limits
21
- * Checks for malformed schemas, circular references, excessive nesting,
22
- * and dangerous patterns that could cause memory issues
23
- * @param schema - JSON Schema to validate
24
- * @param path - Current path in schema tree (for error messages)
25
- * @throws {SchemaValidationError} When schema structure is invalid
26
- * @throws {ResourceLimitError} When schema exceeds safety limits
27
- */
28
- export function validateSchema(schema: JSONSchema7, path = "$"): void {
29
- if (!schema || typeof schema !== "object") {
30
- throw new SchemaValidationError(
31
- path,
32
- "Schema must be a valid JSON Schema object",
33
- );
34
- }
35
-
36
- if (Object.keys(schema).length === 0) {
37
- throw new SchemaValidationError(path, "Schema cannot be empty");
38
- }
39
-
40
- // Check for invalid schema types
41
- const validTypes = [
42
- "object",
43
- "array",
44
- "string",
45
- "number",
46
- "integer",
47
- "boolean",
48
- "null",
49
- ];
50
- if (
51
- schema.type &&
52
- typeof schema.type === "string" &&
53
- !validTypes.includes(schema.type)
54
- ) {
55
- throw new SchemaValidationError(
56
- path,
57
- `Invalid schema type: "${schema.type}"`,
58
- "Supported types are: object, array, string, number, integer, boolean, null",
59
- );
60
- }
61
-
62
- // Check for malformed properties (must be object, not string)
63
- if (schema.type === "object" && schema.properties) {
64
- if (
65
- typeof schema.properties !== "object" ||
66
- Array.isArray(schema.properties)
67
- ) {
68
- throw new SchemaValidationError(
69
- `${path}.properties`,
70
- "Properties must be an object mapping property names to schemas",
71
- 'Use { "propertyName": { "type": "string" } } format',
72
- );
73
- }
74
-
75
- // Validate each property recursively
76
- for (const [propName, propSchema] of Object.entries(schema.properties)) {
77
- if (typeof propSchema === "object" && propSchema !== null) {
78
- // Check for invalid faker methods in property schemas
79
- const fakerProp =
80
- "faker" in propSchema ? String(propSchema.faker) : undefined;
81
- if (fakerProp) {
82
- try {
83
- validateFakerMethod(fakerProp);
84
- } catch (error: unknown) {
85
- // Re-throw with proper path context
86
- if (error instanceof SchemaValidationError) {
87
- const ctx = error.context;
88
- let issue = "Invalid faker method";
89
- let suggestion: string | undefined;
90
- if (ctx && typeof ctx === "object") {
91
- if ("issue" in ctx && typeof ctx.issue === "string")
92
- issue = ctx.issue;
93
- if ("suggestion" in ctx && typeof ctx.suggestion === "string")
94
- suggestion = ctx.suggestion;
95
- }
96
- throw new SchemaValidationError(
97
- `${path}.properties.${propName}.faker`,
98
- issue,
99
- suggestion,
100
- );
101
- }
102
- if (error instanceof Error) throw error;
103
- throw new Error(String(error));
104
- }
105
- }
106
- validateSchema(propSchema, `${path}.properties.${propName}`);
107
- }
108
- }
109
- }
110
-
111
- // Check for invalid array items
112
- if (schema.type === "array") {
113
- // Array must have items defined and non-null
114
- if (schema.items === null || schema.items === undefined) {
115
- throw new SchemaValidationError(
116
- `${path}.items`,
117
- "Array schema must have valid items definition",
118
- "Define items as a schema object or array of schemas",
119
- );
120
- }
121
-
122
- if (Array.isArray(schema.items)) {
123
- if (schema.items.length === 0) {
124
- throw new SchemaValidationError(
125
- `${path}.items`,
126
- "Array items cannot be empty array",
127
- "Provide at least one item schema",
128
- );
129
- }
130
- schema.items.forEach((item, index) => {
131
- if (typeof item === "object" && item !== null) {
132
- validateSchema(item, `${path}.items[${index}]`);
133
- }
134
- });
135
- } else if (typeof schema.items === "object" && schema.items !== null) {
136
- validateSchema(schema.items, `${path}.items`);
137
- }
138
- }
139
-
140
- // Full-tree integrity checks are O(n) each, so only run them once at the
141
- // top of the recursion. Running them at every node turns validation into
142
- // O(n²) on deep schemas without finding anything the top-level pass misses.
143
- // The self-ref/$ref="#" check is part of this group so that an inner $ref
144
- // is reported as "circular references" (caught by hasCircularReference at
145
- // the root), matching the legacy error message.
146
- if (path === "$") {
147
- if (hasCircularReference(schema)) {
148
- throw new SchemaValidationError(
149
- path,
150
- "Schema contains circular references which are not supported",
151
- );
152
- }
153
-
154
- if (schema.$ref === "#") {
155
- throw new SchemaValidationError(
156
- path,
157
- "Self-referencing schemas are not supported",
158
- );
159
- }
160
-
161
- const depth = calculateNestingDepth(schema);
162
- if (depth > MAX_NESTING_DEPTH) {
163
- throw new ResourceLimitError(
164
- "schema_nesting_depth",
165
- MAX_NESTING_DEPTH,
166
- depth,
167
- );
168
- }
169
-
170
- if (depth >= DEEP_NESTING_THRESHOLD) {
171
- checkForDeepNestingWithArrays(schema, path);
172
- }
173
-
174
- checkArraySizeLimits(schema, path);
175
- }
176
- }
177
-
178
- /**
179
- * Detect circular references in JSON Schema using path-based traversal
180
- * Uses backtracking to distinguish between cycles and legitimate schema reuse
181
- * @param schema - Schema to check for cycles
182
- * @param currentPath - Set of schemas currently in traversal path
183
- * @returns true if circular reference detected, false otherwise
184
- * @example
185
- * // Detects: schema A -> B -> A (cycle)
186
- * // Allows: schema A -> B, A -> C (reuse of A)
187
- */
188
- function hasCircularReference(
189
- schema: JSONSchema7,
190
- currentPath = new Set(),
191
- ): boolean {
192
- // Check if this schema is currently being traversed (cycle detected)
193
- if (currentPath.has(schema)) {
194
- return true;
195
- }
196
-
197
- if (schema.$ref === "#") {
198
- return true;
199
- }
200
-
201
- // Add to current path for this traversal branch
202
- currentPath.add(schema);
203
-
204
- if (schema.type === "object" && schema.properties) {
205
- for (const prop of Object.values(schema.properties)) {
206
- if (isJSONSchema7(prop)) {
207
- if (hasCircularReference(prop, currentPath)) {
208
- return true;
209
- }
210
- }
211
- }
212
- }
213
-
214
- if (schema.type === "array" && schema.items) {
215
- const items = Array.isArray(schema.items) ? schema.items : [schema.items];
216
- for (const item of items) {
217
- if (isJSONSchema7(item)) {
218
- if (hasCircularReference(item, currentPath)) {
219
- return true;
220
- }
221
- }
222
- }
223
- }
224
-
225
- // Remove from current path after checking all children (backtrack)
226
- currentPath.delete(schema);
227
-
228
- return false;
229
- }
230
-
231
- /**
232
- * Calculate maximum nesting depth of a JSON Schema
233
- * Recursively traverses object properties and array items
234
- * @param schema - Schema to measure
235
- * @param depth - Current depth (internal recursion parameter)
236
- * @returns Maximum nesting depth found
237
- */
238
- function calculateNestingDepth(schema: JSONSchema7, depth = 0): number {
239
- if (depth > MAX_NESTING_DEPTH) {
240
- return depth;
241
- }
242
-
243
- let maxDepth = depth;
244
-
245
- if (schema.type === "object" && schema.properties) {
246
- for (const prop of Object.values(schema.properties)) {
247
- if (isJSONSchema7(prop)) {
248
- maxDepth = Math.max(maxDepth, calculateNestingDepth(prop, depth + 1));
249
- }
250
- }
251
- }
252
-
253
- if (schema.type === "array" && schema.items) {
254
- const items = Array.isArray(schema.items) ? schema.items : [schema.items];
255
- for (const item of items) {
256
- if (isJSONSchema7(item)) {
257
- maxDepth = Math.max(maxDepth, calculateNestingDepth(item, depth + 1));
258
- }
259
- }
260
- }
261
-
262
- return maxDepth;
263
- }
264
-
265
- /**
266
- * Check for dangerous patterns of deep nesting combined with large arrays
267
- * Prevents memory issues from schemas like: depth 3+ with 100+ item arrays
268
- * @param schema - Schema to check
269
- * @param _path - Path in schema (unused but kept for signature consistency)
270
- * @throws {ResourceLimitError} When dangerous nesting pattern detected
271
- */
272
- function checkForDeepNestingWithArrays(
273
- schema: JSONSchema7,
274
- _path: string,
275
- ): void {
276
- // Look for arrays in deeply nested structures that could cause memory issues
277
- function findArraysInDeepNesting(
278
- node: JSONSchema7,
279
- currentDepth: number,
280
- ): void {
281
- const schemaType = node.type;
282
- const isArray = Array.isArray(schemaType)
283
- ? schemaType.includes("array")
284
- : schemaType === "array";
285
-
286
- if (isArray) {
287
- const maxItems = node.maxItems || DEFAULT_ARRAY_COUNT;
288
- // Be more aggressive about deep nesting detection
289
- if (
290
- currentDepth >= DEEP_NESTING_THRESHOLD &&
291
- maxItems >= LARGE_ARRAY_THRESHOLD
292
- ) {
293
- throw new ResourceLimitError(
294
- "deep_nesting_memory_risk",
295
- DEEP_NESTING_THRESHOLD * LARGE_ARRAY_THRESHOLD,
296
- currentDepth * maxItems,
297
- );
298
- }
299
-
300
- // Check items if they exist
301
- if (node.items) {
302
- const items = Array.isArray(node.items) ? node.items : [node.items];
303
- for (const item of items) {
304
- if (isJSONSchema7(item)) {
305
- findArraysInDeepNesting(item, currentDepth + 1);
306
- }
307
- }
308
- }
309
- return;
310
- }
311
-
312
- if (schemaType === "object" && node.properties) {
313
- for (const prop of Object.values(node.properties)) {
314
- if (isJSONSchema7(prop)) {
315
- findArraysInDeepNesting(prop, currentDepth + 1);
316
- }
317
- }
318
- }
319
- }
320
-
321
- findArraysInDeepNesting(schema, 0);
322
- }
323
-
324
- function checkArraySizeLimits(schema: JSONSchema7, path: string): void {
325
- // Recursively check all array constraints in the schema
326
- if (schema.type === "array") {
327
- // Check for dangerously large maxItems
328
- if (schema.maxItems && schema.maxItems > MAX_ARRAY_SIZE) {
329
- throw new ResourceLimitError(
330
- "array_max_items",
331
- MAX_ARRAY_SIZE,
332
- schema.maxItems,
333
- );
334
- }
335
-
336
- // Check for combination of deep nesting and large arrays
337
- const depth = calculateNestingDepth(schema);
338
- const estimatedSize =
339
- schema.maxItems || schema.minItems || DEFAULT_ARRAY_COUNT;
340
-
341
- // If we have deep nesting and large arrays, it could cause memory issues
342
- if (
343
- depth > DEEP_NESTING_THRESHOLD &&
344
- estimatedSize > LARGE_ARRAY_THRESHOLD
345
- ) {
346
- throw new ResourceLimitError(
347
- "memory_estimation",
348
- DEEP_NESTING_THRESHOLD * LARGE_ARRAY_THRESHOLD,
349
- depth * estimatedSize,
350
- );
351
- }
352
- }
353
-
354
- // Recursively check nested schemas
355
- if (schema.type === "object" && schema.properties) {
356
- for (const [propName, propSchema] of Object.entries(schema.properties)) {
357
- if (isJSONSchema7(propSchema)) {
358
- checkArraySizeLimits(propSchema, `${path}.properties.${propName}`);
359
- }
360
- }
361
- }
362
-
363
- if (schema.type === "array" && schema.items) {
364
- if (Array.isArray(schema.items)) {
365
- schema.items.forEach((item, index) => {
366
- if (isJSONSchema7(item)) {
367
- checkArraySizeLimits(item, `${path}.items[${index}]`);
368
- }
369
- });
370
- } else if (isJSONSchema7(schema.items)) {
371
- checkArraySizeLimits(schema.items, `${path}.items`);
372
- }
373
- }
374
- }
375
-
376
- /**
377
- * Validate that faker method string references a valid Faker.js API
378
- * Checks format (namespace.method) and validates against known namespaces
379
- * @param fakerMethod - Faker method string (e.g., "person.fullName")
380
- * @throws {SchemaValidationError} When faker method format or namespace is invalid
381
- */
382
- export function validateFakerMethod(fakerMethod: string): void {
383
- // Check if faker method follows valid format (namespace.method)
384
- const parts = fakerMethod.split(".");
385
- if (parts.length < 2) {
386
- throw new SchemaValidationError(
387
- "$.faker",
388
- `Invalid faker method format: "${fakerMethod}"`,
389
- "Use format like 'person.firstName' or 'internet.email'",
390
- );
391
- }
392
-
393
- // Validate by resolving the method path on a cached faker instance
394
- if (!validationFaker) {
395
- validationFaker = createFakerInstance();
396
- }
397
- const faker = validationFaker;
398
- let current: unknown = faker;
399
- for (const part of parts) {
400
- if (current && typeof current === "object" && part in current) {
401
- current = Reflect.get(current, part);
402
- } else {
403
- throw new SchemaValidationError(
404
- "$.faker",
405
- `Invalid faker method: "${fakerMethod}"`,
406
- "Check faker.js documentation for valid methods",
407
- );
408
- }
409
- }
410
- if (typeof current !== "function") {
411
- throw new SchemaValidationError(
412
- "$.faker",
413
- `Invalid faker method: "${fakerMethod}" is not a function`,
414
- "Check faker.js documentation for valid methods",
415
- );
416
- }
417
- }