ata-validator 0.5.1 → 0.6.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.
@@ -4,6 +4,34 @@
4
4
  // Closure-based validator — no new Function() or eval().
5
5
  // Returns null if the schema is too complex for JS compilation.
6
6
 
7
+ // AJV-compatible error message templates (compile-time, not runtime)
8
+ const AJV_MESSAGES = {
9
+ type: (p) => `must be ${p.type}`,
10
+ required: (p) => `must have required property '${p.missingProperty}'`,
11
+ additionalProperties: () => 'must NOT have additional properties',
12
+ enum: () => 'must be equal to one of the allowed values',
13
+ const: () => 'must be equal to constant',
14
+ minimum: (p) => `must be >= ${p.limit}`,
15
+ maximum: (p) => `must be <= ${p.limit}`,
16
+ exclusiveMinimum: (p) => `must be > ${p.limit}`,
17
+ exclusiveMaximum: (p) => `must be < ${p.limit}`,
18
+ minLength: (p) => `must NOT have fewer than ${p.limit} characters`,
19
+ maxLength: (p) => `must NOT have more than ${p.limit} characters`,
20
+ pattern: (p) => `must match pattern "${p.pattern}"`,
21
+ format: (p) => `must match format "${p.format}"`,
22
+ minItems: (p) => `must NOT have fewer than ${p.limit} items`,
23
+ maxItems: (p) => `must NOT have more than ${p.limit} items`,
24
+ uniqueItems: (p) => `must NOT have duplicate items (items ## ${p.j} and ${p.i} are identical)`,
25
+ minProperties: (p) => `must NOT have fewer than ${p.limit} properties`,
26
+ maxProperties: (p) => `must NOT have more than ${p.limit} properties`,
27
+ multipleOf: (p) => `must be multiple of ${p.multipleOf}`,
28
+ oneOf: () => 'must match exactly one schema in oneOf',
29
+ anyOf: () => 'must match a schema in anyOf',
30
+ allOf: () => 'must match all schemas in allOf',
31
+ not: () => 'must NOT be valid',
32
+ if: (p) => `must match "${p.failingKeyword}" schema`,
33
+ }
34
+
7
35
  function compileToJS(schema, defs, schemaMap) {
8
36
  if (typeof schema === 'boolean') {
9
37
  return schema ? () => true : () => false
@@ -810,10 +838,15 @@ function genCode(schema, v, lines, ctx, knownType) {
810
838
  if (schema.maxProperties !== undefined) lines.push(`if(${objGuard}Object.keys(${v}).length>${schema.maxProperties})return false`)
811
839
 
812
840
  if (schema.pattern) {
813
- // Use RegExp constructor via helper to avoid injection from untrusted patterns
814
- const ri = ctx.varCounter++
815
- ctx.helperCode.push(`const _re${ri}=new RegExp(${JSON.stringify(schema.pattern)})`)
816
- lines.push(isStr ? `if(!_re${ri}.test(${v}))return false` : `if(typeof ${v}==='string'&&!_re${ri}.test(${v}))return false`)
841
+ // Try inline charCode compilation for simple patterns (avoids RegExp engine)
842
+ const inlineCheck = compilePatternInline(schema.pattern, v)
843
+ if (inlineCheck) {
844
+ lines.push(isStr ? `if(!(${inlineCheck}))return false` : `if(typeof ${v}==='string'&&!(${inlineCheck}))return false`)
845
+ } else {
846
+ const ri = ctx.varCounter++
847
+ ctx.helperCode.push(`const _re${ri}=new RegExp(${JSON.stringify(schema.pattern)})`)
848
+ lines.push(isStr ? `if(!_re${ri}.test(${v}))return false` : `if(typeof ${v}==='string'&&!_re${ri}.test(${v}))return false`)
849
+ }
817
850
  }
818
851
 
819
852
  if (schema.format) {
@@ -1583,6 +1616,79 @@ const FORMAT_CODEGEN = {
1583
1616
  // Safe key escaping: use JSON.stringify to handle all special chars (newlines, null bytes, etc.)
1584
1617
  function esc(s) { return JSON.stringify(s).slice(1, -1) }
1585
1618
 
1619
+ // Resolve child path at codegen time when parent is a static string literal.
1620
+ // This enables frozen pre-allocation for ALL nested error objects.
1621
+ function childPathExpr(parentExpr, suffix) {
1622
+ if (!parentExpr) return `'/${suffix}'`
1623
+ if (parentExpr.startsWith("'") && !parentExpr.includes('+')) {
1624
+ // Static parent: resolve at codegen time → '/parent/child' (single literal)
1625
+ return `'${parentExpr.slice(1, -1)}/${suffix}'`
1626
+ }
1627
+ // Dynamic parent: keep as concat expression
1628
+ return `${parentExpr}+'/${suffix}'`
1629
+ }
1630
+
1631
+ // Compile simple regex patterns to inline charCode checks — avoids RegExp engine overhead.
1632
+ // Returns null if pattern is too complex for inline compilation.
1633
+ // Handles: ^[charclass]{n}$, ^[charclass]+$, ^[charclass]*$, ^[charclass]{m,n}$
1634
+ function compilePatternInline(pattern, varName) {
1635
+ // Match: ^[chars]{exact}$ — e.g., ^[0-9]{5}$
1636
+ let m = pattern.match(/^\^(\[[\w\-]+\])\{(\d+)\}\$$/)
1637
+ if (m) {
1638
+ const rangeCheck = charClassToCheck(m[1], `${varName}.charCodeAt(_pi)`)
1639
+ if (!rangeCheck) return null
1640
+ const len = parseInt(m[2])
1641
+ return `${varName}.length===${len}&&(()=>{for(let _pi=0;_pi<${len};_pi++){if(!(${rangeCheck}))return false}return true})()`
1642
+ }
1643
+ // Match: ^[chars]+$ — e.g., ^[a-z]+$
1644
+ m = pattern.match(/^\^(\[[\w\-]+\])\+\$$/)
1645
+ if (m) {
1646
+ const rangeCheck = charClassToCheck(m[1], `${varName}.charCodeAt(_pi)`)
1647
+ if (!rangeCheck) return null
1648
+ return `${varName}.length>0&&(()=>{for(let _pi=0;_pi<${varName}.length;_pi++){if(!(${rangeCheck}))return false}return true})()`
1649
+ }
1650
+ // Match: ^[chars]{m,n}$ — e.g., ^[a-zA-Z]{2,50}$
1651
+ m = pattern.match(/^\^(\[[\w\-]+\])\{(\d+),(\d+)\}\$$/)
1652
+ if (m) {
1653
+ const rangeCheck = charClassToCheck(m[1], `${varName}.charCodeAt(_pi)`)
1654
+ if (!rangeCheck) return null
1655
+ const min = parseInt(m[2]), max = parseInt(m[3])
1656
+ return `${varName}.length>=${min}&&${varName}.length<=${max}&&(()=>{for(let _pi=0;_pi<${varName}.length;_pi++){if(!(${rangeCheck}))return false}return true})()`
1657
+ }
1658
+ return null
1659
+ }
1660
+
1661
+ // Convert [charclass] to charCode range check expression.
1662
+ // Supports: [0-9], [a-z], [A-Z], [a-zA-Z], [a-zA-Z0-9], [0-9a-f], etc.
1663
+ function charClassToCheck(charClass, codeExpr) {
1664
+ // Strip brackets
1665
+ const inner = charClass.slice(1, -1)
1666
+ // Parse ranges
1667
+ const ranges = []
1668
+ let i = 0
1669
+ while (i < inner.length) {
1670
+ if (i + 2 < inner.length && inner[i + 1] === '-') {
1671
+ ranges.push([inner.charCodeAt(i), inner.charCodeAt(i + 2)])
1672
+ i += 3
1673
+ } else {
1674
+ ranges.push([inner.charCodeAt(i), inner.charCodeAt(i)])
1675
+ i++
1676
+ }
1677
+ }
1678
+ if (ranges.length === 0) return null
1679
+ // Generate check: (c >= 48 && c <= 57) || (c >= 65 && c <= 90)
1680
+ const checks = ranges.map(([lo, hi]) =>
1681
+ lo === hi ? `${codeExpr}===${lo}` : `(${codeExpr}>=${lo}&&${codeExpr}<=${hi})`
1682
+ )
1683
+ return checks.join('||')
1684
+ }
1685
+
1686
+ // Same but for dynamic segments (array indices)
1687
+ function childPathDynExpr(parentExpr, indexExpr) {
1688
+ if (!parentExpr) return `'/'+${indexExpr}`
1689
+ return `${parentExpr}+'/'+${indexExpr}`
1690
+ }
1691
+
1586
1692
  // Detect simple prefix patterns like "^x-", "^_", "^prefix" and generate fast charCodeAt checks
1587
1693
  // Returns a JS expression string or null if pattern is too complex
1588
1694
  function fastPrefixCheck(pattern, keyVar) {
@@ -1639,7 +1745,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap) {
1639
1745
  if (typeof schema === 'boolean') {
1640
1746
  return schema
1641
1747
  ? () => ({ valid: true, errors: [] })
1642
- : () => ({ valid: false, errors: [{ code: 'type_mismatch', path: '', message: 'schema is false' }] })
1748
+ : () => ({ valid: false, errors: [{ keyword: 'false schema', instancePath: '', schemaPath: '#', params: {}, message: 'boolean schema is false' }] })
1643
1749
  }
1644
1750
  if (typeof schema !== 'object' || schema === null) return null
1645
1751
  if (!codegenSafe(schema, schemaMap)) return null
@@ -1666,7 +1772,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap) {
1666
1772
 
1667
1773
  const ctx = { varCounter: 0, helperCode: [], rootDefs: schema.$defs || schema.definitions || null, refStack: new Set(), schemaMap: schemaMap || null }
1668
1774
  const lines = []
1669
- genCodeE(schema, 'd', '', lines, ctx)
1775
+ genCodeE(schema, 'd', '', lines, ctx, '#')
1670
1776
  if (lines.length === 0) return (d) => ({ valid: true, errors: [] })
1671
1777
 
1672
1778
  const body = `const _e=[];\n ` +
@@ -1685,7 +1791,8 @@ function compileToJSCodegenWithErrors(schema, schemaMap) {
1685
1791
  // Error-collecting code generator.
1686
1792
  // Instead of `return false`, pushes to `_e` array and optionally early-returns.
1687
1793
  // `_all` parameter: if falsy, return after first error.
1688
- function genCodeE(schema, v, pathExpr, lines, ctx) {
1794
+ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1795
+ if (!schemaPrefix) schemaPrefix = '#'
1689
1796
  if (typeof schema !== 'object' || schema === null) return
1690
1797
 
1691
1798
  // $ref — resolve local and cross-schema refs
@@ -1694,14 +1801,14 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1694
1801
  if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
1695
1802
  if (ctx.refStack.has(schema.$ref)) return
1696
1803
  ctx.refStack.add(schema.$ref)
1697
- genCodeE(ctx.rootDefs[m[1]], v, pathExpr, lines, ctx)
1804
+ genCodeE(ctx.rootDefs[m[1]], v, pathExpr, lines, ctx, schemaPrefix)
1698
1805
  ctx.refStack.delete(schema.$ref)
1699
1806
  return
1700
1807
  }
1701
1808
  if (ctx.schemaMap && ctx.schemaMap.has(schema.$ref)) {
1702
1809
  if (ctx.refStack.has(schema.$ref)) return
1703
1810
  ctx.refStack.add(schema.$ref)
1704
- genCodeE(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx)
1811
+ genCodeE(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx, schemaPrefix)
1705
1812
  ctx.refStack.delete(schema.$ref)
1706
1813
  return
1707
1814
  }
@@ -1722,7 +1829,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1722
1829
  }
1723
1830
  })
1724
1831
  const expected = types.join(', ')
1725
- lines.push(`if(!(${conds.join('||')})){_e.push({code:'type_mismatch',path:${pathExpr||'""'},message:'expected ${expected}'});if(!_all)return{valid:false,errors:_e}}`)
1832
+ lines.push(`if(!(${conds.join('||')})){_e.push({keyword:'type',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/type',params:{type:'${expected}'},message:'must be ${expected}'});if(!_all)return{valid:false,errors:_e}}`)
1726
1833
  }
1727
1834
 
1728
1835
  // In error mode, never assume type — always guard (data may have failed type check but allErrors continues)
@@ -1731,7 +1838,10 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1731
1838
  const isStr = false
1732
1839
  const isNum = false
1733
1840
 
1734
- const fail = (code, msg) => `_e.push({code:'${code}',path:${pathExpr||'""'},message:${msg}});if(!_all)return{valid:false,errors:_e}`
1841
+ const fail = (keyword, schemaSuffix, paramsCode, msgCode) => {
1842
+ const sp = schemaPrefix + '/' + schemaSuffix
1843
+ return `_e.push({keyword:'${keyword}',instancePath:${pathExpr||'""'},schemaPath:'${sp}',params:${paramsCode},message:${msgCode}});if(!_all)return{valid:false,errors:_e}`
1844
+ }
1735
1845
 
1736
1846
  // enum
1737
1847
  if (schema.enum) {
@@ -1741,21 +1851,21 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1741
1851
  const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
1742
1852
  const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
1743
1853
  const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
1744
- lines.push(`if(!(${allChecks || 'false'})){${fail('enum_mismatch', "'value not in enum'")}}`)
1854
+ lines.push(`if(!(${allChecks || 'false'})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
1745
1855
  }
1746
1856
 
1747
1857
  // const — use canonical (sorted-key) comparison for objects
1748
1858
  if (schema.const !== undefined) {
1749
1859
  const cv = schema.const
1750
1860
  if (cv === null || typeof cv !== 'object') {
1751
- lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const_mismatch', "'value does not match const'")}}`)
1861
+ lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
1752
1862
  } else {
1753
1863
  // Pre-compute canonical form of const value
1754
1864
  const ci = ctx.varCounter++
1755
1865
  const canonFn = `_cnE${ci}`
1756
1866
  ctx.helperCode.push(`const ${canonFn}=function(x){if(x===null||typeof x!=='object')return JSON.stringify(x);if(Array.isArray(x))return'['+x.map(${canonFn}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+${canonFn}(x[k])}).join(',')+'}'};`)
1757
1867
  const expected = canonFn + '(' + JSON.stringify(cv) + ')'
1758
- lines.push(`if(${canonFn}(${v})!==${expected}){${fail('const_mismatch', "'value does not match const'")}}`)
1868
+ lines.push(`if(${canonFn}(${v})!==${expected}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
1759
1869
  }
1760
1870
  }
1761
1871
 
@@ -1764,49 +1874,54 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1764
1874
  const hoisted = {}
1765
1875
  if (schema.required) {
1766
1876
  for (const key of schema.required) {
1767
- const p = pathExpr ? `${pathExpr}+'/${esc(key)}'` : `'/${esc(key)}'`
1768
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!(${JSON.stringify(key)} in ${v})){_e.push({code:'required_missing',path:${p},message:'missing required: ${esc(key)}'});if(!_all)return{valid:false,errors:_e}}`)
1877
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!(${JSON.stringify(key)} in ${v})){_e.push({keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/required',params:{missingProperty:'${esc(key)}'},message:"must have required property '${esc(key)}'"});if(!_all)return{valid:false,errors:_e}}`)
1769
1878
  }
1770
1879
  }
1771
1880
 
1772
1881
  // numeric
1773
1882
  if (schema.minimum !== undefined) {
1774
1883
  const c = isNum ? `${v}<${schema.minimum}` : `typeof ${v}==='number'&&${v}<${schema.minimum}`
1775
- lines.push(`if(${c}){${fail('minimum_violation', `'minimum ${schema.minimum}'`)}}`)
1884
+ lines.push(`if(${c}){${fail('minimum', 'minimum', `{comparison:'>=',limit:${schema.minimum}}`, `'must be >= ${schema.minimum}'`)}}`)
1776
1885
  }
1777
1886
  if (schema.maximum !== undefined) {
1778
1887
  const c = isNum ? `${v}>${schema.maximum}` : `typeof ${v}==='number'&&${v}>${schema.maximum}`
1779
- lines.push(`if(${c}){${fail('maximum_violation', `'maximum ${schema.maximum}'`)}}`)
1888
+ lines.push(`if(${c}){${fail('maximum', 'maximum', `{comparison:'<=',limit:${schema.maximum}}`, `'must be <= ${schema.maximum}'`)}}`)
1780
1889
  }
1781
1890
  if (schema.exclusiveMinimum !== undefined) {
1782
1891
  const c = isNum ? `${v}<=${schema.exclusiveMinimum}` : `typeof ${v}==='number'&&${v}<=${schema.exclusiveMinimum}`
1783
- lines.push(`if(${c}){${fail('exclusive_minimum_violation', `'exclusiveMinimum ${schema.exclusiveMinimum}'`)}}`)
1892
+ lines.push(`if(${c}){${fail('exclusiveMinimum', 'exclusiveMinimum', `{comparison:'>',limit:${schema.exclusiveMinimum}}`, `'must be > ${schema.exclusiveMinimum}'`)}}`)
1784
1893
  }
1785
1894
  if (schema.exclusiveMaximum !== undefined) {
1786
1895
  const c = isNum ? `${v}>=${schema.exclusiveMaximum}` : `typeof ${v}==='number'&&${v}>=${schema.exclusiveMaximum}`
1787
- lines.push(`if(${c}){${fail('exclusive_maximum_violation', `'exclusiveMaximum ${schema.exclusiveMaximum}'`)}}`)
1896
+ lines.push(`if(${c}){${fail('exclusiveMaximum', 'exclusiveMaximum', `{comparison:'<',limit:${schema.exclusiveMaximum}}`, `'must be < ${schema.exclusiveMaximum}'`)}}`)
1788
1897
  }
1789
1898
  if (schema.multipleOf !== undefined) {
1790
1899
  const m = schema.multipleOf
1791
1900
  const ci = ctx.varCounter++
1792
1901
  // Use tolerance-based check for floating point (matches C++ behavior)
1793
- lines.push(`{const _r${ci}=typeof ${v}==='number'?${v}%${m}:NaN;if(typeof ${v}==='number'&&Math.abs(_r${ci})>1e-8&&Math.abs(_r${ci}-${m})>1e-8){${fail('multiple_of_violation', `'multipleOf ${m}'`)}}}`)
1902
+ lines.push(`{const _r${ci}=typeof ${v}==='number'?${v}%${m}:NaN;if(typeof ${v}==='number'&&Math.abs(_r${ci})>1e-8&&Math.abs(_r${ci}-${m})>1e-8){${fail('multipleOf', 'multipleOf', `{multipleOf:${m}}`, `'must be multiple of ${m}'`)}}}`)
1794
1903
  }
1795
1904
 
1796
1905
  // string
1797
1906
  if (schema.minLength !== undefined) {
1798
1907
  const c = isStr ? `${v}.length<${schema.minLength}` : `typeof ${v}==='string'&&${v}.length<${schema.minLength}`
1799
- lines.push(`if(${c}){${fail('min_length_violation', `'minLength ${schema.minLength}'`)}}`)
1908
+ lines.push(`if(${c}){${fail('minLength', 'minLength', `{limit:${schema.minLength}}`, `'must NOT have fewer than ${schema.minLength} characters'`)}}`)
1800
1909
  }
1801
1910
  if (schema.maxLength !== undefined) {
1802
1911
  const c = isStr ? `${v}.length>${schema.maxLength}` : `typeof ${v}==='string'&&${v}.length>${schema.maxLength}`
1803
- lines.push(`if(${c}){${fail('max_length_violation', `'maxLength ${schema.maxLength}'`)}}`)
1912
+ lines.push(`if(${c}){${fail('maxLength', 'maxLength', `{limit:${schema.maxLength}}`, `'must NOT have more than ${schema.maxLength} characters'`)}}`)
1804
1913
  }
1805
1914
  if (schema.pattern) {
1806
- const ri = ctx.varCounter++
1807
- ctx.helperCode.push(`const _re${ri}=new RegExp(${JSON.stringify(schema.pattern)})`)
1808
- const c = isStr ? `!_re${ri}.test(${v})` : `typeof ${v}==='string'&&!_re${ri}.test(${v})`
1809
- lines.push(`if(${c}){${fail('pattern_mismatch', `'pattern mismatch'`)}}`)
1915
+ const inlineCheck = compilePatternInline(schema.pattern, v)
1916
+ if (inlineCheck) {
1917
+ const c = isStr ? `!(${inlineCheck})` : `typeof ${v}==='string'&&!(${inlineCheck})`
1918
+ lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, `'must match pattern "${schema.pattern}"'`)}}`)
1919
+ } else {
1920
+ const ri = ctx.varCounter++
1921
+ ctx.helperCode.push(`const _re${ri}=new RegExp(${JSON.stringify(schema.pattern)})`)
1922
+ const c = isStr ? `!_re${ri}.test(${v})` : `typeof ${v}==='string'&&!_re${ri}.test(${v})`
1923
+ lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, `'must match pattern "${schema.pattern}"'`)}}`)
1924
+ }
1810
1925
  }
