ata-validator 1.61.2 → 1.62.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.
Files changed (52) hide show
  1. package/README.md +159 -37
  2. package/bin/ata.js +35 -2
  3. package/compat.js +15 -2
  4. package/compiled-standard.d.ts +6 -0
  5. package/compiled-standard.mjs +66 -0
  6. package/index.d.ts +7 -0
  7. package/index.js +10 -0
  8. package/lib/aot-build.js +27 -1
  9. package/lib/aot-impl.js +108 -17
  10. package/lib/buffer-gate.js +25 -14
  11. package/lib/bun-target.js +21 -5
  12. package/lib/clone-emit.js +115 -10
  13. package/lib/codegen-engine.js +94 -18
  14. package/lib/compiled-common.js +11 -4
  15. package/lib/compiled-standard.js +33 -0
  16. package/lib/compiled-verdict.js +2 -2
  17. package/lib/compiled.js +1 -1
  18. package/lib/data-positions.js +18 -0
  19. package/lib/defaults.js +101 -23
  20. package/lib/describe-schema.js +38 -5
  21. package/lib/diagnose.js +51 -29
  22. package/lib/enrich-error.js +23 -1
  23. package/lib/error-sort-rank.js +12 -0
  24. package/lib/formats-source.js +12 -1
  25. package/lib/formats.js +19 -19
  26. package/lib/interpreter-core.js +23 -4
  27. package/lib/js-compiler-errors.js +70 -72
  28. package/lib/js-compiler.js +212 -72
  29. package/lib/json-rejections.js +1 -1
  30. package/lib/migrate.js +24 -5
  31. package/lib/native-attach.browser.js +7 -0
  32. package/lib/native-attach.js +76 -0
  33. package/lib/native-positions.js +7 -0
  34. package/lib/plan-compiler.js +7 -1
  35. package/lib/regex-linear.js +88 -0
  36. package/lib/rejections.js +13 -3
  37. package/lib/render-compact.js +5 -5
  38. package/lib/render-pretty.js +23 -10
  39. package/lib/safe-regex.js +29 -4
  40. package/lib/scan-compiler.js +16 -4
  41. package/lib/schema-diff.js +103 -0
  42. package/lib/stream.js +293 -0
  43. package/lib/tier0.js +6 -2
  44. package/lib/ts-gen.js +25 -12
  45. package/lib/unique-items.js +11 -3
  46. package/lib/validator-core.js +61 -66
  47. package/lib/verify.js +25 -6
  48. package/lib/version.js +1 -1
  49. package/package.json +24 -8
  50. package/stream.d.ts +51 -0
  51. package/stream.js +3 -0
  52. package/stream.mjs +3 -0
@@ -9,7 +9,7 @@ const { compileFunction } = require('./compile-fn')
9
9
  // the first call that compiles errors. Everything here was written as part
10
10
  // of js-compiler.js and reads its helpers from that module's _internal.
11
11
  const core = require('./js-compiler')
