@amritk/generate-validators 0.11.4 → 0.11.6

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/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  ![license](https://img.shields.io/badge/license-MIT-22c55e?style=flat-square) 
10
10
  ![JSON Schema](https://img.shields.io/badge/JSON%20Schema-2020--12-f97316?style=flat-square) 
11
11
  ![node](https://img.shields.io/badge/node-%E2%89%A520-339933?style=flat-square&logo=node.js&logoColor=white) 
12
- ![vibe coded](https://img.shields.io/badge/vibe%20coded-100%25-a855f7?style=flat-square)
12
+ ![vibe coded](https://img.shields.io/badge/vibe-coded-a855f7?style=flat-square)
13
13
 
14
14
  </div>
15
15
 
@@ -92,6 +92,27 @@ Returns: `Promise<GeneratedFile[]>` where `GeneratedFile = { filename: string; c
92
92
 
93
93
  ---
94
94
 
95
+ ## Semantics
96
+
97
+ Generated validators track the `@amritk/runtime-validators` interpreter. Array
98
+ items are validated in full — an item's type, `$ref`, nested `properties` /
99
+ `required`, and scalar constraints (`minLength`, `minimum`, …) are all enforced,
100
+ recursing to any depth — and the boolean guard (`isX`) reaches the identical
101
+ verdict. Validating array item *contents* costs throughput proportional to the
102
+ per-item work (a bare `string[]` is free; a closed object with several fields is
103
+ meaningfully slower), which is why array-heavy schemas validate more slowly than
104
+ scalar/object ones.
105
+
106
+ One divergence is worth calling out: **`NaN` satisfies a constrained number.**
107
+ Because the numeric bound checks are the exact negation of the error condition
108
+ (e.g. `!(x < minimum)`), and every comparison against `NaN` is `false`, a `NaN`
109
+ passes `minimum`/`maximum`/`exclusive*`/`multipleOf`. This matches the interpreter
110
+ but differs from validators (e.g. Ajv) that reject `NaN` for `type: "number"`.
111
+ `NaN` never appears in parsed JSON; guard against it upstream if your values can
112
+ be non-JSON.
113
+
114
+ ---
115
+
95
116
  ## Benchmarks
96
117
 
97
118
  Generated validators are straight-line, monomorphic TypeScript with no generic
@@ -45,6 +45,35 @@ export const valuesEqual = (a: unknown, b: unknown): boolean => {
45
45
  }
46
46
  return true
47
47
  }
48
+
49
+ /**
50
+ * True when every element of \`arr\` is distinct under structural equality
51
+ * ({@link valuesEqual}). Backs generated \`uniqueItems\` checks whose items may be
52
+ * objects or arrays, where a \`JSON.stringify\` dedupe key would be key-order
53
+ * sensitive and let a reordered-but-equal duplicate (\`{ a: 1, b: 2 }\` vs
54
+ * \`{ b: 2, a: 1 }\`) slip through. A native \`Set\` dedupes the all-primitive case
55
+ * in one linear pass; object/array elements fall back to an exact pairwise
56
+ * structural comparison.
57
+ */
58
+ export const allUnique = (arr: readonly unknown[]): boolean => {
59
+ const len = arr.length
60
+ if (len < 2) return true
61
+ let allPrimitive = true
62
+ for (let i = 0; i < len; i++) {
63
+ const v = arr[i]
64
+ if (v !== null && typeof v === 'object') {
65
+ allPrimitive = false
66
+ break
67
+ }
68
+ }
69
+ if (allPrimitive) return new Set(arr).size === len
70
+ for (let i = 0; i < len; i++) {
71
+ for (let j = i + 1; j < len; j++) {
72
+ if (valuesEqual(arr[i], arr[j])) return false
73
+ }
74
+ }
75
+ return true
76
+ }
48
77
  `;
49
78
  /**
50
79
  * Builds all TypeScript validator files from a JSON Schema by traversing all
@@ -54,7 +54,7 @@ const collectDirectRefs = (value, refs = []) => {
54
54
  // generated files, not inlined by this validator. `collectDirectRefs`
55
55
  // self-guards on non-objects, so a keyword that is a boolean or missing is a
56
56
  // harmless no-op.
57
- const subSchemaMaps = ['properties', 'patternProperties'];
57
+ const subSchemaMaps = ['properties', 'patternProperties', 'dependentSchemas'];
58
58
  for (const mapKey of subSchemaMaps) {
59
59
  const map = schema[mapKey];
60
60
  if (typeof map === 'object' && map !== null && !Array.isArray(map)) {
@@ -37,11 +37,14 @@ export const generateValidatorFile = (schema, typeName, options) => {
37
37
  const booleanGuard = generateBooleanGuard(schema, typeName, typeSuffix);
38
38
  // `.js` extension so the relative import resolves under Node ESM, not only Bun.
39
39
  let result = `import type { ValidationResult, ValidationError } from './validation-result.js'\n`;
40
- // `const` checks on object/array values call the runtime `valuesEqual` helper.
41
- // Only import it when the generated body actually uses it, so files without a
42
- // structural `const` do not carry an unused import.
43
- if (validatorFunction.includes('valuesEqual(')) {
44
- result += `import { valuesEqual } from './validation-result.js'\n`;
40
+ // Structural `const` checks call the runtime `valuesEqual` helper; structural
41
+ // `uniqueItems` checks call `allUnique`. Both live in `validation-result.js`;
42
+ // import each only when the generated body (validator or boolean guard) uses
43
+ // it, so files that need neither carry no unused import.
44
+ const body = validatorFunction + booleanGuard;
45
+ const runtimeHelpers = ['valuesEqual', 'allUnique'].filter((name) => body.includes(`${name}(`));
46
+ if (runtimeHelpers.length > 0) {
47
+ result += `import { ${runtimeHelpers.join(', ')} } from './validation-result.js'\n`;
45
48
  }
46
49
  for (const imp of refImports) {
47
50
  result += imp + '\n';
@@ -3,7 +3,7 @@ import { getMjstInstanceOf, getMjstPrimitive } from '@amritk/helpers/mjst-extens
3
3
  import { multipleOfFailExpr, multipleOfPassExpr } from '@amritk/helpers/multiple-of-check';
4
4
  import { refToName } from '@amritk/helpers/ref-to-name';
5
5
  import { safeAccessor } from '@amritk/helpers/safe-accessor';
6
- import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasDependentRequired, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMinItems, hasMinimum, hasMinLength, hasMultipleOf, hasOneOf, hasPattern, hasProperties, hasPropertyNames, hasRef, hasRequired, hasStrictExclusiveMaximum, hasStrictExclusiveMinimum, hasType, hasUniqueItems, isObjectSchema, isSchemaObject, } from '@amritk/helpers/schema-guards';
6
+ import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasDependentRequired, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasOneOf, hasPattern, hasProperties, hasPropertyNames, hasRef, hasRequired, hasStrictExclusiveMaximum, hasStrictExclusiveMinimum, hasType, hasUniqueItems, isObjectSchema, isSchemaObject, } from '@amritk/helpers/schema-guards';
7
7
  import { unknownKeyCheck } from '@amritk/helpers/unknown-key-check';
8
8
  /**
9
9
  * Derives the validator function name from a type name.
@@ -31,6 +31,43 @@ const constMismatchCondition = (accessor, value) => {
31
31
  }
32
32
  return `!valuesEqual(${accessor}, ${JSON.stringify(value)})`;
33
33
  };
34
+ const SCALAR_ITEM_TYPES = new Set(['string', 'number', 'integer', 'boolean', 'null']);
35
+ /**
36
+ * True when a schema's values are provably JSON scalars — its `type` is present
37
+ * and every listed type is a primitive. Conservative: a `$ref`, a boolean/absent
38
+ * schema, an `object`/`array` type, or a missing `type` all fail this test.
39
+ */
40
+ const schemaIsScalarOnly = (schema) => {
41
+ if (!isSchemaObject(schema))
42
+ return false;
43
+ const t = schema['type'];
44
+ if (t === undefined)
45
+ return false;
46
+ const types = Array.isArray(t) ? t : [t];
47
+ return types.length > 0 && types.every((x) => typeof x === 'string' && SCALAR_ITEM_TYPES.has(x));
48
+ };
49
+ /**
50
+ * True when an array's elements can only be JSON scalars, so a `uniqueItems`
51
+ * check can dedupe by the cheap `JSON.stringify` projection. When items may be
52
+ * objects or arrays this returns false, and the check must instead compare
53
+ * structurally (the `allUnique` runtime helper): `JSON.stringify` is key-order
54
+ * sensitive and would treat `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` as distinct,
55
+ * disagreeing with the interpreter's order-independent deep equality.
56
+ */
57
+ const arrayItemsAreScalarOnly = (schema) => {
58
+ const prefix = schema['prefixItems'];
59
+ if (Array.isArray(prefix)) {
60
+ if (!prefix.every((p) => schemaIsScalarOnly(p)))
61
+ return false;
62
+ // Tuple tail: a closed tuple (`items`/`additionalItems: false`) has no tail;
63
+ // otherwise the tail schema must itself be scalar-only.
64
+ const tail = 'items' in schema ? schema['items'] : schema['additionalItems'];
65
+ if (tail === false)
66
+ return true;
67
+ return schemaIsScalarOnly(tail);
68
+ }
69
+ return schemaIsScalarOnly(schema['items']);
70
+ };
34
71
  /**
35
72
  * Generates the inline condition that is TRUE when a value is the wrong type.
36
73
  */
@@ -405,35 +442,45 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
405
442
  lines.push(` }`);
406
443
  }
407
444
  }
408
- // Array with typed items
445
+ // Array items. `$ref` items delegate to the referenced validator. Any other
446
+ // item subschema is validated in full — matching the interpreter — but wrapped
447
+ // in a per-item boolean fast-check (`booleanLeafExpr`): a valid item passes the
448
+ // flat expression and skips the error-collecting body entirely, so the common
449
+ // valid case stays allocation-free (the same hot/cold split the top-level
450
+ // validator uses). This keeps array-heavy throughput close to a bare type check
451
+ // while still fully validating every item. The loop variables carry the nesting
452
+ // depth so item loops can nest (array-of-arrays) without colliding.
409
453
  if (hasItems(propSchema)) {
410
454
  const itemSchema = propSchema.items;
455
+ const iv = `_i${ctx.depth}`;
456
+ const itemPath = `\`${path.slice(1, -1)}/\${${iv}}\``;
411
457
  if (hasRef(itemSchema)) {
412
458
  const vName = validatorName(refToName(itemSchema.$ref, suffix));
413
459
  lines.push(` if (Array.isArray(${raw})) {`);
414
- lines.push(` for (let _i = 0; _i < ${raw}.length; _i++) {`);
415
- lines.push(` const _ir = ${vName}(${raw}[_i], \`${path.slice(1, -1)}/\${_i}\`)`);
460
+ lines.push(` for (let ${iv} = 0; ${iv} < ${raw}.length; ${iv}++) {`);
461
+ lines.push(` const _ir = ${vName}(${raw}[${iv}], ${itemPath})`);
416
462
  lines.push(` if (_ir !== true) errors.push(..._ir.errors)`);
417
463
  lines.push(` }`);
418
464
  lines.push(` }`);
419
465
  }
420
- else if (hasType(itemSchema)) {
421
- const itemType = itemSchema.type;
422
- const itemWrong = wrongTypeCondition('_item', itemType);
423
- const itemLabel = typeofString(itemType);
424
- if (itemWrong) {
466
+ else if (isSchemaObject(itemSchema)) {
467
+ const itemVar = `_item${ctx.depth}`;
468
+ const detail = generateValueChecks('', itemVar, itemPath, itemSchema, suffix, ctx, true);
469
+ if (detail.length > 0) {
425
470
  lines.push(` if (Array.isArray(${raw})) {`);
426
- lines.push(` for (let _i = 0; _i < ${raw}.length; _i++) {`);
427
- lines.push(` const _item = ${raw}[_i]`);
428
- lines.push(` if (${itemWrong}) errors.push({ message: 'items must be ${itemLabel}', path: \`${path.slice(1, -1)}/\${_i}\` })`);
471
+ lines.push(` for (let ${iv} = 0; ${iv} < ${raw}.length; ${iv}++) {`);
472
+ lines.push(` const ${itemVar} = ${raw}[${iv}]`);
473
+ lines.push(...detail.map((l) => ` ${l}`));
429
474
  lines.push(` }`);
430
475
  lines.push(` }`);
431
476
  }
432
477
  }
433
478
  }
434
- // Array length / uniqueness. `uniqueItems` dedupes by a JSON projection — exact
435
- // for primitives (what the type guard also uses); deep-but-key-ordered for
436
- // objects, the same pragmatic trade-off the rest of the generator makes.
479
+ // Array length / uniqueness. `uniqueItems` dedupes scalar items by a cheap
480
+ // `JSON.stringify` projection (exact for primitives, what the type guard also
481
+ // uses), but falls back to the structural `allUnique` helper when items may be
482
+ // objects/arrays — `JSON.stringify` is key-order sensitive and would disagree
483
+ // with the interpreter's order-independent deep equality.
437
484
  if (hasMinItems(propSchema) ||
438
485
  hasMaxItems(propSchema) ||
439
486
  (hasUniqueItems(propSchema) && propSchema.uniqueItems === true) ||
@@ -450,7 +497,10 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
450
497
  lines.push(` }`);
451
498
  }
452
499
  if (hasUniqueItems(propSchema) && propSchema.uniqueItems === true) {
453
- lines.push(` if (Array.isArray(${raw}) && new Set((${raw} as unknown[]).map((_u) => JSON.stringify(_u))).size !== ${raw}.length) {`);
500
+ const dupCond = arrayItemsAreScalarOnly(sp)
501
+ ? `new Set((${raw} as unknown[]).map((_u) => JSON.stringify(_u))).size !== ${raw}.length`
502
+ : `!allUnique(${raw} as unknown[])`;
503
+ lines.push(` if (Array.isArray(${raw}) && ${dupCond}) {`);
454
504
  lines.push(` errors.push({ message: 'must NOT have duplicate items', path: ${path} })`);
455
505
  lines.push(` }`);
456
506
  }
@@ -499,34 +549,49 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
499
549
  };
500
550
  /**
501
551
  * Validates a value located at a *dynamic* key (a `patternProperties` or
502
- * `additionalProperties` schema value) against `propSchema`. Mirrors the
503
- * optional-property branch of {@link generatePropertyChecks} the value is
504
- * always present, so each check is the same `!== undefined`-guarded form — but
505
- * `raw` and `path` are caller-supplied expressions (e.g. `obj[_k]` and
506
- * `` `${_path}/${_k}` ``) so the checks read a runtime key.
552
+ * `additionalProperties` value), an array item, a combinator branch, or a
553
+ * `dependentSchemas` subschema against `propSchema`. `raw` and `path` are
554
+ * caller-supplied expressions (e.g. `obj[_k]` and `` `${_path}/${_k}` ``) so the
555
+ * checks read a runtime location. By default the leaf checks are
556
+ * `!== undefined`-guarded (an absent optional value is valid); pass
557
+ * `required = true` for values that must be present (array items — a sparse hole
558
+ * reads as `undefined` and must fail), which drops that guard. `_key` is unused
559
+ * (the location is fully encoded by `path`) but kept for positional-call parity
560
+ * with the combinator generators.
507
561
  */
508
- const generateValueChecks = (key, raw, path, propSchema, suffix, ctx) => {
562
+ const generateValueChecks = (_key, raw, path, propSchema, suffix, ctx, required = false) => {
509
563
  if (!isSchemaObject(propSchema))
510
564
  return [];
511
565
  const lines = [];
566
+ // Optional values skip validation when absent, so their leaf checks are
567
+ // `!== undefined`-guarded. Array items are unconditionally present — a sparse
568
+ // hole reads as `undefined` and must FAIL its type/const/enum check — so
569
+ // `required` drops the guard.
570
+ const presence = required ? '' : `${raw} !== undefined && `;
512
571
  if (hasRef(propSchema)) {
513
572
  const vName = validatorName(refToName(propSchema.$ref, suffix));
514
- lines.push(` if (${raw} !== undefined) {`);
515
- lines.push(` const _r = ${vName}(${raw}, ${path})`);
516
- lines.push(` if (_r !== true) errors.push(..._r.errors)`);
517
- lines.push(` }`);
573
+ if (required) {
574
+ lines.push(` const _r = ${vName}(${raw}, ${path})`);
575
+ lines.push(` if (_r !== true) errors.push(..._r.errors)`);
576
+ }
577
+ else {
578
+ lines.push(` if (${raw} !== undefined) {`);
579
+ lines.push(` const _r = ${vName}(${raw}, ${path})`);
580
+ lines.push(` if (_r !== true) errors.push(..._r.errors)`);
581
+ lines.push(` }`);
582
+ }
518
583
  return lines;
519
584
  }
520
585
  const instanceOf = getMjstInstanceOf(propSchema);
521
586
  if (instanceOf) {
522
- lines.push(` if (${raw} !== undefined && !(${raw} instanceof ${instanceOf})) {`);
587
+ lines.push(` if (${presence}!(${raw} instanceof ${instanceOf})) {`);
523
588
  lines.push(` errors.push({ message: 'must be ${instanceOf}', path: ${path} })`);
524
589
  lines.push(` }`);
525
590
  return lines;
526
591
  }
527
592
  const primitive = getMjstPrimitive(propSchema);
528
593
  if (primitive) {
529
- lines.push(` if (${raw} !== undefined && typeof ${raw} !== "${primitive}") {`);
594
+ lines.push(` if (${presence}typeof ${raw} !== "${primitive}") {`);
530
595
  lines.push(` errors.push({ message: 'must be ${primitive}', path: ${path} })`);
531
596
  lines.push(` }`);
532
597
  return lines;
@@ -534,7 +599,7 @@ const generateValueChecks = (key, raw, path, propSchema, suffix, ctx) => {
534
599
  if (hasConst(propSchema)) {
535
600
  const mismatch = constMismatchCondition(raw, propSchema.const);
536
601
  const msg = JSON.stringify(`must be ${JSON.stringify(propSchema.const)}`);
537
- lines.push(` if (${raw} !== undefined && ${mismatch}) {`);
602
+ lines.push(` if (${presence}${mismatch}) {`);
538
603
  lines.push(` errors.push({ message: ${msg}, path: ${path} })`);
539
604
  lines.push(` }`);
540
605
  return lines;
@@ -542,7 +607,7 @@ const generateValueChecks = (key, raw, path, propSchema, suffix, ctx) => {
542
607
  if (hasEnum(propSchema)) {
543
608
  const allowed = JSON.stringify(propSchema.enum);
544
609
  const label = propSchema.enum.map((v) => JSON.stringify(v)).join(', ');
545
- lines.push(` if (${raw} !== undefined && !(${allowed} as unknown[]).includes(${raw})) {`);
610
+ lines.push(` if (${presence}!(${allowed} as unknown[]).includes(${raw})) {`);
546
611
  lines.push(` errors.push({ message: ${JSON.stringify(`must be one of: ${label}`)}, path: ${path} })`);
547
612
  lines.push(` }`);
548
613
  return lines;
@@ -552,7 +617,7 @@ const generateValueChecks = (key, raw, path, propSchema, suffix, ctx) => {
552
617
  const wrongType = wrongTypeCondition(raw, t);
553
618
  const typLabel = typeofString(t);
554
619
  if (wrongType) {
555
- lines.push(` if (${raw} !== undefined && (${wrongType})) {`);
620
+ lines.push(` if (${presence}(${wrongType})) {`);
556
621
  lines.push(` errors.push({ message: 'must be ${typLabel}', path: ${path} })`);
557
622
  lines.push(` }`);
558
623
  }
@@ -561,8 +626,20 @@ const generateValueChecks = (key, raw, path, propSchema, suffix, ctx) => {
561
626
  // gate on keyword presence + a runtime-type guard, so a type-less subschema
562
627
  // (a combinator branch like `{ required: [...] }` or `{ minItems: 2 }`) is
563
628
  // still validated rather than collapsing to "matches everything".
564
- lines.push(...generateConstraintChecks(key, raw, path, propSchema, suffix, ctx));
565
- lines.push(...generateCombinatorChecks(key, raw, path, propSchema, suffix, ctx));
629
+ //
630
+ // This value lives at `path` (a template literal), so anchor the recursion's
631
+ // context there and one nesting level deeper: any nested object/array it emits
632
+ // then builds paths relative to THIS value and mints collision-free variable
633
+ // names, independent of the caller's context. `key` is intentionally dropped
634
+ // (set to `''`) because `path` already locates the value.
635
+ const valueCtx = {
636
+ objVar: ctx.objVar,
637
+ pathPrefix: path.slice(1, -1),
638
+ depth: ctx.depth + 1,
639
+ hoisted: ctx.hoisted,
640
+ };
641
+ lines.push(...generateConstraintChecks('', raw, path, propSchema, suffix, valueCtx));
642
+ lines.push(...generateCombinatorChecks('', raw, path, propSchema, suffix, valueCtx));
566
643
  return lines;
567
644
  };
568
645
  /**
@@ -698,7 +775,10 @@ const generateInlineObjectChecks = (key, propSchema, raw, suffix, ctx) => {
698
775
  return [];
699
776
  const child = {
700
777
  objVar: `_obj${ctx.depth + 1}`,
701
- pathPrefix: `${ctx.pathPrefix}/${key}`,
778
+ // When `key` is empty the value is located AT `ctx.pathPrefix` already (e.g. an
779
+ // inline object reached through a combinator branch or a dynamic-key value), so
780
+ // appending `/${key}` would emit a spurious `//` or trailing `/` in error paths.
781
+ pathPrefix: key === '' ? ctx.pathPrefix : `${ctx.pathPrefix}/${key}`,
702
782
  depth: ctx.depth + 1,
703
783
  hoisted: ctx.hoisted,
704
784
  };
@@ -712,6 +792,9 @@ const generateInlineObjectChecks = (key, propSchema, raw, suffix, ctx) => {
712
792
  innerLines.push(...generatePatternAndAdditionalChecks(propSchema, suffix, child));
713
793
  innerLines.push(...generateStrictKeyChecks(propSchema, child));
714
794
  innerLines.push(...generateDependentRequiredChecks(propSchema, child));
795
+ innerLines.push(...generateDependentSchemasChecks(propSchema, suffix, child));
796
+ innerLines.push(...generateDependenciesChecks(propSchema, suffix, child));
797
+ innerLines.push(...generateMinMaxPropertiesChecks(propSchema, child));
715
798
  if (hasPropertyNames(propSchema) && isSchemaObject(propSchema.propertyNames)) {
716
799
  innerLines.push(...generatePropertyNameChecks(propSchema.propertyNames, suffix, child));
717
800
  }
@@ -727,49 +810,29 @@ const generateInlineObjectChecks = (key, propSchema, raw, suffix, ctx) => {
727
810
  ];
728
811
  };
729
812
  /**
730
- * Generates the `propertyNames` loop: every object key is a string, so we apply
731
- * the string-relevant constraints of the subschema (or delegate to a `$ref`'s
732
- * validator). This keeps the generator in step with the interpreter, which runs
733
- * the whole subschema against each key not just the `pattern` form.
813
+ * Generates the `propertyNames` loop: every object key is a string, and the
814
+ * *whole* subschema is validated against each key not just the
815
+ * `pattern`/length/`enum`/`const`/`$ref` subset. Delegating to
816
+ * {@link generateValueChecks} keeps the generator in lockstep with the
817
+ * interpreter, which runs `matchesSchema(nameSchema, key)` per key, so a
818
+ * subschema carrying a combinator, `type`, `multipleOf`, etc. is enforced too.
819
+ * A key is always a present string, so the value checks run in `required` mode
820
+ * (no `!== undefined` guard).
734
821
  */
735
822
  const generatePropertyNameChecks = (nameSchema, suffix, ctx) => {
736
823
  if (!isSchemaObject(nameSchema))
737
824
  return [];
738
825
  const at = `\`${ctx.pathPrefix}/\${_name}\``;
739
- const checks = [];
740
- if (hasRef(nameSchema)) {
741
- const vName = validatorName(refToName(nameSchema.$ref, suffix));
742
- checks.push(` const _nr = ${vName}(_name, ${at})`);
743
- checks.push(` if (_nr !== true) errors.push(..._nr.errors)`);
744
- }
745
- else {
746
- if (hasPattern(nameSchema)) {
747
- const re = escapeRegexPattern(nameSchema.pattern);
748
- const msg = JSON.stringify(`property name must match pattern ${nameSchema.pattern}`);
749
- checks.push(` if (!/${re}/.test(_name)) errors.push({ message: ${msg}, path: ${at} })`);
750
- }
751
- if (hasMinLength(nameSchema)) {
752
- const msg = JSON.stringify(`property name must have at least ${nameSchema.minLength} characters`);
753
- checks.push(` if (_name.length < ${nameSchema.minLength}) errors.push({ message: ${msg}, path: ${at} })`);
754
- }
755
- if (hasMaxLength(nameSchema)) {
756
- const msg = JSON.stringify(`property name must have at most ${nameSchema.maxLength} characters`);
757
- checks.push(` if (_name.length > ${nameSchema.maxLength}) errors.push({ message: ${msg}, path: ${at} })`);
758
- }
759
- if (hasEnum(nameSchema)) {
760
- const allowed = JSON.stringify(nameSchema.enum);
761
- const label = nameSchema.enum.map((v) => JSON.stringify(v)).join(', ');
762
- const msg = JSON.stringify(`property name must be one of: ${label}`);
763
- checks.push(` if (!(${allowed} as unknown[]).includes(_name)) errors.push({ message: ${msg}, path: ${at} })`);
764
- }
765
- if (hasConst(nameSchema)) {
766
- const msg = JSON.stringify(`property name must be ${JSON.stringify(nameSchema.const)}`);
767
- checks.push(` if (_name !== ${JSON.stringify(nameSchema.const)}) errors.push({ message: ${msg}, path: ${at} })`);
768
- }
769
- }
826
+ const nameCtx = {
827
+ objVar: ctx.objVar,
828
+ pathPrefix: `${ctx.pathPrefix}/\${_name}`,
829
+ depth: ctx.depth + 1,
830
+ hoisted: ctx.hoisted,
831
+ };
832
+ const checks = generateValueChecks('', '_name', at, nameSchema, suffix, nameCtx, true);
770
833
  if (checks.length === 0)
771
834
  return [];
772
- return [` for (const _name of Object.keys(${ctx.objVar})) {`, ...checks, ` }`];
835
+ return [` for (const _name of Object.keys(${ctx.objVar})) {`, ...checks.map((line) => ` ${line}`), ` }`];
773
836
  };
774
837
  /**
775
838
  * Emits `dependentRequired` checks: when a trigger key is present, each of its
@@ -794,6 +857,129 @@ const generateDependentRequiredChecks = (schema, ctx) => {
794
857
  }
795
858
  return lines;
796
859
  };
860
+ /**
861
+ * Emits `dependentSchemas` checks (2020-12): when a trigger property is present,
862
+ * the *whole object* must also match the associated subschema. Mirrors the
863
+ * interpreter, which applies the subschema in place against the object. A `true`
864
+ * subschema permits everything (no-op); a `false` subschema makes the trigger's
865
+ * presence always invalid.
866
+ */
867
+ const generateDependentSchemasChecks = (schema, suffix, ctx) => {
868
+ if (!isSchemaObject(schema))
869
+ return [];
870
+ const dep = schema['dependentSchemas'];
871
+ if (typeof dep !== 'object' || dep === null || Array.isArray(dep))
872
+ return [];
873
+ const obj = ctx.objVar;
874
+ const at = ctx.depth === 0 ? '_path' : `\`${ctx.pathPrefix}\``;
875
+ const objPath = `\`${ctx.pathPrefix}\``;
876
+ const lines = [];
877
+ for (const [trigger, sub] of Object.entries(dep)) {
878
+ if (sub === true)
879
+ continue;
880
+ if (sub === false) {
881
+ const msg = JSON.stringify(`must NOT have property '${trigger}'`);
882
+ lines.push(` if (${JSON.stringify(trigger)} in ${obj}) {`);
883
+ lines.push(` errors.push({ message: ${msg}, path: ${at} })`);
884
+ lines.push(` }`);
885
+ continue;
886
+ }
887
+ if (!isSchemaObject(sub))
888
+ continue;
889
+ // The subschema applies to the object itself, so validate the current object
890
+ // variable against it and gate the whole block on the trigger's presence.
891
+ const checks = generateValueChecks('', obj, objPath, sub, suffix, ctx);
892
+ if (checks.length === 0)
893
+ continue;
894
+ lines.push(` if (${JSON.stringify(trigger)} in ${obj}) {`);
895
+ lines.push(...checks.map((line) => ` ${line}`));
896
+ lines.push(` }`);
897
+ }
898
+ return lines;
899
+ };
900
+ /**
901
+ * Emits draft-07 `dependencies` — the dual-form predecessor of
902
+ * `dependentRequired` + `dependentSchemas`. When a trigger property is present,
903
+ * an array value requires each listed key, and a schema value is applied to the
904
+ * *whole object*. Mirrors the interpreter, which branches on the value's shape.
905
+ * A `false` subschema makes the trigger's mere presence invalid; a `true`
906
+ * subschema is a no-op.
907
+ */
908
+ const generateDependenciesChecks = (schema, suffix, ctx) => {
909
+ if (!isSchemaObject(schema))
910
+ return [];
911
+ const dep = schema['dependencies'];
912
+ if (typeof dep !== 'object' || dep === null || Array.isArray(dep))
913
+ return [];
914
+ const obj = ctx.objVar;
915
+ const at = ctx.depth === 0 ? '_path' : `\`${ctx.pathPrefix}\``;
916
+ const objPath = `\`${ctx.pathPrefix}\``;
917
+ const lines = [];
918
+ for (const [trigger, value] of Object.entries(dep)) {
919
+ // Array form: each listed key must be present when the trigger is.
920
+ if (Array.isArray(value)) {
921
+ for (const key of value) {
922
+ if (typeof key !== 'string')
923
+ continue;
924
+ const msg = JSON.stringify(`must have property '${key}' when '${trigger}' is present`);
925
+ lines.push(` if (${JSON.stringify(trigger)} in ${obj} && !(${JSON.stringify(key)} in ${obj})) {`);
926
+ lines.push(` errors.push({ message: ${msg}, path: ${at} })`);
927
+ lines.push(` }`);
928
+ }
929
+ continue;
930
+ }
931
+ // Schema form: the subschema applies to the object itself.
932
+ if (value === true)
933
+ continue;
934
+ if (value === false) {
935
+ const msg = JSON.stringify(`must NOT have property '${trigger}'`);
936
+ lines.push(` if (${JSON.stringify(trigger)} in ${obj}) {`);
937
+ lines.push(` errors.push({ message: ${msg}, path: ${at} })`);
938
+ lines.push(` }`);
939
+ continue;
940
+ }
941
+ if (!isSchemaObject(value))
942
+ continue;
943
+ const checks = generateValueChecks('', obj, objPath, value, suffix, ctx);
944
+ if (checks.length === 0)
945
+ continue;
946
+ lines.push(` if (${JSON.stringify(trigger)} in ${obj}) {`);
947
+ lines.push(...checks.map((line) => ` ${line}`));
948
+ lines.push(` }`);
949
+ }
950
+ return lines;
951
+ };
952
+ /**
953
+ * Emits `minProperties` / `maxProperties` bounds on the object's key count,
954
+ * mirroring the interpreter (which counts the object's own enumerable keys and
955
+ * reports at the object node). Counting once into a depth-scoped local keeps the
956
+ * two bounds from re-walking the keys.
957
+ */
958
+ const generateMinMaxPropertiesChecks = (schema, ctx) => {
959
+ if (!isSchemaObject(schema))
960
+ return [];
961
+ const hasMin = hasMinProperties(schema);
962
+ const hasMax = hasMaxProperties(schema);
963
+ if (!hasMin && !hasMax)
964
+ return [];
965
+ const obj = ctx.objVar;
966
+ const at = ctx.depth === 0 ? '_path' : `\`${ctx.pathPrefix}\``;
967
+ const count = `_pc${ctx.depth}`;
968
+ const lines = [` const ${count} = Object.keys(${obj}).length`];
969
+ if (hasMin) {
970
+ const msg = JSON.stringify(`must have at least ${schema.minProperties} properties`);
971
+ lines.push(` if (${count} < ${schema.minProperties}) {`);
972
+ lines.push(` errors.push({ message: ${msg}, path: ${at} })`);
973
+ lines.push(` }`);
974
+ }
975
+ if (hasMax) {
976
+ const msg = JSON.stringify(`must have at most ${schema.maxProperties} properties`);
977
+ lines.push(` if (${count} > ${schema.maxProperties}) {`);
978
+ lines.push(` errors.push({ message: ${msg}, path: ${at} })`);
979
+ lines.push(` }`);
980
+ }
981
+ return lines;
982
+ };
797
983
  /**
798
984
  * Builds the `&&` conditions that prove a single property is valid, or `null`
799
985
  * when the property carries any keyword the slow path enforces beyond a bare
@@ -803,6 +989,17 @@ const generateDependentRequiredChecks = (schema, ctx) => {
803
989
  * input, never weakening a verdict. `objAcc` is the expression yielding the
804
990
  * parent object (already narrowed to a record); `key` indexes into it.
805
991
  */
992
+ /**
993
+ * Whether an object schema carries a combinator keyword (`allOf`, `anyOf`,
994
+ * `oneOf`, `not`, `if`) that the error-collecting slow path enforces but neither
995
+ * flat guard can mirror. When present, both guards must bail so their early
996
+ * `return true` never accepts a document the combinator would reject.
997
+ */
998
+ const hasObjectLevelCombinator = (schema) => {
999
+ if (!isSchemaObject(schema))
1000
+ return false;
1001
+ return hasAllOf(schema) || hasAnyOf(schema) || hasOneOf(schema) || 'not' in schema || 'if' in schema;
1002
+ };
806
1003
  const guardPropConditions = (key, propSchema, objAcc) => {
807
1004
  if (!isSchemaObject(propSchema))
808
1005
  return null;
@@ -898,10 +1095,17 @@ const arrayRejectedByRequiredProp = (keys, required, properties) => {
898
1095
  const guardObjectConditions = (schema, raw, objAcc) => {
899
1096
  if (!isObjectSchema(schema))
900
1097
  return null;
901
- if (hasDependentRequired(schema) || hasPropertyNames(schema))
1098
+ if (hasDependentRequired(schema) || hasPropertyNames(schema) || 'dependentSchemas' in schema)
1099
+ return null;
1100
+ if (hasMinProperties(schema) || hasMaxProperties(schema) || 'dependencies' in schema)
902
1101
  return null;
903
1102
  if (isSchemaObject(schema) && 'patternProperties' in schema)
904
1103
  return null;
1104
+ // Object-level combinators are enforced by the slow path but cannot be mirrored
1105
+ // by this flat guard, so bail — otherwise the guard's early `return true` would
1106
+ // accept documents the combinators reject.
1107
+ if (hasObjectLevelCombinator(schema))
1108
+ return null;
905
1109
  let strict = false;
906
1110
  if (hasAdditionalProperties(schema)) {
907
1111
  // Only `additionalProperties: false` is guardable (via the key-count trick
@@ -969,6 +1173,14 @@ const generateObjectValidator = (schema, typeName, suffix) => {
969
1173
  propertyLines.push(...generateStrictKeyChecks(schema, ctx));
970
1174
  // dependentRequired — when a trigger property is present, its dependencies must be too.
971
1175
  propertyLines.push(...generateDependentRequiredChecks(schema, ctx));
1176
+ // dependentSchemas — when a trigger property is present, the whole object must
1177
+ // also match the associated subschema.
1178
+ propertyLines.push(...generateDependentSchemasChecks(schema, suffix, ctx));
1179
+ // dependencies (draft-07) — the dual-form predecessor of dependentRequired +
1180
+ // dependentSchemas.
1181
+ propertyLines.push(...generateDependenciesChecks(schema, suffix, ctx));
1182
+ // minProperties / maxProperties — bound the object's key count.
1183
+ propertyLines.push(...generateMinMaxPropertiesChecks(schema, ctx));
972
1184
  // propertyNames — every key (always a string) must satisfy the subschema. This
973
1185
  // mirrors the interpreter, which runs the full subschema against each key.
974
1186
  if (hasPropertyNames(schema) && isSchemaObject(schema.propertyNames)) {
@@ -1040,32 +1252,6 @@ const generateObjectValidator = (schema, typeName, suffix) => {
1040
1252
  * e.g. "InfoObject" → "isInfoObject"
1041
1253
  */
1042
1254
  const guardName = (typeName) => `is${typeName}`;
1043
- /**
1044
- * Positive type check for a value — the negation of {@link wrongTypeCondition}.
1045
- * Used by the boolean type-guard, which proves validity with `&&` conditions
1046
- * rather than collecting errors. Object is a shape-only check (matching the
1047
- * validator, which never recurses into array items or untyped object values).
1048
- */
1049
- const rightTypeCondition = (accessor, type) => {
1050
- switch (type) {
1051
- case 'string':
1052
- return `typeof ${accessor} === 'string'`;
1053
- case 'number':
1054
- return `typeof ${accessor} === 'number'`;
1055
- case 'integer':
1056
- return `typeof ${accessor} === 'number' && Number.isInteger(${accessor})`;
1057
- case 'boolean':
1058
- return `typeof ${accessor} === 'boolean'`;
1059
- case 'array':
1060
- return `Array.isArray(${accessor})`;
1061
- case 'null':
1062
- return `${accessor} === null`;
1063
- case 'object':
1064
- return `typeof ${accessor} === 'object' && ${accessor} !== null && !Array.isArray(${accessor})`;
1065
- default:
1066
- return null;
1067
- }
1068
- };
1069
1255
  /**
1070
1256
  * Builds the membership test for an `enum`, matching the slow path's
1071
1257
  * `[...].includes(value)` verdict exactly. For the common all-primitive case it
@@ -1163,10 +1349,10 @@ const booleanLeafExpr = (schema, acc) => {
1163
1349
  }
1164
1350
  };
1165
1351
  /**
1166
- * Boolean expression for an array value. Mirrors the validator, which checks the
1167
- * array shape and — for typed items — only each item's *type* (objects are shape-
1168
- * checked, not recursed into); it never enforces `minItems`/`maxItems` or item
1169
- * constraints. Returns `null` for `$ref` items (those defer to the validator).
1352
+ * Boolean expression for an array value. Mirrors the validator: array shape,
1353
+ * `minItems`/`maxItems`/`uniqueItems`, and each item validated in full via
1354
+ * {@link booleanLeafExpr}. Returns `null` for `$ref` items, or when an item schema
1355
+ * can't be expressed flat, so the whole guard defers to the validator.
1170
1356
  *
1171
1357
  * Item iteration goes through `Array.from` rather than `Array.prototype.every`
1172
1358
  * because `every` *skips holes* in a sparse array (`[, 'x']`), whereas the
@@ -1183,7 +1369,11 @@ const booleanArrayExpr = (schema, acc) => {
1183
1369
  if (hasMaxItems(schema))
1184
1370
  parts.push(`${acc}.length <= ${schema.maxItems}`);
1185
1371
  if (hasUniqueItems(schema) && schema.uniqueItems === true) {
1186
- parts.push(`new Set((${acc} as unknown[]).map((_u) => JSON.stringify(_u))).size === ${acc}.length`);
1372
+ // Same scalar-vs-structural split as the validator, so the guard's verdict
1373
+ // matches the slow path's for object items in a reordered key order.
1374
+ parts.push(arrayItemsAreScalarOnly(schema)
1375
+ ? `new Set((${acc} as unknown[]).map((_u) => JSON.stringify(_u))).size === ${acc}.length`
1376
+ : `allUnique(${acc} as unknown[])`);
1187
1377
  }
1188
1378
  const base = parts.join(' && ');
1189
1379
  if (!hasItems(schema))
@@ -1193,12 +1383,14 @@ const booleanArrayExpr = (schema, acc) => {
1193
1383
  return base;
1194
1384
  if (hasRef(items))
1195
1385
  return null;
1196
- if (!hasType(items))
1197
- return base;
1198
- const itemCheck = rightTypeCondition('_it', items.type);
1199
- if (itemCheck === null)
1200
- return base;
1201
- return `${base} && Array.from(${acc} as unknown[]).every((_it) => ${itemCheck})`;
1386
+ // Validate each item in full, mirroring the validator's per-item checks so the
1387
+ // guard reaches the identical verdict. `booleanLeafExpr` returns `null` for item
1388
+ // schemas it can't express flat — bail so the validator decides, keeping the
1389
+ // guard from ever accepting what the slow path would reject.
1390
+ const itemExpr = booleanLeafExpr(items, '_it');
1391
+ if (itemExpr === null)
1392
+ return null;
1393
+ return `${base} && Array.from(${acc} as unknown[]).every((_it) => (${itemExpr}))`;
1202
1394
  };
1203
1395
  /**
1204
1396
  * Builds the `&&` conditions proving an object value is valid (same verdict as
@@ -1210,10 +1402,16 @@ const booleanObjectParts = (schema, raw, objAcc) => {
1210
1402
  if (!isObjectSchema(schema))
1211
1403
  return null;
1212
1404
  // These need per-key loops or cross-references the flat form can't express.
1213
- if (hasDependentRequired(schema) || hasPropertyNames(schema))
1405
+ if (hasDependentRequired(schema) || hasPropertyNames(schema) || 'dependentSchemas' in schema)
1406
+ return null;
1407
+ if (hasMinProperties(schema) || hasMaxProperties(schema) || 'dependencies' in schema)
1214
1408
  return null;
1215
1409
  if (isSchemaObject(schema) && 'patternProperties' in schema)
1216
1410
  return null;
1411
+ // Object-level combinators change the verdict but can't be expressed flat, so
1412
+ // defer to the validator rather than emit a guard that ignores them.
1413
+ if (hasObjectLevelCombinator(schema))
1414
+ return null;
1217
1415
  let strict = false;
1218
1416
  if (hasAdditionalProperties(schema)) {
1219
1417
  // Only `additionalProperties: false` is expressible; a schema needs per-key
@@ -1272,8 +1470,12 @@ const booleanObjectParts = (schema, raw, objAcc) => {
1272
1470
  export const generateBooleanGuard = (schema, typeName, _suffix = '') => {
1273
1471
  const name = guardName(typeName);
1274
1472
  const fallback = `export const ${name} = (input: unknown): input is ${typeName} => ${validatorName(typeName)}(input) === true`;
1275
- if (isObjectSchema(schema)) {
1276
- const parts = booleanObjectParts(schema, 'input', 'obj');
1473
+ // Fold `nullable: true` into `anyOf` so the guard's verdict matches the
1474
+ // validator's (which applies the same rewrite). Without this a nullable node's
1475
+ // guard would reject `null` while the validator accepts it.
1476
+ const rewritten = rewriteNullable(schema);
1477
+ if (isObjectSchema(rewritten)) {
1478
+ const parts = booleanObjectParts(rewritten, 'input', 'obj');
1277
1479
  if (parts === null)
1278
1480
  return fallback;
1279
1481
  return [
@@ -1286,7 +1488,7 @@ export const generateBooleanGuard = (schema, typeName, _suffix = '') => {
1286
1488
  ].join('\n');
1287
1489
  }
1288
1490
  // Non-object roots (scalar, enum, array) can often be expressed inline too.
1289
- const expr = booleanLeafExpr(schema, 'input');
1491
+ const expr = booleanLeafExpr(rewritten, 'input');
1290
1492
  if (expr === null)
1291
1493
  return fallback;
1292
1494
  return `export const ${name} = (input: unknown): input is ${typeName} => ${expr}`;
@@ -1487,6 +1689,76 @@ const assertNoUnsupportedKeywords = (schema, typeName) => {
1487
1689
  };
1488
1690
  visit(schema);
1489
1691
  };
1692
+ /** Keywords whose value is a single subschema (or a boolean schema). */
1693
+ const SINGLE_SUBSCHEMA_KEYS = new Set([
1694
+ 'additionalProperties',
1695
+ 'additionalItems',
1696
+ 'contains',
1697
+ 'propertyNames',
1698
+ 'not',
1699
+ 'if',
1700
+ 'then',
1701
+ 'else',
1702
+ 'unevaluatedProperties',
1703
+ 'unevaluatedItems',
1704
+ ]);
1705
+ /** Keywords whose value is an array of subschemas. */
1706
+ const SUBSCHEMA_LIST_KEYS = new Set(['allOf', 'anyOf', 'oneOf', 'prefixItems']);
1707
+ /** Keywords whose value is a map of names to subschemas. */
1708
+ const SUBSCHEMA_MAP_KEYS = new Set(['properties', 'patternProperties', 'dependentSchemas', '$defs', 'definitions']);
1709
+ /**
1710
+ * Rewrites OpenAPI 3.0 `nullable: true` into a form the generator already
1711
+ * enforces. A node `{ nullable: true, ...rest }` accepts a value iff the value is
1712
+ * `null` OR it matches `rest` — exactly `{ anyOf: [{ type: 'null' }, rest] }`.
1713
+ * Emitting that lets the existing `anyOf` machinery (and its guard bail-outs)
1714
+ * mirror the interpreter, which short-circuits every keyword when a `nullable`
1715
+ * node sees `null`.
1716
+ *
1717
+ * The walk descends only into genuine subschema positions (never into `enum` /
1718
+ * `const` / `default` data), so a data value that merely looks like a schema is
1719
+ * never rewritten. Returns a fresh tree; the caller's schema is untouched.
1720
+ */
1721
+ const rewriteNullable = (node) => {
1722
+ if (typeof node !== 'object' || node === null || Array.isArray(node))
1723
+ return node;
1724
+ const src = node;
1725
+ const out = {};
1726
+ for (const [key, value] of Object.entries(src)) {
1727
+ if (key === 'nullable')
1728
+ continue; // folded into the `anyOf` wrapper below
1729
+ if (SINGLE_SUBSCHEMA_KEYS.has(key)) {
1730
+ out[key] = rewriteNullable(value);
1731
+ }
1732
+ else if (key === 'items') {
1733
+ // `items` is either a single subschema (2020-12) or a tuple array (draft).
1734
+ out[key] = Array.isArray(value) ? value.map(rewriteNullable) : rewriteNullable(value);
1735
+ }
1736
+ else if (SUBSCHEMA_LIST_KEYS.has(key) && Array.isArray(value)) {
1737
+ out[key] = value.map(rewriteNullable);
1738
+ }
1739
+ else if (SUBSCHEMA_MAP_KEYS.has(key) && typeof value === 'object' && value !== null) {
1740
+ const mapped = {};
1741
+ for (const [name, sub] of Object.entries(value))
1742
+ mapped[name] = rewriteNullable(sub);
1743
+ out[key] = mapped;
1744
+ }
1745
+ else if (key === 'dependencies' && typeof value === 'object' && value !== null) {
1746
+ // Dual-form: an array value lists required keys (data), a schema value is a
1747
+ // subschema.
1748
+ const mapped = {};
1749
+ for (const [name, sub] of Object.entries(value)) {
1750
+ mapped[name] = Array.isArray(sub) ? sub : rewriteNullable(sub);
1751
+ }
1752
+ out[key] = mapped;
1753
+ }
1754
+ else {
1755
+ out[key] = value;
1756
+ }
1757
+ }
1758
+ if (src['nullable'] === true)
1759
+ return { anyOf: [{ type: 'null' }, out] };
1760
+ return out;
1761
+ };
1490
1762
  /**
1491
1763
  * Generates a TypeScript validator function from a JSON Schema.
1492
1764
  *
@@ -1511,8 +1783,11 @@ const assertNoUnsupportedKeywords = (schema, typeName) => {
1511
1783
  */
1512
1784
  export const generateValidatorFunction = (schema, typeName, suffix = '') => {
1513
1785
  assertNoUnsupportedKeywords(schema, typeName);
1514
- if (isObjectSchema(schema)) {
1515
- return generateObjectValidator(schema, typeName, suffix);
1786
+ // Fold OpenAPI `nullable: true` into the `anyOf` form the generator already
1787
+ // enforces, so the emitted checks match the interpreter's null short-circuit.
1788
+ const rewritten = rewriteNullable(schema);
1789
+ if (isObjectSchema(rewritten)) {
1790
+ return generateObjectValidator(rewritten, typeName, suffix);
1516
1791
  }
1517
- return generateScalarValidator(schema, typeName, suffix);
1792
+ return generateScalarValidator(rewritten, typeName, suffix);
1518
1793
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/generate-validators",
3
- "version": "0.11.4",
3
+ "version": "0.11.6",
4
4
  "description": "Generate TypeScript validation functions from JSON Schemas.",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -46,9 +46,10 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "json-schema-typed": "^8.0.1",
49
- "@amritk/helpers": "0.11.0"
49
+ "@amritk/helpers": "0.13.0"
50
50
  },
51
51
  "devDependencies": {
52
+ "@amritk/runtime-validators": "0.7.0",
52
53
  "@ryoppippi/unplugin-typia": "^2.6.5",
53
54
  "@scalar/openapi-parser": "^0.26.1",
54
55
  "@sinclair/typebox": "^0.34.49",