1811
1926
  if (schema.format) {
1812
1927
  const fc = FORMAT_CODEGEN[schema.format]
@@ -1817,7 +1932,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1817
1932
  boolLines.push(fc(v, isStr))
1818
1933
  // Replace `return false` with error push in the format check
1819
1934
  const fmtCode = boolLines.join(';').replace(/return false/g,
1820
- `{_e.push({code:'format_mismatch',path:${pathExpr||'""'},message:'format ${esc(schema.format)}'});if(!_all)return{valid:false,errors:_e}}`)
1935
+ `{_e.push({keyword:'format',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/format',params:{format:'${esc(schema.format)}'},message:'must match format "${esc(schema.format)}"'});if(!_all)return{valid:false,errors:_e}}`)
1821
1936
  lines.push(fmtCode)
1822
1937
  }
1823
1938
  }
@@ -1825,37 +1940,44 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1825
1940
  // array size
1826
1941
  if (schema.minItems !== undefined) {
1827
1942
  const c = isArr ? `${v}.length<${schema.minItems}` : `Array.isArray(${v})&&${v}.length<${schema.minItems}`
1828
- lines.push(`if(${c}){${fail('min_items_violation', `'minItems ${schema.minItems}'`)}}`)
1943
+ lines.push(`if(${c}){${fail('minItems', 'minItems', `{limit:${schema.minItems}}`, `'must NOT have fewer than ${schema.minItems} items'`)}}`)
1829
1944
  }