12
- const { ownHead, plainFlag, hasConstraints, assertEmitted, prepareForCodegen, sharedCodegenGate, codegenSafe, isSimplePN, hasUnresolvableRef, needsBaseTracking, _cpLen, sharedRt, DECLINE, bindRt, compileSafe, emitGuardState, withPlain, REF_NEUTRAL_SIBLINGS, resolveCrossSchemaRef, enumCondition, enumOperand, emitDeq, emitConstant, ownGuard, ownKeyExpr, multipleOfBad, compilePatternInline, useSafeEngine, reFlags, FORMAT_CODEGEN, PROTO_NAMES, setOwn, nestedGenCode, compileToJSCodegen, SKIP_VALUE_KEYS, skipString, CYCLE_DEPTH, cyclicDefNames, sharedDefNames, reFlagArg, hoistOnce, PTR_ESC_HELPER, unevalContributions, emitUq, uqSources, safeReClosure, fastPrefixCheck, replaceTopLevel, refTargetFor, genCode, addLocalAnnotations, addTopApplicatorAnnotations, protoIs, ANNOT_APPLICATORS, SIZE_SKIPS, emitRootGuard, emitGuardedRun, keyLoopEligible, SAME_KEYS_HELPER, seqTag } = core._internal
12
+ const { ownHead, plainFlag, hasConstraints, assertEmitted, prepareForCodegen, sharedCodegenGate, codegenSafe, isSimplePN, hasUnresolvableRef, needsBaseTracking, _cpLen, sharedRt, DECLINE, bindRt, compileSafe, emitGuardState, withPlain, REF_NEUTRAL_SIBLINGS, resolveCrossSchemaRef, enumCondition, enumOperand, emitDeq, emitConstant, ownGuard, ownKeyExpr, multipleOfBad, compilePatternInline, useSafeEngine, reFlags, FORMAT_CODEGEN, PROTO_NAMES, setOwn, nestedGenCode, compileToJSCodegen, SKIP_VALUE_KEYS, skipString, CYCLE_DEPTH, cyclicDefNames, sharedDefNames, reFlagArg, hoistOnce, PTR_ESC_HELPER, unevalContributions, emitUq, uqSources, safeReClosure, fastPrefixCheck, replaceTopLevel, refTargetFor, genCode, addLocalAnnotations, addTopApplicatorAnnotations, protoIs, ANNOT_APPLICATORS, SIZE_SKIPS, emitRootGuard, emitGuardedRun, keyLoopEligible, SAME_KEYS_HELPER, seqTag, numLit, userFormatFn, bindUserFormat, formatNameOf } = core._internal
13
13
 
14
14
 
