ata-validator 1.59.0 → 1.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/compiled.js CHANGED
@@ -17,6 +17,7 @@
17
17
 
18
18
  const { LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy } = require('./rejections');
19
19
  const { COMPILED_OPTIONS, prepare, parse, installExtension, extendJSON, EXT_FALLBACK } = require('./compiled-common');
20
+ const { installStandard } = require('./standard-schema');
20
21
 
21
22
  const VALID_RESULT = Object.freeze({ valid: true, errors: Object.freeze([]) });
22
23
 
@@ -99,6 +100,11 @@ function fromCompiled(mod, schema, options) {
99
100
  isValidJSON: json.isValidJSON,
100
101
  };
101
102
  });
103
+ // Standard Schema V1, as the runtime Validator hands it out, so a validator
104
+ // the build compiled away still works where the runtime one did (a form
105
+ // library, a router, an RPC layer). The schema it publishes is the one the
106
+ // module was built from, as the runtime reads it.
107
+ installStandard(api, () => schema, (self, value) => self.validate(value));
102
108
  return api;
103
109
  }
104
110
 
package/lib/formats.js CHANGED
@@ -538,4 +538,4 @@ function email(s) {
538
538
  const DURATION_RE = /^P(?:\d+W|(?:\d+Y(?:\d+M(?:\d+D)?)?|\d+M(?:\d+D)?|\d+D)(?:T(?:\d+H(?:\d+M(?:\d+S)?)?|\d+M(?:\d+S)?|\d+S))?|T(?:\d+H(?:\d+M(?:\d+S)?)?|\d+M(?:\d+S)?|\d+S))$/;
539
539
  function duration (s) { return DURATION_RE.test(s); }
540
540
 
541
- module.exports = { IPV4, IPV6_FULL, ipv4Range, URI_FAST, date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uriWalk: _uriWalk, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, iri, iriReference, idnEmail, DURATION_RE, URI_TEMPLATE_EXPR };
541
+ module.exports = { IPV4, DURATION_RE, IPV6_FULL, ipv4Range, URI_FAST, date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uriWalk: _uriWalk, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, iri, iriReference, idnEmail, DURATION_RE, URI_TEMPLATE_EXPR };
@@ -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, 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 } = core._internal
13
13
 
14
14
 
