@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.
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/constants.d.ts +82 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/field-name-matcher.d.ts +19 -0
- package/dist/field-name-matcher.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/jsf-config.d.ts +19 -4
- package/dist/jsf-config.d.ts.map +1 -1
- package/dist/output-limits.d.ts +3 -0
- package/dist/output-limits.d.ts.map +1 -0
- package/dist/overrides.d.ts +2 -1
- package/dist/overrides.d.ts.map +1 -1
- package/dist/schema-children.d.ts +14 -0
- package/dist/schema-children.d.ts.map +1 -0
- package/dist/schema-enhancement.d.ts +1 -0
- package/dist/schema-enhancement.d.ts.map +1 -1
- package/dist/validation.d.ts +24 -5
- package/dist/validation.d.ts.map +1 -1
- package/package.json +17 -7
- package/dist/constants.js +0 -8
- package/dist/field-mappings.js +0 -1114
- package/dist/field-name-matcher.js +0 -148
- package/dist/jsf-config.js +0 -117
- package/dist/overrides.js +0 -137
- package/dist/schema-enhancement.js +0 -104
- package/dist/test-utils.d.ts +0 -57
- package/dist/test-utils.d.ts.map +0 -1
- package/dist/test-utils.js +0 -284
- package/dist/validation.js +0 -300
- package/src/advanced-features.test.ts +0 -912
- package/src/audit-faker-args.test.ts +0 -97
- package/src/constants.d.ts.map +0 -1
- package/src/constants.ts +0 -9
- package/src/data-quality.test.ts +0 -415
- package/src/error-handling.test.ts +0 -504
- package/src/field-mappings.test.ts +0 -351
- package/src/field-mappings.ts +0 -1148
- package/src/field-name-matcher.test.ts +0 -292
- package/src/field-name-matcher.ts +0 -187
- package/src/index.d.ts.map +0 -1
- package/src/index.test.ts +0 -1320
- package/src/index.ts +0 -185
- package/src/integration.test.ts +0 -634
- package/src/jsf-config.d.ts.map +0 -1
- package/src/jsf-config.test.ts +0 -65
- package/src/jsf-config.ts +0 -156
- package/src/overrides.d.ts.map +0 -1
- package/src/overrides.property.test.ts +0 -172
- package/src/overrides.test.ts +0 -38
- package/src/overrides.ts +0 -179
- package/src/performance.test.ts +0 -441
- package/src/plugin-integration.test.ts +0 -575
- package/src/post-process.test.ts +0 -177
- package/src/real-world.test.ts +0 -636
- package/src/schema-enhancement.d.ts.map +0 -1
- package/src/schema-enhancement.test.ts +0 -222
- package/src/schema-enhancement.ts +0 -144
- package/src/steps/deterministic-seeds.steps.ts +0 -112
- package/src/steps/faker-plugin.steps.ts +0 -301
- package/src/test-utils.ts +0 -385
- package/src/validation.d.ts.map +0 -1
- package/src/validation.ts +0 -417
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { ALL_FIELD_MAPPINGS } from "./field-mappings.js";
|
|
2
|
-
/**
|
|
3
|
-
* Split a field name (camelCase, snake_case, kebab-case) into lowercase tokens.
|
|
4
|
-
* "userFirstName" → ["user", "first", "name"]
|
|
5
|
-
* "created_at" → ["created", "at"]
|
|
6
|
-
* "HTMLParser" → ["html", "parser"]
|
|
7
|
-
* "is_active" → ["is", "active"]
|
|
8
|
-
*/
|
|
9
|
-
export function tokenizeFieldName(name) {
|
|
10
|
-
// Split on _ and -
|
|
11
|
-
const parts = name.split(/[_-]/);
|
|
12
|
-
const tokens = [];
|
|
13
|
-
for (const part of parts) {
|
|
14
|
-
if (!part)
|
|
15
|
-
continue;
|
|
16
|
-
// Split camelCase and consecutive uppercase (e.g., HTMLParser → HTML, Parser)
|
|
17
|
-
const camelTokens = part
|
|
18
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
|
|
19
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
20
|
-
.split("_");
|
|
21
|
-
for (const t of camelTokens) {
|
|
22
|
-
if (t)
|
|
23
|
-
tokens.push(t.toLowerCase());
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return tokens;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Score how well a set of keyword tokens matches field name tokens.
|
|
30
|
-
* Returns 0-1:
|
|
31
|
-
* 1.0 — exact full match (joined tokens equal joined keywords)
|
|
32
|
-
* 0.9 — all keyword tokens found in field tokens with high coverage (>50%)
|
|
33
|
-
* 0.8 — field ends with keyword tokens
|
|
34
|
-
* 0.7 — substring match (keyword appears in joined field name)
|
|
35
|
-
* 0.65 — all keyword tokens found but low coverage (<=50%)
|
|
36
|
-
*/
|
|
37
|
-
export function scoreMatch(fieldTokens, keywords) {
|
|
38
|
-
const fieldJoined = fieldTokens.join("");
|
|
39
|
-
let bestScore = 0;
|
|
40
|
-
// Try each keyword variant, keep the highest score
|
|
41
|
-
for (const keyword of keywords) {
|
|
42
|
-
const kwTokens = tokenizeFieldName(keyword);
|
|
43
|
-
const kwJoined = kwTokens.join("");
|
|
44
|
-
// Exact match: joined tokens are identical
|
|
45
|
-
if (fieldJoined === kwJoined)
|
|
46
|
-
return 1.0;
|
|
47
|
-
// All keyword tokens present in field tokens with high coverage
|
|
48
|
-
if (kwTokens.length > 0 &&
|
|
49
|
-
kwTokens.every((kt) => fieldTokens.includes(kt))) {
|
|
50
|
-
const coverage = kwTokens.length / fieldTokens.length;
|
|
51
|
-
const score = coverage > 0.5 ? 0.9 : 0.65;
|
|
52
|
-
bestScore = Math.max(bestScore, score);
|
|
53
|
-
}
|
|
54
|
-
// Field ends with keyword tokens
|
|
55
|
-
if (kwTokens.length > 0 && kwTokens.length <= fieldTokens.length) {
|
|
56
|
-
const tail = fieldTokens.slice(-kwTokens.length);
|
|
57
|
-
if (tail.every((t, i) => t === kwTokens[i])) {
|
|
58
|
-
bestScore = Math.max(bestScore, 0.8);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// Substring match: keyword joined appears in field joined
|
|
62
|
-
if (kwJoined.length >= 3 && fieldJoined.includes(kwJoined)) {
|
|
63
|
-
bestScore = Math.max(bestScore, 0.7);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return bestScore;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Find the best field mapping for a given field name and schema.
|
|
70
|
-
* Returns the highest-scoring match above its threshold, or undefined.
|
|
71
|
-
*/
|
|
72
|
-
export function findBestMapping(fieldName, schema, mappings = ALL_FIELD_MAPPINGS) {
|
|
73
|
-
const schemaType = typeof schema.type === "string" ? schema.type : undefined;
|
|
74
|
-
// Priority: format:uuid always maps to string.uuid
|
|
75
|
-
if (schemaType === "string" && schema.format === "uuid") {
|
|
76
|
-
return {
|
|
77
|
-
mapping: {
|
|
78
|
-
keywords: ["uuid"],
|
|
79
|
-
fakerMethod: "string.uuid",
|
|
80
|
-
schemaType: "string",
|
|
81
|
-
minScore: 0.5,
|
|
82
|
-
},
|
|
83
|
-
score: 1.0,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
// Skip if schema already has pattern, enum, faker, or $ref constraint
|
|
87
|
-
// Also skip string mappings when minLength/maxLength is set — name-based
|
|
88
|
-
// mappings (e.g. lorem.word for "label") don't honor JSON Schema length
|
|
89
|
-
// constraints, so they'd produce out-of-range strings ~20% of the time.
|
|
90
|
-
// Mirrors the numeric constraint skip just below.
|
|
91
|
-
if (schema.pattern ||
|
|
92
|
-
schema.enum ||
|
|
93
|
-
("faker" in schema && schema.faker) ||
|
|
94
|
-
schema.$ref ||
|
|
95
|
-
(schemaType === "string" &&
|
|
96
|
-
(schema.minLength !== undefined || schema.maxLength !== undefined))) {
|
|
97
|
-
return undefined;
|
|
98
|
-
}
|
|
99
|
-
// Skip numeric faker mapping when schema already constrains the range
|
|
100
|
-
const hasNumericConstraints = schema.minimum !== undefined ||
|
|
101
|
-
schema.maximum !== undefined ||
|
|
102
|
-
schema.exclusiveMinimum !== undefined ||
|
|
103
|
-
schema.exclusiveMaximum !== undefined;
|
|
104
|
-
const tokens = tokenizeFieldName(fieldName);
|
|
105
|
-
let best;
|
|
106
|
-
for (const mapping of mappings) {
|
|
107
|
-
// Skip numeric faker mappings when schema has explicit constraints
|
|
108
|
-
if (hasNumericConstraints &&
|
|
109
|
-
(mapping.schemaType === "number" || mapping.schemaType === "integer")) {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
// Skip faker mappings for object/array schemas — they generate primitive values
|
|
113
|
-
if (schemaType === "object" || schemaType === "array") {
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
// Check schema type constraint
|
|
117
|
-
if (mapping.schemaType && schemaType && mapping.schemaType !== schemaType) {
|
|
118
|
-
const isNumeric = (mapping.schemaType === "number" && schemaType === "integer") ||
|
|
119
|
-
(mapping.schemaType === "integer" && schemaType === "number");
|
|
120
|
-
if (!isNumeric)
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
const score = scoreMatch(tokens, mapping.keywords);
|
|
124
|
-
if (score >= mapping.minScore && (!best || score > best.score)) {
|
|
125
|
-
best = { mapping, score };
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
// ID suffix detection: fields ending in Id/_id with string type → UUID
|
|
129
|
-
// Only if no better match was found or the existing match is weak
|
|
130
|
-
if (schemaType === "string") {
|
|
131
|
-
const lastToken = tokens[tokens.length - 1];
|
|
132
|
-
if (lastToken === "id" && tokens.length > 1) {
|
|
133
|
-
const idScore = 0.8;
|
|
134
|
-
if (!best || best.score < idScore) {
|
|
135
|
-
best = {
|
|
136
|
-
mapping: {
|
|
137
|
-
keywords: ["id"],
|
|
138
|
-
fakerMethod: "string.uuid",
|
|
139
|
-
schemaType: "string",
|
|
140
|
-
minScore: 0.5,
|
|
141
|
-
},
|
|
142
|
-
score: idScore,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return best;
|
|
148
|
-
}
|
package/dist/jsf-config.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { base, en, Faker } from "@faker-js/faker";
|
|
2
|
-
import { generate, } from "json-schema-faker";
|
|
3
|
-
const MAX_GENERATION_SEED = 2_147_483_647;
|
|
4
|
-
/**
|
|
5
|
-
* Create isolated faker instance to avoid race conditions.
|
|
6
|
-
* Each generation gets its own faker instance to ensure thread-safety.
|
|
7
|
-
*/
|
|
8
|
-
export function createFakerInstance(seed) {
|
|
9
|
-
const faker = new Faker({ locale: [en, base] });
|
|
10
|
-
if (seed !== undefined) {
|
|
11
|
-
faker.seed(seed);
|
|
12
|
-
}
|
|
13
|
-
return faker;
|
|
14
|
-
}
|
|
15
|
-
export function resolveGenerationSeed(seed) {
|
|
16
|
-
return seed ?? Math.floor(Math.random() * MAX_GENERATION_SEED);
|
|
17
|
-
}
|
|
18
|
-
export function createSeededRandom(seed) {
|
|
19
|
-
let state = seed;
|
|
20
|
-
return () => {
|
|
21
|
-
state += 0x6d2b79f5;
|
|
22
|
-
let value = state;
|
|
23
|
-
value = Math.imul(value ^ (value >>> 15), value | 1);
|
|
24
|
-
value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
|
|
25
|
-
return ((value ^ (value >>> 14)) >>> 0) / 4_294_967_296;
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
function normalizeDefinitionForJsf(definition) {
|
|
29
|
-
return typeof definition === "boolean"
|
|
30
|
-
? definition
|
|
31
|
-
: normalizeSchemaForJsf(definition);
|
|
32
|
-
}
|
|
33
|
-
function normalizeDefinitionMap(definitions) {
|
|
34
|
-
return Object.fromEntries(Object.entries(definitions).map(([key, definition]) => [
|
|
35
|
-
key,
|
|
36
|
-
normalizeDefinitionForJsf(definition),
|
|
37
|
-
]));
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* json-schema-faker consumes tuple schemas through the 2020-12 `prefixItems`
|
|
41
|
-
* keyword. Convert Draft 7 tuple `items` recursively at the library boundary.
|
|
42
|
-
*/
|
|
43
|
-
export function normalizeSchemaForJsf(schema) {
|
|
44
|
-
const normalized = {};
|
|
45
|
-
for (const [key, value] of Object.entries(schema)) {
|
|
46
|
-
normalized[key] = value;
|
|
47
|
-
}
|
|
48
|
-
if (schema.properties) {
|
|
49
|
-
normalized.properties = normalizeDefinitionMap(schema.properties);
|
|
50
|
-
}
|
|
51
|
-
if (schema.definitions) {
|
|
52
|
-
normalized.definitions = normalizeDefinitionMap(schema.definitions);
|
|
53
|
-
}
|
|
54
|
-
if (schema.$defs) {
|
|
55
|
-
normalized.$defs = normalizeDefinitionMap(schema.$defs);
|
|
56
|
-
}
|
|
57
|
-
if (schema.patternProperties) {
|
|
58
|
-
normalized.patternProperties = normalizeDefinitionMap(schema.patternProperties);
|
|
59
|
-
}
|
|
60
|
-
if (Array.isArray(schema.items)) {
|
|
61
|
-
normalized.prefixItems = schema.items.map(normalizeDefinitionForJsf);
|
|
62
|
-
normalized.items =
|
|
63
|
-
schema.additionalItems === undefined
|
|
64
|
-
? true
|
|
65
|
-
: normalizeDefinitionForJsf(schema.additionalItems);
|
|
66
|
-
delete normalized.additionalItems;
|
|
67
|
-
}
|
|
68
|
-
else if (schema.items !== undefined) {
|
|
69
|
-
normalized.items = normalizeDefinitionForJsf(schema.items);
|
|
70
|
-
}
|
|
71
|
-
for (const keyword of ["allOf", "anyOf", "oneOf"]) {
|
|
72
|
-
const definitions = schema[keyword];
|
|
73
|
-
if (definitions) {
|
|
74
|
-
normalized[keyword] = definitions.map(normalizeDefinitionForJsf);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (schema.additionalProperties !== undefined) {
|
|
78
|
-
normalized.additionalProperties = normalizeDefinitionForJsf(schema.additionalProperties);
|
|
79
|
-
}
|
|
80
|
-
for (const keyword of [
|
|
81
|
-
"contains",
|
|
82
|
-
"not",
|
|
83
|
-
"if",
|
|
84
|
-
"then",
|
|
85
|
-
"else",
|
|
86
|
-
"propertyNames",
|
|
87
|
-
]) {
|
|
88
|
-
const definition = schema[keyword];
|
|
89
|
-
if (definition !== undefined) {
|
|
90
|
-
normalized[keyword] = normalizeDefinitionForJsf(definition);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (schema.dependencies) {
|
|
94
|
-
normalized.dependencies = Object.fromEntries(Object.entries(schema.dependencies).map(([key, dependency]) => [
|
|
95
|
-
key,
|
|
96
|
-
Array.isArray(dependency)
|
|
97
|
-
? [...dependency]
|
|
98
|
-
: normalizeDefinitionForJsf(dependency),
|
|
99
|
-
]));
|
|
100
|
-
}
|
|
101
|
-
return normalized;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Generate data from a JSON schema using json-schema-faker 0.6.0 async API.
|
|
105
|
-
* Stateless — each call is self-contained with its own faker instance and options.
|
|
106
|
-
*/
|
|
107
|
-
export async function generateWithJsf(schema, seed) {
|
|
108
|
-
const options = {
|
|
109
|
-
seed,
|
|
110
|
-
optionalsProbability: 1.0,
|
|
111
|
-
alwaysFakeOptionals: true,
|
|
112
|
-
useDefaultValue: true,
|
|
113
|
-
failOnInvalidTypes: false,
|
|
114
|
-
extensions: { faker: createFakerInstance(seed) },
|
|
115
|
-
};
|
|
116
|
-
return generate(normalizeSchemaForJsf(schema), options);
|
|
117
|
-
}
|
package/dist/overrides.js
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_ARRAY_COUNT } from "./constants.js";
|
|
2
|
-
const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
3
|
-
function isRecord(value) {
|
|
4
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Determine number of items to generate for array schema
|
|
8
|
-
* Prefers explicit count, then schema minItems/maxItems, with sane defaults
|
|
9
|
-
* @param schema - Array schema with optional minItems/maxItems
|
|
10
|
-
* @param explicitCount - Explicit count override from plugin options
|
|
11
|
-
* @returns Number of array items to generate
|
|
12
|
-
*/
|
|
13
|
-
export function determineArrayCount(schema, explicitCount, random = Math.random) {
|
|
14
|
-
if (explicitCount !== undefined) {
|
|
15
|
-
// Handle negative or invalid counts
|
|
16
|
-
if (explicitCount < 0) {
|
|
17
|
-
return 0;
|
|
18
|
-
}
|
|
19
|
-
return explicitCount;
|
|
20
|
-
}
|
|
21
|
-
if (schema.minItems !== undefined && schema.maxItems !== undefined) {
|
|
22
|
-
const min = schema.minItems;
|
|
23
|
-
const max = Math.max(min, schema.maxItems);
|
|
24
|
-
return Math.floor(random() * (max - min + 1)) + min;
|
|
25
|
-
}
|
|
26
|
-
if (schema.minItems !== undefined) {
|
|
27
|
-
return Math.max(schema.minItems, DEFAULT_ARRAY_COUNT);
|
|
28
|
-
}
|
|
29
|
-
if (schema.maxItems !== undefined) {
|
|
30
|
-
return Math.min(schema.maxItems, DEFAULT_ARRAY_COUNT);
|
|
31
|
-
}
|
|
32
|
-
if (Array.isArray(schema.items)) {
|
|
33
|
-
return schema.items.length;
|
|
34
|
-
}
|
|
35
|
-
return DEFAULT_ARRAY_COUNT;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Apply overrides to generated data with support for templates
|
|
39
|
-
* Supports nested paths (dot notation), templates with {{params.id}}, and state access
|
|
40
|
-
* @param data - Generated data to apply overrides to
|
|
41
|
-
* @param overrides - Override values (can use templates)
|
|
42
|
-
* @param params - Route parameters for template expansion
|
|
43
|
-
* @param state - Plugin state for template expansion
|
|
44
|
-
* @param query - Query parameters for template expansion
|
|
45
|
-
* @returns Data with overrides applied
|
|
46
|
-
*/
|
|
47
|
-
export function applyOverrides(data, overrides, params, state, query) {
|
|
48
|
-
if (!overrides)
|
|
49
|
-
return data;
|
|
50
|
-
if (!isRecord(data))
|
|
51
|
-
return data;
|
|
52
|
-
const result = structuredClone(data);
|
|
53
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
54
|
-
if (DANGEROUS_KEYS.has(key))
|
|
55
|
-
continue;
|
|
56
|
-
// Handle nested paths like "data.id" or "pagination.page"
|
|
57
|
-
if (key.includes(".")) {
|
|
58
|
-
setNestedProperty(result, key, value, { params, state, query });
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
// Handle flat keys and nested objects
|
|
62
|
-
if (isRecord(value)) {
|
|
63
|
-
// Recursively apply nested overrides
|
|
64
|
-
if (isRecord(result[key])) {
|
|
65
|
-
result[key] = applyOverrides(result[key], value, params, state, query);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
result[key] = applyOverrides({}, value, params, state, query);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
else if (typeof value === "string" && value.includes("{{")) {
|
|
72
|
-
// Template processing
|
|
73
|
-
result[key] = processTemplate(value, { params, state, query });
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
result[key] = value;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return result;
|
|
81
|
-
}
|
|
82
|
-
function setNestedProperty(obj, path, value, context) {
|
|
83
|
-
const parts = path.split(".");
|
|
84
|
-
let current = obj;
|
|
85
|
-
// Navigate to the parent of the target property
|
|
86
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
87
|
-
const part = parts[i];
|
|
88
|
-
if (DANGEROUS_KEYS.has(part))
|
|
89
|
-
return;
|
|
90
|
-
const next = current[part];
|
|
91
|
-
if (isRecord(next)) {
|
|
92
|
-
current = next;
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
const nested = {};
|
|
96
|
-
current[part] = nested;
|
|
97
|
-
current = nested;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// Set the final property
|
|
101
|
-
const finalKey = parts[parts.length - 1];
|
|
102
|
-
if (DANGEROUS_KEYS.has(finalKey))
|
|
103
|
-
return;
|
|
104
|
-
if (typeof value === "string" && value.includes("{{")) {
|
|
105
|
-
current[finalKey] = processTemplate(value, context);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
current[finalKey] = value;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function resolveTemplatePath(context, expression) {
|
|
112
|
-
const parts = expression.trim().split(".");
|
|
113
|
-
let result = context;
|
|
114
|
-
for (const part of parts) {
|
|
115
|
-
if (isRecord(result)) {
|
|
116
|
-
result = result[part];
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
return undefined;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return result;
|
|
123
|
-
}
|
|
124
|
-
function processTemplate(template, context) {
|
|
125
|
-
// Check if the template is just a single template expression
|
|
126
|
-
const singleTemplateMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
|
|
127
|
-
if (singleTemplateMatch) {
|
|
128
|
-
// For single templates, return the actual value without string conversion
|
|
129
|
-
const result = resolveTemplatePath(context, singleTemplateMatch[1]);
|
|
130
|
-
return result !== undefined ? result : template;
|
|
131
|
-
}
|
|
132
|
-
// For templates mixed with other text, do string replacement
|
|
133
|
-
return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, expression) => {
|
|
134
|
-
const result = resolveTemplatePath(context, expression);
|
|
135
|
-
return result !== undefined ? String(result) : match;
|
|
136
|
-
});
|
|
137
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { findBestMapping } from "./field-name-matcher.js";
|
|
2
|
-
import { isJSONSchema7, validateFakerMethod } from "./validation.js";
|
|
3
|
-
function needsStringFallback(schema) {
|
|
4
|
-
return (schema.type === "string" &&
|
|
5
|
-
schema.faker === undefined &&
|
|
6
|
-
schema.format === undefined &&
|
|
7
|
-
schema.pattern === undefined &&
|
|
8
|
-
schema.enum === undefined &&
|
|
9
|
-
schema.const === undefined &&
|
|
10
|
-
schema.$ref === undefined &&
|
|
11
|
-
schema.minLength === undefined &&
|
|
12
|
-
schema.maxLength === undefined);
|
|
13
|
-
}
|
|
14
|
-
export function enhanceSchemaWithSmartMapping(schema) {
|
|
15
|
-
if (!schema || typeof schema !== "object") {
|
|
16
|
-
return schema;
|
|
17
|
-
}
|
|
18
|
-
const enhanced = { ...schema };
|
|
19
|
-
// Handle object properties
|
|
20
|
-
if (enhanced.properties) {
|
|
21
|
-
enhanced.properties = { ...enhanced.properties };
|
|
22
|
-
for (const [fieldName, fieldSchema] of Object.entries(enhanced.properties)) {
|
|
23
|
-
if (isJSONSchema7(fieldSchema)) {
|
|
24
|
-
enhanced.properties[fieldName] = enhanceFieldSchema(fieldName, fieldSchema);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
// Recurse into composition keywords
|
|
29
|
-
for (const keyword of ["allOf", "anyOf", "oneOf"]) {
|
|
30
|
-
const branches = enhanced[keyword];
|
|
31
|
-
if (Array.isArray(branches)) {
|
|
32
|
-
enhanced[keyword] = branches.map((branch) => isJSONSchema7(branch) ? enhanceSchemaWithSmartMapping(branch) : branch);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
// Recurse into array items
|
|
36
|
-
if (enhanced.items) {
|
|
37
|
-
if (Array.isArray(enhanced.items)) {
|
|
38
|
-
enhanced.items = enhanced.items.map((item) => isJSONSchema7(item) ? enhanceSchemaWithSmartMapping(item) : item);
|
|
39
|
-
}
|
|
40
|
-
else if (isJSONSchema7(enhanced.items)) {
|
|
41
|
-
enhanced.items = enhanceSchemaWithSmartMapping(enhanced.items);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// Recurse into additionalProperties
|
|
45
|
-
if (isJSONSchema7(enhanced.additionalProperties)) {
|
|
46
|
-
enhanced.additionalProperties = enhanceSchemaWithSmartMapping(enhanced.additionalProperties);
|
|
47
|
-
}
|
|
48
|
-
if (needsStringFallback(enhanced)) {
|
|
49
|
-
enhanced.faker = "lorem.word";
|
|
50
|
-
}
|
|
51
|
-
return enhanced;
|
|
52
|
-
}
|
|
53
|
-
function enhanceFieldSchema(fieldName, fieldSchema) {
|
|
54
|
-
const enhanced = { ...fieldSchema };
|
|
55
|
-
// If already has faker extension, validate it and don't override.
|
|
56
|
-
// User-supplied faker values are always strings; the object form is only
|
|
57
|
-
// produced internally by this function when fakerArgs are present.
|
|
58
|
-
if (enhanced.faker) {
|
|
59
|
-
if (typeof enhanced.faker === "string") {
|
|
60
|
-
validateFakerMethod(enhanced.faker);
|
|
61
|
-
}
|
|
62
|
-
return enhanced;
|
|
63
|
-
}
|
|
64
|
-
// Recursively enhance nested schemas first
|
|
65
|
-
const hasComposition = enhanced.allOf || enhanced.anyOf || enhanced.oneOf;
|
|
66
|
-
if (enhanced.properties || hasComposition || enhanced.items) {
|
|
67
|
-
const recursed = enhanceSchemaWithSmartMapping(enhanced);
|
|
68
|
-
Object.assign(enhanced, recursed);
|
|
69
|
-
}
|
|
70
|
-
// Don't apply field-level faker mapping to composition schemas — the branches define their own types
|
|
71
|
-
if (hasComposition) {
|
|
72
|
-
return enhanced;
|
|
73
|
-
}
|
|
74
|
-
// Don't apply smart mapping when const or enum is defined — these have fixed values
|
|
75
|
-
if (enhanced.const !== undefined || enhanced.enum) {
|
|
76
|
-
return enhanced;
|
|
77
|
-
}
|
|
78
|
-
// Apply smart field name mapping via the scoring matcher
|
|
79
|
-
const match = findBestMapping(fieldName, enhanced);
|
|
80
|
-
if (match) {
|
|
81
|
-
const { fakerMethod, format, trueProbability, fakerArgs } = match.mapping;
|
|
82
|
-
// Use the JSF object form when fakerArgs are present so the options object
|
|
83
|
-
// is forwarded to the faker method (e.g. number.int({ min, max })).
|
|
84
|
-
// JSF calls Q(...J) where J is the value, so we must wrap fakerArgs in an
|
|
85
|
-
// array: { "number.int": [{ min, max }] } → faker.number.int({ min, max }).
|
|
86
|
-
// Fall back to the plain string form when there are no args.
|
|
87
|
-
if (fakerArgs) {
|
|
88
|
-
enhanced.faker = { [fakerMethod]: [fakerArgs] };
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
enhanced.faker = fakerMethod;
|
|
92
|
-
}
|
|
93
|
-
if (format) {
|
|
94
|
-
enhanced.format = format;
|
|
95
|
-
}
|
|
96
|
-
if (trueProbability !== undefined) {
|
|
97
|
-
enhanced.schmockTrueProbability = trueProbability;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
else if (needsStringFallback(enhanced)) {
|
|
101
|
-
enhanced.faker = "lorem.word";
|
|
102
|
-
}
|
|
103
|
-
return enhanced;
|
|
104
|
-
}
|
package/dist/test-utils.d.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { JSONSchema7 } from "json-schema";
|
|
2
|
-
interface FakerSchema extends JSONSchema7 {
|
|
3
|
-
faker?: string;
|
|
4
|
-
}
|
|
5
|
-
export declare const schemas: {
|
|
6
|
-
simple: {
|
|
7
|
-
string: () => JSONSchema7;
|
|
8
|
-
number: () => JSONSchema7;
|
|
9
|
-
object: (properties?: Record<string, JSONSchema7>) => JSONSchema7;
|
|
10
|
-
array: (items: JSONSchema7, constraints?: {
|
|
11
|
-
minItems?: number;
|
|
12
|
-
maxItems?: number;
|
|
13
|
-
}) => JSONSchema7;
|
|
14
|
-
};
|
|
15
|
-
withFaker: (type: JSONSchema7["type"], fakerMethod: string) => FakerSchema;
|
|
16
|
-
nested: {
|
|
17
|
-
deep: (depth: number, leafSchema?: JSONSchema7) => JSONSchema7;
|
|
18
|
-
wide: (width: number, propertySchema?: JSONSchema7) => JSONSchema7;
|
|
19
|
-
};
|
|
20
|
-
complex: {
|
|
21
|
-
user: () => JSONSchema7;
|
|
22
|
-
apiResponse: () => JSONSchema7;
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
export declare const validators: {
|
|
26
|
-
isFieldMapped: (fieldName: string, fieldType?: JSONSchema7["type"]) => Promise<boolean>;
|
|
27
|
-
uniquenessRatio: (samples: unknown[]) => number;
|
|
28
|
-
allMatch: <T>(samples: T[], validator: (sample: T) => boolean) => boolean;
|
|
29
|
-
appearsToBeFromCategory: (samples: string[], category: "email" | "name" | "phone" | "address" | "uuid" | "date") => boolean;
|
|
30
|
-
};
|
|
31
|
-
export declare const performance: {
|
|
32
|
-
measure: <T>(fn: () => T | Promise<T>) => Promise<{
|
|
33
|
-
result: T;
|
|
34
|
-
duration: number;
|
|
35
|
-
}>;
|
|
36
|
-
measureMemory: (fn: () => void) => number;
|
|
37
|
-
benchmark: (_name: string, fn: () => unknown | Promise<unknown>, iterations?: number) => Promise<{
|
|
38
|
-
mean: number;
|
|
39
|
-
min: number;
|
|
40
|
-
max: number;
|
|
41
|
-
}>;
|
|
42
|
-
};
|
|
43
|
-
export declare const generate: {
|
|
44
|
-
samples: (schema: JSONSchema7, count?: number, options?: Omit<Schmock.SchemaGenerationContext, "schema">) => Promise<unknown[]>;
|
|
45
|
-
withSeed: (schema: JSONSchema7, seed?: number) => Promise<unknown>;
|
|
46
|
-
};
|
|
47
|
-
export declare const stats: {
|
|
48
|
-
distribution: (samples: unknown[]) => Map<string, number>;
|
|
49
|
-
entropy: (samples: unknown[]) => number;
|
|
50
|
-
};
|
|
51
|
-
export declare const schemaTests: {
|
|
52
|
-
expectValid: (schema: JSONSchema7) => Promise<void>;
|
|
53
|
-
expectInvalid: (schema: JSONSchema7, errorMessage?: string | RegExp) => Promise<void>;
|
|
54
|
-
expectSchemaError: (schema: JSONSchema7, path: string, issue?: string) => Promise<void>;
|
|
55
|
-
};
|
|
56
|
-
export {};
|
|
57
|
-
//# sourceMappingURL=test-utils.d.ts.map
|
package/dist/test-utils.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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;AAOD,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;+BAmDQ,OAAO,EAAE,KAAG,MAAM;eAMlC,CAAC,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,KAAG,OAAO;uCAM5D,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,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,0BAEnC,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;sBAET,WAAW,4BAET,IAAI,CAAC,OAAO,CAAC,uBAAuB,EAAE,QAAQ,CAAC,KACxD,OAAO,CAAC,OAAO,EAAE,CAAC;uBAQI,WAAW,SAAS,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;CAGvE,CAAC;AAGF,eAAO,MAAM,KAAK;4BACQ,OAAO,EAAE,KAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;uBASpC,OAAO,EAAE,KAAG,MAAM;CActC,CAAC;AAGF,eAAO,MAAM,WAAW;0BACM,WAAW,KAAG,OAAO,CAAC,IAAI,CAAC;4BAK7C,WAAW,iBACJ,MAAM,GAAG,MAAM,KAC7B,OAAO,CAAC,IAAI,CAAC;gCAWN,WAAW,QACb,MAAM,UACJ,MAAM,KACb,OAAO,CAAC,IAAI,CAAC;CAuBjB,CAAC"}
|