15
15
  function emitPtrEsc(ctx) {
@@ -105,9 +105,15 @@ function buildErrorLiteral (opts) {
105
105
  if (hit) {
106
106
  // Support both {line,col,text} (raw output of buildPositionMap) and
107
107
  // [line,col,text] (compact form passed by callers that pre-flattened).
108
- const line = Array.isArray(hit) ? hit[0] : hit.line
109
- const col = Array.isArray(hit) ? hit[1] : hit.col
110
- const text = Array.isArray(hit) ? hit[2] : hit.text
108
+ // The row comes from the caller's `sourceMap` option and is written
109
+ // into source, so a line or column that is not an integer is written
110
+ // as 0 and the text only as a JSON string: a string line ran as code.
111
+ const rawLine = Array.isArray(hit) ? hit[0] : hit.line
112
+ const rawCol = Array.isArray(hit) ? hit[1] : hit.col
113
+ const rawText = Array.isArray(hit) ? hit[2] : hit.text
114
+ const line = Number.isInteger(rawLine) ? rawLine : 0
115
+ const col = Number.isInteger(rawCol) ? rawCol : 0
116
+ const text = typeof rawText === 'string' ? rawText : ''
111
117
  if (sourceMap.frames && text.length > FRAME_INLINE_MAX) {
112
118
  // A long source line is written once at module scope (see
113
119
  // sourceFrameDecls in aot-impl.js) and the literal names it. Inlined,
@@ -419,13 +425,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
419
425
  fn._sharedHelpers = ctx.shared ? ctx.shared.slice() : []
420
426
  fn._usesSafeRe = !!ctx.usesSafeRe
421
427
  if (cvars.length) {
422
- fn._formatClosures = cvars.map((name, i) => {
423
- let format = null
424
- for (const key of Object.keys(ctx.userFormats)) {
425
- if (ctx.userFormats[key] === cvals[i]) { format = key; break }
426
- }
427
- return { name, fn: cvals[i], format }
428
- })
428
+ fn._formatClosures = cvars.map((name, i) => ({ name, fn: cvals[i], format: formatNameOf(ctx, name) }))
429
429
  }
430
430
  return fn
431
431
  } catch (e) {
@@ -1183,26 +1183,26 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1183
1183
 
1184
1184
  // numeric
1185
1185
  if (schema.minimum !== undefined) {
1186
- const c = isNum ? `${v}<${schema.minimum}` : `typeof ${v}==='number'&&${v}<${schema.minimum}`
1187
- lines.push(`if(${c}){${fail('minimum', 'minimum', `{comparison:'>=',limit:${schema.minimum}}`, `'must be >= ${schema.minimum}'`)}}`)
1186
+ const c = isNum ? `${v}<${numLit(schema.minimum)}` : `typeof ${v}==='number'&&${v}<${numLit(schema.minimum)}`
1187
+ lines.push(`if(${c}){${fail('minimum', 'minimum', `{comparison:'>=',limit:${numLit(schema.minimum)}}`, `'must be >= ${numLit(schema.minimum)}'`)}}`)
1188
1188
  }
1189
1189
  if (schema.maximum !== undefined) {
1190
- const c = isNum ? `${v}>${schema.maximum}` : `typeof ${v}==='number'&&${v}>${schema.maximum}`
1191
- lines.push(`if(${c}){${fail('maximum', 'maximum', `{comparison:'<=',limit:${schema.maximum}}`, `'must be <= ${schema.maximum}'`)}}`)
1190
+ const c = isNum ? `${v}>${numLit(schema.maximum)}` : `typeof ${v}==='number'&&${v}>${numLit(schema.maximum)}`
1191
+ lines.push(`if(${c}){${fail('maximum', 'maximum', `{comparison:'<=',limit:${numLit(schema.maximum)}}`, `'must be <= ${numLit(schema.maximum)}'`)}}`)
1192
1192
  }
1193
1193
  if (schema.exclusiveMinimum !== undefined) {
1194
- const c = isNum ? `${v}<=${schema.exclusiveMinimum}` : `typeof ${v}==='number'&&${v}<=${schema.exclusiveMinimum}`
1195
- lines.push(`if(${c}){${fail('exclusiveMinimum', 'exclusiveMinimum', `{comparison:'>',limit:${schema.exclusiveMinimum}}`, `'must be > ${schema.exclusiveMinimum}'`)}}`)
1194
+ const c = isNum ? `${v}<=${numLit(schema.exclusiveMinimum)}` : `typeof ${v}==='number'&&${v}<=${numLit(schema.exclusiveMinimum)}`
1195
+ lines.push(`if(${c}){${fail('exclusiveMinimum', 'exclusiveMinimum', `{comparison:'>',limit:${numLit(schema.exclusiveMinimum)}}`, `'must be > ${numLit(schema.exclusiveMinimum)}'`)}}`)
1196
1196
  }
1197
1197
  if (schema.exclusiveMaximum !== undefined) {
1198
- const c = isNum ? `${v}>=${schema.exclusiveMaximum}` : `typeof ${v}==='number'&&${v}>=${schema.exclusiveMaximum}`
1199
- lines.push(`if(${c}){${fail('exclusiveMaximum', 'exclusiveMaximum', `{comparison:'<',limit:${schema.exclusiveMaximum}}`, `'must be < ${schema.exclusiveMaximum}'`)}}`)
1198
+ const c = isNum ? `${v}>=${numLit(schema.exclusiveMaximum)}` : `typeof ${v}==='number'&&${v}>=${numLit(schema.exclusiveMaximum)}`
1199
+ lines.push(`if(${c}){${fail('exclusiveMaximum', 'exclusiveMaximum', `{comparison:'<',limit:${numLit(schema.exclusiveMaximum)}}`, `'must be < ${numLit(schema.exclusiveMaximum)}'`)}}`)
1200
1200
  }
1201
1201
  if (schema.multipleOf !== undefined) {
1202
- const m = schema.multipleOf
1202
+ const m = numLit(schema.multipleOf)
1203
1203
  const ci = ctx.varCounter++
1204
1204
  // Use tolerance-based check for floating point (matches C++ behavior)
1205
- lines.push(`{if(typeof ${v}==='number'&&${multipleOfBad(v, m)}){${fail('multipleOf', 'multipleOf', `{multipleOf:${m}}`, `'must be multiple of ${m}'`)}}}`)
1205
+ lines.push(`{if(typeof ${v}==='number'&&${multipleOfBad(v, schema.multipleOf)}){${fail('multipleOf', 'multipleOf', `{multipleOf:${m}}`, `'must be multiple of ${m}'`)}}}`)
1206
1206
  }
1207
1207
 
1208
1208
  // string length — same s.length fast paths as the boolean codegen above.
@@ -1210,13 +1210,13 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1210
1210
  // 2*M <= length <= X passes without scanning. Only call _cpLen in the
1211
1211
  // uncertain band (caches it so it isn't called twice when both bounds are set).
1212
1212
  if (schema.minLength !== undefined) {
1213
- const M = schema.minLength
1213
+ const M = +numLit(schema.minLength)
1214
1214
  const inner = `${v}.length<${M}||(${v}.length<${M * 2}&&_cpLen(${v})<${M})`
1215
1215
  const c = isStr ? inner : `typeof ${v}==='string'&&(${inner})`
1216
1216
  lines.push(`if(${c}){${fail('minLength', 'minLength', `{limit:${M}}`, `'must NOT have fewer than ${M} characters'`)}}`)
1217
1217
  }
1218
1218
  if (schema.maxLength !== undefined) {
1219
- const X = schema.maxLength
1219
+ const X = +numLit(schema.maxLength)
1220
1220
  const inner = `${v}.length>${X * 2}||(${v}.length>${X}&&_cpLen(${v})>${X})`
1221
1221
  const c = isStr ? inner : `typeof ${v}==='string'&&(${inner})`
1222
1222
  lines.push(`if(${c}){${fail('maxLength', 'maxLength', `{limit:${X}}`, `'must NOT have more than ${X} characters'`)}}`)
@@ -1243,8 +1243,11 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1243
1243
  lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
1244
1244
  }
1245
1245
  }
1246
- if (schema.format) {
1247
- const fc = FORMAT_CODEGEN[schema.format]
1246
+ // The caller's function for the format wins over the built-in check, as
1247
+ // the interpreted engine reads the option; a format named '' is a name.
1248
+ if (typeof schema.format === 'string') {
1249
+ const uf = userFormatFn(ctx.userFormats, schema.format)
1250
+ const fc = uf === null && Object.hasOwn(FORMAT_CODEGEN, schema.format) ? FORMAT_CODEGEN[schema.format] : null
1248
1251
  const fmtSp = `${schemaPrefix}/format`
1249
1252
  const fmtLit = buildErrorLiteral({ keyword: 'format', format: schema.format, schemaPath: fmtSp, sourceMap: ctx.sourceMap })
1250
1253
  const failPush = `_e.push({code:'${fmtLit.codeStr}',keyword:'format',instancePath:${pathExpr||'""'},schemaPath:'${fmtSp}'${ordinalField(ctx, `${fmtSp}`)},params:{format:'${esc(schema.format)}'},message:'must match format "${esc(schema.format)}"',docUrl:'${fmtLit.docUrl}'${fmtLit.frame}});if(!_all)return{valid:false,errors:_e}`
@@ -1256,17 +1259,12 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1256
1259
  const ri = ctx.varCounter++
1257
1260
  const fmtCode = fc(v, isStr, ctx).replace(/return false/g, `{${failPush};break _fmt${ri}}`)
1258
1261
  lines.push(`_fmt${ri}:{${fmtCode}}`)
1259
- } else if (ctx.userFormats && typeof ctx.userFormats[schema.format] === 'function') {
1262
+ } else if (uf !== null) {
1260
1263
  // User-supplied format checker on the error path: the same closure
1261
1264
  // plumbing as the boolean codegen. The function is bound as a parameter
1262
1265
  // of the compiled error function; bundle output serializes it via
1263
1266
  // Function#toString separately.
1264
- const safeName = schema.format.replace(/[^a-zA-Z0-9_]/g, '_')
1265
- const closureName = `_uf_${safeName}`
1266
- if (ctx.closureVars && !ctx.closureVars.includes(closureName)) {
1267
- ctx.closureVars.push(closureName)
1268
- ctx.closureVals.push(ctx.userFormats[schema.format])
1269
- }
1267
+ const closureName = bindUserFormat(ctx, schema.format, uf)
1270
1268
  const guard = isStr ? '' : `typeof ${v}==='string'&&`
1271
1269
  lines.push(`if(${guard}!${closureName}(${v})){${failPush}}`)
1272
1270
  }
@@ -1274,12 +1272,12 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1274
1272
 
1275
1273
  // array size
1276
1274
  if (schema.minItems !== undefined) {
1277
- const c = isArr ? `${v}.length<${schema.minItems}` : `Array.isArray(${v})&&${v}.length<${schema.minItems}`
1278
- lines.push(`if(${c}){${fail('minItems', 'minItems', `{limit:${schema.minItems}}`, `'must NOT have fewer than ${schema.minItems} items'`)}}`)
1275
+ const c = isArr ? `${v}.length<${numLit(schema.minItems)}` : `Array.isArray(${v})&&${v}.length<${numLit(schema.minItems)}`
1276
+ lines.push(`if(${c}){${fail('minItems', 'minItems', `{limit:${numLit(schema.minItems)}}`, `'must NOT have fewer than ${numLit(schema.minItems)} items'`)}}`)
1279
1277
  }
1280
1278
  if (schema.maxItems !== undefined) {
1281
- const c = isArr ? `${v}.length>${schema.maxItems}` : `Array.isArray(${v})&&${v}.length>${schema.maxItems}`
1282
- lines.push(`if(${c}){${fail('maxItems', 'maxItems', `{limit:${schema.maxItems}}`, `'must NOT have more than ${schema.maxItems} items'`)}}`)
1279
+ const c = isArr ? `${v}.length>${numLit(schema.maxItems)}` : `Array.isArray(${v})&&${v}.length>${numLit(schema.maxItems)}`
1280
+ lines.push(`if(${c}){${fail('maxItems', 'maxItems', `{limit:${numLit(schema.maxItems)}}`, `'must NOT have more than ${numLit(schema.maxItems)} items'`)}}`)
1283
1281
  }
1284
1282
 
1285
1283
  // uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
@@ -1287,10 +1285,10 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1287
1285
 
1288
1286
  // object size
1289
1287
  if (schema.minProperties !== undefined) {
1290
- 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'`)}}`)
1288
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length<${numLit(schema.minProperties)}){${fail('minProperties', 'minProperties', `{limit:${numLit(schema.minProperties)}}`, `'must NOT have fewer than ${numLit(schema.minProperties)} properties'`)}}`)
1291
1289
  }
1292
1290
  if (schema.maxProperties !== undefined) {
1293
- 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'`)}}`)
1291
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length>${numLit(schema.maxProperties)}){${fail('maxProperties', 'maxProperties', `{limit:${numLit(schema.maxProperties)}}`, `'must NOT have more than ${numLit(schema.maxProperties)} properties'`)}}`)
1294
1292
  }
