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 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. Only the buffer and parallel APIs (`isValid` on raw buffers, `countValid`, `batchIsValid`, `validateAndParse`) need the native engine and say so with a clear error.
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`) are native-only and unaffected.
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 SAFE_REGEX_SOURCE = require('./safe-regex-source');
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, inlined verbatim into standalone output so a
35
- // compiled module that uses safe `pattern` matchers has no runtime dependency
36
- // on ata-validator. The engine source is baked into `lib/safe-regex-source.js`
37
- // at build time (see `scripts/regen-safe-regex-source.js`), so this path has
38
- // no `fs`/`path`/`__dirname` reads — safe in browser bundles too. The embed
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 body = SAFE_REGEX_SOURCE
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
- installSlowBufferApis() {},
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
  };
@@ -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 parse and call
126
- // validate(). Installed after compilation, so `validator.validate` is final.
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'));
@@ -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
- if (rawErr.branchErrors) out.branchErrors = rawErr.branchErrors;
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];