15
15
  function emitPtrEsc(ctx) {
@@ -1017,7 +1017,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1017
1017
  const bodyLines = []
1018
1018
  const outerStack = ctx.refStack
1019
1019
  ctx.refStack = new Set()
1020
- try { genCodeE(ctx.rootE, 'd', '_p', bodyLines, ctx, SP_PLACEHOLDER) } finally { ctx.refStack = outerStack }
1020
+ try { isolatedE(ctx, () => genCodeE(ctx.rootE, 'd', '_p', bodyLines, ctx, SP_PLACEHOLDER)) } finally { ctx.refStack = outerStack }
1021
1021
  let body = bodyLines.join('\n ')
1022
1022
  body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
1023
1023
  if (body.includes(SP_PLACEHOLDER)) throw DECLINE
@@ -1056,7 +1056,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1056
1056
  // string it starts. One left anywhere else declines, rather than
1057
1057
  // emitting a path the placeholder stands in for.
1058
1058
  const bodyLines = []
1059
- genCodeE(ctx.rootDefs[defName], 'd', '_p', bodyLines, ctx, SP_PLACEHOLDER)
1059
+ isolatedE(ctx, () => genCodeE(ctx.rootDefs[defName], 'd', '_p', bodyLines, ctx, SP_PLACEHOLDER))
1060
1060
  let body = bodyLines.join('\n ')
1061
1061
  body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
1062
1062
  if (body.includes(SP_PLACEHOLDER)) throw DECLINE
@@ -1321,12 +1321,26 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1321
1321
  // This function walks every node of a document that failed anywhere, so on a
1322
1322
  // large array the clean items were each paying for a key array.
1323
1323
  const gateChecks = patChecks.map((c) => c.split(`_k${ci}[_i]`).join(`_f${ci}`))
1324
- const gateTest = gateChecks.length ? `!_a${ci}.has(_f${ci})&&!(${gateChecks.join('||')})` : `!_a${ci}.has(_f${ci})`
1324
+ // The gate names the declared keys outright when there are few of them: a
1325
+ // Set lookup per key of every object of a document that failed somewhere
1326
+ // was a third of the error path on a ten-item order (31% of the samples
1327
+ // on that one line); three string compares are not.
1328
+ const declaredNames = Object.keys(schema.properties || {})
1329
+ const notDeclared = declaredNames.length > 0 && declaredNames.length <= 8
1330
+ ? declaredNames.map((k) => `_f${ci}!==${JSON.stringify(k)}`).join('&&')
1331
+ : `!_a${ci}.has(_f${ci})`
1332
+ const gateTest = gateChecks.length ? `${notDeclared}&&!(${gateChecks.join('||')})` : notDeclared
1325
1333
  const detail = `const _k${ci}=Object.keys(${v});for(let _i=0;_i<_k${ci}.length;_i++){if(${isAdditional}){_e.push({code:'${apLit.codeStr}',keyword:'additionalProperties',instancePath:${pathExpr||'""'},schemaPath:'${apSp}'${ordinalField(ctx, `${apSp}`)},params:{additionalProperty:_k${ci}[_i]},message:'must NOT have additional properties',docUrl:'${apLit.docUrl}'${apLit.frame}});if(!_all)return{valid:false,errors:_e}}}`
1326
1334
  // The name set is built once per call of the error function, not at every
1327
1335
  // object it checks: on a list of 1723 tokens it was 1723 Sets.
1328
1336
  ctx.helperCode.push(`const _a${ci}=new Set([${allowed}])`)
1329
- const inner = `let _x${ci}=0;${ownHead(ctx, v, `_f${ci}`, 'const')}{if(${gateTest}){_x${ci}=1;break}}if(_x${ci}){${detail}}`
1337
+ // The walk reads the node's plain-object flag where one is in scope
1338
+ // (withPlain declared it for this value) instead of reading the prototype
1339
+ // again for every object: that read was a tenth of the error path.
1340
+ const stdHead = ownHead(ctx, v, `_f${ci}`, 'const')
1341
+ const pk = plainFlag(ctx, v)
1342
+ const head = pk ? `for(const _f${ci} in ${v})if(!${pk}&&!_hop.call(${v},_f${ci}))continue;else ` : stdHead
1343
+ const inner = `let _x${ci}=0;${head}{if(${gateTest}){_x${ci}=1;break}}if(_x${ci}){${detail}}`
1330
1344
  lines.push(isObj ? `{${inner}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
1331
1345
  }
1332
1346
 
@@ -1368,12 +1382,18 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1368
1382
  const sp = schemaPrefix+'/properties/'+ptrSeg(key)
1369
1383
  const from = lines.length
1370
1384
  // A value that is undefined is not checked (see the verdict generator).
1371
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${v}[${JSON.stringify(key)}]!==undefined&&${ownKeyExpr(ctx, v, key)}){`)
1385
+ // The value is read once into a local and the subschema checks that:
1386
+ // read as `${v}[key]` at every keyword below, a nested property was
1387
+ // loaded again for each check (a getter may answer differently, so V8
1388
+ // cannot merge the loads), and a rejected ten-item order cost 378 ns on
1389
+ // this collector against 105 on the one-pass function.
1390
+ const child = `_c${ctx.varCounter++}`
1391
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){const ${child}=${v}[${JSON.stringify(key)}];if(${child}!==undefined&&${ownKeyExpr(ctx, v, key)}){`)
1372
1392
  // A large subschema as a function of its own (emitPropFnE); false means inline.
1373
- if (!propFnCandidateE(prop, ctx, sp) || !emitPropFnE(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, sp)) {
1374
- genCodeE(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, sp)
1393
+ if (!propFnCandidateE(prop, ctx, sp) || !emitPropFnE(prop, child, childPath, lines, ctx, sp)) {
1394
+ genCodeE(prop, child, childPath, lines, ctx, sp)
1375
1395
  }
1376
- lines.push(`}`)
1396
+ lines.push(`}}`)
1377
1397
  countInlineE(lines, from, ctx)
1378
1398
  }
1379
1399
  }
@@ -2080,11 +2100,26 @@ function propFnCandidateE (prop, ctx, schemaPrefix) {
2080
2100
  try { size = JSON.stringify(prop).length } catch { return false }
2081
2101
  return size >= SPLIT_C_SCHEMA || ((ctx.fnBytesE || 0) > SPLIT_C_BUDGET && size >= SPLIT_C_SMALL)
2082
2102
  }
2103
+ // A body that becomes a function of its own (a hoisted property, a
2104
+ // definition, the root through `$ref: "#"`) takes the parameter name `d` as
2105
+ // the node being written does; the ordinary-object flags that node declared
2106
+ // for its own `d` (withPlain) are out of scope in the helper and must not be
2107
+ // read there, as js-compiler's genIsolated keeps them out of the verdict's
2108
+ // helpers. Found by the SchemaStore differential: 24 schemas threw
2109
+ // `ReferenceError: _pk0 is not defined` from validate().
2110
+ function isolatedE (ctx, run) {
2111
+ const outerPlain = ctx.plainOf
2112
+ const outerReuse = ctx.plainReuse
2113
+ ctx.plainOf = undefined
2114
+ ctx.plainReuse = undefined
2115
+ try { return run() } finally { ctx.plainOf = outerPlain; ctx.plainReuse = outerReuse }
2116
+ }
2117
+
2083
2118
  function emitPropFnE (prop, v, pathExpr, lines, ctx, schemaPrefix) {
2084
2119
  const outerBytes = ctx.fnBytesE
2085
2120
  ctx.fnBytesE = 0
2086
2121
  const bodyLines = []
2087
- try { genCodeE(prop, 'd', '_p', bodyLines, ctx, schemaPrefix) } finally { ctx.fnBytesE = outerBytes }
2122
+ try { isolatedE(ctx, () => genCodeE(prop, 'd', '_p', bodyLines, ctx, schemaPrefix)) } finally { ctx.fnBytesE = outerBytes }
2088
2123
  if (bodyLines.length === 0) return false
2089
2124
  const body = bodyLines.join('\n ')
2090
2125
  if (body.includes(SP_PLACEHOLDER)) return false
@@ -105,8 +105,8 @@ function apMembershipCheck (ctx, names, v) {
105
105
  }
106
106
  }
107
107
  // With nothing declared every key is additional; `if()` would not parse.
108
- if (names.length === 0) return `for(var _k in ${v})if(${ownFail(ctx, v)})return false`
109
- return `for(var _k in ${v})if(${names.map((k) => `_k!==${JSON.stringify(k)}`).join('&&')}&&${ownFail(ctx, v)})return false`
108
+ if (names.length === 0) return closedKeyLoop(ctx, v, `if(${ownFail(ctx, v)})return false`)
109
+ return closedKeyLoop(ctx, v, `if(${names.map((k) => `_k!==${JSON.stringify(k)}`).join('&&')}&&${ownFail(ctx, v)})return false`)
110
110
  }
111
111
 
112
112
  function emitDeq(ctx) {
@@ -946,12 +946,26 @@ const PROTO_NAMES = new Set([...Object.getOwnPropertyNames(Object.prototype), '_
946
946
  // every module that asked about presence at all, about a sixth of a small
947
947
  // schema's compiled module.
948
948
  const OWN_HELPER_CODE = {
949
- _PN: 'const _PN=({}).__proto__===Object.prototype;const _OP=Object.prototype;const _gp=Object.getPrototypeOf;',
949
+ // Whether the plain-object test reads `__proto__` (V8: the accessor is
950
+ // the cheaper read) or Object.getPrototypeOf (JavaScriptCore, and Bun's
951
+ // ahead-of-time compiler, where the accessor is a slow path: 212 against
952
+ // 127 ns on a ten-item order). Decided once, where the module loads.
953
+ // The plain-object test reads `__proto__` on V8, where the accessor is the
954
+ // cheaper read, and Object.getPrototypeOf on JavaScriptCore, where the
955
+ // accessor is a slow path under Bun's compiler (212 against 127 ns on a
956
+ // ten-item order). Decided per target engine when the code is generated;
957
+ // the V8 form still tests the accessor at load, since Deno removes it.
958
+ get _PN () { return TARGET_ENGINE !== 'v8' ? 'const _PN=false;const _OP=Object.prototype;const _gp=Object.getPrototypeOf;' : 'const _PN=({}).__proto__===Object.prototype;const _OP=Object.prototype;const _gp=Object.getPrototypeOf;' },
950
959
  _hop: 'const _hop=Object.prototype.hasOwnProperty;',
951
960
  _h: 'function _h(o,k){return _hop.call(o,k)}',
952
961
  _ok: 'function _ok(o,k){return (_PN?o.__proto__:_gp(o))===_OP||_hop.call(o,k)}',
953
962
  _hall: 'function _hall(o,ks){for(let i=0;i<ks.length;i++)if(!_hop.call(o,ks[i]))return false;return true}',
954
963
  _nk: 'function _nk(o,n){return (_PN?o.__proto__:_gp(o))===_OP||Object.keys(o).length!==n}',
964
+ // The own-key count of a closed object, and the walk over its keys, take
965
+ // the form of the engine the code is generated for (targetEngine below):
966
+ // for...in on V8, where it is the cheaper form, Object.keys on
967
+ // JavaScriptCore, where for...in is a slow path. One form is written, so
968
+ // neither engine pays for a branch per object.
955
969
  }
956
970
  const OWN_HELPER_DEPS = { _h: ['_hop'], _ok: ['_PN', '_hop'], _hall: ['_hop'], _nk: ['_PN'] }
957
971
  function useOwnHelper (ctx, name) {
@@ -1032,7 +1046,7 @@ function withPlain (schema, v, lines, ctx, run) {
1032
1046
  }
1033
1047
  // The same test at run time, for the closure compiler.
1034
1048
  const _hop = Object.prototype.hasOwnProperty
1035
- const _PN = ({}).__proto__ === Object.prototype
1049
+ const _PN = typeof Bun === 'undefined' && ({}).__proto__ === Object.prototype // JavaScriptCore reads the prototype through Object.getPrototypeOf, see the _PN declaration
1036
1050
  // One hasOwnProperty call. From 1.31.2 to 1.33.0 this first tested the name
1037
1051
  // against Object.prototype's names, then `in`, then read the prototype through
1038
1052
  // the __proto__ accessor, on every property of every object the eval-free
@@ -3644,7 +3658,9 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
3644
3658
  lines.push(`if(${reqChecks.join('||')})return false`)
3645
3659
  }
3646
3660
  // On an object with another prototype the typed ones may be inherited.
3647
- if (typed.length > 0) {
3661
+ // Where the node's key set is proved exactly (exactKeysNode), the typed
3662
+ // names are own by that proof and the list is not needed.
3663
+ if (typed.length > 0 && !exactKeysNode(schema, ctx)) {
3648
3664
  useOwnHelper(ctx, '_hall')
3649
3665
  // Read directly: the object type is already established here, and a
3650
3666
  // flag declared for this alone costs more than it saves. The list is
@@ -3677,7 +3693,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
3677
3693
  if (allRequired && propCount > 0) {
3678
3694
  // Adaptive: for-in for <=15 keys (V8 fast path), Object.keys for >15
3679
3695
  if (propCount <= 15) {
3680
- lines.push(`var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount}&&${ownCountOff(ctx, v, propCount, plainFlag(ctx, v))})return false`)
3696
+ lines.push(exactKeys(ctx, v, knownKeys))
3681
3697
  } else {
3682
3698
  lines.push(`if(Object.keys(${v}).length!==${propCount})return false`)
3683
3699
  }
@@ -3817,7 +3833,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
3817
3833
  const allRequired = !!schema.required && declaredKeys.every((k) => req.has(k))
3818
3834
  const inner = allRequired
3819
3835
  ? (propCount <= 15
3820
- ? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount}&&${ownCountOff(ctx, v, propCount, plainFlag(ctx, v))})return false`
3836
+ ? exactKeys(ctx, v, declaredKeys)
3821
3837
  : `if(Object.keys(${v}).length!==${propCount})return false`)