1295
1293
 
1296
1294
  // additionalProperties: false. A key matched by any patternProperties
@@ -1608,10 +1606,10 @@ function genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
1608
1606
  return
1609
1607
  }
1610
1608
  if (pn.minLength !== undefined) {
1611
- lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
1609
+ lines.push(`if(_k${ki}.length<${numLit(pn.minLength)}){${fail('minLength', 'propertyNames/minLength', `{limit:${numLit(pn.minLength)}}`, `'must NOT have fewer than ${numLit(pn.minLength)} characters'`)}}`)
1612
1610
  }
1613
1611
  if (pn.maxLength !== undefined) {
1614
- lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
1612
+ lines.push(`if(_k${ki}.length>${numLit(pn.maxLength)}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${numLit(pn.maxLength)}}`, `'must NOT have more than ${numLit(pn.maxLength)} characters'`)}}`)
1615
1613
  }
1616
1614
  if (pn.pattern) {
1617
1615
  const pattern = JSON.stringify(pn.pattern);
@@ -1653,8 +1651,8 @@ function genContainsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
1653
1651
  const subLines = []
1654
1652
  nestedGenCode(schema.contains, `_cv`, subLines, ctx)
1655
1653
  const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
1656
- const minC = schema.minContains !== undefined ? schema.minContains : 1
1657
- const maxC = schema.maxContains
1654
+ const minC = schema.minContains !== undefined ? numLit(schema.minContains) : 1
1655
+ const maxC = schema.maxContains !== undefined ? numLit(schema.maxContains) : undefined
1658
1656
  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}++}`)