1830
1945
  if (schema.maxItems !== undefined) {
1831
1946
  const c = isArr ? `${v}.length>${schema.maxItems}` : `Array.isArray(${v})&&${v}.length>${schema.maxItems}`
1832
- lines.push(`if(${c}){${fail('max_items_violation', `'maxItems ${schema.maxItems}'`)}}`)
1947
+ lines.push(`if(${c}){${fail('maxItems', 'maxItems', `{limit:${schema.maxItems}}`, `'must NOT have more than ${schema.maxItems} items'`)}}`)
1833
1948
  }
1834
1949
 
1835
- // uniqueItems
1950
+ // uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
1836
1951
  if (schema.uniqueItems) {
1837
1952
  const si = ctx.varCounter++
1838
1953
  const itemType = schema.items && typeof schema.items === 'object' && schema.items.type
1839
1954
  const isPrim = itemType === 'string' || itemType === 'number' || itemType === 'integer'
1840
- const inner = isPrim
1841
- ? `const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){if(_s${si}.has(${v}[_i])){${fail('unique_items_violation', "'duplicate items'")};break};_s${si}.add(${v}[_i])}`
1842
- : `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);if(_s${si}.has(_k)){${fail('unique_items_violation', "'duplicate items'")};break};_s${si}.add(_k)}`
1955
+ const maxItems = schema.maxItems
1956
+ const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
1957
+ let inner
1958
+ if (isPrim && maxItems && maxItems <= 16) {
1959
+ inner = `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k]){${failExpr('_k', '_i')};break}}}`
1960
+ } else if (isPrim) {
1961
+ inner = `const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _prev=_s${si}.get(${v}[_i]);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(${v}[_i],_i)}`
1962
+ } else {
1963
+ inner = `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);const _prev=_s${si}.get(_k);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(_k,_i)}`
1964
+ }
1843
1965
  lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
1844
1966
  }
1845
1967
 
1846
1968
  // object size
1847
1969
  if (schema.minProperties !== undefined) {
1848
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length<${schema.minProperties}){${fail('min_properties_violation', `'minProperties ${schema.minProperties}'`)}}`)
1970
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length<${schema.minProperties}){${fail('minProperties', 'minProperties', `{limit:${schema.minProperties}}`, `'must NOT have fewer than ${schema.minProperties} properties'`)}}`)
1849
1971
  }
1850
1972
  if (schema.maxProperties !== undefined) {
1851
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length>${schema.maxProperties}){${fail('max_properties_violation', `'maxProperties ${schema.maxProperties}'`)}}`)
1973
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length>${schema.maxProperties}){${fail('maxProperties', 'maxProperties', `{limit:${schema.maxProperties}}`, `'must NOT have more than ${schema.maxProperties} properties'`)}}`)
1852
1974
  }
1853
1975
 
1854
1976
  // additionalProperties: false
1855
1977
  if (schema.additionalProperties === false && schema.properties) {
1856
1978
  const allowed = Object.keys(schema.properties).map(k => `${JSON.stringify(k)}`).join(',')
1857
1979
  const ci = ctx.varCounter++
1858
- const inner = `const _k${ci}=Object.keys(${v});const _a${ci}=new Set([${allowed}]);for(let _i=0;_i<_k${ci}.length;_i++){if(!_a${ci}.has(_k${ci}[_i])){_e.push({code:'additional_property',path:${pathExpr||'""'},message:'additional property: '+_k${ci}[_i]});if(!_all)return{valid:false,errors:_e}}}`
1980
+ const inner = `const _k${ci}=Object.keys(${v});const _a${ci}=new Set([${allowed}]);for(let _i=0;_i<_k${ci}.length;_i++){if(!_a${ci}.has(_k${ci}[_i])){_e.push({keyword:'additionalProperties',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/additionalProperties',params:{additionalProperty:_k${ci}[_i]},message:'must NOT have additional properties'});if(!_all)return{valid:false,errors:_e}}}`
1859
1981
  lines.push(isObj ? `{${inner}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
1860
1982
  }
1861
1983
 
@@ -1863,8 +1985,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1863
1985
  if (schema.dependentRequired) {
1864
1986
  for (const [key, deps] of Object.entries(schema.dependentRequired)) {
1865
1987
  for (const dep of deps) {
1866
- const p = pathExpr ? `${pathExpr}+'/${esc(dep)}'` : `'/${esc(dep)}'`
1867
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${JSON.stringify(key)} in ${v}&&!(${JSON.stringify(dep)} in ${v})){_e.push({code:'required_missing',path:${p},message:'${esc(key)} requires ${esc(dep)}'});if(!_all)return{valid:false,errors:_e}}`)
1988
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${JSON.stringify(key)} in ${v}&&!(${JSON.stringify(dep)} in ${v})){_e.push({keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/dependentRequired',params:{missingProperty:'${esc(dep)}'},message:"must have required property '${esc(dep)}'"});if(!_all)return{valid:false,errors:_e}}`)
1868
1989
  }
1869
1990
  }
1870
1991
  }
@@ -1872,9 +1993,9 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1872
1993
  // properties — always guard (error mode, data may not be an object or may be array)
1873
1994
  if (schema.properties) {
1874
1995
  for (const [key, prop] of Object.entries(schema.properties)) {
1875
- const childPath = pathExpr ? `${pathExpr}+'/${esc(key)}'` : `'/${esc(key)}'`
1996
+ const childPath = childPathExpr(pathExpr, esc(key))
1876
1997
  lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${JSON.stringify(key)} in ${v}){`)
1877
- genCodeE(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx)
1998
+ genCodeE(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+key)
1878
1999
  lines.push(`}`)
1879
2000
  }
1880
2001
  }
@@ -1887,7 +2008,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1887
2008
  const ki = ctx.varCounter++
1888
2009
  lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){if(_re${ri}.test(_k${ki})){`)
1889
2010
  const p = pathExpr ? `${pathExpr}+'/'+_k${ki}` : `'/'+_k${ki}`
1890
- genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx)
2011
+ genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix+'/patternProperties')
1891
2012
  lines.push(`}}}`)
1892
2013
  }
1893
2014
  }
@@ -1896,7 +2017,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1896
2017
  if (schema.dependentSchemas) {
1897
2018
  for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
1898
2019
  lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${JSON.stringify(key)} in ${v}){`)
1899
- genCodeE(depSchema, v, pathExpr, lines, ctx)
2020
+ genCodeE(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+key)
1900
2021
  lines.push(`}`)
1901
2022
  }
1902
2023
  }
@@ -1907,23 +2028,23 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1907
2028
  const ki = ctx.varCounter++
1908
2029
  lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
1909
2030
  if (pn.minLength !== undefined) {
1910
- lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('min_length_violation', `'propertyNames: key too short: '+_k${ki}`)}}`)
2031
+ lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
1911
2032
  }
1912
2033
  if (pn.maxLength !== undefined) {
1913
- lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('max_length_violation', `'propertyNames: key too long: '+_k${ki}`)}}`)
2034
+ lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
1914
2035
  }
1915
2036
  if (pn.pattern) {
1916
2037
  const ri = ctx.varCounter++
1917
2038
  ctx.helperCode.push(`const _re${ri}=new RegExp(${JSON.stringify(pn.pattern)})`)
1918
- lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern_mismatch', `'propertyNames: pattern mismatch: '+_k${ki}`)}}`)
2039
+ lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
1919
2040
  }
