@schmock/faker 2.2.2 → 2.3.0

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,5 +1,8 @@
1
1
  import { expect } from "vitest";
2
2
  import { generateFromSchema } from "./index";
3
+ function isRecord(value) {
4
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5
+ }
3
6
  // Schema Factory Functions
4
7
  export const schemas = {
5
8
  simple: {
@@ -89,16 +92,22 @@ export const validators = {
89
92
  // Generate multiple samples to check for patterns
90
93
  const mappedSamples = [];
91
94
  for (let i = 0; i < 10; i++) {
92
- const result = (await generateFromSchema({
95
+ const result = await generateFromSchema({
93
96
  schema: mappedSchema,
94
- }));
97
+ });
98
+ if (!isRecord(result)) {
99
+ throw new Error("Expected mapped schema generation to return an object");
100
+ }
95
101
  mappedSamples.push(result[fieldName]);
96
102
  }
97
103
  const unmappedSamples = [];
98
104
  for (let i = 0; i < 10; i++) {
99
- const result = (await generateFromSchema({
105
+ const result = await generateFromSchema({
100
106
  schema: unmappedSchema,
101
- }));
107
+ });
108
+ if (!isRecord(result)) {
109
+ throw new Error("Expected unmapped schema generation to return an object");
110
+ }
102
111
  unmappedSamples.push(result.unmappedRandomField12345);
103
112
  }
104
113
  // If field is mapped to a specific faker method, it should have different characteristics
@@ -179,14 +188,12 @@ export const generate = {
179
188
  samples: async (schema, count = 10, options) => {
180
189
  const results = [];
181
190
  for (let i = 0; i < count; i++) {
182
- results.push((await generateFromSchema({ schema, ...options })));
191
+ results.push(await generateFromSchema({ schema, ...options }));
183
192
  }
184
193
  return results;
185
194
  },
186
- withSeed: async (schema, _seed) => {
187
- // Note: faker.js doesn't support seeding in the same way,
188
- // but we can at least ensure consistent test behavior
189
- return await generateFromSchema({ schema });
195
+ withSeed: async (schema, seed) => {
196
+ return await generateFromSchema({ schema, seed });
190
197
  },
191
198
  };
192
199
  // Statistical Analysis
@@ -194,7 +201,7 @@ export const stats = {
194
201
  distribution: (samples) => {
195
202
  const dist = new Map();
196
203
  for (const sample of samples) {
197
- const key = JSON.stringify(sample);
204
+ const key = JSON.stringify(sample) ?? "undefined";
198
205
  dist.set(key, (dist.get(key) || 0) + 1);
199
206
  }
200
207
  return dist;
@@ -231,9 +238,15 @@ export const schemaTests = {
231
238
  throw new Error("Expected schema validation to fail");
232
239
  }
233
240
  catch (error) {
241
+ expect(error).toBeInstanceOf(Error);
242
+ if (!(error instanceof Error)) {
243
+ throw new Error("Expected schema validation to throw an Error");
244
+ }
234
245
  expect(error.name).toBe("SchemaValidationError");
235
246
  // The schemaPath is in the context
236
- if (error.context?.schemaPath) {
247
+ if ("context" in error &&
248
+ isRecord(error.context) &&
249
+ error.context.schemaPath) {
237
250
  expect(error.context.schemaPath).toBe(path);
238
251
  }
239
252
  if (issue) {
@@ -248,7 +261,7 @@ function analyzeDataCharacteristics(samples) {
248
261
  return "empty";
249
262
  const first = samples[0];
250
263
  const type = typeof first;
251
- if (type !== "string")
264
+ if (!samples.every((sample) => typeof sample === "string"))
252
265
  return type;
253
266
  // Analyze string characteristics
254
267
  const characteristics = [type];
@@ -288,7 +288,7 @@ export function validateFakerMethod(fakerMethod) {
288
288
  let current = faker;
289
289
  for (const part of parts) {
290
290
  if (current && typeof current === "object" && part in current) {
291
- current = current[part];
291
+ current = Reflect.get(current, part);
292
292
  }
293
293
  else {
294
294
  throw new SchemaValidationError("$.faker", `Invalid faker method: "${fakerMethod}"`, "Check faker.js documentation for valid methods");
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": "2.2.2",
4
+ "version": "2.3.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "build": "bun build:lib && bun build:types",
22
22
  "build:lib": "bun build --minify --target browser --outdir=dist src/index.ts --external @faker-js/faker --external json-schema-faker --external @schmock/core",
23
23
  "build:types": "tsc -p tsconfig.json",
24
- "test": "vitest",
24
+ "test": "vitest run",
25
25
  "test:watch": "vitest --watch",
26
26
  "test:bdd": "vitest run --config vitest.config.bdd.ts",
27
27
  "lint": "biome check src/*.ts",
@@ -30,15 +30,15 @@
30
30
  },
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
+ "@types/json-schema": "^7.0.15",
33
34
  "@faker-js/faker": "^10.5.0",
34
35
  "json-schema-faker": "^0.6.2"
35
36
  },
36
37
  "peerDependencies": {
37
- "@schmock/core": "^2.2.2"
38
+ "@schmock/core": "^2.3.0"
38
39
  },
39
40
  "devDependencies": {
40
41
  "@amiceli/vitest-cucumber": "^7.0.0",
41
- "@types/json-schema": "^7.0.15",
42
42
  "@types/node": "^26.1.0",
43
43
  "vitest": "^4.1.9"
44
44
  }
@@ -412,7 +412,14 @@ const text: FieldMapping[] = [
412
412
  // ── Business/Commerce ───────────────────────────────────────────────
413
413
  const business: FieldMapping[] = [
414
414
  {
415
- keywords: ["company", "organization", "org", "employer", "brand"],
415
+ keywords: [
416
+ "company",
417
+ "organization",
418
+ "org",
419
+ "employer",
420
+ "brand",
421
+ "operator",
422
+ ],
416
423
  fakerMethod: "company.name",
417
424
  schemaType: "string",
418
425
  minScore: 0.6,
@@ -93,7 +93,6 @@ export function findBestMapping(
93
93
  schema: JSONSchema7,
94
94
  mappings: FieldMapping[] = ALL_FIELD_MAPPINGS,
95
95
  ): MatchResult | undefined {
96
- const schemaAny = schema as Record<string, unknown>;
97
96
  const schemaType = typeof schema.type === "string" ? schema.type : undefined;
98
97
 
99
98
  // Priority: format:uuid always maps to string.uuid
@@ -115,10 +114,10 @@ export function findBestMapping(
115
114
  // constraints, so they'd produce out-of-range strings ~20% of the time.
116
115
  // Mirrors the numeric constraint skip just below.
117
116
  if (
118
- schemaAny.pattern ||
119
- schemaAny.enum ||
120
- schemaAny.faker ||
121
- schemaAny.$ref ||
117
+ schema.pattern ||
118
+ schema.enum ||
119
+ ("faker" in schema && schema.faker) ||
120
+ schema.$ref ||
122
121
  (schemaType === "string" &&
123
122
  (schema.minLength !== undefined || schema.maxLength !== undefined))
124
123
  ) {
package/src/index.test.ts CHANGED
@@ -41,6 +41,108 @@ describe("Schema Generator", () => {
41
41
  });
42
42
  });
43
43
 
44
+ it("uses one seeded sequence across a generated array", async () => {
45
+ const schema: JSONSchema7 = {
46
+ type: "array",
47
+ items: {
48
+ type: "object",
49
+ properties: {
50
+ id: { type: "integer" },
51
+ name: { type: "string" },
52
+ },
53
+ required: ["id", "name"],
54
+ },
55
+ };
56
+
57
+ const first = await generateFromSchema({ schema, count: 5, seed: 42 });
58
+ const second = await generateFromSchema({ schema, count: 5, seed: 42 });
59
+
60
+ expect(first).toEqual(second);
61
+ expect(Array.isArray(first)).toBe(true);
62
+ if (!Array.isArray(first)) {
63
+ throw new Error("Expected generated data to be an array");
64
+ }
65
+ expect(new Set(first.map((item) => JSON.stringify(item))).size).toBe(5);
66
+ });
67
+
68
+ it("generates tuple positions from their respective schemas", async () => {
69
+ const schema: JSONSchema7 = {
70
+ type: "array",
71
+ items: [{ type: "string" }, { type: "integer" }, { type: "boolean" }],
72
+ minItems: 3,
73
+ maxItems: 3,
74
+ };
75
+
76
+ const result = await generateFromSchema({ schema, seed: 42 });
77
+
78
+ expect(Array.isArray(result)).toBe(true);
79
+ if (!Array.isArray(result)) {
80
+ throw new Error("Expected generated data to be a tuple");
81
+ }
82
+ expect(result).toHaveLength(3);
83
+ expect(typeof result[0]).toBe("string");
84
+ expect(typeof result[1]).toBe("number");
85
+ expect(typeof result[2]).toBe("boolean");
86
+ });
87
+
88
+ it("generates a nested tuple reached through a definition reference", async () => {
89
+ const schema: JSONSchema7 = {
90
+ type: "object",
91
+ properties: {
92
+ payload: { $ref: "#/definitions/payloadTuple" },
93
+ },
94
+ required: ["payload"],
95
+ definitions: {
96
+ payloadTuple: {
97
+ type: "array",
98
+ items: [
99
+ { type: "string" },
100
+ { type: "integer" },
101
+ { type: "boolean" },
102
+ ],
103
+ minItems: 3,
104
+ maxItems: 3,
105
+ additionalItems: false,
106
+ },
107
+ },
108
+ };
109
+
110
+ const result = await generateFromSchema({ schema, seed: 42 });
111
+
112
+ expect(result).toMatchObject({ payload: expect.any(Array) });
113
+ if (
114
+ typeof result !== "object" ||
115
+ result === null ||
116
+ !("payload" in result) ||
117
+ !Array.isArray(result.payload)
118
+ ) {
119
+ throw new Error("Expected generated payload to be a tuple");
120
+ }
121
+ expect(result.payload).toHaveLength(3);
122
+ expect(typeof result.payload[0]).toBe("string");
123
+ expect(typeof result.payload[1]).toBe("number");
124
+ expect(typeof result.payload[2]).toBe("boolean");
125
+ });
126
+
127
+ it("honors uniqueItems while generating the complete array", async () => {
128
+ const schema: JSONSchema7 = {
129
+ type: "array",
130
+ items: { type: "integer" },
131
+ minItems: 8,
132
+ maxItems: 8,
133
+ uniqueItems: true,
134
+ };
135
+
136
+ const result = await generateFromSchema({ schema, seed: 42 });
137
+
138
+ expect(Array.isArray(result)).toBe(true);
139
+ if (!Array.isArray(result)) {
140
+ throw new Error("Expected generated data to be an array");
141
+ }
142
+ expect(result).toHaveLength(8);
143
+ expect(new Set(result).size).toBe(8);
144
+ });
145
+
44
146
  it("respects array constraints from schema", async () => {
45
147
  const schema = schemas.simple.array(schemas.simple.string(), {
46
148
  minItems: 2,
package/src/index.ts CHANGED
@@ -8,7 +8,11 @@ import {
8
8
  import type { JSONSchema7 } from "json-schema";
9
9
  import { version as packageVersion } from "../package.json";
10
10
  import { MAX_ARRAY_SIZE, NULLABLE_NULL_PROBABILITY } from "./constants.js";
11
- import { generateWithJsf } from "./jsf-config.js";
11
+ import {
12
+ createSeededRandom,
13
+ generateWithJsf,
14
+ resolveGenerationSeed,
15
+ } from "./jsf-config.js";
12
16
  import { applyOverrides, determineArrayCount } from "./overrides.js";
13
17
  import { enhanceSchemaWithSmartMapping } from "./schema-enhancement.js";
14
18
  import { isJSONSchema7, validateSchema } from "./validation.js";
@@ -17,6 +21,10 @@ export type SchemaGenerationContext = Schmock.SchemaGenerationContext;
17
21
 
18
22
  export type FakerPluginOptions = Schmock.FakerPluginOptions;
19
23
 
24
+ function isRecord(value: unknown): value is Record<string, unknown> {
25
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26
+ }
27
+
20
28
  export function fakerPlugin(options: FakerPluginOptions): Schmock.Plugin {
21
29
  // Validate schema immediately when plugin is created (fail-fast)
22
30
  validateSchema(options.schema);
@@ -73,43 +81,39 @@ export async function generateFromSchema(
73
81
 
74
82
  validateSchema(schema);
75
83
 
76
- // Handle array schemas with count
84
+ const generationSeed = resolveGenerationSeed(seed);
85
+ const random = createSeededRandom(generationSeed);
86
+
87
+ let enhancedSchema = enhanceSchemaWithSmartMapping(schema);
88
+
89
+ // Resolve the top-level array size once, then let JSF generate the complete
90
+ // array so tuple positions, uniqueness, and a seeded sequence are preserved.
77
91
  if (schema.type === "array" && schema.items) {
78
- const itemCount = determineArrayCount(schema, count);
92
+ const itemCount = determineArrayCount(schema, count, random);
79
93
 
80
- // Check for resource limits
81
94
  if (itemCount > MAX_ARRAY_SIZE) {
82
95
  throw new ResourceLimitError("array_size", MAX_ARRAY_SIZE, itemCount);
83
96
  }
84
97
 
85
- const rawItemSchema = Array.isArray(schema.items)
86
- ? schema.items[0]
87
- : schema.items;
88
-
89
- if (!rawItemSchema || typeof rawItemSchema === "boolean") {
90
- throw new SchemaValidationError(
91
- "$.items",
92
- "Array schema must have valid items definition",
93
- );
94
- }
98
+ enhancedSchema = {
99
+ ...enhancedSchema,
100
+ minItems: itemCount,
101
+ maxItems: itemCount,
102
+ };
103
+ }
95
104
 
96
- const itemSchema = rawItemSchema;
97
- const enhancedItemSchema = enhanceSchemaWithSmartMapping(itemSchema);
105
+ let generated: unknown = await generateWithJsf(
106
+ enhancedSchema,
107
+ generationSeed,
108
+ );
109
+ generated = postProcessGenerated(generated, enhancedSchema, random);
98
110
 
99
- const items: unknown[] = [];
100
- for (let i = 0; i < itemCount; i++) {
101
- let item: unknown = await generateWithJsf(enhancedItemSchema, seed);
102
- item = postProcessGenerated(item, enhancedItemSchema);
103
- item = applyOverrides(item, overrides, params, state, query);
104
- items.push(item);
105
- }
106
- return items;
111
+ if (Array.isArray(generated)) {
112
+ return generated.map((item) =>
113
+ applyOverrides(item, overrides, params, state, query),
114
+ );
107
115
  }
108
116
 
109
- // Handle object schemas
110
- const enhancedSchema = enhanceSchemaWithSmartMapping(schema);
111
- let generated: unknown = await generateWithJsf(enhancedSchema, seed);
112
- generated = postProcessGenerated(generated, enhancedSchema);
113
117
  generated = applyOverrides(generated, overrides, params, state, query);
114
118
  return generated;
115
119
  }
@@ -120,7 +124,11 @@ export async function generateFromSchema(
120
124
  * - schmockNullable: ~5% chance of null
121
125
  * - schmockTrueProbability: weighted boolean generation
122
126
  */
123
- function postProcessGenerated(data: unknown, schema: JSONSchema7): unknown {
127
+ function postProcessGenerated(
128
+ data: unknown,
129
+ schema: JSONSchema7,
130
+ random: () => number,
131
+ ): unknown {
124
132
  if (
125
133
  data === null ||
126
134
  data === undefined ||
@@ -130,11 +138,9 @@ function postProcessGenerated(data: unknown, schema: JSONSchema7): unknown {
130
138
  return data;
131
139
  }
132
140
 
133
- const schemaExt = schema as Record<string, unknown>;
134
-
135
141
  // Apply nullable probability at this level
136
- if (schemaExt.schmockNullable === true) {
137
- if (Math.random() < NULLABLE_NULL_PROBABILITY) {
142
+ if ("schmockNullable" in schema && schema.schmockNullable === true) {
143
+ if (random() < NULLABLE_NULL_PROBABILITY) {
138
144
  return null;
139
145
  }
140
146
  }
@@ -142,25 +148,36 @@ function postProcessGenerated(data: unknown, schema: JSONSchema7): unknown {
142
148
  // Apply boolean weighting at this level
143
149
  if (
144
150
  schema.type === "boolean" &&
145
- typeof schemaExt.schmockTrueProbability === "number"
151
+ "schmockTrueProbability" in schema &&
152
+ typeof schema.schmockTrueProbability === "number"
146
153
  ) {
147
- return Math.random() < schemaExt.schmockTrueProbability;
154
+ return random() < schema.schmockTrueProbability;
148
155
  }
149
156
 
150
157
  // Recurse into object properties
151
- if (typeof data === "object" && !Array.isArray(data) && schema.properties) {
152
- const record = data as Record<string, unknown>;
158
+ if (isRecord(data) && schema.properties) {
153
159
  for (const [key, propSchema] of Object.entries(schema.properties)) {
154
- if (key in record && isJSONSchema7(propSchema)) {
155
- record[key] = postProcessGenerated(record[key], propSchema);
160
+ if (key in data && isJSONSchema7(propSchema)) {
161
+ data[key] = postProcessGenerated(data[key], propSchema, random);
156
162
  }
157
163
  }
158
164
  }
159
165
 
160
166
  // Recurse into array items
161
- if (Array.isArray(data) && schema.items && isJSONSchema7(schema.items)) {
162
- for (let i = 0; i < data.length; i++) {
163
- data[i] = postProcessGenerated(data[i], schema.items);
167
+ if (Array.isArray(data) && Array.isArray(schema.items)) {
168
+ for (let index = 0; index < data.length; index++) {
169
+ const itemSchema = schema.items[index];
170
+ if (itemSchema && isJSONSchema7(itemSchema)) {
171
+ data[index] = postProcessGenerated(data[index], itemSchema, random);
172
+ }
173
+ }
174
+ } else if (
175
+ Array.isArray(data) &&
176
+ schema.items &&
177
+ isJSONSchema7(schema.items)
178
+ ) {
179
+ for (let index = 0; index < data.length; index++) {
180
+ data[index] = postProcessGenerated(data[index], schema.items, random);
164
181
  }
165
182
  }
166
183
 
@@ -0,0 +1,65 @@
1
+ import type { JSONSchema7 } from "json-schema";
2
+ import { describe, expect, it } from "vitest";
3
+ import { normalizeSchemaForJsf } from "./jsf-config.js";
4
+
5
+ const tuple: JSONSchema7 = {
6
+ type: "array",
7
+ items: [{ type: "string" }, { type: "integer" }],
8
+ minItems: 2,
9
+ maxItems: 2,
10
+ additionalItems: false,
11
+ };
12
+
13
+ function expectNormalizedTuple(value: unknown): void {
14
+ expect(value).toMatchObject({
15
+ type: "array",
16
+ prefixItems: [{ type: "string" }, { type: "integer" }],
17
+ items: false,
18
+ });
19
+ expect(value).not.toHaveProperty("additionalItems");
20
+ }
21
+
22
+ describe("normalizeSchemaForJsf", () => {
23
+ it("recurses through Draft-7 schema-bearing keywords", () => {
24
+ const normalized = normalizeSchemaForJsf({
25
+ type: "object",
26
+ definitions: { legacy: tuple },
27
+ $defs: { modern: tuple },
28
+ patternProperties: { "^tuple$": tuple },
29
+ contains: tuple,
30
+ not: tuple,
31
+ if: tuple,
32
+ // biome-ignore lint/suspicious/noThenProperty: JSON Schema's conditional keyword is named "then"
33
+ then: tuple,
34
+ else: tuple,
35
+ });
36
+
37
+ const record: Record<string, unknown> = normalized;
38
+ const definitions = record.definitions;
39
+ const defs = record.$defs;
40
+ const patternProperties = record.patternProperties;
41
+
42
+ expect(definitions).toBeTypeOf("object");
43
+ expect(defs).toBeTypeOf("object");
44
+ expect(patternProperties).toBeTypeOf("object");
45
+ if (
46
+ definitions === null ||
47
+ typeof definitions !== "object" ||
48
+ defs === null ||
49
+ typeof defs !== "object" ||
50
+ patternProperties === null ||
51
+ typeof patternProperties !== "object"
52
+ ) {
53
+ throw new Error("Expected normalized schema maps");
54
+ }
55
+
56
+ expectNormalizedTuple(Reflect.get(definitions, "legacy"));
57
+ expectNormalizedTuple(Reflect.get(defs, "modern"));
58
+ expectNormalizedTuple(Reflect.get(patternProperties, "^tuple$"));
59
+ expectNormalizedTuple(record.contains);
60
+ expectNormalizedTuple(record.not);
61
+ expectNormalizedTuple(record.if);
62
+ expectNormalizedTuple(record.then);
63
+ expectNormalizedTuple(record.else);
64
+ });
65
+ });
package/src/jsf-config.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  import { base, en, Faker } from "@faker-js/faker";
2
- import type { JSONSchema7 } from "json-schema";
2
+ import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
3
3
  import {
4
4
  type GenerateOptions,
5
5
  generate,
6
6
  type JsonSchema,
7
7
  } from "json-schema-faker";
8
8
 
9
+ const MAX_GENERATION_SEED = 2_147_483_647;
10
+ type JsfObjectSchema = Exclude<JsonSchema, boolean>;
11
+
9
12
  /**
10
13
  * Create isolated faker instance to avoid race conditions.
11
14
  * Each generation gets its own faker instance to ensure thread-safety.
@@ -18,31 +21,136 @@ export function createFakerInstance(seed?: number) {
18
21
  return faker;
19
22
  }
20
23
 
24
+ export function resolveGenerationSeed(seed?: number): number {
25
+ return seed ?? Math.floor(Math.random() * MAX_GENERATION_SEED);
26
+ }
27
+
28
+ export function createSeededRandom(seed: number): () => number {
29
+ let state = seed;
30
+ return () => {
31
+ state += 0x6d2b79f5;
32
+ let value = state;
33
+ value = Math.imul(value ^ (value >>> 15), value | 1);
34
+ value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
35
+ return ((value ^ (value >>> 14)) >>> 0) / 4_294_967_296;
36
+ };
37
+ }
38
+
39
+ function normalizeDefinitionForJsf(
40
+ definition: JSONSchema7Definition,
41
+ ): JsonSchema {
42
+ return typeof definition === "boolean"
43
+ ? definition
44
+ : normalizeSchemaForJsf(definition);
45
+ }
46
+
47
+ function normalizeDefinitionMap(
48
+ definitions: Record<string, JSONSchema7Definition>,
49
+ ): Record<string, JsonSchema> {
50
+ return Object.fromEntries(
51
+ Object.entries(definitions).map(([key, definition]) => [
52
+ key,
53
+ normalizeDefinitionForJsf(definition),
54
+ ]),
55
+ );
56
+ }
57
+
58
+ /**
59
+ * json-schema-faker consumes tuple schemas through the 2020-12 `prefixItems`
60
+ * keyword. Convert Draft 7 tuple `items` recursively at the library boundary.
61
+ */
62
+ export function normalizeSchemaForJsf(schema: JSONSchema7): JsfObjectSchema {
63
+ const normalized: JsfObjectSchema = {};
64
+ for (const [key, value] of Object.entries(schema)) {
65
+ normalized[key] = value;
66
+ }
67
+
68
+ if (schema.properties) {
69
+ normalized.properties = normalizeDefinitionMap(schema.properties);
70
+ }
71
+
72
+ if (schema.definitions) {
73
+ normalized.definitions = normalizeDefinitionMap(schema.definitions);
74
+ }
75
+
76
+ if (schema.$defs) {
77
+ normalized.$defs = normalizeDefinitionMap(schema.$defs);
78
+ }
79
+
80
+ if (schema.patternProperties) {
81
+ normalized.patternProperties = normalizeDefinitionMap(
82
+ schema.patternProperties,
83
+ );
84
+ }
85
+
86
+ if (Array.isArray(schema.items)) {
87
+ normalized.prefixItems = schema.items.map(normalizeDefinitionForJsf);
88
+ normalized.items =
89
+ schema.additionalItems === undefined
90
+ ? true
91
+ : normalizeDefinitionForJsf(schema.additionalItems);
92
+ delete normalized.additionalItems;
93
+ } else if (schema.items !== undefined) {
94
+ normalized.items = normalizeDefinitionForJsf(schema.items);
95
+ }
96
+
97
+ for (const keyword of ["allOf", "anyOf", "oneOf"] as const) {
98
+ const definitions = schema[keyword];
99
+ if (definitions) {
100
+ normalized[keyword] = definitions.map(normalizeDefinitionForJsf);
101
+ }
102
+ }
103
+
104
+ if (schema.additionalProperties !== undefined) {
105
+ normalized.additionalProperties = normalizeDefinitionForJsf(
106
+ schema.additionalProperties,
107
+ );
108
+ }
109
+
110
+ for (const keyword of [
111
+ "contains",
112
+ "not",
113
+ "if",
114
+ "then",
115
+ "else",
116
+ "propertyNames",
117
+ ] as const) {
118
+ const definition = schema[keyword];
119
+ if (definition !== undefined) {
120
+ normalized[keyword] = normalizeDefinitionForJsf(definition);
121
+ }
122
+ }
123
+
124
+ if (schema.dependencies) {
125
+ normalized.dependencies = Object.fromEntries(
126
+ Object.entries(schema.dependencies).map(([key, dependency]) => [
127
+ key,
128
+ Array.isArray(dependency)
129
+ ? [...dependency]
130
+ : normalizeDefinitionForJsf(dependency),
131
+ ]),
132
+ );
133
+ }
134
+
135
+ return normalized;
136
+ }
137
+
21
138
  /**
22
139
  * Generate data from a JSON schema using json-schema-faker 0.6.0 async API.
23
140
  * Stateless — each call is self-contained with its own faker instance and options.
24
141
  */
25
142
  export async function generateWithJsf(
26
143
  schema: JSONSchema7,
27
- seed?: number,
144
+ seed: number,
28
145
  ): Promise<unknown> {
29
- // When no seed is specified, generate a random seed to ensure different
30
- // results on each call. JSF 0.6.0 treats seed=undefined as deterministic.
31
- const effectiveSeed = seed ?? Math.floor(Math.random() * 2147483647);
32
-
33
- return generate(
34
- // JSONSchema7.items allows arrays but JsonSchema.items does not — structural
35
- // mismatch requires routing through `unknown`.
36
- schema as unknown as JsonSchema,
37
- {
38
- seed: effectiveSeed,
39
- optionalsProbability: 1.0,
40
- alwaysFakeOptionals: true,
41
- useDefaultValue: true,
42
- ignoreMissingRefs: true,
43
- failOnInvalidTypes: false,
44
- failOnInvalidFormat: false,
45
- extensions: { faker: createFakerInstance(seed) },
46
- } as GenerateOptions & Record<string, unknown>,
47
- );
146
+ const options: GenerateOptions = {
147
+ seed,
148
+ optionalsProbability: 1.0,
149
+ alwaysFakeOptionals: true,
150
+ useDefaultValue: true,
151
+ failOnInvalidTypes: false,
152
+ extensions: { faker: createFakerInstance(seed) },
153
+ };
154
+
155
+ return generate(normalizeSchemaForJsf(schema), options);
48
156
  }