ata-validator 1.6.2 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -53,7 +53,8 @@ function buildErrorLiteral (opts) {
53
53
  function _cpLen(s) {
54
54
  const len = s.length;
55
55
  for (let i = 0; i < len; i++) {
56
- if (s.charCodeAt(i) >= 0xD800 && s.charCodeAt(i) <= 0xDBFF) {
56
+ // Wraparound test: one compare classifies a high surrogate.
57
+ if (((s.charCodeAt(i) - 0xD800) >>> 0) < 0x400) {
57
58
  // Found a high surrogate — count code points the slow way
58
59
  let n = 0; for (const _ of s) n++; return n;
59
60
  }
@@ -108,6 +109,14 @@ function compileToJS(schema, defs, schemaMap) {
108
109
  }
109
110
 
110
111
  if (!defs && needsBaseTracking(schema, schemaMap, new Set())) return null
112
+ if (!defs && externalDocsNeedInterpreter(schema, schemaMap)) return null
113
+
114
+ // unevaluatedProperties / unevaluatedItems need annotation tracking this
115
+ // path does not do. Decline so the schema reaches an engine that does.
116
+ if (!defs) {
117
+ const str = JSON.stringify(schema)
118
+ if (str.includes('"unevaluatedProperties"') || str.includes('"unevaluatedItems"')) return null
119
+ }
111
120
 
112
121
  // Collect $defs early so sub-schemas can resolve $ref
113
122
  const rootDefs = defs || collectDefs(schema)
@@ -158,13 +167,13 @@ function compileToJS(schema, defs, schemaMap) {
158
167
  const primitives = vals.filter(v => v === null || typeof v !== 'object')
159
168
  const objects = vals.filter(v => v !== null && typeof v === 'object')
160
169
  const primSet = new Set(primitives.map(v => v === null ? 'null' : typeof v === 'string' ? 's:' + v : 'n:' + v))
161
- const objStrs = objects.map(v => JSON.stringify(v))
170
+ const objStrs = objects.map(v => _canonical(v))
162
171
  checks.push((d) => {
163
172
  // Fast primitive check
164
173
  const key = d === null ? 'null' : typeof d === 'string' ? 's:' + d : typeof d === 'number' || typeof d === 'boolean' ? 'n:' + d : null
165
174
  if (key !== null && primSet.has(key)) return true
166
- // Slow object check
167
- const ds = JSON.stringify(d)
175
+ // Slow object check, key order independent
176
+ const ds = _canonical(d)
168
177
  for (let i = 0; i < objStrs.length; i++) {
169
178
  if (ds === objStrs[i]) return true
170
179
  }
@@ -182,15 +191,15 @@ function compileToJS(schema, defs, schemaMap) {
182
191
  if (cv === null || typeof cv !== 'object') {
183
192
  checks.push((d) => d === cv)
184
193
  } else {
185
- const cs = JSON.stringify(cv)
186
- checks.push((d) => JSON.stringify(d) === cs)
194
+ const cs = _canonical(cv)
195
+ checks.push((d) => _canonical(d) === cs)
187
196
  }
188
197
  }
189
198
 
190
- // required
199
+ // required applies to objects only; other types are ignored.
191
200
  if (schema.required && Array.isArray(schema.required)) {
192
201
  for (const key of schema.required) {
193
- checks.push((d) => typeof d === 'object' && d !== null && key in d)
202
+ checks.push((d) => typeof d !== 'object' || d === null || Array.isArray(d) || d[key] !== undefined)
194
203
  }
195
204
  }
196
205
 
@@ -206,10 +215,10 @@ function compileToJS(schema, defs, schemaMap) {
206
215
  }
207
216
  }
208
217
 
209
- // additionalProperties
210
- if (schema.additionalProperties !== undefined && schema.properties) {
218
+ // additionalProperties (with or without a properties map)
219
+ if (schema.additionalProperties !== undefined) {
211
220
  if (schema.additionalProperties === false) {
212
- const allowed = new Set(Object.keys(schema.properties))
221
+ const allowed = new Set(Object.keys(schema.properties || {}))
213
222
  checks.push((d) => {
214
223
  if (typeof d !== 'object' || d === null || Array.isArray(d)) return true
215
224
  const keys = Object.keys(d)
@@ -246,13 +255,14 @@ function compileToJS(schema, defs, schemaMap) {
246
255
  }
247
256
  }
248
257
 
249
- // items
258
+ // items, applied after the prefixItems positions
250
259
  if (schema.items) {
251
260
  const itemCheck = compileToJS(schema.items, rootDefs)
252
261
  if (!itemCheck) return null
262
+ const start = Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0
253
263
  checks.push((d) => {
254
264
  if (!Array.isArray(d)) return true
255
- for (let i = 0; i < d.length; i++) {
265
+ for (let i = start; i < d.length; i++) {
256
266
  if (!itemCheck(d[i])) return false
257
267
  }
258
268
  return true
@@ -330,7 +340,11 @@ function compileToJS(schema, defs, schemaMap) {
330
340
  }
331
341
  if (schema.multipleOf !== undefined) {
332
342
  const div = schema.multipleOf
333
- checks.push((d) => typeof d !== 'number' || d % div === 0)
343
+ checks.push((d) => {
344
+ if (typeof d !== 'number') return true
345
+ const r = d % div
346
+ return Math.abs(r) <= 1e-8 || Math.abs(r - div) <= 1e-8
347
+ })
334
348
  }
335
349
 
336
350
  // string
@@ -341,7 +355,7 @@ function compileToJS(schema, defs, schemaMap) {
341
355
  }
342
356
  if (schema.maxLength !== undefined) {
343
357
  const max = schema.maxLength
344
- checks.push((d) => typeof d !== 'string' || d.length <= max || _cpLen(d) <= max)
358
+ checks.push((d) => typeof d !== 'string' || d.length <= max || (d.length <= 2 * max + 1 && _cpLen(d) <= max))
345
359
  }
346
360
  if (schema.pattern) {
347
361
  try {
@@ -352,10 +366,12 @@ function compileToJS(schema, defs, schemaMap) {
352
366
  }
353
367
  }
354
368
 
355
- // format — hand-written fast checks
369
+ // format — hand-written fast checks. A format the code generator asserts
370
+ // but this path cannot is a decline, not a pass.
356
371
  if (schema.format) {
357
372
  const fc = FORMAT_CHECKS[schema.format]
358
373
  if (fc) checks.push((d) => typeof d !== 'string' || fc(d))
374
+ else if (FORMAT_CODEGEN[schema.format]) return null
359
375
  }
360
376
 
361
377
  // array size
@@ -371,11 +387,11 @@ function compileToJS(schema, defs, schemaMap) {
371
387
  // object size
372
388
  if (schema.minProperties !== undefined) {
373
389
  const min = schema.minProperties
374
- checks.push((d) => typeof d !== 'object' || d === null || Object.keys(d).length >= min)
390
+ checks.push((d) => typeof d !== 'object' || d === null || Array.isArray(d) || Object.keys(d).length >= min)
375
391
  }
376
392
  if (schema.maxProperties !== undefined) {
377
393
  const max = schema.maxProperties
378
- checks.push((d) => typeof d !== 'object' || d === null || Object.keys(d).length <= max)
394
+ checks.push((d) => typeof d !== 'object' || d === null || Array.isArray(d) || Object.keys(d).length <= max)
379
395
  }
380
396
 
381
397
  // allOf
@@ -558,7 +574,7 @@ function walkJsonPointer(root, fragment) {
558
574
  for (const p of parts) {
559
575
  if (target == null || typeof target !== 'object') return null
560
576
  if (!(p in target)) {
561
- const alt = p === 'definitions' ? '$defs' : p === '$defs' ? 'definitions' : null
577
+ const alt = p === 'definitions' ? '$defs' : p === '$defs' ? 'definitions' : p === 'items' && Array.isArray(target.prefixItems) ? 'prefixItems' : null
562
578
  if (alt !== null && alt in target) { target = target[alt]; continue }
563
579
  return null
564
580
  }
@@ -597,8 +613,9 @@ function resolveCrossSchemaRef(ref, schemaMap) {
597
613
  }
598
614
 
599
615
  function resolveRef(ref, defs, schemaMap) {
600
- // Self-reference: "#" — treat as permissive to avoid infinite recursion
601
- if (ref === '#') return () => true
616
+ // Self-reference: "#" cannot be expressed as a closure without a recursion
617
+ // guard. Decline rather than substitute a vacuous check.
618
+ if (ref === '#') return null
602
619
 
603
620
  // 1. Local ref
604
621
  if (defs) {
@@ -660,6 +677,13 @@ const TYPE_CHECKS = {
660
677
  object: (d) => typeof d === 'object' && d !== null && !Array.isArray(d),
661
678
  }
662
679
 
680
+ // Key-order independent JSON rendering for const/enum comparison.
681
+ function _canonical(x) {
682
+ if (x === null || typeof x !== 'object') return JSON.stringify(x)
683
+ if (Array.isArray(x)) return '[' + x.map(_canonical).join(',') + ']'
684
+ return '{' + Object.keys(x).sort().map((k) => JSON.stringify(k) + ':' + _canonical(x[k])).join(',') + '}'
685
+ }
686
+
663
687
  const FORMAT_CHECKS = {
664
688
  email: (s) => { const at = s.indexOf('@'); return at > 0 && at < s.length - 1 && s.indexOf('.', at) > at + 1 },
665
689
  date: (s) => { if (s.length !== 10 || !/^\d{4}-\d{2}-\d{2}$/.test(s)) return false; const m = +s.slice(5, 7), d = +s.slice(8, 10); return m >= 1 && m <= 12 && d >= 1 && d <= 31 },
@@ -1147,14 +1171,85 @@ function hasUnresolvableRef(node, rootDefs, anchors, schemaMap, seen) {
1147
1171
  return false
1148
1172
  }
1149
1173
 
1174
+
1175
+ // --- The one gate every code generation entry point runs first ---
1176
+ //
1177
+ // Four entry points build four contexts, and three times a bail present in
1178
+ // one was missing from another; each time the generator emitted nothing for
1179
+ // a construct it could not represent and returned the empty program as
1180
+ // always-valid. The checks that do not depend on an entry point's own anchor
1181
+ // map live here, so adding a bail is one edit. Entry-point-specific checks
1182
+ // (hasUnresolvableRef against each path's anchors) still run after this.
1183
+ // tests/test_codegen_entrypoint_agreement.js holds the four to one verdict.
1184
+
1185
+ function collectExternalRefKeys(node, out, seen) {
1186
+ if (typeof node !== 'object' || node === null) return
1187
+ if (seen.has(node)) return
1188
+ seen.add(node)
1189
+ if (Array.isArray(node)) { for (const n of node) collectExternalRefKeys(n, out, seen); return }
1190
+ for (const key of Object.keys(node)) {
1191
+ const v = node[key]
1192
+ if ((key === '$ref' || key === '$dynamicRef') && typeof v === 'string' && !v.startsWith('#')) out.add(v.split('#')[0])
1193
+ else if (typeof v === 'object' && v !== null && key !== 'enum' && key !== 'const' && key !== 'default' && key !== 'examples') collectExternalRefKeys(v, out, seen)
1194
+ }
1195
+ }
1196
+
1197
+ function lookupExternal(key, schemaMap) {
1198
+ if (schemaMap.has(key)) return schemaMap.get(key)
1199
+ if (!key.includes('://')) {
1200
+ for (const [id, doc] of schemaMap) if (id.endsWith('/' + key)) return doc
1201
+ }
1202
+ return null
1203
+ }
1204
+
1205
+ // Documents reachable from `schema` through cross-document references.
1206
+ function reachableExternalDocs(schema, schemaMap) {
1207
+ const docs = new Set()
1208
+ if (!schemaMap || schemaMap.size === 0) return docs
1209
+ const queue = [schema]
1210
+ const seenDocs = new Set([schema])
1211
+ while (queue.length) {
1212
+ const doc = queue.shift()
1213
+ const keys = new Set()
1214
+ collectExternalRefKeys(doc, keys, new Set())
1215
+ for (const key of keys) {
1216
+ const target = lookupExternal(key, schemaMap)
1217
+ if (target && !seenDocs.has(target)) { seenDocs.add(target); docs.add(target); queue.push(target) }
1218
+ }
1219
+ }
1220
+ return docs
1221
+ }
1222
+
1223
+ // Dynamic scope and annotation tracking across documents is interpreter
1224
+ // work. A referenced document that uses either must decline the whole
1225
+ // schema, or the generator emits a vacuous check for the reference.
1226
+ function externalDocsNeedInterpreter(schema, schemaMap) {
1227
+ for (const doc of reachableExternalDocs(schema, schemaMap)) {
1228
+ const str = JSON.stringify(doc)
1229
+ if (str.includes('"$dynamicRef"') || str.includes('"$dynamicAnchor"') ||
1230
+ str.includes('"unevaluatedProperties"') || str.includes('"unevaluatedItems"')) return true
1231
+ // An embedded $id inside a referenced document opens a resource the
1232
+ // generator never registers, so references to it would be vacuous.
1233
+ if (typeof doc === 'object' && doc !== null && hasNestedIdScope(doc)) return true
1234
+ }
1235
+ return false
1236
+ }
1237
+
1238
+ function sharedCodegenGate(schema, schemaMap) {
1239
+ if (typeof schema !== 'object' || schema === null) return true
1240
+ if (!codegenSafe(schema, schemaMap)) return false
1241
+ if (needsBaseTracking(schema, schemaMap, new Set())) return false
1242
+ if (externalDocsNeedInterpreter(schema, schemaMap)) return false
1243
+ return true
1244
+ }
1245
+
1150
1246
  // --- Codegen mode: generates a single Function (NOT CSP-safe) ---
1151
1247
  // This matches ajv's approach: one monolithic function, V8 JIT fully inlines it
1152
1248
  function compileToJSCodegen(schema, schemaMap, userFormats) {
1153
1249
  if (typeof schema === 'boolean') return schema ? () => true : () => false
1154
1250
  if (typeof schema !== 'object' || schema === null) return null
1155
1251
 
1156
- // Bail if schema contains features that codegen can't handle correctly
1157
- if (!codegenSafe(schema, schemaMap)) return null
1252
+ if (!sharedCodegenGate(schema, schemaMap)) return null
1158
1253
 
1159
1254
  // Collect defs for $ref resolution
1160
1255
  const rootDefs = schema.$defs || schema.definitions || null
@@ -1290,7 +1385,13 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
1290
1385
  const fmtEntries = []
1291
1386
  for (let i = 0; i < closureNames.length; i++) {
1292
1387
  if (closureNames[i].startsWith('_uf_')) {
1293
- fmtEntries.push({ name: closureNames[i], fn: closureValues[i] })
1388
+ // The format's own name, so an emitter can look it up at runtime
1389
+ // instead of embedding the function.
1390
+ let format = null
1391
+ for (const key of Object.keys(ctx.userFormats)) {
1392
+ if (ctx.userFormats[key] === closureValues[i]) { format = key; break }
1393
+ }
1394
+ fmtEntries.push({ name: closureNames[i], fn: closureValues[i], format })
1294
1395
  }
1295
1396
  }
1296
1397
  if (fmtEntries.length) boolFn._formatClosures = fmtEntries
@@ -1434,7 +1535,7 @@ function tryGenCombined(schema, access, ctx) {
1434
1535
  if (schema.maximum !== undefined) conds.push(`_v>${schema.maximum}`)
1435
1536
  if (schema.exclusiveMinimum !== undefined) conds.push(`_v<=${schema.exclusiveMinimum}`)
1436
1537
  if (schema.exclusiveMaximum !== undefined) conds.push(`_v>=${schema.exclusiveMaximum}`)
1437
- if (schema.multipleOf !== undefined) conds.push(`_v%${schema.multipleOf}!==0`)
1538
+ if (schema.multipleOf !== undefined) conds.push(`(Math.abs(_v%${schema.multipleOf})>1e-8&&Math.abs(_v%${schema.multipleOf}-${schema.multipleOf})>1e-8)`)
1438
1539
  if (conds.length < 2) return null
1439
1540
  return bind(conds)
1440
1541
  }
@@ -1445,7 +1546,7 @@ function tryGenCombined(schema, access, ctx) {
1445
1546
  if (schema.maximum !== undefined) conds.push(`_v>${schema.maximum}`)
1446
1547
  if (schema.exclusiveMinimum !== undefined) conds.push(`_v<=${schema.exclusiveMinimum}`)
1447
1548
  if (schema.exclusiveMaximum !== undefined) conds.push(`_v>=${schema.exclusiveMaximum}`)
1448
- if (schema.multipleOf !== undefined) conds.push(`_v%${schema.multipleOf}!==0`)
1549
+ if (schema.multipleOf !== undefined) conds.push(`(Math.abs(_v%${schema.multipleOf})>1e-8&&Math.abs(_v%${schema.multipleOf}-${schema.multipleOf})>1e-8)`)
1449
1550
  if (conds.length < 2) return null
1450
1551
  return bind(conds)
1451
1552
  }
@@ -1458,7 +1559,9 @@ function tryGenCombined(schema, access, ctx) {
1458
1559
  // function is only safe when we're at the root (`v === 'd'`). For nested
1459
1560
  // nodes, emit inline so block-scoped variables like `_o0` stay in scope.
1460
1561
  function _deferOrInline(ctx, lines, v, check) {
1461
- if (v === 'd') {
1562
+ // Deferring is only sound at the top level of the root function. Inside a
1563
+ // conditional applicator (dependentSchemas) the check must stay in its block.
1564
+ if (v === 'd' && !ctx.condDepth) {
1462
1565
  if (!ctx.deferredChecks) ctx.deferredChecks = []
1463
1566
  ctx.deferredChecks.push(check)
1464
1567
  } else {
@@ -1599,7 +1702,7 @@ function genCode(schema, v, lines, ctx, knownType) {
1599
1702
  const isArr = effectiveType === 'array'
1600
1703
  const isStr = effectiveType === 'string'
1601
1704
  const isNum = effectiveType === 'number' || effectiveType === 'integer'
1602
- const objGuard = isObj ? '' : `typeof ${v}==='object'&&${v}!==null&&`
1705
+ const objGuard = isObj ? '' : `typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&`
1603
1706
  const objCheck = isObj ? '' : `if(typeof ${v}!=='object'||${v}===null)return false;`
1604
1707
 
1605
1708
  // enum
@@ -1619,7 +1722,12 @@ function genCode(schema, v, lines, ctx, knownType) {
1619
1722
  if (cv === null || typeof cv !== 'object') {
1620
1723
  lines.push(`if(${v}!==${JSON.stringify(cv)})return false`)
1621
1724
  } else {
1622
- lines.push(`if(JSON.stringify(${v})!==${JSON.stringify(JSON.stringify(cv))})return false`)
1725
+ // Key-order independent comparison, defined inline so every emitter
1726
+ // (runtime, hybrid, standalone) carries it.
1727
+ const ci = ctx.varCounter++
1728
+ const canonFn = `_cnB${ci}`
1729
+ const expected = _canonical(cv)
1730
+ lines.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(',')+'}'};if(${canonFn}(${v})!==${JSON.stringify(expected)})return false}`)
1623
1731
  }
1624
1732
  }
1625
1733
 
@@ -1647,9 +1755,9 @@ function genCode(schema, v, lines, ctx, knownType) {
1647
1755
  const checks = schema.required.map(key => `${v}[${JSON.stringify(key)}]===undefined`)
1648
1756
  lines.push(`if(${checks.join('||')})return false`)
1649
1757
  } else {
1650
- for (const key of schema.required) {
1651
- lines.push(`if(typeof ${v}!=='object'||${v}===null||!(${JSON.stringify(key)} in ${v}))return false`)
1652
- }
1758
+ // required applies to objects only; other types are ignored.
1759
+ const checks = schema.required.map(key => `${v}[${JSON.stringify(key)}]===undefined`)
1760
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&(${checks.join('||')}))return false`)
1653
1761
  }
1654
1762
  }
1655
1763
 
@@ -1679,7 +1787,11 @@ function genCode(schema, v, lines, ctx, knownType) {
1679
1787
  if (schema.maximum !== undefined) lines.push(isNum ? `if(${v}>${schema.maximum})return false` : `if(typeof ${v}==='number'&&${v}>${schema.maximum})return false`)
1680
1788
  if (schema.exclusiveMinimum !== undefined) lines.push(isNum ? `if(${v}<=${schema.exclusiveMinimum})return false` : `if(typeof ${v}==='number'&&${v}<=${schema.exclusiveMinimum})return false`)
1681
1789
  if (schema.exclusiveMaximum !== undefined) lines.push(isNum ? `if(${v}>=${schema.exclusiveMaximum})return false` : `if(typeof ${v}==='number'&&${v}>=${schema.exclusiveMaximum})return false`)
1682
- if (schema.multipleOf !== undefined) lines.push(isNum ? `if(${v}%${schema.multipleOf}!==0)return false` : `if(typeof ${v}==='number'&&${v}%${schema.multipleOf}!==0)return false`)
1790
+ if (schema.multipleOf !== undefined) {
1791
+ const m = schema.multipleOf
1792
+ const bad = `(Math.abs(${v}%${m})>1e-8&&Math.abs(${v}%${m}-${m})>1e-8)`
1793
+ lines.push(isNum ? `if${bad}return false` : `if(typeof ${v}==='number'&&${bad})return false`)
1794
+ }
1683
1795
 
1684
1796
  // string length — skip type guard if known string.
1685
1797
  // s.length (UTF-16 code units) is an upper bound on cpLen, and at least cpLen
@@ -1938,7 +2050,9 @@ function genCode(schema, v, lines, ctx, knownType) {
1938
2050
  for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
1939
2051
  const guard = isObj ? '' : `typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&`
1940
2052
  lines.push(`if(${guard}${JSON.stringify(key)} in ${v}){`)
2053
+ ctx.condDepth = (ctx.condDepth || 0) + 1
1941
2054
  genCode(depSchema, v, lines, ctx, effectiveType)
2055
+ ctx.condDepth--
1942
2056
  lines.push(`}`)
1943
2057
  }
1944
2058
  }
@@ -2015,9 +2129,11 @@ function genCode(schema, v, lines, ctx, knownType) {
2015
2129
  const idx = `_j${ctx.varCounter}`
2016
2130
  const elem = `_e${ctx.varCounter}`
2017
2131
  ctx.varCounter++
2132
+ // items applies after the prefixItems positions (Draft 2020-12).
2133
+ const start = Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0
2018
2134
  lines.push(isArr
2019
- ? `for(let ${idx}=0;${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`
2020
- : `if(Array.isArray(${v})){for(let ${idx}=0;${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
2135
+ ? `for(let ${idx}=${start};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`
2136
+ : `if(Array.isArray(${v})){for(let ${idx}=${start};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
2021
2137
  genCode(schema.items, elem, lines, ctx)
2022
2138
  lines.push(isArr ? `}` : `}}`)
2023
2139
  }
@@ -2682,6 +2798,9 @@ const FORMAT_CODEGEN = {
2682
2798
 
2683
2799
  // Safe key escaping: use JSON.stringify to handle all special chars (newlines, null bytes, etc.)
2684
2800
  function esc(s) { return JSON.stringify(s).slice(1, -1) }
2801
+ // A schema pointer segment for a pattern: JSON Pointer escaping, then made
2802
+ // safe for the single-quoted literal the error emitters use.
2803
+ function ptrSeg(s) { return s.replace(/~/g, '~0').replace(/\//g, '~1').replace(/\\/g, '\\\\').replace(/'/g, "\\'") }
2685
2804
 
2686
2805
  // Resolve child path at codegen time when parent is a static string literal.
2687
2806
  // This enables frozen pre-allocation for ALL nested error objects.
@@ -2828,7 +2947,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
2828
2947
  : () => ({ valid: false, errors: [{ keyword: 'false schema', instancePath: '', schemaPath: '#', params: {}, message: 'boolean schema is false' }] })
2829
2948
  }
2830
2949
  if (typeof schema !== 'object' || schema === null) return null
2831
- if (!codegenSafe(schema, schemaMap)) return null
2950
+ if (!sharedCodegenGate(schema, schemaMap)) return null
2832
2951
  if (schema.patternProperties) {
2833
2952
  for (const [pat, sub] of Object.entries(schema.patternProperties)) {
2834
2953
  if (typeof sub === 'boolean') return null
@@ -3226,13 +3345,30 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3226
3345
  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'`)}}`)
3227
3346
  }
3228
3347
 
3229
- // additionalProperties: false
3230
- if (schema.additionalProperties === false && schema.properties) {
3231
- const allowed = Object.keys(schema.properties).map(k => `${JSON.stringify(k)}`).join(',')
3348
+ // additionalProperties: false. A key matched by any patternProperties
3349
+ // entry is not additional, so those patterns are consulted here as well.
3350
+ if (schema.additionalProperties === false && (schema.properties || schema.patternProperties)) {
3351
+ const allowed = Object.keys(schema.properties || {}).map(k => `${JSON.stringify(k)}`).join(',')
3232
3352
  const ci = ctx.varCounter++
3233
3353
  const apSp = `${schemaPrefix}/additionalProperties`
3234
3354
  const apLit = buildErrorLiteral({ keyword: 'additionalProperties', schemaPath: apSp, sourceMap: ctx.sourceMap })
3235
- 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:'${apLit.codeStr}',keyword:'additionalProperties',instancePath:${pathExpr||'""'},schemaPath:'${apSp}',params:{additionalProperty:_k${ci}[_i]},message:'must NOT have additional properties',docUrl:'${apLit.docUrl}'${apLit.frame}});if(!_all)return{valid:false,errors:_e}}}`
3355
+ const patChecks = []
3356
+ for (const pat of Object.keys(schema.patternProperties || {})) {
3357
+ const pattern = JSON.stringify(pat)
3358
+ if (!ctx.regExpMap.has(pattern)) {
3359
+ const ri = ctx.varCounter++
3360
+ ctx.regExpMap.set(pattern, ri)
3361
+ if (patternIsSafe(pat)) {
3362
+ ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
3363
+ ctx.usesSafeRe = true
3364
+ } else {
3365
+ ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
3366
+ }
3367
+ }
3368
+ patChecks.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ci}[_i])`)
3369
+ }
3370
+ const isAdditional = patChecks.length ? `!_a${ci}.has(_k${ci}[_i])&&!(${patChecks.join('||')})` : `!_a${ci}.has(_k${ci}[_i])`
3371
+ const inner = `const _k${ci}=Object.keys(${v});const _a${ci}=new Set([${allowed}]);for(let _i=0;_i<_k${ci}.length;_i++){if(${isAdditional}){_e.push({code:'${apLit.codeStr}',keyword:'additionalProperties',instancePath:${pathExpr||'""'},schemaPath:'${apSp}',params:{additionalProperty:_k${ci}[_i]},message:'must NOT have additional properties',docUrl:'${apLit.docUrl}'${apLit.frame}});if(!_all)return{valid:false,errors:_e}}}`
3236
3372
  lines.push(isObj ? `{${inner}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
3237
3373
  }
3238
3374
 
@@ -3275,7 +3411,8 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3275
3411
  const ki = ctx.varCounter++
3276
3412
  lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){if(_re${ri}.test(_k${ki})){`)
3277
3413
  const p = pathExpr ? `${pathExpr}+'/'+_k${ki}` : `'/'+_k${ki}`
3278
- genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix+'/patternProperties')
3414
+ // The real pointer, so source maps can locate the failing keyword.
3415
+ genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/patternProperties/' + pat.replace(/~/g, '~0').replace(/\//g, '~1'))
3279
3416
  lines.push(`}}}`)
3280
3417
  }
3281
3418
  }
@@ -3454,7 +3591,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats) {
3454
3591
  : () => ({ valid: false, errors: [{ keyword: 'false schema', instancePath: '', schemaPath: '#', params: {}, message: 'boolean schema is false' }] })
3455
3592
  }
3456
3593
  if (typeof schema !== 'object' || schema === null) return null
3457
- if (!codegenSafe(schema, schemaMap)) return null
3594
+ if (!sharedCodegenGate(schema, schemaMap)) return null
3458
3595
  if (schema.patternProperties) {
3459
3596
  for (const [pat, sub] of Object.entries(schema.patternProperties)) {
3460
3597
  if (typeof sub === 'boolean') return null
@@ -3888,17 +4025,6 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3888
4025
  }
3889
4026
  }
3890
4027
 
3891
- // Build sub-schema validators as closure vars
3892
- for (let i = 0; i < ppEntries.length; i++) {
3893
- const [, sub] = ppEntries[i]
3894
- const subLines = []
3895
- genCode(sub, `_ppv`, subLines, ctx)
3896
- const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
3897
- const fnVar = `_ppf${pi}_${i}`
3898
- ctx.closureVars.push(fnVar)
3899
- ctx.closureVals.push(new Function('_ppv', fnBody))
3900
- }
3901
-
3902
4028
  const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
3903
4029
  const kVar = `_k${pi}`
3904
4030
 
@@ -3942,7 +4068,11 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3942
4068
  const matchExpr = keyCheck || `_as${pi}.has(${kVar})`
3943
4069
  lines.push(`let _m${pi}=${matchExpr}`)
3944
4070
  for (let i = 0; i < ppEntries.length; i++) {
3945
- 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}`)}}}`)
4071
+ // The subschema is generated in place so its own keywords report
4072
+ // the real instance path and schema pointer.
4073
+ lines.push(`if(${matchers[i].check}){_m${pi}=true;{const _ppv${pi}_${i}=${v}[${kVar}]`)
4074
+ genCodeC(ppEntries[i][1], `_ppv${pi}_${i}`, childPathDynExpr(pathExpr, kVar), lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(ppEntries[i][0]))
4075
+ lines.push(`}}`)
3946
4076
  }
3947
4077
  lines.push(`if(!_m${pi}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:${kVar}}`, "'must NOT have additional properties'")}}`)
3948
4078
  lines.push(`}}`)
@@ -3972,7 +4102,9 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3972
4102
  }
3973
4103
  }
3974
4104
  for (let i = 0; i < ppEntries.length; i++) {
3975
- lines.push(`if(${matchers[i].check}&&!_ppf${pi}_${i}(${v}[${kVar}])){${fail('pattern', 'patternProperties', `{pattern:'${ppEntries[i][0]}'}`, `'patternProperties: value invalid for key '+${kVar}`)}}`)
4105
+ lines.push(`if(${matchers[i].check}){const _ppv${pi}_${i}=${v}[${kVar}]`)
4106
+ genCodeC(ppEntries[i][1], `_ppv${pi}_${i}`, childPathDynExpr(pathExpr, kVar), lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(ppEntries[i][0]))
4107
+ lines.push(`}`)
3976
4108
  }
3977
4109
  lines.push(`}}`)
3978
4110
  }
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ // The official meta-schemas, vendored so a `$ref` to one of them resolves
4
+ // without a network. Draft 2020-12 is eight documents joined by $dynamicRef;
5
+ // draft-07 is one. Sources: https://json-schema.org/draft/2020-12/schema and
6
+ // http://json-schema.org/draft-07/schema, fetched 2026-08-23, unmodified.
7
+ // Keyed by $id; index.js also registers the http/https and trailing-# spellings.
8
+
9
+ const METASCHEMAS = new Map([
10
+ ["https://json-schema.org/draft/2020-12/schema", {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/schema","$vocabulary":{"https://json-schema.org/draft/2020-12/vocab/core":true,"https://json-schema.org/draft/2020-12/vocab/applicator":true,"https://json-schema.org/draft/2020-12/vocab/unevaluated":true,"https://json-schema.org/draft/2020-12/vocab/validation":true,"https://json-schema.org/draft/2020-12/vocab/meta-data":true,"https://json-schema.org/draft/2020-12/vocab/format-annotation":true,"https://json-schema.org/draft/2020-12/vocab/content":true},"$dynamicAnchor":"meta","title":"Core and Validation specifications meta-schema","allOf":[{"$ref":"meta/core"},{"$ref":"meta/applicator"},{"$ref":"meta/unevaluated"},{"$ref":"meta/validation"},{"$ref":"meta/meta-data"},{"$ref":"meta/format-annotation"},{"$ref":"meta/content"}],"type":["object","boolean"],"$comment":"This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.","properties":{"definitions":{"$comment":"\"definitions\" has been replaced by \"$defs\".","type":"object","additionalProperties":{"$dynamicRef":"#meta"},"deprecated":true,"default":{}},"dependencies":{"$comment":"\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.","type":"object","additionalProperties":{"anyOf":[{"$dynamicRef":"#meta"},{"$ref":"meta/validation#/$defs/stringArray"}]},"deprecated":true,"default":{}},"$recursiveAnchor":{"$comment":"\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".","$ref":"meta/core#/$defs/anchorString","deprecated":true},"$recursiveRef":{"$comment":"\"$recursiveRef\" has been replaced by \"$dynamicRef\".","$ref":"meta/core#/$defs/uriReferenceString","deprecated":true}}}],
11
+ ["https://json-schema.org/draft/2020-12/meta/core", {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/core","$dynamicAnchor":"meta","title":"Core vocabulary meta-schema","type":["object","boolean"],"properties":{"$id":{"$ref":"#/$defs/uriReferenceString","$comment":"Non-empty fragments not allowed.","pattern":"^[^#]*#?$"},"$schema":{"$ref":"#/$defs/uriString"},"$ref":{"$ref":"#/$defs/uriReferenceString"},"$anchor":{"$ref":"#/$defs/anchorString"},"$dynamicRef":{"$ref":"#/$defs/uriReferenceString"},"$dynamicAnchor":{"$ref":"#/$defs/anchorString"},"$vocabulary":{"type":"object","propertyNames":{"$ref":"#/$defs/uriString"},"additionalProperties":{"type":"boolean"}},"$comment":{"type":"string"},"$defs":{"type":"object","additionalProperties":{"$dynamicRef":"#meta"}}},"$defs":{"anchorString":{"type":"string","pattern":"^[A-Za-z_][-A-Za-z0-9._]*$"},"uriString":{"type":"string","format":"uri"},"uriReferenceString":{"type":"string","format":"uri-reference"}}}],
12
+ ["https://json-schema.org/draft/2020-12/meta/applicator", {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/applicator","$dynamicAnchor":"meta","title":"Applicator vocabulary meta-schema","type":["object","boolean"],"properties":{"prefixItems":{"$ref":"#/$defs/schemaArray"},"items":{"$dynamicRef":"#meta"},"contains":{"$dynamicRef":"#meta"},"additionalProperties":{"$dynamicRef":"#meta"},"properties":{"type":"object","additionalProperties":{"$dynamicRef":"#meta"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$dynamicRef":"#meta"},"propertyNames":{"format":"regex"},"default":{}},"dependentSchemas":{"type":"object","additionalProperties":{"$dynamicRef":"#meta"},"default":{}},"propertyNames":{"$dynamicRef":"#meta"},"if":{"$dynamicRef":"#meta"},"then":{"$dynamicRef":"#meta"},"else":{"$dynamicRef":"#meta"},"allOf":{"$ref":"#/$defs/schemaArray"},"anyOf":{"$ref":"#/$defs/schemaArray"},"oneOf":{"$ref":"#/$defs/schemaArray"},"not":{"$dynamicRef":"#meta"}},"$defs":{"schemaArray":{"type":"array","minItems":1,"items":{"$dynamicRef":"#meta"}}}}],
13
+ ["https://json-schema.org/draft/2020-12/meta/validation", {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/validation","$dynamicAnchor":"meta","title":"Validation vocabulary meta-schema","type":["object","boolean"],"properties":{"type":{"anyOf":[{"$ref":"#/$defs/simpleTypes"},{"type":"array","items":{"$ref":"#/$defs/simpleTypes"},"minItems":1,"uniqueItems":true}]},"const":true,"enum":{"type":"array","items":true},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/$defs/nonNegativeInteger"},"minLength":{"$ref":"#/$defs/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"maxItems":{"$ref":"#/$defs/nonNegativeInteger"},"minItems":{"$ref":"#/$defs/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"maxContains":{"$ref":"#/$defs/nonNegativeInteger"},"minContains":{"$ref":"#/$defs/nonNegativeInteger","default":1},"maxProperties":{"$ref":"#/$defs/nonNegativeInteger"},"minProperties":{"$ref":"#/$defs/nonNegativeIntegerDefault0"},"required":{"$ref":"#/$defs/stringArray"},"dependentRequired":{"type":"object","additionalProperties":{"$ref":"#/$defs/stringArray"}}},"$defs":{"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"$ref":"#/$defs/nonNegativeInteger","default":0},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}}}],
14
+ ["https://json-schema.org/draft/2020-12/meta/meta-data", {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/meta-data","$dynamicAnchor":"meta","title":"Meta-data vocabulary meta-schema","type":["object","boolean"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"default":true,"deprecated":{"type":"boolean","default":false},"readOnly":{"type":"boolean","default":false},"writeOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true}}}],
15
+ ["https://json-schema.org/draft/2020-12/meta/format-annotation", {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/format-annotation","$dynamicAnchor":"meta","title":"Format vocabulary meta-schema for annotation results","type":["object","boolean"],"properties":{"format":{"type":"string"}}}],
16
+ ["https://json-schema.org/draft/2020-12/meta/content", {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/content","$dynamicAnchor":"meta","title":"Content vocabulary meta-schema","type":["object","boolean"],"properties":{"contentEncoding":{"type":"string"},"contentMediaType":{"type":"string"},"contentSchema":{"$dynamicRef":"#meta"}}}],
17
+ ["https://json-schema.org/draft/2020-12/meta/unevaluated", {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/unevaluated","$dynamicAnchor":"meta","title":"Unevaluated applicator vocabulary meta-schema","type":["object","boolean"],"properties":{"unevaluatedItems":{"$dynamicRef":"#meta"},"unevaluatedProperties":{"$dynamicRef":"#meta"}}}],
18
+ ["http://json-schema.org/draft-07/schema#", {"$schema":"http://json-schema.org/draft-07/schema#","$id":"http://json-schema.org/draft-07/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"$comment":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":true,"readOnly":{"type":"boolean","default":false},"writeOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":true},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":true,"enum":{"type":"array","items":true,"minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"contentMediaType":{"type":"string"},"contentEncoding":{"type":"string"},"if":{"$ref":"#"},"then":{"$ref":"#"},"else":{"$ref":"#"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":true}],
19
+ ]);
20
+
21
+ module.exports = { METASCHEMAS };