ata-validator 1.58.1 → 1.60.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 CHANGED
@@ -661,9 +661,11 @@ through a `setFormats()` export the module carries. `docs/API.md` has the
661
661
  details.
662
662
 
663
663
  **Fastify startup, 10 route schemas, from a cold process to the first validated request:
664
- ajv 18.3 ms, ata 2.67 ms, no build step required.** ata registers in 0.22 ms of that and
665
- compiles on the first request, so counting only registration would overstate the gap.
666
- Reproduce with `node benchmark/bench_fastify_boot.mjs`.
664
+ ajv 30.3 ms, ata 9.7 ms, no build step required** (Node 25, ajv 8.20.0, loading each
665
+ library included). ata loads in 3.7 ms, registers the routes in 0.3 ms and compiles on the
666
+ first request, so counting only registration would overstate the gap. The first request
667
+ here comes in the same tick as setup; a server that listens first has the code generator
668
+ loaded by then. Reproduce with `node benchmark/bench_fastify_boot.mjs`.
667
669
 
668
670
  ### Standard Schema V1
669
671
 
package/codegen.js CHANGED
@@ -4,7 +4,8 @@
4
4
  // loads it on demand, once a validator gets hot; importing this once at
5
5
  // startup has every schema compiled to generated code from its first call,
6
6
  // and makes parse() and the ahead-of-time bundle methods available in the
7
- // browser. On Node, ata-validator already includes it and this does nothing.
7
+ // browser. On Node, ata-validator loads it when a validator first compiles or
8
+ // once the event loop is free; importing this loads it now.
8
9
  const core = require('./lib/validator-core.js');
9
10
  if (!core._codegenRegistered()) core._registerCodegen(require('./lib/codegen-engine.js'));
10
11
  module.exports = {};
package/index.js CHANGED
@@ -6,7 +6,19 @@
6
6
  // lib/codegen-engine.js, so that the edge entry (index.edge.js) and
7
7
  // ata-validator/lite can load the core without it.
8
8
  const core = require('./lib/validator-core.js');
9
- core._registerCodegen(require('./lib/codegen-engine.js'));
9
+ // The code generator loads on first need, or once the event loop is free,
10
+ // not with the package: it is most of what requiring the package parsed. The
11
+ // timer is unref'd, so a script that has finished does not stay to load it.
12
+ // The loader lives here rather than in the core, which the browser build
13
+ // shares and which has no use for it there.
14
+ let engine = null;
15
+ const loadCodegen = () => {
16
+ if (engine === null) core._registerCodegen(engine = require('./lib/codegen-engine.js'));
17
+ return engine;
18
+ };
19
+ core._setCodegenLoader(loadCodegen);
20
+ const warm = setTimeout(loadCodegen, 0);
21
+ if (typeof warm === 'object' && typeof warm.unref === 'function') warm.unref();
10
22
 
11
23
  // The public names. lib/public-api.js holds the same for index.edge.js; they
12
24
  // are written out here rather than required so that loading the package in
package/lib/aot-impl.js CHANGED
@@ -371,6 +371,30 @@ function defaultsDecl(validator) {
371
371
  `let _ataFill;\nfunction applyDefaults(d) { (_ataFill || (_ataFill = _ataDefaults(JSON.parse(${text}))))(d); }\n`;
372
372
  }
373
373
 
