@amritk/generate-examples 0.6.4 → 0.7.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.
package/AI.md CHANGED
@@ -47,6 +47,25 @@ const files = await buildExampleSchema(schema, 'User') // → user.ts, index.ts
47
47
  5. **`deriveExample` memoizes each `$ref` per root document**, so the returned
48
48
  value can share sub-objects with the value derived for a sibling schema.
49
49
  Treat it as read-only.
50
+ 6. **`fooExample` is sometimes emitted as `… as Foo`.** A schema can require a
51
+ key the generated type never declares (`required` naming something absent from
52
+ `properties`, a `dependentRequired`/`dependentSchemas` dependency, a
53
+ `minProperties` filler on an object with no index signature). The value keeps
54
+ the key — dropping it would ship a fixture missing what its schema demands —
55
+ so the assertion is what lets the file compile. Do not "clean it up" into a
56
+ bare literal; TypeScript rejects the excess property, and `satisfies` runs the
57
+ very same check.
58
+ 7. **Derived values are capped at 10,000** characters / elements / keys, so a
59
+ schema with an enormous `minLength`/`minItems`/`minProperties` gets a
60
+ short value plus a `console.warn` rather than a file nobody can open.
61
+ `FooArbitrary` still honours the real bound — reach for it when the exact size
62
+ matters.
63
+
64
+ 8. **A `default`/`examples` hint that contradicts its own schema is ignored.**
65
+ The generated type follows the schema, so emitting the hint verbatim produced
66
+ a file that would not compile (`const fooExample: string = 42`). `const` is
67
+ always honoured — the type is its literal type. Do not "restore" a mismatched
68
+ hint; fix the schema instead.
50
69
 
51
70
  Exports: `buildExampleSchema`, `generateArbitrary`, `generateExampleConst`,
52
71
  `deriveExample`, `serializeValue`, `GeneratedFile`. Only the `.` entry.
package/README.md CHANGED
@@ -163,7 +163,7 @@ compiles — but the generator prints a `console.warn` naming the type. Reach fo
163
163
  `FooArbitrary` in those cases: the arbitrary carries a runtime validating filter
164
164
  and stays correct where the static value cannot.
165
165
 
166
- The value falls short for two reasons:
166
+ The value falls short for three reasons:
167
167
 
168
168
  - **The schema has no instance.** `{ pattern: '^ab$', minLength: 5 }`,
169
169
  `uniqueItems` over booleans with `minItems: 3`, a `required` key that
@@ -173,6 +173,61 @@ The value falls short for two reasons:
173
173
  - **The constraint is beyond the deriver.** `pattern` is sampled by a
174
174
  best-effort recursive-descent walk of the regex, so lookarounds and
175
175
  backreferences fall back to `"string"`; an unrecognized `format` does the same.
176
+ - **The bound is larger than any fixture should be.** A derived string, array, or
177
+ object stops growing at 10,000 characters / elements / keys, so a document
178
+ asking for `minLength: 50000000` yields a capped value and a warning rather than
179
+ a 50 MB literal. `FooArbitrary` still honours the real bound.
180
+
181
+ Two more shapes worth knowing about, both of which keep the generated file
182
+ compiling rather than making it correct:
183
+
184
+ - A schema can require a key its **generated type never declares** — `required`
185
+ naming something absent from `properties`, a `dependentRequired` /
186
+ `dependentSchemas` dependency, or a `minProperties` filler on an object with no
187
+ index signature. The example keeps the key (a fixture missing what its schema
188
+ demands is broken data) and is emitted as `… as Foo`, since a bare object
189
+ literal with an excess property fails to compile.
190
+ - An authored `default` or `examples[0]` is used **only when it satisfies its own
191
+ schema**. A hint that does not (`{ type: 'string', default: 42 }` — common in
192
+ documents whose field types changed after the hint was written) is ignored in
193
+ favour of a structurally derived value, because the generated type follows the
194
+ schema and would reject the hint outright. `const` is always honoured: the type
195
+ is the const's own literal type, so the two cannot disagree.
196
+ - An **unsatisfiable range** (`minLength: 10, maxLength: 2`) collapses onto its
197
+ upper bound in the arbitrary. Every bounded `fc.*` combinator asserts
198
+ `min <= max` and throws at *import*, which would take down every other export in
199
+ the file alongside it. Integer bounds are also confined to fast-check's own
200
+ 32-bit range, and length/count bounds to non-negative integers.
201
+ - A **recursive definition's** example has to stop somewhere and stops with
202
+ `null`, which the non-nullable type does not admit — so it is emitted as
203
+ `… as unknown as Node`. `NodeArbitrary` ties the recursion properly through
204
+ `fc.letrec` and needs no such escape.
205
+ - A `pattern` that is **not a valid JavaScript regex**, or that uses a lookahead
206
+ or lookbehind, falls back to a plain `fc.string()`. `fc.stringMatching` compiles
207
+ the pattern at module scope and cannot generate from an assertion, so honouring
208
+ it would throw where the whole file becomes unusable rather than just that one
209
+ arbitrary being loose.
210
+ - **Nesting deeper than 400 levels** is refused with an error naming the limit.
211
+ Building an arbitrary costs several stack frames per schema level, so a deeper
212
+ document exhausts the stack — the cap turns that into a message that says what
213
+ is wrong.
214
+
215
+ Two shapes stay impossible to generate from, and the arbitrary will retry
216
+ forever if you sample it. Both are schemas with no instance, and the example
217
+ warns:
218
+
219
+ - A `pattern` no string of the required length can match
220
+ (`{ pattern: '^[a-z]{2}$', minLength: 5 }`). A satisfiable-but-narrow pairing
221
+ (`{ pattern: '^[a-f0-9]+$', minLength: 32, maxLength: 32 }`) is slow for the
222
+ same reason — `fc.stringMatching` rarely lands on the exact length.
223
+ - A `minLength`/`minItems` so large that no value of that size can be built.
224
+
225
+ One more gap is not this package's to close: a `$ref` that resolves nowhere in
226
+ the document is typed by its name (`Nope`) but never imported, because it was
227
+ never generated as a file. The arbitrary degrades to `fc.anything()`, but the
228
+ type still names it, so the file does not compile. Same for `{ "type": [] }`,
229
+ which types as `export type Foo = ;`. Both come from
230
+ `@amritk/helpers/generate-type-definition`.
176
231
 