3822
3838
  : apMembershipCheck(ctx, Object.keys(schema.properties || {}), v)
3823
3839
  _deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
@@ -4833,7 +4849,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
4833
4849
  if (!earlyKeyCount) {
4834
4850
  // Adaptive: for-in for <=15 keys, Object.keys for >15
4835
4851
  inner = propCount <= 15
4836
- ? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount}&&${ownCountOff(ctx, v, propCount, plainFlag(ctx, v))})return false`
4852
+ ? exactKeys(ctx, v, knownKeys)
4837
4853
  : `if(Object.keys(${v}).length!==${propCount})return false`
4838
4854
  _deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
4839
4855
  }
@@ -5647,6 +5663,69 @@ function ownFail (ctx, v, k = '_k') {
5647
5663
  useOwnHelper(ctx, '_ok')
5648
5664
  return `_ok(${v},${k})`
5649
5665
  }
5666
+ // The engine the generated code is written for. The runtime compiles for the
5667
+ // engine it runs on; `ata build --target bun` and the plugin's Bun entry write
5668
+ // modules for JavaScriptCore (setTargetEngine). The two engines want opposite
5669
+ // forms for walking a closed object's keys: for...in is the cheaper form on
5670
+ // V8 and a slow path on JavaScriptCore (a ten-item order under Bun's
5671
+ // ahead-of-time compiler: 227 against 108 ns), and Object.keys the reverse. A
5672
+ // branch per object on a load-time flag cost V8 5 to 7% on small schemas, so
5673
+ // one form is written, chosen here.
5674
+ // 'jsc-aot' is JavaScriptCore under Bun's ahead-of-time compiler, where the
5675
+ // cheapest proof that a closed object holds exactly its declared keys is to
5676
+ // read Object.keys once and compare it with the declared list in order (the
5677
+ // order JSON.parse gives), falling back to a membership walk when the order
5678
+ // differs. On Bun's JIT that form lost 5 to 15 ns a ten-item order to the
5679
+ // plain count, and under the compiler it won 4 to 10, which brought the
5680
+ // module level with Bun's own validate<T>; so it is its own target, the one
5681
+ // `ata build --target bun` writes for.
5682
+ let TARGET_ENGINE = typeof Bun !== 'undefined' ? 'jsc' : 'v8'
5683
+ const ENGINES = ['v8', 'jsc', 'jsc-aot']
5684
+ function setTargetEngine (engine) {
5685
+ const prev = TARGET_ENGINE
5686
+ if (ENGINES.includes(engine)) TARGET_ENGINE = engine
5687
+ else if (engine === null || engine === undefined) TARGET_ENGINE = typeof Bun !== 'undefined' ? 'jsc' : 'v8'
5688
+ else throw new Error(`unknown target engine ${JSON.stringify(engine)}; expected "v8", "jsc" or "jsc-aot"`)
5689
+ return prev
5690
+ }
5691
+ function targetEngine () { return TARGET_ENGINE }
5692
+ // `var _n=<own key count of v>;`
5693
+ function keyCount (v) {
5694
+ return TARGET_ENGINE !== 'v8' ? `var _n=Object.keys(${v}).length;` : `var _n=0;for(var _k in ${v})_n++;`
5695
+ }
5696
+ // Whether this node's check under the compiler is the exact key list: a
5697
+ // closed object, no pattern properties, every declared key required, at
5698
+ // most fifteen of them, not one the removing verdict rewrites. The proof then
5699
+ // covers presence and ownership of the typed keys too.
5700
+ function exactKeysNode (schema, ctx) {
5701
+ if (TARGET_ENGINE !== 'jsc-aot') return false
5702
+ if (schema.additionalProperties !== false || schema.patternProperties || !schema.properties || !schema.required) return false
5703
+ if (ctx.removeNodes && ctx.removeNodes.has(schema)) return false
5704
+ const keys = Object.keys(schema.properties)
5705
+ if (keys.length === 0 || keys.length > 15) return false
5706
+ const req = new Set(schema.required)
5707
+ return keys.every((k) => req.has(k))
5708
+ }
5709
+ // The check that a closed object whose declared keys are all required holds
5710
+ // exactly those keys: the count, compared against the own-key count when it
5711
+ // is off (ownCountOff), or under the compiler the key list itself.
5712
+ function exactKeys (ctx, v, keys) {
5713
+ const n = keys.length
5714
+ // The plain-object flag is asked for only on the counting form: asking marks
5715
+ // it used, and the exact key list needs no prototype read at all.
5716
+ if (TARGET_ENGINE !== 'jsc-aot' || n === 0) return `${keyCount(v)}if(_n!==${n}&&${ownCountOff(ctx, v, n, plainFlag(ctx, v))})return false`
5717
+ const i = ctx.varCounter++
5718
+ const names = keys.map((k) => JSON.stringify(k))
5719
+ const inOrder = names.map((k, j) => `_ks${i}[${j}]===${k}`).join('&&')
5720
+ const member = names.map((k) => `_k!==${k}`).join('&&')
5721
+ return `{const _ks${i}=Object.keys(${v});if(_ks${i}.length!==${n})return false;if(!(${inOrder}))for(let _i=0;_i<${n};_i++){const _k=_ks${i}[_i];if(${member})return false}}`
5722
+ }
5723
+ // A loop over the keys of a closed object, `body` reading each as `_k`.
5724
+ function closedKeyLoop (ctx, v, body) {
5725
+ if (TARGET_ENGINE === 'v8') return `for(var _k in ${v}){${body}}`
5726
+ const i = ctx.varCounter++
5727
+ return `{var _ks${i}=Object.keys(${v});for(var _ki${i}=0;_ki${i}<_ks${i}.length;_ki${i}++){var _k=_ks${i}[_ki${i}];${body}}}`
5728
+ }
5650
5729
  // For a key count that came out wrong: whether it is wrong counting own keys
5651
5730
  // only. An ordinary object's count stands; any other object is counted again.
5652
5731
  // A oneOf of closed objects fails its other branches here on every value, so
@@ -5677,10 +5756,10 @@ function ownHead (ctx, v, k = '_k', kind = 'var') {
5677
5756
  // Generate a charCodeAt(0)-based switch tree for fast key validation.
5678
5757
  // V8 compiles switch to jump tables — O(1) dispatch vs O(n) chain.
5679
5758
  function genCharCodeSwitch(keys, v, ctx) {
5680
- if (keys.length === 0) return `for(var _k in ${v})if(${ownFail(ctx, v)})return false`
5759
+ if (keys.length === 0) return closedKeyLoop(ctx, v, `if(${ownFail(ctx, v)})return false`)
5681
5760
  if (keys.length <= 3) {
5682
5761
  // Small set: simple chain is faster than switch overhead
5683
- return `for(var _k in ${v})if(${keys.map(k => `_k!==${JSON.stringify(k)}`).join('&&')}&&${ownFail(ctx, v)})return false`
5762
+ return closedKeyLoop(ctx, v, `if(${keys.map(k => `_k!==${JSON.stringify(k)}`).join('&&')}&&${ownFail(ctx, v)})return false`)
5684
5763
  }
5685
5764
 
5686
5765
  // Group keys by first charCode
@@ -5697,7 +5776,7 @@ function genCharCodeSwitch(keys, v, ctx) {
5697
5776
  cases += `case ${cc}:if(${cond})continue;break;`
5698
5777
  }
5699
5778
 
5700
- return `for(var _k in ${v}){switch(_k.charCodeAt(0)){${cases}default:break}if(${ownFail(ctx, v)})return false}`
5779
+ return closedKeyLoop(ctx, v, `switch(_k.charCodeAt(0)){${cases}default:break}if(${ownFail(ctx, v)})return false`)
5701
5780
  }
5702
5781
 
5703
5782
  // The verdict form of genUnevaluatedDynamicC: the same set, built the same
@@ -6036,10 +6115,10 @@ function compileToJSCodegenWithErrors (schema, schemaMap, userFormats, sourceOpt
6036
6115
  function compileToJSCombined (schema, VALID_RESULT, schemaMap, userFormats, opts) { return errorsModule().compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts) }
6037
6116
 
6038
6117
 
6039
- module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, unevalContributions, expandedChars, AJV_MESSAGES, _setCodeBudget, _helperSources: { DEQ_HELPER, PTR_ESC_HELPER, OWN_HELPER_CODE } }
6118
+ module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, setTargetEngine, targetEngine, collectEvaluated, unevalContributions, expandedChars, AJV_MESSAGES, _setCodeBudget, _helperSources: { DEQ_HELPER, PTR_ESC_HELPER, OWN_HELPER_CODE } }
6040
6119
  // For tests: the dynamic-scope unrolling, to hold its output to the interpreter.
6041
6120
  Object.defineProperty(module.exports, '_expandDynamicScopes', { value: expandDynamicScopes, enumerable: false })
6042
6121
  // For tests/test_custom_keyword_codegen.js, which holds these copies to the interpreter's.
6043
6122
  Object.defineProperty(module.exports, '_kwTypeBits', { value: { kwTypeBit, kwDataBits, KW_T_ANY }, enumerable: false })
6044
6123
 
6045
- module.exports._internal = { ownHead, 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 }
6124
+ module.exports._internal = { 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 }
package/lib/public-api.js CHANGED
@@ -70,6 +70,7 @@ module.exports = {
70
70
  parseJSON: core.parseJSON,
71
71
  toTypeScript,
72
72
  defineSchema: core.defineSchema,
73
+ schemaOf: core.schemaOf,
73
74
  renderPretty,
74
75
  renderCompact,
75
76
  toOutput,
@@ -0,0 +1,145 @@
1
+ 'use strict';
2
+
3
+ // Standard Schema V1 for a validator: the `~standard` member a form library,
4
+ // a router or an RPC layer reads instead of ata's own API. One module, so the
5
+ // runtime Validator (lib/validator-core.js) and the wrapper around a module
6
+ // written ahead of time (lib/compiled.js) hand out the same object built the
7
+ // same way, and a validator the build compiled away still works where the
8
+ // runtime one did.
9
+
10
+ // Paths repeat across rejections of the same shape, so the parsed segment
11
+ // list is cached per path string. Entries are frozen: the same array is
12
+ // handed to every issue that names the path.
13
+ //
14
+ // The cache is bucketed by length and searched with ===, not keyed in a Map.
15
+ // A path with an array index in it is concatenated afresh by every rejection,
16
+ // and a Map has to hash each such string from scratch before it can look
17
+ // anything up; that hashing was 30% of a Standard Schema rejection with
18
+ // sixteen issues. Comparing against the few paths of the same length costs
19
+ // less. Both levels are bounded so a stream of array indexes cannot grow it
20
+ // without limit: a full bucket stops caching, too many lengths clear it.
21
+ const _pathBuckets = new Map();
22
+ const PATH_BUCKET_MAX = 32;
23
+ const PATH_LENGTHS_MAX = 256;
24
+ const EMPTY_PATH = Object.freeze([]);
25
+ function parsePointerPath(path) {
26
+ if (!path) return EMPTY_PATH;
27
+ const n = path.length;
28
+ let bucket = _pathBuckets.get(n);
29
+ if (bucket !== undefined) {
30
+ for (let i = 0; i < bucket.length; i += 2) if (bucket[i] === path) return bucket[i + 1];
31
+ } else {
32
+ if (_pathBuckets.size >= PATH_LENGTHS_MAX) _pathBuckets.clear();
33
+ bucket = [];
34
+ _pathBuckets.set(n, bucket);
35
+ }
36
+ const segs = Object.freeze(parsePointerPathUncached(path));
37
+ if (bucket.length < PATH_BUCKET_MAX * 2) bucket.push(path, segs);
38
+ return segs;
39
+ }
40
+
41
+ function parsePointerPathUncached(path) {
42
+ // One pass, no intermediate arrays. Per Standard Schema V1 an array index
43
+ // is emitted as a number and an object key as a string; a segment is an
44
+ // index when it is all digits with no leading zero.
45
+ const out = [];
46
+ const n = path.length;
47
+ let start = 1;
48
+ for (let i = 1; i <= n; i++) {
49
+ if (i !== n && path.charCodeAt(i) !== 47) continue;
50
+ if (i > start) {
51
+ let seg = path.slice(start, i);
52
+ if (seg.indexOf('~') >= 0) seg = seg.replace(/~1/g, '/').replace(/~0/g, '~');
53
+ const c0 = seg.charCodeAt(0);
54
+ let numeric = c0 >= 48 && c0 <= 57 && (seg.length === 1 || c0 !== 48);
55
+ if (numeric) {
56
+ for (let k = 1; k < seg.length; k++) {
57
+ const c = seg.charCodeAt(k);
58
+ if (c < 48 || c > 57) { numeric = false; break; }
59
+ }
60
+ }
61
+ out.push({ key: numeric ? Number(seg) : seg });
62
+ }
63
+ start = i + 1;
64
+ }
65
+ return out;
66
+ }
67
+
68
+ // Standard JSON Schema, the `jsonSchema` member of `~standard`: what a consumer
69
+ // such as the MCP SDK reads to publish a schema, from `input({ target })`.
70
+ // ata validates the JSON Schema it was given and does not transform values, so
71
+ // input and output are the same document, handed back as a copy when the
72
+ // target is the schema's dialect: draft-07 when it declares draft-07, 2020-12
73
+ // when it declares 2020-12 or nothing, as ata reads it. Any other target
74
+ // throws, as the specification asks, rather than returning a document that
75
+ // would mean something else there.
76
+ function standardJsonSchema(getSchema) {
77
+ const convert = (options) => {
78
+ const target = options && options.target;
79
+ const schema = getSchema();
80
+ if (schema === true) return {};
81
+ if (schema === false) return { not: {} };
82
+ const declared = typeof schema.$schema === 'string' ? schema.$schema : '';
83
+ const dialect = declared === '' || declared.includes('draft/2020-12/schema') ? 'draft-2020-12'
84
+ : declared.includes('draft-07/schema') ? 'draft-07' : declared;
85
+ if (target !== dialect) {
86
+ throw new TypeError(`ata-validator cannot provide a ${dialect} schema as ${String(target)}; it does not convert between dialects`);
87
+ }
88
+ return JSON.parse(JSON.stringify(schema));
89
+ };
90
+ return Object.freeze({ input: convert, output: convert });
91
+ }
92
+
93
+ // The `~standard` object for `self`, a validator whose validate(value)
94
+ // answers as ata's does: `getSchema(self)` returns the schema as the caller
95
+ // wrote it, `validate(self, value)` is called each time, so an extension
96
+ // installed later counts.
97
+ function standardSchemaFor(self, getSchema, validate) {
98
+ return Object.freeze({
99
+ version: 1,
100
+ vendor: 'ata-validator',
101
+ jsonSchema: standardJsonSchema(() => getSchema(self)),
102
+ validate(value) {
103
+ const result = validate(self, value);
104
+ if (result.valid) {
105
+ return { value };
106
+ }
107
+ // An issue carries a message and a path and nothing else, so the
108
+ // suggestion and source-frame work the rich error path does would be
109
+ // thrown away here. Take the raw, schema-ordered list when the result
110
+ // offers one; fall back to the public list otherwise.
111
+ const raw = typeof result._ataRaw === 'function' ? result._ataRaw() : result.errors;
112
+ const issues = new Array(raw.length);
113
+ for (let i = 0; i < raw.length; i++) {
114
+ const err = raw[i];
115
+ const path = err.instancePath != null ? err.instancePath : (err.path || '');
116
+ let message = err.message;
117
+ if (!message) {
118
+ // The native engine reports numeric codes without a message; the
119
+ // enrich pass knows how to word those. Rare, so required lazily.
120
+ message = require('./enrich-error').enrich(err, {}).message;
121
+ }
122
+ issues[i] = { message, path: parsePointerPath(path) };
123
+ }
124
+ return { issues };
125
+ },
126
+ });
127
+ }
128
+
129
+ // Installs `~standard` on `target` (an instance, or a prototype) as a lazy
130
+ // accessor: built on first read for the object it is read from, then pinned
131
+ // to that object as a plain, non-enumerable value, so a validator nobody asks
132
+ // never builds it.
133
+ function installStandard(target, getSchema, validate) {
134
+ Object.defineProperty(target, '~standard', {
135
+ configurable: true,
136
+ enumerable: false,
137
+ get() {
138
+ const std = standardSchemaFor(this, getSchema, validate);
139
+ Object.defineProperty(this, '~standard', { value: std, writable: false, enumerable: false, configurable: false });
140
+ return std;
141
+ },
142
+ });
143
+ }
144
+
145
+ module.exports = { parsePointerPath, standardJsonSchema, standardSchemaFor, installStandard };
package/lib/tier0.js CHANGED
@@ -47,7 +47,7 @@ const F_MULT = 16;
47
47
  const PROTO_NAMES = new Set([...Object.getOwnPropertyNames(Object.prototype), '__proto__']);
48
48
  const _hop = Object.prototype.hasOwnProperty;
49
49
  // Deno removes the __proto__ accessor; read the prototype another way there.
50
- const _PN = ({}).__proto__ === Object.prototype;
50
+ const _PN = typeof Bun === 'undefined' && ({}).__proto__ === Object.prototype; // JavaScriptCore goes through Object.getPrototypeOf, see js-compiler's _PN
51
51
 
52
52
  function primConstraint(key, propSchema) {
53
53
  const t = propSchema.type;