1920
2041
  if (pn.const !== undefined) {
1921
- lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const_mismatch', `'propertyNames: expected '+${JSON.stringify(pn.const)}`)}}`)
2042
+ lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
1922
2043
  }
1923
2044
  if (pn.enum) {
1924
2045
  const ei = ctx.varCounter++
1925
2046
  ctx.helperCode.push(`const _es${ei}=new Set(${JSON.stringify(pn.enum)})`)
1926
- lines.push(`if(!_es${ei}.has(_k${ki})){${fail('enum_mismatch', `'propertyNames: key not in enum: '+_k${ki}`)}}`)
2047
+ lines.push(`if(!_es${ei}.has(_k${ki})){${fail('enum', 'propertyNames/enum', `{allowedValues:${JSON.stringify(pn.enum)}}`, "'must be equal to one of the allowed values'")}}`)
1927
2048
  }
1928
2049
  lines.push(`}}`)
1929
2050
  }
@@ -1934,18 +2055,18 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1934
2055
  const idx = `_j${ctx.varCounter}`
1935
2056
  const elem = `_ei${ctx.varCounter}`
1936
2057
  ctx.varCounter++
1937
- const childPath = pathExpr ? `${pathExpr}+'/'+${idx}` : `'/'+${idx}`
2058
+ const childPath = childPathDynExpr(pathExpr, idx)
1938
2059
  lines.push(`if(Array.isArray(${v})){for(let ${idx}=${startIdx};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
1939
- genCodeE(schema.items, elem, childPath, lines, ctx)
2060
+ genCodeE(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
1940
2061
  lines.push(`}}`)
1941
2062
  }
1942
2063
 
1943
2064
  // prefixItems
1944
2065
  if (schema.prefixItems) {
1945
2066
  for (let i = 0; i < schema.prefixItems.length; i++) {
1946
- const childPath = pathExpr ? `${pathExpr}+'/${i}'` : `'/${i}'`
2067
+ const childPath = childPathExpr(pathExpr, String(i))
1947
2068
  lines.push(`if(Array.isArray(${v})&&${v}.length>${i}){`)
1948
- genCodeE(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx)
2069
+ genCodeE(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx, schemaPrefix+'/prefixItems/'+i)
1949
2070
  lines.push(`}`)
1950
2071
  }
1951
2072
  }
@@ -1959,17 +2080,17 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1959
2080
  const minC = schema.minContains !== undefined ? schema.minContains : 1
1960
2081
  const maxC = schema.maxContains
1961
2082
  lines.push(`if(Array.isArray(${v})){const _cf${ci}=function(_cv){${fnBody}};let _cc${ci}=0;for(let _ci${ci}=0;_ci${ci}<${v}.length;_ci${ci}++){if(_cf${ci}(${v}[_ci${ci}]))_cc${ci}++}`)
1962
- lines.push(`if(_cc${ci}<${minC}){${fail('contains_violation', `'contains: need at least ${minC} match(es)'`)}}`)
2083
+ lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{limit:${minC}}`, `'contains: need at least ${minC} match(es)'`)}}`)
1963
2084
  if (maxC !== undefined) {
1964
- lines.push(`if(_cc${ci}>${maxC}){${fail('contains_violation', `'contains: at most ${maxC} match(es)'`)}}`)
2085
+ lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{limit:${maxC}}`, `'contains: at most ${maxC} match(es)'`)}}`)
1965
2086
  }
1966
2087
  lines.push(`}`)
1967
2088
  }
1968
2089
 
1969
2090
  // allOf
1970
2091
  if (schema.allOf) {
1971
- for (const sub of schema.allOf) {
1972
- genCodeE(sub, v, pathExpr, lines, ctx)
2092
+ for (let _ai = 0; _ai < schema.allOf.length; _ai++) {
2093
+ genCodeE(schema.allOf[_ai], v, pathExpr, lines, ctx, schemaPrefix+'/allOf/'+_ai)
1973
2094
  }
1974
2095
  }
1975
2096
 
@@ -1981,7 +2102,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1981
2102
  genCode(sub, '_av', subLines, ctx)
1982
2103
  return subLines.length === 0 ? `function(_av){return true}` : `function(_av){${subLines.join(';')};return true}`
1983
2104
  })
1984
- lines.push(`{const _af${fi}=[${fns.join(',')}];let _am${fi}=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am${fi}=true;break}}if(!_am${fi}){${fail('any_of_failed', "'no anyOf matched'")}}}`)
2105
+ lines.push(`{const _af${fi}=[${fns.join(',')}];let _am${fi}=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am${fi}=true;break}}if(!_am${fi}){${fail('anyOf', 'anyOf', '{}', "'must match a schema in anyOf'")}}}`)
1985
2106
  }
1986
2107
 
1987
2108
  // oneOf
@@ -1992,7 +2113,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
1992
2113
  genCode(sub, '_ov', subLines, ctx)
1993
2114
  return subLines.length === 0 ? `function(_ov){return true}` : `function(_ov){${subLines.join(';')};return true}`
1994
2115
  })
1995
- lines.push(`{const _of${fi}=[${fns.join(',')}];let _oc${fi}=0;for(let _oi=0;_oi<_of${fi}.length;_oi++){if(_of${fi}[_oi](${v}))_oc${fi}++;if(_oc${fi}>1)break}if(_oc${fi}!==1){${fail('one_of_failed', "'oneOf: expected 1 match, got '+_oc"+fi)}}}`)
2116
+ lines.push(`{const _of${fi}=[${fns.join(',')}];let _oc${fi}=0;for(let _oi=0;_oi<_of${fi}.length;_oi++){if(_of${fi}[_oi](${v}))_oc${fi}++;if(_oc${fi}>1)break}if(_oc${fi}!==1){${fail('oneOf', 'oneOf', '{}', "'must match exactly one schema in oneOf'")}}}`)
1996
2117
  }
1997
2118
 
1998
2119
  // not
@@ -2001,7 +2122,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
2001
2122
  genCode(schema.not, '_nv', subLines, ctx)
2002
2123
  const nfn = subLines.length === 0 ? `function(_nv){return true}` : `function(_nv){${subLines.join(';')};return true}`
2003
2124
  const fi = ctx.varCounter++
2004
- lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not_failed', "'should not match'")}}}`)
2125
+ lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
2005
2126
  }
2006
2127
 
2007
2128
  // if/then/else
@@ -2015,12 +2136,12 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
2015
2136
  lines.push(`{const _if${fi}=${ifFn}`)
2016
2137
  if (schema.then) {
2017
2138
  lines.push(`if(_if${fi}(${v})){`)
2018
- genCodeE(schema.then, v, pathExpr, lines, ctx)
2139
+ genCodeE(schema.then, v, pathExpr, lines, ctx, schemaPrefix+'/then')
2019
2140
  lines.push(`}`)
2020
2141
  }
2021
2142
  if (schema.else) {
2022
2143
  lines.push(`${schema.then ? 'else' : `if(!_if${fi}(${v}))`}{`)
2023
- genCodeE(schema.else, v, pathExpr, lines, ctx)
2144
+ genCodeE(schema.else, v, pathExpr, lines, ctx, schemaPrefix+'/else')
2024
2145
  lines.push(`}`)
2025
2146
  }
2026
2147
  lines.push(`}`)