177
232
  > [!TIP]
178
233
  > The example for a `$ref` is inlined by value, so a definition graph with wide
@@ -0,0 +1,28 @@
1
+ /**
2
+ * How deep a schema may nest before the two generator recursions give up.
3
+ *
4
+ * Lower than `@amritk/helpers`' `MAX_SCHEMA_DEPTH` of 2000, and deliberately so.
5
+ * That cap suits the helpers' walkers, which spend roughly two stack frames per
6
+ * schema level. Building an arbitrary or deriving a value costs far more —
7
+ * `derive` → `deriveBase` → `deriveForType` → `deriveObject` → `derive` is five
8
+ * frames before the next level even starts, plus whatever `needsValidationFilter`
9
+ * adds — so the real stack runs out first: measured, the arbitrary generator dies
10
+ * somewhere between 500 and 1000 levels and the deriver between 1000 and 1500,
11
+ * both well under 2000. A cap the stack beats to the punch is no cap at all.
12
+ *
13
+ * 400 sits inside the range both were measured to handle, with room for a
14
+ * shallower stack than this one, and it is still an order of magnitude past
15
+ * anything a real document nests — hand-written schemas rarely pass ten levels,
16
+ * and generated ones rarely pass fifty.
17
+ */
18
+ export declare const MAX_GENERATOR_DEPTH = 400;
19
+ /**
20
+ * Throws a message naming the nesting limit once a generator recursion passes it.
21
+ *
22
+ * Call this at the top of each recursive step with the *current* depth. Without
23
+ * it a pathologically nested document — a fuzz case, or a document that lost a
24
+ * closing brace — died with a bare `RangeError: Maximum call stack size exceeded`
25
+ * pointing at whichever helper happened to be deepest, which says nothing about
26
+ * the input that caused it.
27
+ */
28
+ export declare const assertGeneratorDepth: (depth: number, generator: string) => void;
@@ -0,0 +1,10 @@
1
+ const MAX_GENERATOR_DEPTH = 400;
2
+ const assertGeneratorDepth = (depth, generator) => {
3
+ if (depth > MAX_GENERATOR_DEPTH) {
4
+ throw new Error(`Schema nesting exceeds ${MAX_GENERATOR_DEPTH} levels while running ${generator}. Flatten the document (or split the deepest subschema into a $defs entry and $ref it) and try again.`);
5
+ }
6
+ };
7
+ export {
8
+ MAX_GENERATOR_DEPTH,
9
+ assertGeneratorDepth
10
+ };
@@ -0,0 +1,14 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
+ /**
3
+ * The complete set of values an item schema admits, when that set is finite and
4
+ * small enough to enumerate: `const`, `enum`, `boolean`, `null`. `uniqueItems`
5
+ * needs distinct elements, and for these schemas perturbing one value would step
6
+ * straight out of the schema, so the deriver walks this set instead of nudging.
7
+ * `undefined` means the value space is open and perturbing is fine.
8
+ *
9
+ * The arbitrary generator asks the same question for a different reason: a set
10
+ * of N values cannot fill a `uniqueArray` of more than N slots, and fast-check
11
+ * retries forever rather than admitting it — so `{ items: { type: 'boolean' },
12
+ * minItems: 5, uniqueItems: true }` produced an arbitrary that hung when sampled.
13
+ */
14
+ export declare const closedValueSet: (schema: JSONSchema | undefined) => unknown[] | undefined;
@@ -0,0 +1,20 @@
1
+ import { hasConst, hasEnum, hasType, isSchemaObject } from "@amritk/helpers/schema-guards";
2
+ import { satisfiesScalarConstraints } from "./satisfies-scalar-constraints.js";
3
+ const closedValueSet = (schema) => {
4
+ if (schema === void 0 || !isSchemaObject(schema))
5
+ return void 0;
6
+ if (hasConst(schema))
7
+ return [schema.const];
8
+ if (hasEnum(schema)) {
9
+ const fitting = schema.enum.filter((value) => satisfiesScalarConstraints(schema, value));
10
+ return fitting.length > 0 ? fitting : [...schema.enum];
11
+ }
12
+ if (hasType(schema) && schema.type === "boolean")
13
+ return [true, false];
14
+ if (hasType(schema) && schema.type === "null")
15
+ return [null];
16
+ return void 0;
17
+ };
18
+ export {
19
+ closedValueSet
20
+ };
@@ -1,20 +1,22 @@
1
1
  import { refToFilename } from "@amritk/helpers/ref-to-filename";