1659
1657
  lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'must contain at least ${minC} valid item(s)'`)}}`)
1660
1658
  if (maxC !== undefined) {
@@ -2494,25 +2492,25 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
2494
2492
  }
2495
2493
 
2496
2494
  // numeric — skip type guard if known
2497
- 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}'`)}}`) }
2498
- 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}'`)}}`) }
2499
- 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}'`)}}`) }
2500
- 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}'`)}}`) }
2495
+ if (schema.minimum !== undefined) { const c = isNum ? `${v}<${numLit(schema.minimum)}` : `typeof ${v}==='number'&&${v}<${numLit(schema.minimum)}`; lines.push(`if(${c}){${fail('minimum', 'minimum', `{comparison:'>=',limit:${numLit(schema.minimum)}}`, `'must be >= ${numLit(schema.minimum)}'`)}}`) }
2496
+ if (schema.maximum !== undefined) { const c = isNum ? `${v}>${numLit(schema.maximum)}` : `typeof ${v}==='number'&&${v}>${numLit(schema.maximum)}`; lines.push(`if(${c}){${fail('maximum', 'maximum', `{comparison:'<=',limit:${numLit(schema.maximum)}}`, `'must be <= ${numLit(schema.maximum)}'`)}}`) }
2497
+ if (schema.exclusiveMinimum !== undefined) { const c = isNum ? `${v}<=${numLit(schema.exclusiveMinimum)}` : `typeof ${v}==='number'&&${v}<=${numLit(schema.exclusiveMinimum)}`; lines.push(`if(${c}){${fail('exclusiveMinimum', 'exclusiveMinimum', `{comparison:'>',limit:${numLit(schema.exclusiveMinimum)}}`, `'must be > ${numLit(schema.exclusiveMinimum)}'`)}}`) }
2498
+ if (schema.exclusiveMaximum !== undefined) { const c = isNum ? `${v}>=${numLit(schema.exclusiveMaximum)}` : `typeof ${v}==='number'&&${v}>=${numLit(schema.exclusiveMaximum)}`; lines.push(`if(${c}){${fail('exclusiveMaximum', 'exclusiveMaximum', `{comparison:'<',limit:${numLit(schema.exclusiveMaximum)}}`, `'must be < ${numLit(schema.exclusiveMaximum)}'`)}}`) }
2501
2499
  if (schema.multipleOf !== undefined) {
2502
- const m = schema.multipleOf
2500
+ const m = numLit(schema.multipleOf)
2503
2501
  const ci = ctx.varCounter++
2504
- lines.push(`{if(typeof ${v}==='number'&&${multipleOfBad(v, m)}){${fail('multipleOf', 'multipleOf', `{multipleOf:${m}}`, `'must be multiple of ${m}'`)}}}`)
2502
+ lines.push(`{if(typeof ${v}==='number'&&${multipleOfBad(v, schema.multipleOf)}){${fail('multipleOf', 'multipleOf', `{multipleOf:${m}}`, `'must be multiple of ${m}'`)}}}`)
2505
2503
  }