@@ -2040,7 +2161,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap) {
2040
2161
  if (typeof schema === 'boolean') {
2041
2162
  return schema
2042
2163
  ? () => VALID_RESULT
2043
- : () => ({ valid: false, errors: [{ code: 'type_mismatch', path: '', message: 'schema is false' }] })
2164
+ : () => ({ valid: false, errors: [{ keyword: 'false schema', instancePath: '', schemaPath: '#', params: {}, message: 'boolean schema is false' }] })
2044
2165
  }
2045
2166
  if (typeof schema !== 'object' || schema === null) return null
2046
2167
  if (!codegenSafe(schema, schemaMap)) return null
@@ -2068,7 +2189,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap) {
2068
2189
  const ctx = { varCounter: 0, helperCode: [], closureVars: [], closureVals: [],
2069
2190
  rootDefs: schema.$defs || schema.definitions || null, refStack: new Set(), schemaMap: schemaMap || null }
2070
2191
  const lines = []
2071
- genCodeC(schema, 'd', '', lines, ctx)
2192
+ genCodeC(schema, 'd', '', lines, ctx, '#')
2072
2193
  if (lines.length === 0) return () => VALID_RESULT
2073
2194
 
2074
2195
  // Use factory pattern: closure vars (regexes, etc.) created once, not per call
@@ -2080,6 +2201,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap) {
2080
2201
  `\n return _e?{valid:false,errors:_e}:R`
2081
2202
 
2082
2203
  try {
2204
+ if (process.env.ATA_DUMP_CODEGEN) console.log('=== COMBINED CODEGEN ===\n' + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
2083
2205
  const factory = new Function('R' + (closureParams ? ',' + closureParams : ''),
2084
2206
  `return function(d){${inner}}`)
2085
2207
  return factory(VALID_RESULT, ...ctx.closureVals)
@@ -2092,7 +2214,8 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap) {
2092
2214
  // Combined code generator: type-aware like genCode, error-collecting like genCodeE.
2093
2215
  // After type check passes → use optimizations (destructuring, no guards).
2094
2216
  // If type check fails → push error, skip property checks (they'd crash).
2095
- function genCodeC(schema, v, pathExpr, lines, ctx) {
2217
+ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
2218
+ if (!schemaPrefix) schemaPrefix = '#'
2096
2219
  if (typeof schema !== 'object' || schema === null) return
2097
2220
 
2098
2221
  // $ref — resolve local and cross-schema refs
@@ -2101,14 +2224,14 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2101
2224
  if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
2102
2225
  if (ctx.refStack.has(schema.$ref)) return
2103
2226
  ctx.refStack.add(schema.$ref)
2104
- genCodeC(ctx.rootDefs[m[1]], v, pathExpr, lines, ctx)
2227
+ genCodeC(ctx.rootDefs[m[1]], v, pathExpr, lines, ctx, schemaPrefix)
2105
2228
  ctx.refStack.delete(schema.$ref)
2106
2229
  return
2107
2230
  }
2108
2231
  if (ctx.schemaMap && ctx.schemaMap.has(schema.$ref)) {
2109
2232
  if (ctx.refStack.has(schema.$ref)) return
2110
2233
  ctx.refStack.add(schema.$ref)
2111
- genCodeC(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx)
2234
+ genCodeC(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx, schemaPrefix)
2112
2235
  ctx.refStack.delete(schema.$ref)
2113
2236
  return
2114
2237
  }
@@ -2120,19 +2243,25 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2120
2243
  // Pre-allocate error objects as closure variables for static paths.
2121
2244
  // This shrinks the generated function body → better V8 JIT on valid path.
2122
2245
  const isStaticPath = !pathExpr || (pathExpr.startsWith("'") && !pathExpr.includes('+'))
2123
- const fail = (code, msg) => {
2124
- if (isStaticPath && msg.startsWith("'") && !msg.includes('+')) {
2125
- // Static error: pre-allocate as frozen closure variable
2126
- const ei = ctx.varCounter++
2127
- const errVar = `_E${ei}`
2128
- const pathVal = pathExpr ? pathExpr.slice(1, -1) : ''
2129
- const msgVal = msg.slice(1, -1)
2130
- ctx.closureVars.push(errVar)
2131
- ctx.closureVals.push(Object.freeze({code, path: pathVal, message: msgVal}))
2132
- return `(_e||(_e=[])).push(${errVar})`
2246
+ const fail = (keyword, schemaSuffix, paramsCode, msgCode) => {
2247
+ const sp = schemaPrefix + '/' + schemaSuffix
2248
+ if (isStaticPath && msgCode.startsWith("'") && !msgCode.includes('+')) {
2249
+ // Try to evaluate paramsCode as a static constant
2250
+ let paramsVal
2251
+ try { paramsVal = Function('return ' + paramsCode)() } catch { /* dynamic params — fall through */ }
2252
+ if (paramsVal !== undefined) {
2253
+ // Static error: pre-allocate as frozen closure variable
2254
+ const ei = ctx.varCounter++
2255
+ const errVar = `_E${ei}`
2256
+ const pathVal = pathExpr ? pathExpr.slice(1, -1) : ''
2257
+ const msgVal = msgCode.slice(1, -1)
2258
+ ctx.closureVars.push(errVar)
2259
+ ctx.closureVals.push(Object.freeze({keyword, instancePath: pathVal, schemaPath: sp, params: Object.freeze(paramsVal), message: msgVal}))
2260
+ return `(_e||(_e=[])).push(${errVar})`
2261
+ }
2133
2262
  }
2134
2263
  // Dynamic path (e.g., array index): inline as before
2135
- return `(_e||(_e=[])).push({code:'${code}',path:${pathExpr||'""'},message:${msg}})`
2264
+ return `(_e||(_e=[])).push({keyword:'${keyword}',instancePath:${pathExpr||'""'},schemaPath:'${sp}',params:${paramsCode},message:${msgCode}})`
2136
2265
  }
2137
2266
 
2138
2267
  if (types) {
@@ -2152,7 +2281,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2152
2281
  // Type check: push error but continue — wrap remaining in type-success block
2153
2282
  const typeOk = `_tok${ctx.varCounter++}`
2154
2283
  lines.push(`const ${typeOk}=${conds.join('||')}`)
2155
- lines.push(`if(!${typeOk}){${fail('type_mismatch', `'expected ${expected}'`)}}`)
2284
+ lines.push(`if(!${typeOk}){${fail('type', 'type', `{type:'${expected}'}`, `'must be ${expected}'`)}}`)
2156
2285
  // Subsequent optimized code runs inside if(typeOk){...}
2157
2286
  if (types.length === 1) {
2158
2287
  isObj = types[0] === 'object'
@@ -2171,19 +2300,19 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2171
2300
  const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
2172
2301
  const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
2173
2302
  const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
2174
- lines.push(`if(!(${allChecks || 'false'})){${fail('enum_mismatch', "'value not in enum'")}}`)
2303
+ lines.push(`if(!(${allChecks || 'false'})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
2175
2304
  }
2176
2305
 
2177
2306
  // const
2178
2307
  if (schema.const !== undefined) {
2179
2308
  const cv = schema.const
2180
2309
  if (cv === null || typeof cv !== 'object') {
2181
- lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const_mismatch', "'const mismatch'")}}`)
2310
+ lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
2182
2311
  } else {
2183
2312
  const ci = ctx.varCounter++
2184
2313
  const canonFn = `_cn${ci}`
2185
2314
  ctx.helperCode.push(`const ${canonFn}=function(x){if(x===null||typeof x!=='object')return JSON.stringify(x);if(Array.isArray(x))return'['+x.map(${canonFn}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+${canonFn}(x[k])}).join(',')+'}'};`)
2186
- lines.push(`if(${canonFn}(${v})!==${canonFn}(${JSON.stringify(cv)})){${fail('const_mismatch', "'const mismatch'")}}`)
2315
+ lines.push(`if(${canonFn}(${v})!==${canonFn}(${JSON.stringify(cv)})){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
2187
2316
  }
2188
2317
  }
2189
2318
 
@@ -2202,80 +2331,127 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2202
2331
  if (destructKeys.length > 0) lines.push(`const{${destructKeys.join(',')}}=${v}`)
2203
2332
  for (const key of schema.required) {
2204
2333
  const check = hoisted[key] ? `${hoisted[key]}===undefined` : `${v}[${JSON.stringify(key)}]===undefined`
2205
- const p = pathExpr ? `${pathExpr}+'/${esc(key)}'` : `'/${esc(key)}'`
2206
- lines.push(`if(${check}){${`(_e||(_e=[])).push({code:'required_missing',path:${p},message:'missing: ${esc(key)}'})`}}`)
2334
+ // Pre-allocate required errors as frozen closure variables
2335
+ const ei = ctx.varCounter++
2336
+ const errVar = `_E${ei}`
2337
+ const pathVal = pathExpr ? pathExpr.slice(1, -1) : ''
2338
+ ctx.closureVars.push(errVar)
2339
+ ctx.closureVals.push(Object.freeze({keyword: 'required', instancePath: pathVal, schemaPath: `${schemaPrefix}/required`, params: Object.freeze({missingProperty: key}), message: `must have required property '${key}'`}))
2340
+ lines.push(`if(${check}){(_e||(_e=[])).push(${errVar})}`)
2207
2341
  }
2208
2342
  } else if (schema.required) {
2209
2343
  for (const key of schema.required) {
2210
- const p = pathExpr ? `${pathExpr}+'/${esc(key)}'` : `'/${esc(key)}'`
2211
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!(${JSON.stringify(key)} in ${v})){(_e||(_e=[])).push({code:'required_missing',path:${p},message:'missing: ${esc(key)}'})}`)
2344
+ const isStatic = !pathExpr || (pathExpr.startsWith("'") && !pathExpr.includes('+'))
2345
+ if (isStatic) {
2346
+ const ei = ctx.varCounter++
2347
+ const errVar = `_E${ei}`
2348
+ const pathVal = pathExpr ? pathExpr.slice(1, -1) : ''
2349
+ ctx.closureVars.push(errVar)
2350
+ ctx.closureVals.push(Object.freeze({keyword: 'required', instancePath: pathVal, schemaPath: `${schemaPrefix}/required`, params: Object.freeze({missingProperty: key}), message: `must have required property '${key}'`}))
2351
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!(${JSON.stringify(key)} in ${v})){(_e||(_e=[])).push(${errVar})}`)
2352
+ } else {
2353
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!(${JSON.stringify(key)} in ${v})){(_e||(_e=[])).push({keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/required',params:{missingProperty:'${esc(key)}'},message:"must have required property '${esc(key)}'"})}`)
2354
+ }
2212
2355
  }
2213
2356
  }
2214
2357
 
2215
2358
  // numeric — skip type guard if known
2216
- if (schema.minimum !== undefined) { const c = isNum ? `${v}<${schema.minimum}` : `typeof ${v}==='number'&&${v}<${schema.minimum}`; lines.push(`if(${c}){${fail('minimum_violation', `'min ${schema.minimum}'`)}}`) }
2217
- if (schema.maximum !== undefined) { const c = isNum ? `${v}>${schema.maximum}` : `typeof ${v}==='number'&&${v}>${schema.maximum}`; lines.push(`if(${c}){${fail('maximum_violation', `'max ${schema.maximum}'`)}}`) }
2218
- if (schema.exclusiveMinimum !== undefined) { const c = isNum ? `${v}<=${schema.exclusiveMinimum}` : `typeof ${v}==='number'&&${v}<=${schema.exclusiveMinimum}`; lines.push(`if(${c}){${fail('exclusive_minimum_violation', `'excMin ${schema.exclusiveMinimum}'`)}}`) }
2219
- if (schema.exclusiveMaximum !== undefined) { const c = isNum ? `${v}>=${schema.exclusiveMaximum}` : `typeof ${v}==='number'&&${v}>=${schema.exclusiveMaximum}`; lines.push(`if(${c}){${fail('exclusive_maximum_violation', `'excMax ${schema.exclusiveMaximum}'`)}}`) }
2359
+ if (schema.minimum !== undefined) { const c = isNum ? `${v}<${schema.minimum}` : `typeof ${v}==='number'&&${v}<${schema.minimum}`; lines.push(`if(${c}){${fail('minimum', 'minimum', `{comparison:'>=',limit:${schema.minimum}}`, `'must be >= ${schema.minimum}'`)}}`) }
2360
+ if (schema.maximum !== undefined) { const c = isNum ? `${v}>${schema.maximum}` : `typeof ${v}==='number'&&${v}>${schema.maximum}`; lines.push(`if(${c}){${fail('maximum', 'maximum', `{comparison:'<=',limit:${schema.maximum}}`, `'must be <= ${schema.maximum}'`)}}`) }
2361
+ if (schema.exclusiveMinimum !== undefined) { const c = isNum ? `${v}<=${schema.exclusiveMinimum}` : `typeof ${v}==='number'&&${v}<=${schema.exclusiveMinimum}`; lines.push(`if(${c}){${fail('exclusiveMinimum', 'exclusiveMinimum', `{comparison:'>',limit:${schema.exclusiveMinimum}}`, `'must be > ${schema.exclusiveMinimum}'`)}}`) }
2362
+ if (schema.exclusiveMaximum !== undefined) { const c = isNum ? `${v}>=${schema.exclusiveMaximum}` : `typeof ${v}==='number'&&${v}>=${schema.exclusiveMaximum}`; lines.push(`if(${c}){${fail('exclusiveMaximum', 'exclusiveMaximum', `{comparison:'<',limit:${schema.exclusiveMaximum}}`, `'must be < ${schema.exclusiveMaximum}'`)}}`) }
2220
2363
  if (schema.multipleOf !== undefined) {
2221
2364
  const m = schema.multipleOf
2222
2365
  const ci = ctx.varCounter++
2223
- lines.push(`{const _r${ci}=typeof ${v}==='number'?${v}%${m}:NaN;if(typeof ${v}==='number'&&Math.abs(_r${ci})>1e-8&&Math.abs(_r${ci}-${m})>1e-8){${fail('multiple_of_violation', `'multipleOf ${m}'`)}}}`)
2366
+ lines.push(`{const _r${ci}=typeof ${v}==='number'?${v}%${m}:NaN;if(typeof ${v}==='number'&&Math.abs(_r${ci})>1e-8&&Math.abs(_r${ci}-${m})>1e-8){${fail('multipleOf', 'multipleOf', `{multipleOf:${m}}`, `'must be multiple of ${m}'`)}}}`)
2224
2367
  }
2225
2368
 
2226
2369
  // string — skip guard if known
2227
- if (schema.minLength !== undefined) { const c = isStr ? `${v}.length<${schema.minLength}` : `typeof ${v}==='string'&&${v}.length<${schema.minLength}`; lines.push(`if(${c}){${fail('min_length_violation', `'minLength ${schema.minLength}'`)}}`) }
2228
- if (schema.maxLength !== undefined) { const c = isStr ? `${v}.length>${schema.maxLength}` : `typeof ${v}==='string'&&${v}.length>${schema.maxLength}`; lines.push(`if(${c}){${fail('max_length_violation', `'maxLength ${schema.maxLength}'`)}}`) }
2370
+ if (schema.minLength !== undefined) { const c = isStr ? `${v}.length<${schema.minLength}` : `typeof ${v}==='string'&&${v}.length<${schema.minLength}`; lines.push(`if(${c}){${fail('minLength', 'minLength', `{limit:${schema.minLength}}`, `'must NOT have fewer than ${schema.minLength} characters'`)}}`) }
2371
+ if (schema.maxLength !== undefined) { const c = isStr ? `${v}.length>${schema.maxLength}` : `typeof ${v}==='string'&&${v}.length>${schema.maxLength}`; lines.push(`if(${c}){${fail('maxLength', 'maxLength', `{limit:${schema.maxLength}}`, `'must NOT have more than ${schema.maxLength} characters'`)}}`) }
2229
2372
  if (schema.pattern) {
2230
- const ri = ctx.varCounter++
2231
- const reVar = `_re${ri}`
2232
- ctx.closureVars.push(reVar)
2233
- ctx.closureVals.push(new RegExp(schema.pattern))
2234
- const c = isStr ? `!${reVar}.test(${v})` : `typeof ${v}==='string'&&!${reVar}.test(${v})`
2235
- lines.push(`if(${c}){${fail('pattern_mismatch', "'pattern mismatch'")}}`)
2373
+ const inlineCheck = compilePatternInline(schema.pattern, v)
2374
+ if (inlineCheck) {
2375
+ const c = isStr ? `!(${inlineCheck})` : `typeof ${v}==='string'&&!(${inlineCheck})`
2376
+ lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, `'must match pattern "${schema.pattern}"'`)}}`)
2377
+ } else {
2378
+ const ri = ctx.varCounter++
2379
+ const reVar = `_re${ri}`
2380
+ ctx.closureVars.push(reVar)
2381
+ ctx.closureVals.push(new RegExp(schema.pattern))
2382
+ const c = isStr ? `!${reVar}.test(${v})` : `typeof ${v}==='string'&&!${reVar}.test(${v})`
2383
+ lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, `'must match pattern "${schema.pattern}"'`)}}`)
2384
+ }
2236
2385
  }
