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/README.md +107 -4
- package/bin/ata.js +84 -0
- package/build.d.ts +29 -0
- package/build.mjs +2 -0
- package/compiled.d.ts +3 -1
- package/index.browser.mjs +2 -2
- package/index.bundle.mjs +2 -2
- package/index.d.ts +9 -0
- package/index.edge.mjs +2 -2
- package/index.js +48 -0
- package/index.mjs +1 -1
- package/index.node.mjs +1 -1
- package/lib/aot-build.js +73 -6
- package/lib/aot-impl.js +28 -27
- package/lib/bun-target.js +612 -0
- package/lib/codegen-engine.js +9 -3
- package/lib/combined-runtime.js +1 -1
- package/lib/compiled.js +6 -0
- package/lib/formats.js +1 -1
- package/lib/js-compiler-errors.js +45 -10
- package/lib/js-compiler.js +92 -13
- package/lib/public-api.js +1 -0
- package/lib/standard-schema.js +145 -0
- package/lib/tier0.js +1 -1
- package/lib/validator-core.js +44 -138
- package/lib/verify.js +321 -0
- package/lib/version.js +1 -1
- package/lite.js +1 -0
- package/lite.mjs +1 -1
- package/package.json +10 -9
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
1374
|
-
genCodeE(prop,
|
|
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
|
package/lib/js-compiler.js
CHANGED
|
@@ -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
|
|
109
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
?
|
|
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
|
-
?
|
|
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
|
|
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
|
|
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
|
|
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
|
@@ -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;
|