2506
2504
 
2507
2505
  // string length — s.length fast paths, _cpLen only in the uncertain band.
2508
2506
  if (schema.minLength !== undefined) {
2509
- const M = schema.minLength
2507
+ const M = +numLit(schema.minLength)
2510
2508
  const inner = `${v}.length<${M}||(${v}.length<${M * 2}&&_cpLen(${v})<${M})`
2511
2509
  const c = isStr ? inner : `typeof ${v}==='string'&&(${inner})`
2512
2510
  lines.push(`if(${c}){${fail('minLength', 'minLength', `{limit:${M}}`, `'must NOT have fewer than ${M} characters'`)}}`)
2513
2511
  }
2514
2512
  if (schema.maxLength !== undefined) {
2515
- const X = schema.maxLength
2513
+ const X = +numLit(schema.maxLength)
2516
2514
  const inner = `${v}.length>${X * 2}||(${v}.length>${X}&&_cpLen(${v})>${X})`
2517
2515
  const c = isStr ? inner : `typeof ${v}==='string'&&(${inner})`
2518
2516
  lines.push(`if(${c}){${fail('maxLength', 'maxLength', `{limit:${X}}`, `'must NOT have more than ${X} characters'`)}}`)
@@ -2531,36 +2529,32 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
2531
2529
  lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
2532
2530
  }
2533
2531
  }