2237
2386
  if (schema.format) {
2238
2387
  const fc = FORMAT_CODEGEN[schema.format]
2239
2388
  if (fc) {
2240
- const code = fc(v, isStr).replace(/return false/g, `{${fail('format_mismatch', `'format ${esc(schema.format)}'`)}}`)
2389
+ const code = fc(v, isStr).replace(/return false/g, `{${fail('format', 'format', `{format:'${esc(schema.format)}'}`, `'must match format "${esc(schema.format)}"'`)}}`)
2241
2390
  lines.push(code)
2242
2391
  }
2243
2392
  }
2244
2393
 
2245
2394
  // array size
2246
- if (schema.minItems !== undefined) { const c = isArr ? `${v}.length<${schema.minItems}` : `Array.isArray(${v})&&${v}.length<${schema.minItems}`; lines.push(`if(${c}){${fail('min_items_violation', `'minItems ${schema.minItems}'`)}}`) }
2247
- if (schema.maxItems !== undefined) { const c = isArr ? `${v}.length>${schema.maxItems}` : `Array.isArray(${v})&&${v}.length>${schema.maxItems}`; lines.push(`if(${c}){${fail('max_items_violation', `'maxItems ${schema.maxItems}'`)}}`) }
2395
+ if (schema.minItems !== undefined) { const c = isArr ? `${v}.length<${schema.minItems}` : `Array.isArray(${v})&&${v}.length<${schema.minItems}`; lines.push(`if(${c}){${fail('minItems', 'minItems', `{limit:${schema.minItems}}`, `'must NOT have fewer than ${schema.minItems} items'`)}}`) }
2396
+ if (schema.maxItems !== undefined) { const c = isArr ? `${v}.length>${schema.maxItems}` : `Array.isArray(${v})&&${v}.length>${schema.maxItems}`; lines.push(`if(${c}){${fail('maxItems', 'maxItems', `{limit:${schema.maxItems}}`, `'must NOT have more than ${schema.maxItems} items'`)}}`) }
2248
2397
 
2249
- // uniqueItems
2398
+ // uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
2250
2399
  if (schema.uniqueItems) {
2251
2400
  const si = ctx.varCounter++
2252
2401
  const itemType = schema.items && typeof schema.items === 'object' && schema.items.type
2253
2402
  const isPrim = itemType === 'string' || itemType === 'number' || itemType === 'integer'
2254
- const inner = isPrim
2255
- ? `const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){if(_s${si}.has(${v}[_i])){${fail('unique_items_violation', "'duplicates'")};break};_s${si}.add(${v}[_i])}`
2256
- : `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);if(_s${si}.has(_k)){${fail('unique_items_violation', "'duplicates'")};break};_s${si}.add(_k)}`
2403
+ const maxItems = schema.maxItems
2404
+ const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
2405
+ let inner
2406
+ if (isPrim && maxItems && maxItems <= 16) {
2407
+ // Small primitive arrays: O(n²) nested loop, zero allocation
2408
+ inner = `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k]){${failExpr('_k', '_i')};break}}}`
2409
+ } else if (isPrim) {
2410
+ inner = `const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _prev=_s${si}.get(${v}[_i]);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(${v}[_i],_i)}`
2411
+ } else {
2412
+ inner = `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);const _prev=_s${si}.get(_k);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(_k,_i)}`
2413
+ }
2257
2414
  lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
2258
2415
  }
2259
2416
 
2260
2417
  // object size
2261
- if (schema.minProperties !== undefined) lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length<${schema.minProperties}){${fail('min_properties_violation', `'minProperties ${schema.minProperties}'`)}}`)
2262
- if (schema.maxProperties !== undefined) lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length>${schema.maxProperties}){${fail('max_properties_violation', `'maxProperties ${schema.maxProperties}'`)}}`)
2418
+ if (schema.minProperties !== undefined) lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length<${schema.minProperties}){${fail('minProperties', 'minProperties', `{limit:${schema.minProperties}}`, `'must NOT have fewer than ${schema.minProperties} properties'`)}}`)
2419
+ if (schema.maxProperties !== undefined) lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length>${schema.maxProperties}){${fail('maxProperties', 'maxProperties', `{limit:${schema.maxProperties}}`, `'must NOT have more than ${schema.maxProperties} properties'`)}}`)
2263
2420
 
2264
2421
  // additionalProperties — skip if patternProperties present (handled in unified loop below)