2
2
  import { refToName } from "@amritk/helpers/ref-to-name";
3
3
  import { resolveRef } from "@amritk/helpers/resolve-ref";
4
- import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasOneOf, hasProperties, hasRef, isSchemaObject } from "@amritk/helpers/schema-guards";
4
+ import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasContains, hasDependentSchemas, hasOneOf, hasProperties, hasRef, isSchemaObject } from "@amritk/helpers/schema-guards";
5
+ import { assertGeneratorDepth } from "./assert-generator-depth.js";
5
6
  const buildImport = (ref, suffix) => {
6
7
  const filename = refToFilename(ref);
7
8
  const typeName = refToName(ref, suffix);
8
9
  return `import { type ${typeName}, ${typeName}Arbitrary } from './${filename}.js'`;
9
10
  };
10
- const collectRefs = (schema) => {
11
+ const collectRefs = (schema, depth = 0) => {
12
+ assertGeneratorDepth(depth, "collectExampleImports");
11
13
  if (!isSchemaObject(schema))
12
14
  return [];
13
15
  if (hasRef(schema))
14
16
  return [schema.$ref];
15
17
  const refs = [];
16
18
  const visit = (sub) => {
17
- refs.push(...collectRefs(sub));
19
+ refs.push(...collectRefs(sub, depth + 1));
18
20
  };
19
21
  if (hasOneOf(schema))
20
22
  schema.oneOf.forEach(visit);
@@ -32,6 +34,8 @@ const collectRefs = (schema) => {
32
34
  if (hasAdditionalProperties(schema) && isSchemaObject(schema.additionalProperties)) {
33
35
  visit(schema.additionalProperties);
34
36
  }
37
+ if (hasDependentSchemas(schema))
38
+ Object.values(schema.dependentSchemas).forEach(visit);
35
39
  const prefixItems = raw["prefixItems"];
36
40
  if (Array.isArray(prefixItems))
37
41
  prefixItems.forEach(visit);
@@ -40,6 +44,8 @@ const collectRefs = (schema) => {
40
44
  items.forEach(visit);
41
45
  else if (isSchemaObject(items))
42
46
  visit(items);
47
+ if (hasContains(schema) && isSchemaObject(schema.contains))
48
+ visit(schema.contains);
43
49
  return refs;
44
50
  };
45
51
  const collectExampleImports = (schema, options) => {
@@ -2,6 +2,10 @@ import { getMjstInstanceOf, getMjstPrimitive } from "@amritk/helpers/mjst-extens
2
2
  import { readKey } from "@amritk/helpers/read-key";
3
3
  import { resolveRef } from "@amritk/helpers/resolve-ref";
4
4
  import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasDefault, hasDependentRequired, hasDependentSchemas, hasEnum, hasExamples, hasExclusiveMaximum, hasExclusiveMinimum, hasFormat, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasOneOf, hasPattern, hasPatternProperties, hasProperties, hasPropertyNames, hasRef, hasRequired, hasType, hasUniqueItems, isSchemaObject } from "@amritk/helpers/schema-guards";
5
+ import { assertGeneratorDepth } from "./assert-generator-depth.js";
6
+ import { closedValueSet } from "./closed-value-set.js";
7
+ import { declaresOwnShape, isObjectLike, typesConflict } from "./is-object-like.js";
8
+ import { satisfiesScalarConstraints } from "./satisfies-scalar-constraints.js";
5
9
  import { makeInstanceCheck, needsValidationFilter } from "./schema-validation.js";
6
10
  const lowerFirst = (name) => name.charAt(0).toLowerCase() + name.slice(1);
7
11
  const exampleName = (typeName) => `${lowerFirst(typeName)}Example`;
@@ -186,13 +190,14 @@ const FORMAT_EXAMPLES = {
186
190
  // The `regex` format asks for a string that *compiles* as a regular expression.
187
191
  regex: "^example$"
188
192
  };
193
+ const MAX_DERIVED_SIZE = 1e4;
189
194
  const exampleString = (schema) => {
190
195
  if (hasFormat(schema)) {
191
196
  const formatted = readKey(FORMAT_EXAMPLES, schema.format);
192
197
  if (formatted !== void 0)
193
198
  return formatted;
194
199
  }
195
- const minLength = hasMinLength(schema) ? schema.minLength : 0;
200
+ const minLength = Math.min(hasMinLength(schema) ? schema.minLength : 0, MAX_DERIVED_SIZE);
196
201
  if (hasPattern(schema)) {
197
202
  const sampled = sampleFromPattern(schema.pattern, minLength);
198
203
  if (sampled !== void 0 && sampled.length >= minLength && matchesPattern(schema.pattern, sampled)) {
@@ -212,7 +217,9 @@ const newContext = (rootSchema) => ({
212
217
  rootSchema,
213
218
  values: /* @__PURE__ */ new Map(),
214
219
  active: /* @__PURE__ */ new Set(),
215
- cycleBroken: false
220
+ cycleBroken: false,
221
+ needsAssertion: false,
222
+ depth: 0
216
223
  });
217
224
  const contextFor = (rootSchema) => {
218
225
  if (rootSchema === void 0)
@@ -224,52 +231,76 @@ const contextFor = (rootSchema) => {
224
231
  contexts.set(rootSchema, created);
225
232
  return created;
226
233
  };
227
- const deriveExample = (schema, rootSchema) => {
234
+ const deriveExample = (schema, rootSchema) => deriveReported(schema, rootSchema).value;
235
+ const deriveReported = (schema, rootSchema) => {
228
236
  const ctx = contextFor(rootSchema);
229
237
  ctx.active.clear();
230
238
  ctx.cycleBroken = false;
231
- return derive(schema, ctx);
239
+ ctx.needsAssertion = false;
240
+ ctx.depth = 0;
241
+ const value = derive(schema, ctx);
242
+ return { value, needsAssertion: ctx.needsAssertion, cycleBroken: ctx.cycleBroken };
232
243
  };
233
244
  const derive = (schema, ctx) => {
245
+ assertGeneratorDepth(ctx.depth, "deriveExample");
234
246
  if (!isSchemaObject(schema))
235
247
  return null;
236
- const base = deriveBase(schema, ctx);
237
- return needsValidationFilter(schema) ? refineExample(schema, base, ctx) : base;
248
+ ctx.depth++;
249
+ try {
250
+ const base = deriveBase(schema, ctx);
251
+ return needsValidationFilter(schema) ? refineExample(schema, base, ctx) : base;
252
+ } finally {
253
+ ctx.depth--;
254
+ }
238
255
  };
239
256
  const deriveRef = (ref, ctx) => {
240
257
  if (ctx.active.has(ref)) {
241
258
  ctx.cycleBroken = true;
242
259
  return null;
243
260
  }
244
- if (ctx.values.has(ref))
245
- return ctx.values.get(ref);
261
+ const memoized = ctx.values.get(ref);
262
+ if (memoized !== void 0) {
263
+ if (memoized.needsAssertion)
264
+ ctx.needsAssertion = true;
265
+ return memoized.value;
266
+ }
246
267
  if (!ctx.rootSchema)
247
268
  return null;
248
269
  const resolved = resolveRef(ref, ctx.rootSchema);
249
270
  if (!resolved)
250
271
  return null;
251
272
  const outerBroken = ctx.cycleBroken;
273
+ const outerUndeclared = ctx.needsAssertion;
252
274
  ctx.cycleBroken = false;
275
+ ctx.needsAssertion = false;
253
276
  ctx.active.add(ref);
254
277
  try {
255
278
  const value = derive(resolved, ctx);
256
279
  if (!ctx.cycleBroken)
257
- ctx.values.set(ref, value);
280
+ ctx.values.set(ref, { value, needsAssertion: ctx.needsAssertion });
258
281
  ctx.cycleBroken = outerBroken || ctx.cycleBroken;
282
+ ctx.needsAssertion = outerUndeclared || ctx.needsAssertion;
259
283
  return value;
260
284
  } finally {
261
285
  ctx.active.delete(ref);
262
286
  }
263
287
  };
288
+ const authoredHint = (schema) => {
289
+ if (hasExamples(schema) && Array.isArray(schema.examples) && schema.examples.length > 0) {
290
+ return { value: schema.examples[0] };
291
+ }
292
+ if (hasDefault(schema))
293
+ return { value: schema.default };
294
+ return void 0;
295
+ };
264
296
  const deriveBase = (schema, ctx) => {
265
297
  if (!isSchemaObject(schema))
266
298
  return null;
267
299
  if (hasConst(schema))
268
300
  return schema.const;
269
- if (hasExamples(schema) && Array.isArray(schema.examples) && schema.examples.length > 0)
270
- return schema.examples[0];
271
- if (hasDefault(schema))
272
- return schema.default;
301
+ const hint = authoredHint(schema);
302
+ if (hint !== void 0 && makeInstanceCheck(schema, ctx.rootSchema)(hint.value))
303
+ return hint.value;
273
304
  if (hasEnum(schema) && schema.enum.length > 0) {
274
305
  const fitting = schema.enum.find((value) => satisfiesScalarConstraints(schema, value));
275
306
  return fitting !== void 0 ? fitting : schema.enum[0];
@@ -284,10 +315,16 @@ const deriveBase = (schema, ctx) => {
284
315
  return 0n;
285
316
  if (hasAllOf(schema))
286
317
  return derive(mergeAllOf(schema), ctx);
287
- if (hasOneOf(schema) && schema.oneOf[0] !== void 0)
288
- return derive(schema.oneOf[0], ctx);
289
- if (hasAnyOf(schema) && schema.anyOf[0] !== void 0)
290
- return derive(schema.anyOf[0], ctx);
318
+ const branches = hasOneOf(schema) ? schema.oneOf : hasAnyOf(schema) ? schema.anyOf : void 0;
319
+ if (branches !== void 0 && branches.length > 0) {
320
+ const rest = withoutCombinators(schema);
321
+ if (!declaresOwnShape(rest))
322
+ return derive(branches[0], ctx);
323
+ const chosen = branches.find((branch) => !typesConflict(rest, branch)) ?? branches[0];
324
+ return derive({ allOf: [rest, chosen] }, ctx);
325
+ }
326
+ if (isObjectLike(schema))
327
+ return deriveObject(schema, ctx);
291
328
  if (hasType(schema))
292
329
  return deriveForType(schema.type, schema, ctx);
293
330
  if (Array.isArray(schema.type) && schema.type.length > 0) {
@@ -295,6 +332,12 @@ const deriveBase = (schema, ctx) => {
295
332
  }
296
333
  return null;
297
334
  };
335
+ const withoutCombinators = (schema) => {
336
+ const rest = { ...schema };
337
+ delete rest["oneOf"];
338
+ delete rest["anyOf"];
339
+ return rest;
340
+ };
298
341
  const REFINED_APPLICATORS = ["if", "then", "else", "not", "oneOf"];
299
342
  const structuralOnly = (schema) => {
300
343
  const clone = { ...schema };
@@ -344,26 +387,6 @@ const refineExample = (schema, base, ctx) => {
344
387
  return candidate;
345
388
  return base;
346
389
  };
347
- const satisfiesScalarConstraints = (schema, value) => {
348
- if (typeof value === "string") {
349
- if (hasMinLength(schema) && value.length < schema.minLength)
350
- return false;
351
- if (hasMaxLength(schema) && value.length > schema.maxLength)
352
- return false;
353
- } else if (typeof value === "number") {
354
- if (hasMinimum(schema) && value < schema.minimum)
355
- return false;
356
- if (hasMaximum(schema) && value > schema.maximum)
357
- return false;
358
- if (hasExclusiveMinimum(schema) && value <= schema.exclusiveMinimum)
359
- return false;
360
- if (hasExclusiveMaximum(schema) && value >= schema.exclusiveMaximum)
361
- return false;
362
- if (hasMultipleOf(schema) && schema.multipleOf > 0 && value % schema.multipleOf !== 0)
363
- return false;
364
- }
365
- return true;
366
- };
367
390
  const deriveForType = (type, schema, ctx) => {
368
391
  switch (type) {
369
392
  case "string":
@@ -404,6 +427,15 @@ const deriveObject = (schema, ctx) => {
404
427
  const additionalClosed = hasAdditionalProperties(schema) && schema.additionalProperties === false;
405
428
  const extrasAllowed = !additionalClosed || patternEntries.length > 0;
406
429
  const nameCheck = hasPropertyNames(schema) ? makeInstanceCheck(schema.propertyNames, ctx.rootSchema) : void 0;
430
+ const declaredKeys = new Set(hasProperties(schema) ? Object.keys(schema.properties) : []);
431
+ const hasIndexSignature = additionalSchema !== void 0 || hasPatternProperties(schema) && Object.keys(schema.patternProperties).length > 0;
432
+ const noteKey = (key) => {
433
+ if (!hasIndexSignature && !declaredKeys.has(key))
434
+ ctx.needsAssertion = true;
435
+ };
436
+ const raw = schema;
437
+ if (Object.hasOwn(raw, "if") && Object.hasOwn(raw, "then"))
438
+ ctx.needsAssertion = true;
407
439
  const valueSchemaFor = (key) => {
408
440
  const matches = patternEntries.filter(([re]) => re.test(key)).map(([, sub]) => sub);
409
441
  if (matches.length === 1)
@@ -416,6 +448,7 @@ const deriveObject = (schema, ctx) => {
416
448
  const sub = valueSchemaFor(key);
417
449
  if (sub === void 0 && additionalClosed)
418
450
  return;
451
+ noteKey(key);
419
452
  setProperty(out, key, sub !== void 0 ? derive(sub, ctx) : null);
420
453
  };
421
454
  if (hasProperties(schema)) {
@@ -424,9 +457,10 @@ const deriveObject = (schema, ctx) => {
424
457
  }
425
458
  }
426
459
  if (hasRequired(schema)) {
427
- for (const key of schema.required)
428
- if (!Object.hasOwn(out, key))
460
+ for (const key of schema.required) {
461
+ if (typeof key === "string" && !Object.hasOwn(out, key))
429
462
  addKey(key);
463
+ }
430
464
  }
431
465
  if (hasDependentRequired(schema)) {
432
466
  for (const [trigger, deps] of Object.entries(schema.dependentRequired)) {
@@ -440,63 +474,78 @@ const deriveObject = (schema, ctx) => {
440
474
  if (hasDependentSchemas(schema)) {
441
475
  for (const [trigger, sub] of Object.entries(schema.dependentSchemas)) {
442
476
  if (Object.hasOwn(out, trigger) && isSchemaObject(sub))
443
- applyDependentSchema(out, sub, ctx);
477
+ applyDependentSchema(out, sub, ctx, noteKey);
444
478
  }
445
479
  }
446
480
  if (hasMinProperties(schema) && extrasAllowed) {
481
+ const target = Math.min(schema.minProperties, MAX_DERIVED_SIZE);
482
+ let count = Object.keys(out).length;
447
483
  let n = 0;
448
- let guard = 0;
449
- while (Object.keys(out).length < schema.minProperties && guard++ < schema.minProperties + 50) {
484
+ let attempts = 0;
485
+ while (count < target && attempts++ < target + 50) {
450
486
  const key = synthKey(n++, patternEntries, schema, nameCheck);
451
- if (key === void 0 || Object.hasOwn(out, key))
487
+ if (key === void 0)
488
+ break;
489
+ if (Object.hasOwn(out, key))
452
490
  continue;
453
491
  addKey(key);
492
+ if (Object.hasOwn(out, key))
493
+ count++;
454
494
  }
455
495
  }
456
496
  if (hasMaxProperties(schema))
457
497
  enforceMaxProperties(out, schema, schema.maxProperties);
458
498
  return out;
459
499
  };
460
- const applyDependentSchema = (out, sub, ctx) => {
500
+ const applyDependentSchema = (out, sub, ctx, noteKey) => {
501
+ const add = (key, propSchema) => {
502
+ noteKey(key);
503
+ setProperty(out, key, propSchema !== void 0 ? derive(propSchema, ctx) : null);
504
+ };
461
505
  if (hasProperties(sub)) {
462
506
  for (const [key, propSchema] of Object.entries(sub.properties)) {
463
507
  if (!Object.hasOwn(out, key))
464
- setProperty(out, key, derive(propSchema, ctx));
508
+ add(key, propSchema);
465
509
  }
466
510
  }
467
511
  if (hasRequired(sub)) {
468
512
  const propSchemas = hasProperties(sub) ? sub.properties : {};
469
513
  for (const key of sub.required) {
470
- if (Object.hasOwn(out, key))
514
+ if (typeof key !== "string" || Object.hasOwn(out, key))
471
515
  continue;
472
- const propSchema = propSchemas[key];
473
- setProperty(out, key, propSchema !== void 0 ? derive(propSchema, ctx) : null);
516
+ add(key, propSchemas[key]);
474
517
  }
475
518
  }
476
519
  };
520
+ const MAX_SYNTH_KEY_LENGTH = 64;
477
521
  const synthKey = (i, patternEntries, schema, nameCheck) => {
478
- const vary = (seed) => seed.repeat(i + 1);
522
+ const variants = (seed) => i === 0 ? [seed] : [`${seed}${i}`, seed.repeat(i + 1)].filter((key) => key.length <= MAX_SYNTH_KEY_LENGTH);
479
523
  const seeds = [];
480
524
  for (const [re] of patternEntries) {
481
525
  const sampled = sampleFromPattern(re.source, 0);
482
- if (sampled !== void 0 && sampled.length > 0)
483
- seeds.push(vary(sampled));
526
+ if (sampled !== void 0 && sampled.length > 0) {
527
+ seeds.push({ seed: sampled, matches: (key) => re.test(key) });
528
+ }
484
529
  }
485
530
  const propertyNames = isSchemaObject(schema) && hasPropertyNames(schema) ? schema.propertyNames : void 0;
486
531
  if (propertyNames !== void 0 && isSchemaObject(propertyNames) && hasPattern(propertyNames)) {
487
532
  const sampled = sampleFromPattern(propertyNames.pattern, 0);
488
533
  if (sampled !== void 0 && sampled.length > 0)
489
- seeds.push(vary(sampled));
534
+ seeds.push({ seed: sampled, matches: () => true });
490
535
  }
491
- seeds.push(vary("extra"));
492
- for (const seed of seeds) {
493
- if (!nameCheck || nameCheck(seed))
494
- return seed;
536
+ seeds.push({ seed: "extra", matches: () => true });
537
+ for (const { seed, matches } of seeds) {
538
+ for (const key of variants(seed)) {
539
+ if (matches(key) && (!nameCheck || nameCheck(key)))
540
+ return key;
541
+ }
495
542
  }
496
543
  return void 0;
497
544
  };
498
545
  const enforceMaxProperties = (out, schema, max) => {
499
- if (Object.keys(out).length <= max)
546
+ const keys = Object.keys(out);
547
+ let count = keys.length;
548
+ if (count <= max)
500
549
  return;
501
550
  const protectedKeys = new Set(hasRequired(schema) ? schema.required : []);
502
551
  if (hasDependentRequired(schema)) {
@@ -506,11 +555,13 @@ const enforceMaxProperties = (out, schema, max) => {
506
555
  protectedKeys.add(dep);
507
556
  }
508
557
  }
509
- for (const key of Object.keys(out)) {
510
- if (Object.keys(out).length <= max)
558
+ for (const key of keys) {
559
+ if (count <= max)
511
560
  break;
512
- if (!protectedKeys.has(key))
513
- delete out[key];
561
+ if (protectedKeys.has(key))
562
+ continue;
563
+ delete out[key];
564
+ count--;
514
565
  }
515
566
  };
516
567
  const deriveNumber = (schema, isInteger) => {
@@ -524,7 +575,7 @@ const deriveNumber = (schema, isInteger) => {
524
575
  let value = Number.isFinite(lo) ? lo : Number.isFinite(hi) ? Math.min(0, hi) : 0;
525
576
  if (isInteger)
526
577
  value = Math.ceil(value);
527
- if (hasMultipleOf(schema) && schema.multipleOf > 0) {
578
+ if (hasMultipleOf(schema) && Number.isFinite(schema.multipleOf) && schema.multipleOf > 0) {
528
579
  const m = schema.multipleOf;
529
580
  value = Math.ceil(value / m - 1e-9) * m;
530
581
  if (value > hi && Number.isFinite(hi))
@@ -559,35 +610,27 @@ const deriveArray = (schema, ctx) => {
559
610
  const unique = hasUniqueItems(schema) && schema.uniqueItems === true;
560
611
  const elem = rest ?? contains;
561
612
  const choices = unique && contains === void 0 ? closedValueSet(elem) : void 0;
562
- const wanted = Math.min(Math.max(min, minContains, max === 0 ? 0 : 1), max);
613
+ const wanted = Math.min(Math.max(min, minContains, max === 0 ? 0 : 1), max, MAX_DERIVED_SIZE);
563
614
  const count = choices !== void 0 ? Math.min(wanted, choices.length) : wanted;
615
+ const itemsCheck = rest !== void 0 && contains !== void 0 ? makeInstanceCheck(rest, ctx.rootSchema) : void 0;
564
616
  const result = [];
565
617
  for (let i = 0; i < count; i++) {
566
618
  if (choices !== void 0) {
567
619
  result.push(choices[i]);
568
620
  continue;
569
621
  }
570
- const itemSchema = contains !== void 0 && i < minContains ? contains : elem;
571
- const base = itemSchema !== void 0 ? derive(itemSchema, ctx) : null;
572
- result.push(unique ? distinctify(base, i, itemSchema) : base);
622
+ const useContains = contains !== void 0 && i < minContains;
623
+ const itemSchema = useContains ? contains : elem;
624
+ let chosen = itemSchema;
625
+ let base = itemSchema !== void 0 ? derive(itemSchema, ctx) : null;
626
+ if (useContains && itemsCheck !== void 0 && !itemsCheck(base)) {
627
+ chosen = rest;
628
+ base = rest !== void 0 ? derive(rest, ctx) : null;
629
+ }
630
+ result.push(unique ? distinctify(base, i, chosen) : base);
573
631
  }
574
632
  return result;
575
633
  };
576
- const closedValueSet = (schema) => {
577
- if (schema === void 0 || !isSchemaObject(schema))
578
- return void 0;
579
- if (hasConst(schema))
580
- return [schema.const];
581
- if (hasEnum(schema)) {
582
- const fitting = schema.enum.filter((value) => satisfiesScalarConstraints(schema, value));
583
- return fitting.length > 0 ? fitting : [...schema.enum];
584
- }
585
- if (hasType(schema) && schema.type === "boolean")
586
- return [true, false];
587
- if (hasType(schema) && schema.type === "null")
588
- return [null];
589
- return void 0;
590
- };
591
634
  const distinctify = (base, i, itemSchema) => {
592
635
  if (i === 0)
593
636
  return base;
@@ -613,10 +656,20 @@ const TIGHTEST = /* @__PURE__ */ new Map([
613
656
  ["maxItems", "min"],
614
657
  ["maxProperties", "min"]
615
658
  ]);
659
+ const flattenBranches = (nodes, depth = 0) => {
660
+ assertGeneratorDepth(depth, "mergeAllOf");
661
+ return nodes.flatMap((node) => {
662
+ if (!isSchemaObject(node) || !hasAllOf(node))
663
+ return [node];
664
+ const own = { ...node };
665
+ delete own["allOf"];
666
+ return [...flattenBranches(node.allOf, depth + 1), own];
667
+ });
668
+ };
616
669
  const mergeAllOf = (schema) => {
617
- const branches = hasAllOf(schema) ? schema.allOf : [];
618
- const merged = {};
619
- const properties = {};
670
+ const branches = hasAllOf(schema) ? flattenBranches(schema.allOf) : [];
671
+ const merged = /* @__PURE__ */ Object.create(null);
672
+ const properties = /* @__PURE__ */ Object.create(null);
620
673
  const required = /* @__PURE__ */ new Set();
621
674
  for (const branch of [...branches, schema]) {
622
675
  if (!isSchemaObject(branch))
@@ -644,7 +697,7 @@ const mergeAllOf = (schema) => {
644
697
  }
645
698
  }
646
699
  }
647
- const mergedProps = {};
700
+ const mergedProps = /* @__PURE__ */ Object.create(null);
648
701
  for (const [prop, schemas] of Object.entries(properties)) {
649
702
  mergedProps[prop] = schemas.length === 1 ? schemas[0] : { allOf: schemas };
650
703
  }
@@ -679,9 +732,13 @@ const warnInvalidExample = (schema, typeName, value, rootSchema) => {
679
732
  console.warn(`Warning: the derived example for ${typeName} does not validate against its own schema \u2014 ${quoted}. It is emitted anyway so the file still compiles, but treat it as a placeholder: either the schema has no instance, or it uses a constraint the deriver cannot satisfy structurally (see the README's "Known limits"). ${arbitraryName(typeName)} is unaffected.`);
680
733
  };
681
734
  const generateExampleConst = (schema, typeName, rootSchema) => {
682
- const value = deriveExample(schema, rootSchema);
735
+ const { value, needsAssertion, cycleBroken } = deriveReported(schema, rootSchema);
683
736
  warnInvalidExample(schema, typeName, value, rootSchema);
684
- return `export const ${exampleName(typeName)}: ${typeName} = ${serializeValue(value)}`;
737
+ const literal = serializeValue(value);
738
+ const admitsNothing = schema === false;
739
+ const assertion = cycleBroken || admitsNothing ? ` as unknown as ${typeName}` : needsAssertion ? ` as ${typeName}` : "";
740
+ const expression = `${literal}${assertion}`;
741
+ return `export const ${exampleName(typeName)}: ${typeName} = ${expression}`;
685
742
  };
686
743
  export {
687
744
  deriveExample,