ata-validator 1.37.1 → 1.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/index.d.ts +2 -1
- package/lib/aot-impl.js +19 -13
- package/lib/buffer-gate.browser.js +6 -1
- package/lib/buffer-gate.js +6 -7
- package/lib/enrich-error.js +4 -2
- package/lib/js-compiler.js +352 -61
- package/lib/regex-linear.js +65 -0
- package/lib/rejections.js +44 -6
- package/lib/safe-regex.js +424 -413
- package/lib/validator-core.js +7 -1
- package/lib/version.js +1 -1
- package/package.json +12 -12
- package/lib/safe-regex-source.js +0 -8
package/README.md
CHANGED
|
@@ -32,13 +32,13 @@ npm install --save-dev ata-validator
|
|
|
32
32
|
npx ata build 'schemas/*.json' --out-dir src/generated
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
The `ata-validator` package itself is pure JavaScript. The native accelerator (simdjson parsing, parallel NDJSON, buffer APIs) ships as per-platform optional packages that npm installs automatically where they fit, the same pattern Vite uses for esbuild. Seven targets are built: macOS on arm64 and x64, Linux on x64 and arm64 against both glibc and musl, and Windows on x64. A platform without a prebuild still installs and validates, on the pure-JS engine. For a guaranteed zero-binary install:
|
|
35
|
+
The `ata-validator` package itself is pure JavaScript. The native accelerator (simdjson parsing, parallel NDJSON, buffer APIs) ships as per-platform optional packages that npm installs automatically where they fit, the same pattern Vite uses for esbuild. Seven targets are built: macOS on arm64 and x64, Linux on x64 and arm64 against both glibc (2.17 or later, below the 2.28 Node itself needs) and musl, and Windows on x64. A platform without a prebuild still installs and validates, on the pure-JS engine. For a guaranteed zero-binary install:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
npm install ata-validator --omit=optional
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
or set `ATA_NO_NATIVE=1` at runtime. Typical schemas compile to specialized JS; shapes the compiler cannot represent (some `$dynamicRef`, cyclic `$ref`, unusual keyword interactions) fall back to an interpreted engine, so every schema validates in every environment. The pure-JS setup scores the same on the official suite as the native one, 1301 of 1301 Draft 2020-12 cases.
|
|
41
|
+
or set `ATA_NO_NATIVE=1` at runtime. Typical schemas compile to specialized JS; shapes the compiler cannot represent (some `$dynamicRef`, cyclic `$ref`, unusual keyword interactions) fall back to an interpreted engine, so every schema validates in every environment. The pure-JS setup scores the same on the official suite as the native one, 1301 of 1301 Draft 2020-12 cases. The buffer and parallel APIs (`isValid` on raw buffers, `isValidPrepadded`, `isValidNDJSON`, `isValidParallel`, `countValid`, `batchIsValid`, `validateAndParse`) work without the addon too, answering through the same checks as `isValidJSON()`, slower than the addon: 177 ns against 92 for a small document on Node 25. In a browser, which has no `Buffer`, they throw and name the methods to use instead.
|
|
42
42
|
|
|
43
43
|
Those four now agree with `validate()` on every case of the official suite, 3365 across three dialects. The native walker behind them does not handle every shape (`contains`, `unevaluatedProperties`, `patternProperties`, tuple `items`, cross-document `$ref`, a few formats), so for schemas using one of those the buffer APIs parse the bytes and answer through `validate()`; the list is in `lib/buffer-gate.js`. Typical request schemas stay on the zero-copy path. `npm test` holds the disagreement count at zero.
|
|
44
44
|
|
package/index.d.ts
CHANGED
|
@@ -439,7 +439,8 @@ export interface ValidatorOptions {
|
|
|
439
439
|
* answers `validate()`, `isValidObject()` and `validateJSON()`. For a schema
|
|
440
440
|
* that arrives from outside the trust boundary. The verdict is the same on
|
|
441
441
|
* every engine; the cost is not. The buffer APIs (`isValid`, `countValid`,
|
|
442
|
-
* `batchIsValid`)
|
|
442
|
+
* `batchIsValid`) use the native addon where it loads, which this option
|
|
443
|
+
* does not affect; without it they answer through the engine it picks.
|
|
443
444
|
*/
|
|
444
445
|
engine?: 'auto' | 'interpreter';
|
|
445
446
|
}
|
package/lib/aot-impl.js
CHANGED
|
@@ -17,7 +17,7 @@ const { compileToJSCodegenWithErrors, compileToJSCodegen, unevalContributions }
|
|
|
17
17
|
const { buildTargetedPositionMap } = require('./data-positions');
|
|
18
18
|
const { schemaHash } = require('./schema-hash');
|
|
19
19
|
const ATA_VERSION = require('./version');
|
|
20
|
-
const
|
|
20
|
+
const { engineSource: safeRegexEngineSource } = require('./safe-regex');
|
|
21
21
|
|
|
22
22
|
// Embedded verbatim in standalone modules so the output file has no runtime
|
|
23
23
|
// dependency on ata-validator. ASCII fast-path plus surrogate-aware slow path.
|
|
@@ -31,21 +31,16 @@ const _CP_LEN_SOURCE = `function _cpLen(s) {
|
|
|
31
31
|
return len;
|
|
32
32
|
}`;
|
|
33
33
|
|
|
34
|
-
// The linear-time regex engine,
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
// strips the strict directive and CommonJS exports and adds the `__ataSafeRe`
|
|
40
|
-
// alias the emitted code calls. The engine has no eval/new Function, so the
|
|
34
|
+
// The linear-time regex engine, embedded in standalone modules whose patterns
|
|
35
|
+
// need it so the output does not depend on ata-validator at run time. The text
|
|
36
|
+
// is the engine's own function (lib/safe-regex.js), called once for the
|
|
37
|
+
// `__ataSafeRe` the emitted code uses. No fs, path or __dirname, so this is
|
|
38
|
+
// safe in browser bundles, and the engine has no eval or new Function, so the
|
|
41
39
|
// embed is CSP-safe.
|
|
42
40
|
let _safeRegexEmbed = null;
|
|
43
41
|
function getSafeRegexEmbed() {
|
|
44
42
|
if (_safeRegexEmbed === null) {
|
|
45
|
-
const
|
|
46
|
-
.replace(/^'use strict'\s*\n/, '')
|
|
47
|
-
.replace(/\nmodule\.exports[^\n]*\n?/, '\n');
|
|
48
|
-
_safeRegexEmbed = body.trimEnd() + '\nconst __ataSafeRe = compileSafe;';
|
|
43
|
+
_safeRegexEmbed = 'const __ataSafeRe = (' + safeRegexEngineSource() + ')().compileSafe;';
|
|
49
44
|
}
|
|
50
45
|
return _safeRegexEmbed;
|
|
51
46
|
}
|
|
@@ -213,7 +208,18 @@ function toStandaloneModule(validator, opts) {
|
|
|
213
208
|
(source && sourceMap && schemaFile) ? { sourceMap, schemaFile } : null,
|
|
214
209
|
);
|
|
215
210
|
const errSrc = jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : '';
|
|
216
|
-
if (errSrc) {
|
|
211
|
+
if (errSrc && jsErrFn._errFactory) {
|
|
212
|
+
// The helpers (patterns, name sets, definition and branch functions) are
|
|
213
|
+
// built once, not on every call. With $defs the
|
|
214
|
+
// cycle guard's state sits beside them, so a call made while one is
|
|
215
|
+
// running takes a function from a fresh factory call.
|
|
216
|
+
// Built on the first error read rather than at import, so a page that
|
|
217
|
+
// only ever accepts pays nothing for them at load.
|
|
218
|
+
const fac = jsErrFn._errFactory;
|
|
219
|
+
errCore = jsErrFn._errGuarded
|
|
220
|
+
? `const _mkErr = function() {\n ${fac}\n};\nlet _errMain = null;\nlet _errBusy = false;\nconst errFn = function(d, _all) { if (_errBusy) return _mkErr()(d, _all); if (_errMain === null) _errMain = _mkErr(); _errBusy = true; try { return _errMain(d, _all); } finally { _errBusy = false; } };\n`
|
|
221
|
+
: `const _mkErr = function() {\n ${fac}\n};\nlet _errMain = null;\nconst errFn = function(d, _all) { return (_errMain || (_errMain = _mkErr()))(d, _all); };\n`;
|
|
222
|
+
} else if (errSrc) {
|
|
217
223
|
errCore = `const errFn = function(d, _all) {\n ${errSrc}\n};\n`;
|
|
218
224
|
} else if (opts && typeof opts.onWarning === 'function') {
|
|
219
225
|
// The caller asked for error detail and is not getting it: the error
|
|
@@ -9,5 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
11
|
bufferNeedsSlowPath: () => false,
|
|
12
|
-
|
|
12
|
+
// A browser build has no addon and no Buffer, and the core throws before
|
|
13
|
+
// it gets here. A polyfilled Buffer would reach this: fail loudly rather
|
|
14
|
+
// than return with the methods still uninstalled.
|
|
15
|
+
installSlowBufferApis() {
|
|
16
|
+
throw new Error('The buffer APIs are not available in the browser build. Use validate(), isValidObject() or validateJSON().');
|
|
17
|
+
},
|
|
13
18
|
};
|
package/lib/buffer-gate.js
CHANGED
|
@@ -122,14 +122,13 @@ function toText(input, name) {
|
|
|
122
122
|
throw new TypeError(`${name}() requires a Buffer, Uint8Array, or string. For parsed objects, use isValidObject().`);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
// Replaces the instance's buffer APIs with versions that
|
|
126
|
-
// validate()
|
|
125
|
+
// Replaces the instance's buffer APIs with versions that decode the bytes and
|
|
126
|
+
// ask isValidJSON(), which answers as validate() does on the parsed value and
|
|
127
|
+
// reads text the scanner covers without parsing it (a small document went from
|
|
128
|
+
// 350 to 155 ns without the addon). Its own native route is behind this same
|
|
129
|
+
// gate, so a schema routed here never reaches the walker through it.
|
|
127
130
|
function installSlowBufferApis(validator) {
|
|
128
|
-
const isValidText = (text) =>
|
|
129
|
-
let value;
|
|
130
|
-
try { value = JSON.parse(text); } catch { return false; }
|
|
131
|
-
return validator.validate(value).valid;
|
|
132
|
-
};
|
|
131
|
+
const isValidText = (text) => validator.isValidJSON(text);
|
|
133
132
|
validator.isValid = (input) => isValidText(toText(input, 'isValid'));
|
|
134
133
|
validator.isValidPrepadded = (paddedBuffer, jsonLength) =>
|
|
135
134
|
isValidText(Buffer.from(paddedBuffer.buffer, paddedBuffer.byteOffset, jsonLength).toString('utf8'));
|
package/lib/enrich-error.js
CHANGED
|
@@ -233,8 +233,10 @@ function enrich (rawErr, opts) {
|
|
|
233
233
|
if ('schema' in rawErr) out.schema = rawErr.schema;
|
|
234
234
|
|
|
235
235
|
// oneOf/anyOf collapse: preserve the nested branch errors so the pretty
|
|
236
|
-
// renderer can surface the closest variant's diagnostics.
|
|
237
|
-
|
|
236
|
+
// renderer can surface the closest variant's diagnostics. They are enriched
|
|
237
|
+
// like any other error: passed through raw, they carried whatever the engine
|
|
238
|
+
// that answered wrote, the generator's internal ordering key included.
|
|
239
|
+
if (rawErr.branchErrors) out.branchErrors = rawErr.branchErrors.map((b) => enrich(b, data !== undefined ? { data } : null));
|
|
238
240
|
|
|
239
241
|
if (positions && positions[path]) {
|
|
240
242
|
const p = positions[path];
|