2422
+ // Small property sets: direct === chain (no Set allocation)
2265
2423
  if (schema.additionalProperties === false && schema.properties && !schema.patternProperties) {
2266
- const allowed = Object.keys(schema.properties).map(k => JSON.stringify(k)).join(',')
2424
+ const propKeys = Object.keys(schema.properties)
2267
2425
  const ci = ctx.varCounter++
2268
- lines.push(isObj
2269
- ? `{const _k${ci}=Object.keys(${v});const _a${ci}=new Set([${allowed}]);for(let _i=0;_i<_k${ci}.length;_i++)if(!_a${ci}.has(_k${ci}[_i])){${fail('additional_property', `'extra: '+_k${ci}[_i]`)}}}`
2270
- : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){const _k${ci}=Object.keys(${v});const _a${ci}=new Set([${allowed}]);for(let _i=0;_i<_k${ci}.length;_i++)if(!_a${ci}.has(_k${ci}[_i])){${fail('additional_property', `'extra: '+_k${ci}[_i]`)}}}`)
2426
+ if (propKeys.length <= 8) {
2427
+ // Direct chain: no Set allocation for small schemas
2428
+ const checks = propKeys.map(k => `_k${ci}[_i]!==${JSON.stringify(k)}`).join('&&')
2429
+ lines.push(isObj
2430
+ ? `{const _k${ci}=Object.keys(${v});for(let _i=0;_i<_k${ci}.length;_i++)if(${checks}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:_k${ci}[_i]}`, "'must NOT have additional properties'")}}}`
2431
+ : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){const _k${ci}=Object.keys(${v});for(let _i=0;_i<_k${ci}.length;_i++)if(${checks}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:_k${ci}[_i]}`, "'must NOT have additional properties'")}}}`)
2432
+ } else {
2433
+ const allowed = propKeys.map(k => JSON.stringify(k)).join(',')
2434
+ lines.push(isObj
2435
+ ? `{const _k${ci}=Object.keys(${v});const _a${ci}=new Set([${allowed}]);for(let _i=0;_i<_k${ci}.length;_i++)if(!_a${ci}.has(_k${ci}[_i])){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:_k${ci}[_i]}`, "'must NOT have additional properties'")}}}`
2436
+ : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){const _k${ci}=Object.keys(${v});const _a${ci}=new Set([${allowed}]);for(let _i=0;_i<_k${ci}.length;_i++)if(!_a${ci}.has(_k${ci}[_i])){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:_k${ci}[_i]}`, "'must NOT have additional properties'")}}}`)
2437
+ }
2271
2438
  }
2272
2439
 
2273
2440
  // dependentRequired
2274
2441
  if (schema.dependentRequired) {
2275
2442
  for (const [key, deps] of Object.entries(schema.dependentRequired)) {
2276
2443
  for (const dep of deps) {
2277
- const p = pathExpr ? `${pathExpr}+'/${esc(dep)}'` : `'/${esc(dep)}'`
2278
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${JSON.stringify(key)} in ${v}&&!(${JSON.stringify(dep)} in ${v})){(_e||(_e=[])).push({code:'required_missing',path:${p},message:'${esc(key)} requires ${esc(dep)}'})}`)
2444
+ const isStatic = !pathExpr || (pathExpr.startsWith("'") && !pathExpr.includes('+'))
2445
+ if (isStatic) {
2446
+ const ei = ctx.varCounter++
2447
+ const errVar = `_E${ei}`
2448
+ const pathVal = pathExpr ? pathExpr.slice(1, -1) : ''
2449
+ ctx.closureVars.push(errVar)
2450
+ ctx.closureVals.push(Object.freeze({keyword: 'required', instancePath: pathVal, schemaPath: `${schemaPrefix}/dependentRequired`, params: Object.freeze({missingProperty: dep}), message: `must have required property '${dep}'`}))
2451
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${JSON.stringify(key)} in ${v}&&!(${JSON.stringify(dep)} in ${v})){(_e||(_e=[])).push(${errVar})}`)
2452
+ } else {
2453
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${JSON.stringify(key)} in ${v}&&!(${JSON.stringify(dep)} in ${v})){(_e||(_e=[])).push({keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/dependentRequired',params:{missingProperty:'${esc(dep)}'},message:"must have required property '${esc(dep)}'"})}`)
2454
+ }
2279
2455
  }
2280
2456
  }
2281
2457
  }
@@ -2284,19 +2460,19 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2284
2460
  if (schema.properties) {
2285
2461
  for (const [key, prop] of Object.entries(schema.properties)) {
2286
2462
  const pv = hoisted[key] || `${v}[${JSON.stringify(key)}]`
2287
- const childPath = pathExpr ? `${pathExpr}+'/${esc(key)}'` : `'/${esc(key)}'`
2463
+ const childPath = childPathExpr(pathExpr, esc(key))
2288
2464
  if (requiredSet.has(key) && isObj) {
2289
2465
  lines.push(`if(${pv}!==undefined){`)
2290
- genCodeC(prop, pv, childPath, lines, ctx)
2466
+ genCodeC(prop, pv, childPath, lines, ctx, schemaPrefix+'/properties/'+key)
2291
2467
  lines.push(`}`)
2292
2468
  } else if (isObj) {
2293
2469
  const oi = ctx.varCounter++
2294
2470
  lines.push(`{const _o${oi}=${v}[${JSON.stringify(key)}];if(_o${oi}!==undefined){`)
2295
- genCodeC(prop, `_o${oi}`, childPath, lines, ctx)
2471
+ genCodeC(prop, `_o${oi}`, childPath, lines, ctx, schemaPrefix+'/properties/'+key)
2296
2472
  lines.push(`}}`)
2297
2473
  } else {
2298
2474
  lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${JSON.stringify(key)} in ${v}){`)
2299
- genCodeC(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx)
2475
+ genCodeC(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+key)
2300
2476
  lines.push(`}`)
2301
2477
  }
2302
2478
  }
@@ -2353,61 +2529,61 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2353
2529
  lines.push(`${guard}{for(const ${kVar} in ${v}){`)
2354
2530
  // propertyNames checks (merged)
2355
2531
  if (pn) {
2356
- if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength}){${fail('min_length_violation', `'propertyNames: key too short: '+${kVar}`)}}`)
2357
- if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength}){${fail('max_length_violation', `'propertyNames: key too long: '+${kVar}`)}}`)
2532
+ if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
2533
+ if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
2358
2534
  if (pn.pattern) {
2359
2535
  const fast = fastPrefixCheck(pn.pattern, kVar)
2360
2536
  if (fast) {
2361
- lines.push(`if(!(${fast})){${fail('pattern_mismatch', `'propertyNames: pattern mismatch: '+${kVar}`)}}`)
2537
+ lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
2362
2538
  } else {
2363
2539
  const ri = ctx.varCounter++
2364
2540
  ctx.closureVars.push(`_re${ri}`)
2365
2541
  ctx.closureVals.push(new RegExp(pn.pattern))
2366
- lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern_mismatch', `'propertyNames: pattern mismatch: '+${kVar}`)}}`)
2542
+ lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
2367
2543
  }
2368
2544
  }
2369
- if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)}){${fail('const_mismatch', `'propertyNames: expected '+${JSON.stringify(pn.const)}`)}}`)
2545
+ if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
2370
2546
  if (pn.enum) {
2371
2547
  const ei = ctx.varCounter++
2372
2548
  ctx.closureVars.push(`_es${ei}`)
2373
2549
  ctx.closureVals.push(new Set(pn.enum))
2374
- lines.push(`if(!_es${ei}.has(${kVar})){${fail('enum_mismatch', `'propertyNames: key not in enum: '+${kVar}`)}}`)
2550
+ lines.push(`if(!_es${ei}.has(${kVar})){${fail('enum', 'propertyNames/enum', `{allowedValues:${JSON.stringify(pn.enum)}}`, "'must be equal to one of the allowed values'")}}`)
2375
2551
  }
2376
2552
  }
2377
2553
  const matchExpr = keyCheck || `_as${pi}.has(${kVar})`
2378
2554
  lines.push(`let _m${pi}=${matchExpr}`)
2379
2555
  for (let i = 0; i < ppEntries.length; i++) {
2380
- lines.push(`if(${matchers[i].check}){_m${pi}=true;if(!_ppf${pi}_${i}(${v}[${kVar}])){${fail('pattern_mismatch', `'patternProperties: value invalid for key '+${kVar}`)}}}`)
2556
+ lines.push(`if(${matchers[i].check}){_m${pi}=true;if(!_ppf${pi}_${i}(${v}[${kVar}])){${fail('pattern', 'patternProperties', `{pattern:'${ppEntries[i][0]}'}`, `'patternProperties: value invalid for key '+${kVar}`)}}}`)
2381
2557
  }
2382
- lines.push(`if(!_m${pi}){${fail('additional_property', `'extra: '+${kVar}`)}}`)
2558
+ lines.push(`if(!_m${pi}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:${kVar}}`, "'must NOT have additional properties'")}}`)
2383
2559
  lines.push(`}}`)
2384
2560
  } else {
2385
2561
  ctx._ppHandledPropertyNamesC = !!pn
2386
2562
  lines.push(`${guard}{for(const ${kVar} in ${v}){`)
2387
2563
  if (pn) {
2388
- if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength}){${fail('min_length_violation', `'propertyNames: key too short: '+${kVar}`)}}`)
2389
- if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength}){${fail('max_length_violation', `'propertyNames: key too long: '+${kVar}`)}}`)
2564
+ if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
2565
+ if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
2390
2566
  if (pn.pattern) {
2391
2567
  const fast = fastPrefixCheck(pn.pattern, kVar)
2392
2568
  if (fast) {
2393
- lines.push(`if(!(${fast})){${fail('pattern_mismatch', `'propertyNames: pattern mismatch: '+${kVar}`)}}`)
2569
+ lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
2394
2570
  } else {
2395
2571
  const ri = ctx.varCounter++
2396
2572
  ctx.closureVars.push(`_re${ri}`)
2397
2573
  ctx.closureVals.push(new RegExp(pn.pattern))
2398
- lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern_mismatch', `'propertyNames: pattern mismatch: '+${kVar}`)}}`)
2574
+ lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
2399
2575
  }
2400
2576
  }
