@schmock/faker 1.9.0 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ import { Faker } from "@faker-js/faker";
2
+ import jsf from "json-schema-faker";
3
+ /**
4
+ * Create isolated faker instance to avoid race conditions
5
+ * Each generation gets its own faker instance to ensure thread-safety
6
+ * @returns Fresh Faker instance with English locale
7
+ */
8
+ export declare function createFakerInstance(seed?: number): Faker;
9
+ export declare function getJsf(seed?: number): typeof jsf;
10
+ //# sourceMappingURL=jsf-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsf-config.d.ts","sourceRoot":"","sources":["../src/jsf-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAM,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,GAAG,MAAM,mBAAmB,CAAC;AAEpC;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,CAAC,EAAE,MAAM,SAMhD;AAoBD,wBAAgB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,CAuBhD"}
@@ -0,0 +1,55 @@
1
+ import { en, Faker } from "@faker-js/faker";
2
+ import jsf from "json-schema-faker";
3
+ /**
4
+ * Create isolated faker instance to avoid race conditions
5
+ * Each generation gets its own faker instance to ensure thread-safety
6
+ * @returns Fresh Faker instance with English locale
7
+ */
8
+ export function createFakerInstance(seed) {
9
+ const faker = new Faker({ locale: [en] });
10
+ if (seed !== undefined) {
11
+ faker.seed(seed);
12
+ }
13
+ return faker;
14
+ }
15
+ let jsfConfigured = false;
16
+ let currentSeed;
17
+ /**
18
+ * Create a seeded PRNG using the mulberry32 algorithm.
19
+ * Returns a function that produces deterministic values in [0, 1).
20
+ */
21
+ function createSeededRandom(seed) {
22
+ let state = seed | 0;
23
+ return () => {
24
+ state = (state + 0x6d2b79f5) | 0;
25
+ let t = state;
26
+ t = Math.imul(t ^ (t >>> 15), t | 1);
27
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
28
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
29
+ };
30
+ }
31
+ export function getJsf(seed) {
32
+ const seedChanged = seed !== currentSeed;
33
+ if (!jsfConfigured || seedChanged) {
34
+ currentSeed = seed;
35
+ jsf.extend("faker", () => createFakerInstance(seed));
36
+ jsf.option({
37
+ requiredOnly: false,
38
+ alwaysFakeOptionals: true,
39
+ useDefaultValue: true,
40
+ ignoreMissingRefs: true,
41
+ failOnInvalidTypes: false,
42
+ failOnInvalidFormat: false,
43
+ });
44
+ jsfConfigured = true;
45
+ }
46
+ // Always reset PRNG for deterministic output per call
47
+ if (seed !== undefined) {
48
+ jsf.option({ random: createSeededRandom(seed) });
49
+ jsf.extend("faker", () => createFakerInstance(seed));
50
+ }
51
+ else {
52
+ jsf.option({ random: Math.random });
53
+ }
54
+ return jsf;
55
+ }
@@ -0,0 +1,21 @@
1
+ import type { JSONSchema7 } from "json-schema";
2
+ /**
3
+ * Determine number of items to generate for array schema
4
+ * Prefers explicit count, then schema minItems/maxItems, with sane defaults
5
+ * @param schema - Array schema with optional minItems/maxItems
6
+ * @param explicitCount - Explicit count override from plugin options
7
+ * @returns Number of array items to generate
8
+ */
9
+ export declare function determineArrayCount(schema: JSONSchema7, explicitCount?: number, random?: () => number): number;
10
+ /**
11
+ * Apply overrides to generated data with support for templates
12
+ * Supports nested paths (dot notation), templates with {{params.id}}, and state access
13
+ * @param data - Generated data to apply overrides to
14
+ * @param overrides - Override values (can use templates)
15
+ * @param params - Route parameters for template expansion
16
+ * @param state - Plugin state for template expansion
17
+ * @param query - Query parameters for template expansion
18
+ * @returns Data with overrides applied
19
+ */
20
+ export declare function applyOverrides(data: any, overrides?: Record<string, any>, params?: Record<string, string>, state?: any, query?: Record<string, string>): any;
21
+ //# sourceMappingURL=overrides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overrides.d.ts","sourceRoot":"","sources":["../src/overrides.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,EACnB,aAAa,CAAC,EAAE,MAAM,EACtB,MAAM,GAAE,MAAM,MAAoB,GACjC,MAAM,CAyBR;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,GAAG,EACT,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,KAAK,CAAC,EAAE,GAAG,EACX,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,GAAG,CAuCL"}
@@ -0,0 +1,134 @@
1
+ import { DEFAULT_ARRAY_COUNT } from "./constants.js";
2
+ const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
3
+ /**
4
+ * Determine number of items to generate for array schema
5
+ * Prefers explicit count, then schema minItems/maxItems, with sane defaults
6
+ * @param schema - Array schema with optional minItems/maxItems
7
+ * @param explicitCount - Explicit count override from plugin options
8
+ * @returns Number of array items to generate
9
+ */
10
+ export function determineArrayCount(schema, explicitCount, random = Math.random) {
11
+ if (explicitCount !== undefined) {
12
+ // Handle negative or invalid counts
13
+ if (explicitCount < 0) {
14
+ return 0;
15
+ }
16
+ return explicitCount;
17
+ }
18
+ if (schema.minItems !== undefined && schema.maxItems !== undefined) {
19
+ return (Math.floor(random() * (schema.maxItems - schema.minItems + 1)) +
20
+ schema.minItems);
21
+ }
22
+ if (schema.minItems !== undefined) {
23
+ return Math.max(schema.minItems, DEFAULT_ARRAY_COUNT);
24
+ }
25
+ if (schema.maxItems !== undefined) {
26
+ return Math.min(schema.maxItems, DEFAULT_ARRAY_COUNT);
27
+ }
28
+ return DEFAULT_ARRAY_COUNT;
29
+ }
30
+ /**
31
+ * Apply overrides to generated data with support for templates
32
+ * Supports nested paths (dot notation), templates with {{params.id}}, and state access
33
+ * @param data - Generated data to apply overrides to
34
+ * @param overrides - Override values (can use templates)
35
+ * @param params - Route parameters for template expansion
36
+ * @param state - Plugin state for template expansion
37
+ * @param query - Query parameters for template expansion
38
+ * @returns Data with overrides applied
39
+ */
40
+ export function applyOverrides(data, overrides, params, state, query) {
41
+ if (!overrides)
42
+ return data;
43
+ const result = structuredClone(data);
44
+ for (const [key, value] of Object.entries(overrides)) {
45
+ if (DANGEROUS_KEYS.has(key))
46
+ continue;
47
+ // Handle nested paths like "data.id" or "pagination.page"
48
+ if (key.includes(".")) {
49
+ setNestedProperty(result, key, value, { params, state, query });
50
+ }
51
+ else {
52
+ // Handle flat keys and nested objects
53
+ if (typeof value === "object" &&
54
+ value !== null &&
55
+ !Array.isArray(value)) {
56
+ // Recursively apply nested overrides
57
+ if (result[key] && typeof result[key] === "object") {
58
+ result[key] = applyOverrides(result[key], value, params, state, query);
59
+ }
60
+ else {
61
+ result[key] = applyOverrides({}, value, params, state, query);
62
+ }
63
+ }
64
+ else if (typeof value === "string" && value.includes("{{")) {
65
+ // Template processing
66
+ result[key] = processTemplate(value, { params, state, query });
67
+ }
68
+ else {
69
+ result[key] = value;
70
+ }
71
+ }
72
+ }
73
+ return result;
74
+ }
75
+ function setNestedProperty(obj, path, value, context) {
76
+ const parts = path.split(".");
77
+ let current = obj;
78
+ // Navigate to the parent of the target property
79
+ for (let i = 0; i < parts.length - 1; i++) {
80
+ const part = parts[i];
81
+ if (DANGEROUS_KEYS.has(part))
82
+ return;
83
+ if (!(part in current) ||
84
+ typeof current[part] !== "object" ||
85
+ current[part] === null) {
86
+ current[part] = {};
87
+ }
88
+ current = current[part];
89
+ }
90
+ // Set the final property
91
+ const finalKey = parts[parts.length - 1];
92
+ if (DANGEROUS_KEYS.has(finalKey))
93
+ return;
94
+ if (typeof value === "string" && value.includes("{{")) {
95
+ current[finalKey] = processTemplate(value, context);
96
+ }
97
+ else {
98
+ current[finalKey] = value;
99
+ }
100
+ }
101
+ function processTemplate(template, context) {
102
+ // Check if the template is just a single template expression
103
+ const singleTemplateMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
104
+ if (singleTemplateMatch) {
105
+ // For single templates, return the actual value without string conversion
106
+ const expression = singleTemplateMatch[1];
107
+ const parts = expression.trim().split(".");
108
+ let result = context;
109
+ for (const part of parts) {
110
+ if (result && typeof result === "object") {
111
+ result = result[part];
112
+ }
113
+ else {
114
+ return template; // Return original if can't resolve
115
+ }
116
+ }
117
+ return result !== undefined ? result : template;
118
+ }
119
+ // For templates mixed with other text, do string replacement
120
+ const processed = template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, expression) => {
121
+ const parts = expression.trim().split(".");
122
+ let result = context;
123
+ for (const part of parts) {
124
+ if (result && typeof result === "object") {
125
+ result = result[part];
126
+ }
127
+ else {
128
+ return match; // Return original if can't resolve
129
+ }
130
+ }
131
+ return result !== undefined ? String(result) : match;
132
+ });
133
+ return processed;
134
+ }
@@ -0,0 +1,3 @@
1
+ import type { JSONSchema7 } from "json-schema";
2
+ export declare function enhanceSchemaWithSmartMapping(schema: JSONSchema7): JSONSchema7;
3
+ //# sourceMappingURL=schema-enhancement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-enhancement.d.ts","sourceRoot":"","sources":["../src/schema-enhancement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQ/C,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,WAAW,GAClB,WAAW,CAwBb"}
@@ -0,0 +1,81 @@
1
+ import { isJSONSchema7, validateFakerMethod } from "./validation.js";
2
+ export function enhanceSchemaWithSmartMapping(schema) {
3
+ if (!schema || typeof schema !== "object") {
4
+ return schema;
5
+ }
6
+ const enhanced = { ...schema };
7
+ // Handle object properties
8
+ if (enhanced.type === "object" && enhanced.properties) {
9
+ enhanced.properties = { ...enhanced.properties };
10
+ for (const [fieldName, fieldSchema] of Object.entries(enhanced.properties)) {
11
+ if (isJSONSchema7(fieldSchema)) {
12
+ enhanced.properties[fieldName] = enhanceFieldSchema(fieldName, fieldSchema);
13
+ }
14
+ }
15
+ }
16
+ return enhanced;
17
+ }
18
+ function enhanceFieldSchema(fieldName, fieldSchema) {
19
+ const enhanced = { ...fieldSchema };
20
+ // If already has faker extension, validate it and don't override
21
+ if (enhanced.faker) {
22
+ validateFakerMethod(enhanced.faker);
23
+ return enhanced;
24
+ }
25
+ // Apply smart field name mapping
26
+ const lowerFieldName = fieldName.toLowerCase();
27
+ // Email fields
28
+ if (lowerFieldName.includes("email")) {
29
+ enhanced.format = "email";
30
+ enhanced.faker = "internet.email";
31
+ }
32
+ // Name fields
33
+ else if (lowerFieldName === "firstname" || lowerFieldName === "first_name") {
34
+ enhanced.faker = "person.firstName";
35
+ }
36
+ else if (lowerFieldName === "lastname" || lowerFieldName === "last_name") {
37
+ enhanced.faker = "person.lastName";
38
+ }
39
+ else if (lowerFieldName === "name" || lowerFieldName === "fullname") {
40
+ enhanced.faker = "person.fullName";
41
+ }
42
+ // Phone fields
43
+ else if (lowerFieldName.includes("phone") || lowerFieldName === "mobile") {
44
+ enhanced.faker = "phone.number";
45
+ }
46
+ // Address fields
47
+ else if (lowerFieldName === "street" || lowerFieldName === "address") {
48
+ enhanced.faker = "location.streetAddress";
49
+ }
50
+ else if (lowerFieldName === "city") {
51
+ enhanced.faker = "location.city";
52
+ }
53
+ else if (lowerFieldName === "zipcode" || lowerFieldName === "zip") {
54
+ enhanced.faker = "location.zipCode";
55
+ }
56
+ // UUID fields
57
+ else if (lowerFieldName === "uuid" ||
58
+ (lowerFieldName === "id" && enhanced.format === "uuid")) {
59
+ enhanced.faker = "string.uuid";
60
+ }
61
+ // Date fields
62
+ else if (lowerFieldName.includes("createdat") ||
63
+ lowerFieldName.includes("created_at") ||
64
+ lowerFieldName.includes("updatedat") ||
65
+ lowerFieldName.includes("updated_at")) {
66
+ enhanced.format = "date-time";
67
+ enhanced.faker = "date.recent";
68
+ }
69
+ // Company fields
70
+ else if (lowerFieldName.includes("company")) {
71
+ enhanced.faker = "company.name";
72
+ }
73
+ else if (lowerFieldName === "position" || lowerFieldName === "jobtitle") {
74
+ enhanced.faker = "person.jobTitle";
75
+ }
76
+ // Price/money fields
77
+ else if (lowerFieldName === "price" || lowerFieldName === "amount") {
78
+ enhanced.faker = "commerce.price";
79
+ }
80
+ return enhanced;
81
+ }
@@ -0,0 +1,20 @@
1
+ import type { JSONSchema7 } from "json-schema";
2
+ export declare function isJSONSchema7(value: unknown): value is JSONSchema7;
3
+ /**
4
+ * Validate JSON Schema structure and enforce resource limits
5
+ * Checks for malformed schemas, circular references, excessive nesting,
6
+ * and dangerous patterns that could cause memory issues
7
+ * @param schema - JSON Schema to validate
8
+ * @param path - Current path in schema tree (for error messages)
9
+ * @throws {SchemaValidationError} When schema structure is invalid
10
+ * @throws {ResourceLimitError} When schema exceeds safety limits
11
+ */
12
+ export declare function validateSchema(schema: JSONSchema7, path?: string): void;
13
+ /**
14
+ * Validate that faker method string references a valid Faker.js API
15
+ * Checks format (namespace.method) and validates against known namespaces
16
+ * @param fakerMethod - Faker method string (e.g., "person.fullName")
17
+ * @throws {SchemaValidationError} When faker method format or namespace is invalid
18
+ */
19
+ export declare function validateFakerMethod(fakerMethod: string): void;
20
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAY/C,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAElE;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,SAAM,GAAG,IAAI,CAiJpE;AAwMD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAmC7D"}
@@ -0,0 +1,297 @@
1
+ import { ResourceLimitError, SchemaValidationError } from "@schmock/core";
2
+ import { DEEP_NESTING_THRESHOLD, DEFAULT_ARRAY_COUNT, LARGE_ARRAY_THRESHOLD, MAX_ARRAY_SIZE, MAX_NESTING_DEPTH, } from "./constants.js";
3
+ import { createFakerInstance } from "./jsf-config.js";
4
+ let validationFaker;
5
+ export function isJSONSchema7(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ /**
9
+ * Validate JSON Schema structure and enforce resource limits
10
+ * Checks for malformed schemas, circular references, excessive nesting,
11
+ * and dangerous patterns that could cause memory issues
12
+ * @param schema - JSON Schema to validate
13
+ * @param path - Current path in schema tree (for error messages)
14
+ * @throws {SchemaValidationError} When schema structure is invalid
15
+ * @throws {ResourceLimitError} When schema exceeds safety limits
16
+ */
17
+ export function validateSchema(schema, path = "$") {
18
+ if (!schema || typeof schema !== "object") {
19
+ throw new SchemaValidationError(path, "Schema must be a valid JSON Schema object");
20
+ }
21
+ if (Object.keys(schema).length === 0) {
22
+ throw new SchemaValidationError(path, "Schema cannot be empty");
23
+ }
24
+ // Check for invalid schema types
25
+ const validTypes = [
26
+ "object",
27
+ "array",
28
+ "string",
29
+ "number",
30
+ "integer",
31
+ "boolean",
32
+ "null",
33
+ ];
34
+ if (schema.type &&
35
+ typeof schema.type === "string" &&
36
+ !validTypes.includes(schema.type)) {
37
+ throw new SchemaValidationError(path, `Invalid schema type: "${schema.type}"`, "Supported types are: object, array, string, number, integer, boolean, null");
38
+ }
39
+ // Check for malformed properties (must be object, not string)
40
+ if (schema.type === "object" && schema.properties) {
41
+ if (typeof schema.properties !== "object" ||
42
+ Array.isArray(schema.properties)) {
43
+ throw new SchemaValidationError(`${path}.properties`, "Properties must be an object mapping property names to schemas", 'Use { "propertyName": { "type": "string" } } format');
44
+ }
45
+ // Validate each property recursively
46
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
47
+ if (typeof propSchema === "object" && propSchema !== null) {
48
+ // Check for invalid faker methods in property schemas
49
+ const fakerProp = "faker" in propSchema ? String(propSchema.faker) : undefined;
50
+ if (fakerProp) {
51
+ try {
52
+ validateFakerMethod(fakerProp);
53
+ }
54
+ catch (error) {
55
+ // Re-throw with proper path context
56
+ if (error instanceof SchemaValidationError) {
57
+ const ctx = error.context;
58
+ let issue = "Invalid faker method";
59
+ let suggestion;
60
+ if (ctx && typeof ctx === "object") {
61
+ if ("issue" in ctx && typeof ctx.issue === "string")
62
+ issue = ctx.issue;
63
+ if ("suggestion" in ctx && typeof ctx.suggestion === "string")
64
+ suggestion = ctx.suggestion;
65
+ }
66
+ throw new SchemaValidationError(`${path}.properties.${propName}.faker`, issue, suggestion);
67
+ }
68
+ if (error instanceof Error)
69
+ throw error;
70
+ throw new Error(String(error));
71
+ }
72
+ }
73
+ validateSchema(propSchema, `${path}.properties.${propName}`);
74
+ }
75
+ }
76
+ }
77
+ // Check for invalid array items
78
+ if (schema.type === "array") {
79
+ // Array must have items defined and non-null
80
+ if (schema.items === null || schema.items === undefined) {
81
+ throw new SchemaValidationError(`${path}.items`, "Array schema must have valid items definition", "Define items as a schema object or array of schemas");
82
+ }
83
+ if (Array.isArray(schema.items)) {
84
+ if (schema.items.length === 0) {
85
+ throw new SchemaValidationError(`${path}.items`, "Array items cannot be empty array", "Provide at least one item schema");
86
+ }
87
+ schema.items.forEach((item, index) => {
88
+ if (typeof item === "object" && item !== null) {
89
+ validateSchema(item, `${path}.items[${index}]`);
90
+ }
91
+ });
92
+ }
93
+ else if (typeof schema.items === "object" && schema.items !== null) {
94
+ validateSchema(schema.items, `${path}.items`);
95
+ }
96
+ }
97
+ // Check for circular references
98
+ if (hasCircularReference(schema)) {
99
+ throw new SchemaValidationError(path, "Schema contains circular references which are not supported");
100
+ }
101
+ // Check nesting depth
102
+ const depth = calculateNestingDepth(schema);
103
+ if (depth > MAX_NESTING_DEPTH) {
104
+ throw new ResourceLimitError("schema_nesting_depth", MAX_NESTING_DEPTH, depth);
105
+ }
106
+ // Check for dangerous combination of deep nesting + large arrays
107
+ if (depth >= DEEP_NESTING_THRESHOLD) {
108
+ checkForDeepNestingWithArrays(schema, path);
109
+ }
110
+ // Check for potentially dangerous array sizes in schema definition
111
+ checkArraySizeLimits(schema, path);
112
+ // Check for forbidden features
113
+ if (schema.$ref === "#") {
114
+ throw new SchemaValidationError(path, "Self-referencing schemas are not supported");
115
+ }
116
+ }
117
+ /**
118
+ * Detect circular references in JSON Schema using path-based traversal
119
+ * Uses backtracking to distinguish between cycles and legitimate schema reuse
120
+ * @param schema - Schema to check for cycles
121
+ * @param currentPath - Set of schemas currently in traversal path
122
+ * @returns true if circular reference detected, false otherwise
123
+ * @example
124
+ * // Detects: schema A -> B -> A (cycle)
125
+ * // Allows: schema A -> B, A -> C (reuse of A)
126
+ */
127
+ function hasCircularReference(schema, currentPath = new Set()) {
128
+ // Check if this schema is currently being traversed (cycle detected)
129
+ if (currentPath.has(schema)) {
130
+ return true;
131
+ }
132
+ if (schema.$ref === "#") {
133
+ return true;
134
+ }
135
+ // Add to current path for this traversal branch
136
+ currentPath.add(schema);
137
+ if (schema.type === "object" && schema.properties) {
138
+ for (const prop of Object.values(schema.properties)) {
139
+ if (isJSONSchema7(prop)) {
140
+ if (hasCircularReference(prop, currentPath)) {
141
+ return true;
142
+ }
143
+ }
144
+ }
145
+ }
146
+ if (schema.type === "array" && schema.items) {
147
+ const items = Array.isArray(schema.items) ? schema.items : [schema.items];
148
+ for (const item of items) {
149
+ if (isJSONSchema7(item)) {
150
+ if (hasCircularReference(item, currentPath)) {
151
+ return true;
152
+ }
153
+ }
154
+ }
155
+ }
156
+ // Remove from current path after checking all children (backtrack)
157
+ currentPath.delete(schema);
158
+ return false;
159
+ }
160
+ /**
161
+ * Calculate maximum nesting depth of a JSON Schema
162
+ * Recursively traverses object properties and array items
163
+ * @param schema - Schema to measure
164
+ * @param depth - Current depth (internal recursion parameter)
165
+ * @returns Maximum nesting depth found
166
+ */
167
+ function calculateNestingDepth(schema, depth = 0) {
168
+ if (depth > MAX_NESTING_DEPTH) {
169
+ return depth;
170
+ }
171
+ let maxDepth = depth;
172
+ if (schema.type === "object" && schema.properties) {
173
+ for (const prop of Object.values(schema.properties)) {
174
+ if (isJSONSchema7(prop)) {
175
+ maxDepth = Math.max(maxDepth, calculateNestingDepth(prop, depth + 1));
176
+ }
177
+ }
178
+ }
179
+ if (schema.type === "array" && schema.items) {
180
+ const items = Array.isArray(schema.items) ? schema.items : [schema.items];
181
+ for (const item of items) {
182
+ if (isJSONSchema7(item)) {
183
+ maxDepth = Math.max(maxDepth, calculateNestingDepth(item, depth + 1));
184
+ }
185
+ }
186
+ }
187
+ return maxDepth;
188
+ }
189
+ /**
190
+ * Check for dangerous patterns of deep nesting combined with large arrays
191
+ * Prevents memory issues from schemas like: depth 3+ with 100+ item arrays
192
+ * @param schema - Schema to check
193
+ * @param _path - Path in schema (unused but kept for signature consistency)
194
+ * @throws {ResourceLimitError} When dangerous nesting pattern detected
195
+ */
196
+ function checkForDeepNestingWithArrays(schema, _path) {
197
+ // Look for arrays in deeply nested structures that could cause memory issues
198
+ function findArraysInDeepNesting(node, currentDepth) {
199
+ const schemaType = node.type;
200
+ const isArray = Array.isArray(schemaType)
201
+ ? schemaType.includes("array")
202
+ : schemaType === "array";
203
+ if (isArray) {
204
+ const maxItems = node.maxItems || DEFAULT_ARRAY_COUNT;
205
+ // Be more aggressive about deep nesting detection
206
+ if (currentDepth >= DEEP_NESTING_THRESHOLD &&
207
+ maxItems >= LARGE_ARRAY_THRESHOLD) {
208
+ throw new ResourceLimitError("deep_nesting_memory_risk", DEEP_NESTING_THRESHOLD * LARGE_ARRAY_THRESHOLD, currentDepth * maxItems);
209
+ }
210
+ // Check items if they exist
211
+ if (node.items) {
212
+ const items = Array.isArray(node.items) ? node.items : [node.items];
213
+ for (const item of items) {
214
+ if (isJSONSchema7(item)) {
215
+ findArraysInDeepNesting(item, currentDepth + 1);
216
+ }
217
+ }
218
+ }
219
+ return;
220
+ }
221
+ if (schemaType === "object" && node.properties) {
222
+ for (const prop of Object.values(node.properties)) {
223
+ if (isJSONSchema7(prop)) {
224
+ findArraysInDeepNesting(prop, currentDepth + 1);
225
+ }
226
+ }
227
+ }
228
+ }
229
+ findArraysInDeepNesting(schema, 0);
230
+ }
231
+ function checkArraySizeLimits(schema, path) {
232
+ // Recursively check all array constraints in the schema
233
+ if (schema.type === "array") {
234
+ // Check for dangerously large maxItems
235
+ if (schema.maxItems && schema.maxItems > MAX_ARRAY_SIZE) {
236
+ throw new ResourceLimitError("array_max_items", MAX_ARRAY_SIZE, schema.maxItems);
237
+ }
238
+ // Check for combination of deep nesting and large arrays
239
+ const depth = calculateNestingDepth(schema);
240
+ const estimatedSize = schema.maxItems || schema.minItems || DEFAULT_ARRAY_COUNT;
241
+ // If we have deep nesting and large arrays, it could cause memory issues
242
+ if (depth > DEEP_NESTING_THRESHOLD &&
243
+ estimatedSize > LARGE_ARRAY_THRESHOLD) {
244
+ throw new ResourceLimitError("memory_estimation", DEEP_NESTING_THRESHOLD * LARGE_ARRAY_THRESHOLD, depth * estimatedSize);
245
+ }
246
+ }
247
+ // Recursively check nested schemas
248
+ if (schema.type === "object" && schema.properties) {
249
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
250
+ if (isJSONSchema7(propSchema)) {
251
+ checkArraySizeLimits(propSchema, `${path}.properties.${propName}`);
252
+ }
253
+ }
254
+ }
255
+ if (schema.type === "array" && schema.items) {
256
+ if (Array.isArray(schema.items)) {
257
+ schema.items.forEach((item, index) => {
258
+ if (isJSONSchema7(item)) {
259
+ checkArraySizeLimits(item, `${path}.items[${index}]`);
260
+ }
261
+ });
262
+ }
263
+ else if (isJSONSchema7(schema.items)) {
264
+ checkArraySizeLimits(schema.items, `${path}.items`);
265
+ }
266
+ }
267
+ }
268
+ /**
269
+ * Validate that faker method string references a valid Faker.js API
270
+ * Checks format (namespace.method) and validates against known namespaces
271
+ * @param fakerMethod - Faker method string (e.g., "person.fullName")
272
+ * @throws {SchemaValidationError} When faker method format or namespace is invalid
273
+ */
274
+ export function validateFakerMethod(fakerMethod) {
275
+ // Check if faker method follows valid format (namespace.method)
276
+ const parts = fakerMethod.split(".");
277
+ if (parts.length < 2) {
278
+ throw new SchemaValidationError("$.faker", `Invalid faker method format: "${fakerMethod}"`, "Use format like 'person.firstName' or 'internet.email'");
279
+ }
280
+ // Validate by resolving the method path on a cached faker instance
281
+ if (!validationFaker) {
282
+ validationFaker = createFakerInstance();
283
+ }
284
+ const faker = validationFaker;
285
+ let current = faker;
286
+ for (const part of parts) {
287
+ if (current && typeof current === "object" && part in current) {
288
+ current = current[part];
289
+ }
290
+ else {
291
+ throw new SchemaValidationError("$.faker", `Invalid faker method: "${fakerMethod}"`, "Check faker.js documentation for valid methods");
292
+ }
293
+ }
294
+ if (typeof current !== "function") {
295
+ throw new SchemaValidationError("$.faker", `Invalid faker method: "${fakerMethod}" is not a function`, "Check faker.js documentation for valid methods");
296
+ }
297
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@schmock/faker",
3
3
  "description": "Faker-powered automatic data generation for Schmock",
4
- "version": "1.9.0",
4
+ "version": "1.9.2",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -34,12 +34,12 @@
34
34
  "json-schema-faker": "^0.5.6"
35
35
  },
36
36
  "peerDependencies": {
37
- "@schmock/core": "^1.9.0"
37
+ "@schmock/core": "^1.9.2"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@amiceli/vitest-cucumber": "^6.2.0",
41
41
  "@types/json-schema": "^7.0.15",
42
- "@types/node": "^25.2.3",
42
+ "@types/node": "^25.3.0",
43
43
  "vitest": "^4.0.18"
44
44
  }
45
45
  }
@@ -0,0 +1,6 @@
1
+ // Resource limits for safety
2
+ export const MAX_ARRAY_SIZE = 10000;
3
+ export const MAX_NESTING_DEPTH = 10; // Reasonable limit for schema nesting
4
+ export const DEFAULT_ARRAY_COUNT = 3; // Default items to generate when not specified
5
+ export const DEEP_NESTING_THRESHOLD = 3; // Depth at which to check for memory risks
6
+ export const LARGE_ARRAY_THRESHOLD = 100; // Array size considered "large"