2534
- if (schema.format) {
2535
- const fc = FORMAT_CODEGEN[schema.format]
2532
+ if (typeof schema.format === 'string') {
2533
+ const uf = userFormatFn(ctx.userFormats, schema.format)
2534
+ const fc = uf === null && Object.hasOwn(FORMAT_CODEGEN, schema.format) ? FORMAT_CODEGEN[schema.format] : null
2536
2535
  if (fc) {
2537
2536
  // Same rule as the error codegen: one violation, one error. The first
2538
2537
  // failing statement pushes and leaves the labeled block.
2539
2538
  const ri = ctx.varCounter++
2540
2539
  const code = fc(v, isStr, ctx).replace(/return false/g, `{${fail('format', 'format', `{format:'${esc(schema.format)}'}`, `'must match format "${esc(schema.format)}"'`)};break _fmt${ri}}`)
2541
2540
  lines.push(`_fmt${ri}:{${code}}`)
2542
- } else if (ctx.userFormats && typeof ctx.userFormats[schema.format] === 'function') {
2543
- const safeName = schema.format.replace(/[^a-zA-Z0-9_]/g, '_')
2544
- const closureName = `_uf_${safeName}`
2545
- if (!ctx.closureVars.includes(closureName)) {
2546
- ctx.closureVars.push(closureName)
2547
- ctx.closureVals.push(ctx.userFormats[schema.format])
2548
- }
2541
+ } else if (uf !== null) {
2542
+ const closureName = bindUserFormat(ctx, schema.format, uf)
2549
2543
  const guard = isStr ? '' : `typeof ${v}==='string'&&`
2550
2544
  lines.push(`if(${guard}!${closureName}(${v})){${fail('format', 'format', `{format:'${esc(schema.format)}'}`, `'must match format "${esc(schema.format)}"'`)}}`)
2551
2545
  }
2552
2546
  }
2553
2547
 
2554
2548
  // array size
2555
- 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'`)}}`) }
2556
- 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'`)}}`) }
2549
+ if (schema.minItems !== undefined) { const c = isArr ? `${v}.length<${numLit(schema.minItems)}` : `Array.isArray(${v})&&${v}.length<${numLit(schema.minItems)}`; lines.push(`if(${c}){${fail('minItems', 'minItems', `{limit:${numLit(schema.minItems)}}`, `'must NOT have fewer than ${numLit(schema.minItems)} items'`)}}`) }
2550
+ if (schema.maxItems !== undefined) { const c = isArr ? `${v}.length>${numLit(schema.maxItems)}` : `Array.isArray(${v})&&${v}.length>${numLit(schema.maxItems)}`; lines.push(`if(${c}){${fail('maxItems', 'maxItems', `{limit:${numLit(schema.maxItems)}}`, `'must NOT have more than ${numLit(schema.maxItems)} items'`)}}`) }
2557
2551
 
2558
2552
  // uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
2559
2553
  if (schema.uniqueItems) genUniqueItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr)
2560
2554
 
2561
2555
  // object size
2562
- 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'`)}}`)
2563
- 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'`)}}`)
2556
+ if (schema.minProperties !== undefined) lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length<${numLit(schema.minProperties)}){${fail('minProperties', 'minProperties', `{limit:${numLit(schema.minProperties)}}`, `'must NOT have fewer than ${numLit(schema.minProperties)} properties'`)}}`)
2557
+ if (schema.maxProperties !== undefined) lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length>${numLit(schema.maxProperties)}){${fail('maxProperties', 'maxProperties', `{limit:${numLit(schema.maxProperties)}}`, `'must NOT have more than ${numLit(schema.maxProperties)} properties'`)}}`)
2564
2558
 
2565
2559
  // additionalProperties — skip if patternProperties present (handled in unified loop below)
2566
2560
  // Small property sets: direct === chain (no Set allocation)