2401
- if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)}){${fail('const_mismatch', `'propertyNames: expected '+${JSON.stringify(pn.const)}`)}}`)
2577
+ if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
2402
2578
  if (pn.enum) {
2403
2579
  const ei = ctx.varCounter++
2404
2580
  ctx.closureVars.push(`_es${ei}`)
2405
2581
  ctx.closureVals.push(new Set(pn.enum))
2406
- lines.push(`if(!_es${ei}.has(${kVar})){${fail('enum_mismatch', `'propertyNames: key not in enum: '+${kVar}`)}}`)
2582
+ lines.push(`if(!_es${ei}.has(${kVar})){${fail('enum', 'propertyNames/enum', `{allowedValues:${JSON.stringify(pn.enum)}}`, "'must be equal to one of the allowed values'")}}`)
2407
2583
  }
2408
2584
  }
2409
2585
  for (let i = 0; i < ppEntries.length; i++) {
2410
- lines.push(`if(${matchers[i].check}&&!_ppf${pi}_${i}(${v}[${kVar}])){${fail('pattern_mismatch', `'patternProperties: value invalid for key '+${kVar}`)}}`)
2586
+ lines.push(`if(${matchers[i].check}&&!_ppf${pi}_${i}(${v}[${kVar}])){${fail('pattern', 'patternProperties', `{pattern:'${ppEntries[i][0]}'}`, `'patternProperties: value invalid for key '+${kVar}`)}}`)
2411
2587
  }
2412
2588
  lines.push(`}}`)
2413
2589
  }
@@ -2417,7 +2593,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2417
2593
  if (schema.dependentSchemas) {
2418
2594
  for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
2419
2595
  lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${JSON.stringify(key)} in ${v}){`)
2420
- genCodeC(depSchema, v, pathExpr, lines, ctx)
2596
+ genCodeC(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+key)
2421
2597
  lines.push(`}`)
2422
2598
  }
2423
2599
  }
@@ -2428,25 +2604,25 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2428
2604
  const ki = ctx.varCounter++
2429
2605
  lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
2430
2606
  if (pn.minLength !== undefined) {
2431
- lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('min_length_violation', `'propertyNames: key too short: '+_k${ki}`)}}`)
2607
+ lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
2432
2608
  }
2433
2609
  if (pn.maxLength !== undefined) {
2434
- lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('max_length_violation', `'propertyNames: key too long: '+_k${ki}`)}}`)
2610
+ lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
2435
2611
  }
2436
2612
  if (pn.pattern) {
2437
2613
  const ri = ctx.varCounter++
2438
2614
  ctx.closureVars.push(`_re${ri}`)
2439
2615
  ctx.closureVals.push(new RegExp(pn.pattern))
2440
- lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern_mismatch', `'propertyNames: pattern mismatch: '+_k${ki}`)}}`)
2616
+ lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
2441
2617
  }
2442
2618
  if (pn.const !== undefined) {
2443
- lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const_mismatch', `'propertyNames: expected '+${JSON.stringify(pn.const)}`)}}`)
2619
+ lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
2444
2620
  }
2445
2621
  if (pn.enum) {
2446
2622
  const ei = ctx.varCounter++
2447
2623
  ctx.closureVars.push(`_es${ei}`)
2448
2624
  ctx.closureVals.push(new Set(pn.enum))
2449
- lines.push(`if(!_es${ei}.has(_k${ki})){${fail('enum_mismatch', `'propertyNames: key not in enum: '+_k${ki}`)}}`)
2625
+ lines.push(`if(!_es${ei}.has(_k${ki})){${fail('enum', 'propertyNames/enum', `{allowedValues:${JSON.stringify(pn.enum)}}`, "'must be equal to one of the allowed values'")}}`)
2450
2626
  }
2451
2627
  lines.push(`}}`)
2452
2628
  }
@@ -2456,18 +2632,18 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2456
2632
  const startIdx = schema.prefixItems ? schema.prefixItems.length : 0
2457
2633
  const idx = `_j${ctx.varCounter}`, elem = `_ei${ctx.varCounter}`
2458
2634
  ctx.varCounter++
2459
- const childPath = pathExpr ? `${pathExpr}+'/'+${idx}` : `'/'+${idx}`
2635
+ const childPath = childPathDynExpr(pathExpr, idx)
2460
2636
  lines.push(`if(Array.isArray(${v})){for(let ${idx}=${startIdx};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
2461
- genCodeC(schema.items, elem, childPath, lines, ctx)
2637
+ genCodeC(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
2462
2638
  lines.push(`}}`)
2463
2639
  }
2464
2640
 
2465
2641
  // prefixItems
2466
2642
  if (schema.prefixItems) {
2467
2643
  for (let i = 0; i < schema.prefixItems.length; i++) {
2468
- const childPath = pathExpr ? `${pathExpr}+'/${i}'` : `'/${i}'`
2644
+ const childPath = childPathExpr(pathExpr, String(i))
2469
2645
  lines.push(`if(Array.isArray(${v})&&${v}.length>${i}){`)
2470
- genCodeC(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx)
2646
+ genCodeC(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx, schemaPrefix+'/prefixItems/'+i)
2471
2647
  lines.push(`}`)
2472
2648
  }
2473
2649
  }
@@ -2481,26 +2657,26 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2481
2657
  const minC = schema.minContains !== undefined ? schema.minContains : 1
2482
2658
  const maxC = schema.maxContains
2483
2659
  lines.push(`if(Array.isArray(${v})){const _cf${ci}=function(_cv){${fnBody}};let _cc${ci}=0;for(let _ci${ci}=0;_ci${ci}<${v}.length;_ci${ci}++){if(_cf${ci}(${v}[_ci${ci}]))_cc${ci}++}`)
2484
- lines.push(`if(_cc${ci}<${minC}){${fail('contains_violation', `'need ${minC}+ matches'`)}}`)
2485
- if (maxC !== undefined) lines.push(`if(_cc${ci}>${maxC}){${fail('contains_violation', `'max ${maxC} matches'`)}}`)
2660
+ lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{limit:${minC}}`, `'contains: need at least ${minC} match(es)'`)}}`)
2661
+ if (maxC !== undefined) lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{limit:${maxC}}`, `'contains: at most ${maxC} match(es)'`)}}`)
2486
2662
  lines.push(`}`)
2487
2663
  }
2488
2664
 
2489
2665
  // allOf
2490
- if (schema.allOf) { for (const sub of schema.allOf) genCodeC(sub, v, pathExpr, lines, ctx) }
2666
+ if (schema.allOf) { for (let _ai = 0; _ai < schema.allOf.length; _ai++) genCodeC(schema.allOf[_ai], v, pathExpr, lines, ctx, schemaPrefix+'/allOf/'+_ai) }
2491
2667
 
2492
2668
  // anyOf
2493
2669
  if (schema.anyOf) {
2494
2670
  const fi = ctx.varCounter++
2495
2671
  const fns = schema.anyOf.map(sub => { const sl = []; genCode(sub, '_av', sl, ctx); return sl.length === 0 ? `function(_av){return true}` : `function(_av){${sl.join(';')};return true}` })
2496
- lines.push(`{const _af${fi}=[${fns.join(',')}];let _am=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am=true;break}}if(!_am){${fail('any_of_failed', "'no match'")}}}`)
2672
+ lines.push(`{const _af${fi}=[${fns.join(',')}];let _am=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am=true;break}}if(!_am){${fail('anyOf', 'anyOf', '{}', "'must match a schema in anyOf'")}}}`)
2497
2673
  }
2498
2674
 
2499
2675
  // oneOf
2500
2676
  if (schema.oneOf) {
2501
2677
  const fi = ctx.varCounter++
2502
2678
  const fns = schema.oneOf.map(sub => { const sl = []; genCode(sub, '_ov', sl, ctx); return sl.length === 0 ? `function(_ov){return true}` : `function(_ov){${sl.join(';')};return true}` })
2503
- lines.push(`{const _of${fi}=[${fns.join(',')}];let _oc=0;for(let _oi=0;_oi<_of${fi}.length;_oi++){if(_of${fi}[_oi](${v}))_oc++;if(_oc>1)break}if(_oc!==1){${fail('one_of_failed', "'need exactly 1'")}}}`)
2679
+ lines.push(`{const _of${fi}=[${fns.join(',')}];let _oc=0;for(let _oi=0;_oi<_of${fi}.length;_oi++){if(_of${fi}[_oi](${v}))_oc++;if(_oc>1)break}if(_oc!==1){${fail('oneOf', 'oneOf', '{}', "'must match exactly one schema in oneOf'")}}}`)
2504
2680
  }
2505
2681
 
2506
2682
  // not
@@ -2508,7 +2684,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2508
2684
  const sl = []; genCode(schema.not, '_nv', sl, ctx)
2509
2685
  const nfn = sl.length === 0 ? `function(_nv){return true}` : `function(_nv){${sl.join(';')};return true}`
2510
2686
  const fi = ctx.varCounter++
2511
- lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not_failed', "'should not match'")}}}`)
2687
+ lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
2512
2688
  }
2513
2689
 
2514
2690
  // if/then/else
@@ -2517,8 +2693,8 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
2517
2693
  const fi = ctx.varCounter++
2518
2694
  const ifFn = sl.length === 0 ? `function(_iv){return true}` : `function(_iv){${sl.join(';')};return true}`
2519
2695
  lines.push(`{const _if${fi}=${ifFn}`)
2520
- if (schema.then) { lines.push(`if(_if${fi}(${v})){`); genCodeC(schema.then, v, pathExpr, lines, ctx); lines.push(`}`) }
2521
- if (schema.else) { lines.push(`${schema.then ? 'else' : `if(!_if${fi}(${v}))`}{`); genCodeC(schema.else, v, pathExpr, lines, ctx); lines.push(`}`) }
2696
+ if (schema.then) { lines.push(`if(_if${fi}(${v})){`); genCodeC(schema.then, v, pathExpr, lines, ctx, schemaPrefix+'/then'); lines.push(`}`) }
2697
+ if (schema.else) { lines.push(`${schema.then ? 'else' : `if(!_if${fi}(${v}))`}{`); genCodeC(schema.else, v, pathExpr, lines, ctx, schemaPrefix+'/else'); lines.push(`}`) }
2522
2698
  lines.push(`}`)
2523
2699
  }
2524
2700
 
@@ -2648,4 +2824,4 @@ function _collectEval(schema, result, defs, schemaMap, refStack, isRoot) {
2648
2824
  // not → contributes nothing (spec: annotations from not are discarded)
2649
2825
  }
2650
2826
 
2651
- module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated }
2827
+ module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, AJV_MESSAGES }