374
+ // What a bundled validator runs on the data before it checks it, for the
375
+ // options the caller of bundleStandalone named: the runtime's pass for
376
+ // `coerceTypes`, `removeAdditional` and `useDefaults: true`, as one generated
377
+ // function (preprocessSource), in the runtime's order. A bundle used to leave
378
+ // coerceTypes and removeAdditional out without a word, and then rejected what
379
+ // the runtime accepts. Where the generated pass declines and the runtime
380
+ // would change the data with its closure pass, there is nothing to carry it
381
+ // in, and the bundle refuses. `useDefaults` alone runs applyDefaults.
382
+ // Returns { decl, call }, the call naming the data parameter `arg`.
383
+ function bundlePreprocess(validator, opts, fillDecl, label, arg = 'd') {
384
+ const coerceTypes = opts && opts.coerceTypes ? opts.coerceTypes : false;
385
+ const removeAdditional = opts && opts.removeAdditional ? opts.removeAdditional : false;
386
+ const useDefaults = !!(opts && opts.useDefaults === true);
387
+ if (!coerceTypes && !removeAdditional) return { decl: '', call: useDefaults && fillDecl ? `applyDefaults(${arg});` : '' };
388
+ const internals = require('./validator-core')._internals;
389
+ const schema = internals.resolveSchemaForPreprocess(validator._schemaObj, validator._schemaMap);
390
+ let src = null;
391
+ try { src = require('./codegen-engine').preprocessSource(schema, { coerceTypes, removeAdditional, useDefaults }); } catch { src = null; }
392
+ if (src !== null) return { decl: `function _ataPre(d) {\n${src}\n}\n`, call: `_ataPre(${arg});` };
393
+ const changes = (coerceTypes && internals.buildCoercer(schema) !== null) || (removeAdditional && internals.buildRemover(schema) !== null);
394
+ if (changes) throw new Error(`${label}: ${coerceTypes ? 'coerceTypes' : 'removeAdditional'} cannot be compiled into a standalone module for this schema; validate with the runtime Validator`);
395
+ return { decl: '', call: useDefaults && fillDecl ? `applyDefaults(${arg});` : '' };
396
+ }
397
+
374
398
  function toStandaloneModule(validator, opts) {
375
399
  assertEmittable(validator, 'toStandaloneModule');
376
400
  validator._ensureCompiled();
@@ -384,6 +408,12 @@ function toStandaloneModule(validator, opts) {
384
408
  const source = !!(opts && opts.source);
385
409
  const sourceMap = opts && opts.sourceMap ? opts.sourceMap : null;
386
410
  const schemaFile = opts && opts.schemaFile ? opts.schemaFile : null;
411
+ // The validator's own coerceTypes and removeAdditional run before every
412
+ // check, as they do in the validator; a module used to leave them out and
413
+ // reject what the validator accepts. Defaults are not filled here: a module
414
+ // exports applyDefaults for that, and validate() leaves the data as it is.
415
+ const vopts = validator._options || {};
416
+ const pre = bundlePreprocess(validator, { coerceTypes: vopts.coerceTypes, removeAdditional: vopts.removeAdditional }, '', 'toStandaloneModule', 'data');
387
417
  const src = jsFn._source;
388
418
 
389
419
  let errCore = '';
@@ -612,7 +642,7 @@ function toStandaloneModule(validator, opts) {
612
642
  // reading patterns. A second declaration of that name shadowed it and the
613
643
  // prelude called this one instead, reaching _fn before its initialiser ran.
614
644
  const parseCore = cloneExpr
615
- ? `function _ataParse(data) {\n if (!_fn(data)) { const e = new Error('validation failed'); e.name = 'AtaValidationError'; throw e; }\n return ${cloneExpr};\n}\n`
645
+ ? `function _ataParse(data) {\n ${pre.call}if (!_fn(data)) { const e = new Error('validation failed'); e.name = 'AtaValidationError'; throw e; }\n return ${cloneExpr};\n}\n`
616
646
  : '';
617
647
 
618
648
  // validateJSON(): parse the text, validate, and on failure attach a
@@ -701,8 +731,8 @@ const ABORT = Object.freeze({
701
731
  ${closureDecls}${sharedBlock}${preambleDecls}${formatDecls}const _fn = function(d) {
702
732
  ${src}
703
733
  };
704
- ${errCore}${orderDecl}${compactCore}function isValid(data) { return _fn(data); }
705
- function validate(data) { ${validBody}; }
734
+ ${errCore}${orderDecl}${compactCore}${pre.decl}function isValid(data) { ${pre.call}return _fn(data); }
735
+ function validate(data) { ${pre.call}${validBody}; }
706
736
  ${parseCore}${positionsCore}${fillDecl}${hashDecl}${exports}`;
707
737
  }
708
738
 
@@ -912,6 +942,12 @@ function bundle(Validator, schemas, opts) {
912
942
  // Zero-dependency self-contained bundle — no require('ata-validator') needed at runtime.
913
943
  // opts.format: 'cjs' (default) or 'esm'.
914
944
  // opts.formats: { name: fn } — embedded in the output via Function#toString.
945
+ // opts.order: true hands each validator's errors out in the runtime's order,
946
+ // as toStandaloneModule does (lib/aot-order.js).
947
+ // Each validator of a schema with a default carries `applyDefaults(data)`, the
948
+ // runtime's defaults pass, as a property; validate does not run it unless
949
+ // `useDefaults: true` is passed. `coerceTypes` and `removeAdditional` run
950
+ // before the check, as at runtime (bundlePreprocess).
915
951
  function bundleStandalone(Validator, schemas, opts) {
916
952
  // A custom keyword is a function, and a standalone module imports nothing,
917
953
  // so there is no way to carry it. Refuse rather than emit a module that
@@ -930,11 +966,14 @@ function bundleStandalone(Validator, schemas, opts) {
930
966
  // Pass one compiles; pass two emits. The helpers that several schemas hoist
931
967
  // are the same text declaring the same name, so the bundle keeps one copy at
932
968
  // module scope, and pass two needs to know the set before it writes anything.
969
+ const ordered = !!(opts && opts.order === true);
970
+ const validatorOpts = ordered ? Object.fromEntries(Object.entries(bundleOpts).filter(([k]) => k !== 'order')) : bundleOpts;
933
971
  const compiledEntries = schemas.map((schema) => {
934
- const v = new Validator(schema, bundleOpts);
972
+ const v = new Validator(schema, validatorOpts);
935
973
  v._ensureCompiled();
936
974
  const jsFn = v._jsFn;
937
975
  if (!jsFn || !jsFn._hybridSource) return null;
976
+ const noOrdinal = !ordered || !require(AOT_ORDER_MODULE).wantsOrdinals(v._schemaObj);
938
977
  // The schema the validator compiled, not the one the caller passed. They
939
978
  // differ whenever anything prepared the schema: draft-07 normalization,
940
979
  // `format` removed under assertFormat: false, keywords outside the
@@ -944,7 +983,7 @@ function bundleStandalone(Validator, schemas, opts) {
944
983
  v._schemaObj,
945
984
  v._schemaMap,
946
985
  v._userFormats,
947
- { noOrdinal: true },
986
+ { noOrdinal },
948
987
  );
949
988
  if (jsFn._usesSafeRe || (jsErrFn && jsErrFn._usesSafeRe)) bundleUsesSafeRe = true;
950
989
  return { v, jsFn, jsErrFn };
@@ -990,12 +1029,25 @@ function bundleStandalone(Validator, schemas, opts) {
990
1029
  if (preambleSrc) preamble = preamble ? `${preamble}\n${preambleSrc}` : preambleSrc;
991
1030
  const closureSrc = closureDeclLines(jsFn, 'var').join('\n');
992
1031
  if (closureSrc) preamble = preamble ? `${preamble}\n${closureSrc}\n` : closureSrc + '\n';
1032
+ // The runtime's passes over the data, for the options asked for, and its
1033
+ // defaults pass as the validator's applyDefaults.
1034
+ const fillDecl = defaultsDecl(v);
1035
+ const pre = bundlePreprocess(v, opts, fillDecl, 'bundleStandalone');
1036
+ preamble = preamble ? `${preamble}\n${fillDecl}${pre.decl}` : fillDecl + pre.decl;
1037
+ const ret = (body) => fillDecl
1038
+ ? `var _f=function(d){${pre.call}${body}};_f.applyDefaults=applyDefaults;return _f`
1039
+ : `return function(d){${pre.call}${body}}`;
1040
+ // In order: the collector's errors sorted as the runtime sorts them.
1041
+ const hasErr = !!(jsErrFn && jsErrFn._errSource);
1042
+ const errDecl = ordered && hasErr
1043
+ ? `${require(AOT_ORDER_MODULE).orderDecl(v._schemaObj)}var E=function(d){var r=(function(d){var _all=true;${errBody}})(d);return r&&r.errors?{valid:false,errors:_ataOrder(r.errors)}:r};`
1044
+ : `var E=function(d){var _all=true;${errBody}};`;
993
1045
  if (opts && opts.verbose) {
994
1046
  // Embed the schema and a small resolver so errors carry parentSchema.
995
1047
  const schemaLit = JSON.stringify(v._schemaObj);
996
- return `(function(R){${preamble}var _S=${schemaLit};function _PS(p){if(!p||p[0]!=='#')return undefined;var s=p.slice(1);if(!s)return _S;var ps=s.split('/').filter(Boolean).map(function(x){return x.replace(/~1/g,'/').replace(/~0/g,'~')});var t=_S;for(var i=0;i<ps.length-1;i++){if(t==null||typeof t!=='object')return undefined;t=t[ps[i]]}return t}var E=function(d){var _all=true;${errBody}};var _v=function(d){${jsFn._hybridSource}};return function(d){var r=_v(d);if(r&&r.valid===false&&r.errors){var es=[];for(var i=0;i<r.errors.length;i++){var e=r.errors[i];es.push(Object.assign({},e,{parentSchema:_PS(e.schemaPath)}))}return{valid:false,errors:es}}return r}})(R)`;
1048
+ return `(function(R){${preamble}var _S=${schemaLit};function _PS(p){if(!p||p[0]!=='#')return undefined;var s=p.slice(1);if(!s)return _S;var ps=s.split('/').filter(Boolean).map(function(x){return x.replace(/~1/g,'/').replace(/~0/g,'~')});var t=_S;for(var i=0;i<ps.length-1;i++){if(t==null||typeof t!=='object')return undefined;t=t[ps[i]]}return t}${errDecl}var _v=function(d){${jsFn._hybridSource}};${ret('var r=_v(d);if(r&&r.valid===false&&r.errors){var es=[];for(var i=0;i<r.errors.length;i++){var e=r.errors[i];es.push(Object.assign({},e,{parentSchema:_PS(e.schemaPath)}))}return{valid:false,errors:es}}return r')}})(R)`;
997
1049
  }
998
- return `(function(R){${preamble}var E=function(d){var _all=true;${errBody}};return function(d){${jsFn._hybridSource}}})(R)`;
1050
+ return `(function(R){${preamble}${errDecl}${ret(jsFn._hybridSource)}})(R)`;
999
1051
  });
1000
1052
  const arr = `[${fns.join(',')}]`;
1001
1053
  // The code-point counter the length checks call. toStandaloneModule embeds
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
 
@@ -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 };
@@ -25,6 +25,9 @@ function _registerCodegen(engine) {
25
25
  _codegen = engine;
26
26
  ({ compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined } = engine.jsCompiler);
27
27
  }
28
+ // The code generator, loading it first where the entry loads it on need
29
+ // (index.js); everywhere else, whatever is registered.
30
+ let _loadCodegen = () => _codegen;
28
31
  // The ahead-of-time loaders without the code generator: the edge entry
29
32
  // (index.edge.js) registers lib/aot.browser.js here, so modules written by
30
33
  // `ata build` or Validator.bundle() load where generated code cannot run.
@@ -219,7 +222,7 @@ function collectRemovals(schema, actions, path) {
219
222
  // change within a realm and the probe itself is a code generation attempt.
220
223
  let _codegenAvailable = null;
221
224
  function codegenAvailable() {
222
- if (_codegen === null) return false;
225
+ if (_loadCodegen() === null) return false;
223
226
  if (_codegenAvailable === null) {
224
227
  try {
225
228
  _codegenAvailable = compileFunction('return 1')() === 1;
@@ -355,64 +358,6 @@ function _jsonSyntaxRejection(e) {
355
358
 
356
359
 
357
360
 
358
- // Paths repeat across rejections of the same shape, so the parsed segment
359
- // list is cached per path string. Entries are frozen: the same array is
360
- // handed to every issue that names the path.
361
- //
362
- // The cache is bucketed by length and searched with ===, not keyed in a Map.
363
- // A path with an array index in it is concatenated afresh by every rejection,
364
- // and a Map has to hash each such string from scratch before it can look
365
- // anything up; that hashing was 30% of a Standard Schema rejection with
366
- // sixteen issues. Comparing against the few paths of the same length costs
367
- // less. Both levels are bounded so a stream of array indexes cannot grow it
368
- // without limit: a full bucket stops caching, too many lengths clear it.
369
- const _pathBuckets = new Map();
370
- const PATH_BUCKET_MAX = 32;
371
- const PATH_LENGTHS_MAX = 256;
372
- function parsePointerPath(path) {
373
- if (!path) return EMPTY_PATH;
374
- const n = path.length;
375
- let bucket = _pathBuckets.get(n);
376
- if (bucket !== undefined) {
377
- for (let i = 0; i < bucket.length; i += 2) if (bucket[i] === path) return bucket[i + 1];
378
- } else {
379
- if (_pathBuckets.size >= PATH_LENGTHS_MAX) _pathBuckets.clear();
380
- bucket = [];
381
- _pathBuckets.set(n, bucket);
382
- }
383
- const segs = Object.freeze(parsePointerPathUncached(path));
384
- if (bucket.length < PATH_BUCKET_MAX * 2) bucket.push(path, segs);
385
- return segs;
386
- }
387
- const EMPTY_PATH = Object.freeze([]);
388
-
389
- function parsePointerPathUncached(path) {
390
- // One pass, no intermediate arrays. Per Standard Schema V1 an array index
391
- // is emitted as a number and an object key as a string; a segment is an
392
- // index when it is all digits with no leading zero.
393
- const out = [];
394
- const n = path.length;
395
- let start = 1;
396
- for (let i = 1; i <= n; i++) {
397
- if (i !== n && path.charCodeAt(i) !== 47) continue;
398
- if (i > start) {
399
- let seg = path.slice(start, i);
400
- if (seg.indexOf('~') >= 0) seg = seg.replace(/~1/g, '/').replace(/~0/g, '~');
401
- const c0 = seg.charCodeAt(0);
402
- let numeric = c0 >= 48 && c0 <= 57 && (seg.length === 1 || c0 !== 48);
403
- if (numeric) {
404
- for (let k = 1; k < seg.length; k++) {
405
- const c = seg.charCodeAt(k);
406
- if (c < 48 || c > 57) { numeric = false; break; }
407
- }
408
- }
409
- out.push({ key: numeric ? Number(seg) : seg });
410
- }
411
- start = i + 1;
412
- }
413
- return out;
414
- }
415
-
416
361
  function createPaddedBuffer(jsonStr) {
417
362
  if (typeof Buffer === 'undefined') throw new Error('createPaddedBuffer requires Node.js Buffer');
418
363
  const jsonBuf = Buffer.from(jsonStr);
@@ -951,7 +896,7 @@ class Validator {
951
896
  }
952
897
 
953
898
  _pos() {
954
- return this._posCache || (this._posCache = require('./data-position-cache').createCache(_codegen !== null && _codegen.nativePositions ? _codegen.nativePositions() : null));
899
+ return this._posCache || (this._posCache = require('./data-position-cache').createCache(_loadCodegen() !== null && _codegen.nativePositions ? _codegen.nativePositions() : null));
955
900
  }
956
901
 
957
902
  _ensureVocabularies() {
@@ -1102,7 +1047,7 @@ class Validator {
1102
1047
  // pass looks for one. One reached through a reference into another
1103
1048
  // document is walked, since its defaults are not in this text.
1104
1049
  const mayHaveDefaults = preprocessSchema !== schemaObj || this._schemaStr.includes('"default"');
1105
- if (!this._interpretOnly && _codegen !== null) {
1050
+ if (!this._interpretOnly && _loadCodegen() !== null) {
1106
1051
  const cacheable = !this._userFormats && !this._usesKeywords;
1107
1052
  const ppKey = cacheable ? mapKey + '\u0000' + String(options.coerceTypes) + '|' + (options.useDefaults !== false) + '|' + String(options.removeAdditional) : null;
1108
1053
  const hit = ppKey !== null ? _preprocessCache.get(ppKey) : undefined;
@@ -1726,7 +1671,7 @@ class Validator {
1726
1671
  return;
1727
1672
  }
1728
1673
  this._ensureVocabularies();
1729
- if (this._interpretOnly || _codegen === null || env('ATA_FORCE_NAPI')) return;
1674
+ if (this._interpretOnly || env('ATA_FORCE_NAPI') || _loadCodegen() === null) return;
1730
1675
  _codegen.compileVerdict.call(this);
1731
1676
  }
1732
1677
 
@@ -1736,7 +1681,7 @@ class Validator {
1736
1681
  static fromStandalone(mod, schema, opts) {
1737
1682
  // It loads what Validator.bundleStandalone() writes, and compiles an error
1738
1683
  // function when the module carries none, so it goes with those methods.
1739
- if (_codegen === null && _aotLoader === null && _tiering === null) throw new TypeError('Validator.fromStandalone is not part of ata-validator/lite, like the bundle methods it loads for. Import it from ata-validator, or import a module from `ata build` directly.');
1684
+ if (_loadCodegen() === null && _aotLoader === null && _tiering === null) throw new TypeError('Validator.fromStandalone is not part of ata-validator/lite, like the bundle methods it loads for. Import it from ata-validator, or import a module from `ata build` directly.');
1740
1685
  const options = opts || {};
1741
1686
  const schemaObj = typeof schema === "string" ? JSON.parse(schema) : schema;
1742
1687
 
@@ -1835,23 +1780,9 @@ class Validator {
1835
1780
  }
1836
1781
  };
1837
1782
 
1838
- // Standard Schema V1
1783
+ // Standard Schema V1, the same object a Validator hands out.
1839
1784
  Object.defineProperty(v, "~standard", {
1840
- value: Object.freeze({
1841
- version: 1,
1842
- vendor: "ata-validator",
1843
- jsonSchema: standardJsonSchema(() => schemaObj),
1844
- validate(value) {
1845
- const result = v.validate(value);
1846
- if (result.valid) return { value };
1847
- return {
1848
- issues: result.errors.map((e) => ({
1849
- message: e.message,
1850
- path: parsePointerPath(e.instancePath),
1851
- })),
1852
- };
1853
- },
1854
- }),
1785
+ value: require('./standard-schema').standardSchemaFor(v, () => schemaObj, (self, value) => self.validate(value)),
1855
1786
  writable: false,
1856
1787
  enumerable: false,
1857
1788
  configurable: false,
@@ -1922,7 +1853,7 @@ function version() {
1922
1853
  // generated module shapes; browser bundles get `lib/aot.browser.js` (a stub
1923
1854
  // that throws) via the package.json `browser` field.
1924
1855
  function _aot() {
1925
- if (_codegen !== null) return _codegen.aot();
1856
+ if (_loadCodegen() !== null) return _codegen.aot();
1926
1857
  if (_aotLoader !== null) return _aotLoader();
1927
1858
  // The browser build: emitters stay out of the page, as they did when the
1928
1859
  // generator was in it (the package.json browser field maps lib/aot.js to
@@ -1991,74 +1922,19 @@ function defineSchema (schema) {
1991
1922
  // bound closure, stores it on the instance as an ordinary writable property
1992
1923
  // and returns it. The setter keeps the compile step's plain assignments
1993
1924
  // (`this.validate = fn`) working before the getter has ever run. Detached
1994
- // Standard JSON Schema, the `jsonSchema` member of `~standard`: what a consumer
1995
- // such as the MCP SDK reads to publish a schema, from `input({ target })`.
1996
- // ata validates the JSON Schema it was given and does not transform values, so
1997
- // input and output are the same document, handed back as a copy when the
1998
- // target is the schema's dialect: draft-07 when it declares draft-07, 2020-12
1999
- // when it declares 2020-12 or nothing, as ata reads it. Any other target
2000
- // throws, as the specification asks, rather than returning a document that
2001
- // would mean something else there.
2002
- function standardJsonSchema(getSchema) {
2003
- const convert = (options) => {
2004
- const target = options && options.target;
2005
- const schema = getSchema();
2006
- if (schema === true) return {};
2007
- if (schema === false) return { not: {} };
2008
- const declared = typeof schema.$schema === 'string' ? schema.$schema : '';
2009
- const dialect = declared === '' || declared.includes('draft/2020-12/schema') ? 'draft-2020-12'
2010
- : declared.includes('draft-07/schema') ? 'draft-07' : declared;
2011
- if (target !== dialect) {
2012
- throw new TypeError(`ata-validator cannot provide a ${dialect} schema as ${String(target)}; it does not convert between dialects`);
2013
- }
2014
- return JSON.parse(JSON.stringify(schema));
2015
- };
2016
- return Object.freeze({ input: convert, output: convert });
2017
- }
2018
-
2019
1925
  // use (`const f = v.validate`) keeps working because the closure binds the
2020
1926
  // instance.
2021
1927
  // Standard Schema V1. Built on first read, then pinned to the instance with
2022
1928
  // the same descriptor the constructor used to install eagerly.
1929
+ // Lazy on the prototype, pinned to the instance on first read; the object
1930
+ // comes from lib/standard-schema.js, shared with the wrapper around a module
1931
+ // written ahead of time, and loaded only when something reads `~standard`.
2023
1932
  Object.defineProperty(Validator.prototype, "~standard", {
2024
1933
  configurable: true,
1934
+ enumerable: false,
2025
1935
  get() {
2026
- const self = this;
2027
- const std = Object.freeze({
2028
- version: 1,
2029
- vendor: "ata-validator",
2030
- jsonSchema: standardJsonSchema(() => self._rawSchema),
2031
- validate(value) {
2032
- const result = self.validate(value);
2033
- if (result.valid) {
2034
- return { value };
2035
- }
2036
- // An issue carries a message and a path and nothing else, so the
2037
- // suggestion and source-frame work the rich error path does would be
2038
- // thrown away here. Take the raw, schema-ordered list when the result
2039
- // offers one; fall back to the public list otherwise.
2040
- const raw = typeof result._ataRaw === 'function' ? result._ataRaw() : result.errors;
2041
- const issues = new Array(raw.length);
2042
- for (let i = 0; i < raw.length; i++) {
2043
- const err = raw[i];
2044
- const path = err.instancePath != null ? err.instancePath : (err.path || '');
2045
- let message = err.message;
2046
- if (!message) {
2047
- // The native engine reports numeric codes without a message; the
2048
- // enrich pass knows how to word those. Rare, so required lazily.
2049
- message = require('./enrich-error').enrich(err, {}).message;
2050
- }
2051
- issues[i] = { message, path: parsePointerPath(path) };
2052
- }
2053
- return { issues };
2054
- },
2055
- });
2056
- Object.defineProperty(this, "~standard", {
2057
- value: std,
2058
- writable: false,
2059
- enumerable: false,
2060
- configurable: false,
2061
- });
1936
+ const std = require('./standard-schema').standardSchemaFor(this, (self) => self._rawSchema, (self, value) => self.validate(value));
1937
+ Object.defineProperty(this, "~standard", { value: std, writable: false, enumerable: false, configurable: false });
2062
1938
  return std;
2063
1939
  },
2064
1940
  });
@@ -2346,7 +2222,7 @@ function _buildParse(self) {
2346
2222
  later._untilCodegen = true;
2347
2223
  return later;
2348
2224
  }
2349
- if (_codegen === null) return decline('this entry has no code generator to build the copy with', 'Use validate(); a valid result carries the input as it is.');
2225
+ if (_loadCodegen() === null) return decline('this entry has no code generator to build the copy with', 'Use validate(); a valid result carries the input as it is.');
2350
2226
  return _codegen.buildParse(self, decline, extended);
2351
2227
  }
2352
2228
 
@@ -2417,7 +2293,8 @@ for (const [name, pick] of [
2417
2293
  // Cold start: an interpreted twin answers a large schema's first calls where
2418
2294
  // the code generator is registered (see coldTwin in index.js). Lite has none.
2419
2295
  Validator.prototype._coldTwin = function () {
2420
- return _codegen === null || _codegen.coldTwin === undefined ? null : _codegen.coldTwin(this, Validator);
2296
+ const cg = _loadCodegen();
2297
+ return cg === null || cg.coldTwin === undefined ? null : cg.coldTwin(this, Validator);
2421
2298
  };
2422
2299
 
2423
2300
  _defineLazyMethod('validate', (self) => (data) => {
@@ -2529,9 +2406,10 @@ module.exports = {
2529
2406
  // How an entry registers the code generator. Not enumerable, so the exported
2530
2407
  // surface of `require('ata-validator')` is what it was.
2531
2408
  Object.defineProperty(module.exports, '_registerCodegen', { value: _registerCodegen, enumerable: false });
2409
+ Object.defineProperty(module.exports, '_setCodegenLoader', { value: (load) => { _loadCodegen = load; }, enumerable: false });
2532
2410
  Object.defineProperty(module.exports, '_registerTiering', { value: _registerTiering, enumerable: false });
2533
2411
  Object.defineProperty(module.exports, '_codegenRegistered', { value: () => _codegen !== null, enumerable: false });
2534
2412
  Object.defineProperty(module.exports, '_tiering', { get: () => _tiering, enumerable: false });
2535
2413
  Object.defineProperty(module.exports, '_registerAot', { value: _registerAot, enumerable: false });
2536
2414
  // What the compiled paths in index.js need from this module.
2537
- Object.defineProperty(module.exports, '_internals', { value: { _interpArgs, _normalizeCallerSchemaUncached, resolveSchemaForPreprocess, _jsonSyntaxRejection, ABORT_EARLY_RESULT, HYBRID_TIER_CALLS, SIMDJSON_THRESHOLD, VALID_RESULT, _bindEntry, _bindVerdict, _compileCache, _compileCacheSet, _mustReject, _rememberInstance, _verboseWrap, compileCacheKey, getNative, isV1Dialect, resolveSchemaByPath }, enumerable: false });
2415
+ Object.defineProperty(module.exports, '_internals', { value: { _interpArgs, _normalizeCallerSchemaUncached, resolveSchemaForPreprocess, buildCoercer, buildRemover, _jsonSyntaxRejection, ABORT_EARLY_RESULT, HYBRID_TIER_CALLS, SIMDJSON_THRESHOLD, VALID_RESULT, _bindEntry, _bindVerdict, _compileCache, _compileCacheSet, _mustReject, _rememberInstance, _verboseWrap, compileCacheKey, getNative, isV1Dialect, resolveSchemaByPath }, enumerable: false });
package/lib/version.js CHANGED
@@ -7,4 +7,4 @@
7
7
  //
8
8
  // Kept in lockstep with package.json by `tests/test_version_sync.js`.
9
9
 
10
- module.exports = '1.58.1';
10
+ module.exports = '1.60.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.58.1",
3
+ "version": "1.60.0",
4
4
  "description": "JSON Schema validator that compiles to plain JavaScript, still runs where code generation is blocked, and can be compiled away at build time. Passes the full official test suite.",
5
5
  "type": "commonjs",
6
6
  "main": "index.js",
@@ -186,13 +186,13 @@
186
186
  "codegen.d.ts"
187
187
  ],
188
188
  "optionalDependencies": {
189
- "@ata-validator/native-darwin-arm64": "1.58.1",
190
- "@ata-validator/native-darwin-x64": "1.58.1",
191
- "@ata-validator/native-linux-arm64-gnu": "1.58.1",
192
- "@ata-validator/native-linux-arm64-musl": "1.58.1",
193
- "@ata-validator/native-linux-x64-gnu": "1.58.1",
194
- "@ata-validator/native-linux-x64-musl": "1.58.1",
195
- "@ata-validator/native-win32-x64": "1.58.1"
189
+ "@ata-validator/native-darwin-arm64": "1.60.0",
190
+ "@ata-validator/native-darwin-x64": "1.60.0",
191
+ "@ata-validator/native-linux-arm64-gnu": "1.60.0",
192
+ "@ata-validator/native-linux-arm64-musl": "1.60.0",
193
+ "@ata-validator/native-linux-x64-gnu": "1.60.0",
194
+ "@ata-validator/native-linux-x64-musl": "1.60.0",
195
+ "@ata-validator/native-win32-x64": "1.60.0"
196
196
  },
197
197
  "peerDependencies": {
198
198
  "yaml": "^2.0.0"