@@ -2846,7 +2840,11 @@ function annotNames (node, ctx, names, seen, top) {
2846
2840
  for (const k of ANNOT_BLOCKERS) if (node[k] !== undefined) return false
2847
2841
  if (!top && node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== true) return false
2848
2842
  const add = (key) => { if (!names.has(key)) { if (names.size >= ANNOT_NAME_MAX) return false; names.set(key, names.size) } return true }
2849
- if (node.properties && typeof node.properties === 'object') for (const k of Object.keys(node.properties)) if (!add(k)) return false
2843
+ // One table holds the declared names, the patterns as '/' + pattern
2844
+ // and the all bit: a declared name that starts with '/' or equals the
2845
+ // all bit's key would be read as one of those (a property named '/x'
2846
+ // let 'axb' through as if it matched /x/), so the inline form declines.
2847
+ if (node.properties && typeof node.properties === 'object') for (const k of Object.keys(node.properties)) if (k[0] === '/' || k === ALL_BIT_KEY || !add(k)) return false
2850
2848
  if (node.patternProperties && typeof node.patternProperties === 'object') for (const pat of Object.keys(node.patternProperties)) if (!add('/' + pat)) return false
2851
2849
  if ((!top && node.additionalProperties !== undefined) || (!top && node.unevaluatedProperties === true)) if (!add(ALL_BIT_KEY)) return false
2852
2850
  let subs
@@ -3475,8 +3473,8 @@ function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fa
3475
3473
  lines.push(`${guard}{${ownHead(ctx, v, kVar, 'const')}{`)
3476
3474
  // propertyNames checks (merged)
3477
3475
  if (pn) {
3478
- 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'`)}}`)
3479
- 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'`)}}`)
3476
+ if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${numLit(pn.minLength)}){${fail('minLength', 'propertyNames/minLength', `{limit:${numLit(pn.minLength)}}`, `'must NOT have fewer than ${numLit(pn.minLength)} characters'`)}}`)
3477
+ if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${numLit(pn.maxLength)}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${numLit(pn.maxLength)}}`, `'must NOT have more than ${numLit(pn.maxLength)} characters'`)}}`)
3480
3478
  if (pn.pattern) {
3481
3479
  const fast = fastPrefixCheck(pn.pattern, kVar)
3482
3480
  if (fast) {
@@ -3511,8 +3509,8 @@ function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fa
3511
3509
  handledPropertyNames = !!pn
3512
3510
  lines.push(`${guard}{${ownHead(ctx, v, kVar, 'const')}{`)
3513
3511
  if (pn) {
3514
- 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'`)}}`)
3515
- 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'`)}}`)
3512
+ if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${numLit(pn.minLength)}){${fail('minLength', 'propertyNames/minLength', `{limit:${numLit(pn.minLength)}}`, `'must NOT have fewer than ${numLit(pn.minLength)} characters'`)}}`)
3513
+ if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${numLit(pn.maxLength)}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${numLit(pn.maxLength)}}`, `'must NOT have more than ${numLit(pn.maxLength)} characters'`)}}`)
3516
3514
  if (pn.pattern) {
3517
3515
  const fast = fastPrefixCheck(pn.pattern, kVar)
3518
3516
  if (fast) {
@@ -3570,10 +3568,10 @@ function genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
3570
3568
  return
3571
3569
  }
3572
3570
  if (pn.minLength !== undefined) {
3573
- lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
3571
+ lines.push(`if(_k${ki}.length<${numLit(pn.minLength)}){${fail('minLength', 'propertyNames/minLength', `{limit:${numLit(pn.minLength)}}`, `'must NOT have fewer than ${numLit(pn.minLength)} characters'`)}}`)
3574
3572
  }
3575
3573
  if (pn.maxLength !== undefined) {
3576
- lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
3574
+ lines.push(`if(_k${ki}.length>${numLit(pn.maxLength)}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${numLit(pn.maxLength)}}`, `'must NOT have more than ${numLit(pn.maxLength)} characters'`)}}`)
3577
3575
  }
3578
3576
  if (pn.pattern) {
3579
3577
  const ri = ctx.varCounter++
@@ -3607,8 +3605,8 @@ function genContainsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
3607
3605
  const subLines = []
3608
3606
  nestedGenCode(schema.contains, `_cv`, subLines, ctx)
3609
3607
  const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
3610
- const minC = schema.minContains !== undefined ? schema.minContains : 1
3611
- const maxC = schema.maxContains
3608
+ const minC = schema.minContains !== undefined ? numLit(schema.minContains) : 1
3609
+ const maxC = schema.maxContains !== undefined ? numLit(schema.maxContains) : undefined
3612
3610
  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}++}`)
3613
3611
  lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'must contain at least ${minC} valid item(s)'`)}}`)
3614
3612
  if (maxC !== undefined) lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{minContains:${minC},maxContains:${maxC}}`, `'must NOT contain more than ${maxC} valid item(s)'`)}}`)