ata-validator 1.59.0 → 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/lib/aot-impl.js CHANGED
@@ -379,20 +379,20 @@ function defaultsDecl(validator) {
379
379
  // the runtime accepts. Where the generated pass declines and the runtime
380
380
  // would change the data with its closure pass, there is nothing to carry it
381
381
  // in, and the bundle refuses. `useDefaults` alone runs applyDefaults.
382
- // Returns { decl, call }.
383
- function bundlePreprocess(validator, opts, fillDecl, label) {
382
+ // Returns { decl, call }, the call naming the data parameter `arg`.
383
+ function bundlePreprocess(validator, opts, fillDecl, label, arg = 'd') {
384
384
  const coerceTypes = opts && opts.coerceTypes ? opts.coerceTypes : false;
385
385
  const removeAdditional = opts && opts.removeAdditional ? opts.removeAdditional : false;
386
386
  const useDefaults = !!(opts && opts.useDefaults === true);
387
- if (!coerceTypes && !removeAdditional) return { decl: '', call: useDefaults && fillDecl ? 'applyDefaults(d);' : '' };
387
+ if (!coerceTypes && !removeAdditional) return { decl: '', call: useDefaults && fillDecl ? `applyDefaults(${arg});` : '' };
388
388
  const internals = require('./validator-core')._internals;
389
389
  const schema = internals.resolveSchemaForPreprocess(validator._schemaObj, validator._schemaMap);
390
390
  let src = null;
391
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(d);' };
392
+ if (src !== null) return { decl: `function _ataPre(d) {\n${src}\n}\n`, call: `_ataPre(${arg});` };
393
393
  const changes = (coerceTypes && internals.buildCoercer(schema) !== null) || (removeAdditional && internals.buildRemover(schema) !== null);
394
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(d);' : '' };
395
+ return { decl: '', call: useDefaults && fillDecl ? `applyDefaults(${arg});` : '' };
396
396
  }
397
397
 
398
398
  function toStandaloneModule(validator, opts) {
@@ -408,6 +408,12 @@ function toStandaloneModule(validator, opts) {
408
408
  const source = !!(opts && opts.source);
409
409
  const sourceMap = opts && opts.sourceMap ? opts.sourceMap : null;
410
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');
411
417
  const src = jsFn._source;
412
418
 
413
419
  let errCore = '';
@@ -636,7 +642,7 @@ function toStandaloneModule(validator, opts) {
636
642
  // reading patterns. A second declaration of that name shadowed it and the
637
643
  // prelude called this one instead, reaching _fn before its initialiser ran.
638
644
  const parseCore = cloneExpr
639
- ? `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`
640
646
  : '';
641
647
 
642
648
  // validateJSON(): parse the text, validate, and on failure attach a
@@ -725,8 +731,8 @@ const ABORT = Object.freeze({
725
731
  ${closureDecls}${sharedBlock}${preambleDecls}${formatDecls}const _fn = function(d) {
726
732
  ${src}
727
733
  };
728
- ${errCore}${orderDecl}${compactCore}function isValid(data) { return _fn(data); }
729
- 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}; }
730
736
  ${parseCore}${positionsCore}${fillDecl}${hashDecl}${exports}`;
731
737
  }
732
738
 
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 };
@@ -358,64 +358,6 @@ function _jsonSyntaxRejection(e) {
358
358
 
359
359
 
360
360
 
361
- // Paths repeat across rejections of the same shape, so the parsed segment
362
- // list is cached per path string. Entries are frozen: the same array is
363
- // handed to every issue that names the path.
364
- //
365
- // The cache is bucketed by length and searched with ===, not keyed in a Map.
366
- // A path with an array index in it is concatenated afresh by every rejection,
367
- // and a Map has to hash each such string from scratch before it can look
368
- // anything up; that hashing was 30% of a Standard Schema rejection with
369
- // sixteen issues. Comparing against the few paths of the same length costs
370
- // less. Both levels are bounded so a stream of array indexes cannot grow it
371
- // without limit: a full bucket stops caching, too many lengths clear it.
372
- const _pathBuckets = new Map();
373
- const PATH_BUCKET_MAX = 32;
374
- const PATH_LENGTHS_MAX = 256;
375
- function parsePointerPath(path) {
376
- if (!path) return EMPTY_PATH;
377
- const n = path.length;
378
- let bucket = _pathBuckets.get(n);
379
- if (bucket !== undefined) {
380
- for (let i = 0; i < bucket.length; i += 2) if (bucket[i] === path) return bucket[i + 1];
381
- } else {
382
- if (_pathBuckets.size >= PATH_LENGTHS_MAX) _pathBuckets.clear();
383
- bucket = [];
384
- _pathBuckets.set(n, bucket);
385
- }
386
- const segs = Object.freeze(parsePointerPathUncached(path));
387
- if (bucket.length < PATH_BUCKET_MAX * 2) bucket.push(path, segs);
388
- return segs;
389
- }
390
- const EMPTY_PATH = Object.freeze([]);
391
-
392
- function parsePointerPathUncached(path) {
393
- // One pass, no intermediate arrays. Per Standard Schema V1 an array index
394
- // is emitted as a number and an object key as a string; a segment is an
395
- // index when it is all digits with no leading zero.
396
- const out = [];
397
- const n = path.length;
398
- let start = 1;
399
- for (let i = 1; i <= n; i++) {
400
- if (i !== n && path.charCodeAt(i) !== 47) continue;
401
- if (i > start) {
402
- let seg = path.slice(start, i);
403
- if (seg.indexOf('~') >= 0) seg = seg.replace(/~1/g, '/').replace(/~0/g, '~');
404
- const c0 = seg.charCodeAt(0);
405
- let numeric = c0 >= 48 && c0 <= 57 && (seg.length === 1 || c0 !== 48);
406
- if (numeric) {
407
- for (let k = 1; k < seg.length; k++) {
408
- const c = seg.charCodeAt(k);
409
- if (c < 48 || c > 57) { numeric = false; break; }
410
- }
411
- }
412
- out.push({ key: numeric ? Number(seg) : seg });
413
- }
414
- start = i + 1;
415
- }
416
- return out;
417
- }
418
-
419
361
  function createPaddedBuffer(jsonStr) {
420
362
  if (typeof Buffer === 'undefined') throw new Error('createPaddedBuffer requires Node.js Buffer');
421
363
  const jsonBuf = Buffer.from(jsonStr);
@@ -1838,23 +1780,9 @@ class Validator {
1838
1780
  }
1839
1781
  };
1840
1782
 
1841
- // Standard Schema V1
1783
+ // Standard Schema V1, the same object a Validator hands out.
1842
1784
  Object.defineProperty(v, "~standard", {
1843
- value: Object.freeze({
1844
- version: 1,
1845
- vendor: "ata-validator",
1846
- jsonSchema: standardJsonSchema(() => schemaObj),
1847
- validate(value) {
1848
- const result = v.validate(value);
1849
- if (result.valid) return { value };
1850
- return {
1851
- issues: result.errors.map((e) => ({
1852
- message: e.message,
1853
- path: parsePointerPath(e.instancePath),
1854
- })),
1855
- };
1856
- },
1857
- }),
1785
+ value: require('./standard-schema').standardSchemaFor(v, () => schemaObj, (self, value) => self.validate(value)),
1858
1786
  writable: false,
1859
1787
  enumerable: false,
1860
1788
  configurable: false,
@@ -1994,74 +1922,19 @@ function defineSchema (schema) {
1994
1922
  // bound closure, stores it on the instance as an ordinary writable property
1995
1923
  // and returns it. The setter keeps the compile step's plain assignments
1996
1924
  // (`this.validate = fn`) working before the getter has ever run. Detached
1997
- // Standard JSON Schema, the `jsonSchema` member of `~standard`: what a consumer
1998
- // such as the MCP SDK reads to publish a schema, from `input({ target })`.
1999
- // ata validates the JSON Schema it was given and does not transform values, so
2000
- // input and output are the same document, handed back as a copy when the
2001
- // target is the schema's dialect: draft-07 when it declares draft-07, 2020-12
2002
- // when it declares 2020-12 or nothing, as ata reads it. Any other target
2003
- // throws, as the specification asks, rather than returning a document that
2004
- // would mean something else there.
2005
- function standardJsonSchema(getSchema) {
2006
- const convert = (options) => {
2007
- const target = options && options.target;
2008
- const schema = getSchema();
2009
- if (schema === true) return {};
2010
- if (schema === false) return { not: {} };
2011
- const declared = typeof schema.$schema === 'string' ? schema.$schema : '';
2012
- const dialect = declared === '' || declared.includes('draft/2020-12/schema') ? 'draft-2020-12'
2013
- : declared.includes('draft-07/schema') ? 'draft-07' : declared;
2014
- if (target !== dialect) {
2015
- throw new TypeError(`ata-validator cannot provide a ${dialect} schema as ${String(target)}; it does not convert between dialects`);
2016
- }
2017
- return JSON.parse(JSON.stringify(schema));
2018
- };
2019
- return Object.freeze({ input: convert, output: convert });
2020
- }
2021
-
2022
1925
  // use (`const f = v.validate`) keeps working because the closure binds the
2023
1926
  // instance.
2024
1927
  // Standard Schema V1. Built on first read, then pinned to the instance with
2025
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`.
2026
1932
  Object.defineProperty(Validator.prototype, "~standard", {
2027
1933
  configurable: true,
1934
+ enumerable: false,
2028
1935
  get() {
2029
- const self = this;
2030
- const std = Object.freeze({
2031
- version: 1,
2032
- vendor: "ata-validator",
2033
- jsonSchema: standardJsonSchema(() => self._rawSchema),
2034
- validate(value) {
2035
- const result = self.validate(value);
2036
- if (result.valid) {
2037
- return { value };
2038
- }
2039
- // An issue carries a message and a path and nothing else, so the
2040
- // suggestion and source-frame work the rich error path does would be
2041
- // thrown away here. Take the raw, schema-ordered list when the result
2042
- // offers one; fall back to the public list otherwise.
2043
- const raw = typeof result._ataRaw === 'function' ? result._ataRaw() : result.errors;
2044
- const issues = new Array(raw.length);
2045
- for (let i = 0; i < raw.length; i++) {
2046
- const err = raw[i];
2047
- const path = err.instancePath != null ? err.instancePath : (err.path || '');
2048
- let message = err.message;
2049
- if (!message) {
2050
- // The native engine reports numeric codes without a message; the
2051
- // enrich pass knows how to word those. Rare, so required lazily.
2052
- message = require('./enrich-error').enrich(err, {}).message;
2053
- }
2054
- issues[i] = { message, path: parsePointerPath(path) };
2055
- }
2056
- return { issues };
2057
- },
2058
- });
2059
- Object.defineProperty(this, "~standard", {
2060
- value: std,
2061
- writable: false,
2062
- enumerable: false,
2063
- configurable: false,
2064
- });
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 });
2065
1938
  return std;
2066
1939
  },
2067
1940
  });
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.59.0';
10
+ module.exports = '1.60.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.59.0",
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.59.0",
190
- "@ata-validator/native-darwin-x64": "1.59.0",
191
- "@ata-validator/native-linux-arm64-gnu": "1.59.0",
192
- "@ata-validator/native-linux-arm64-musl": "1.59.0",
193
- "@ata-validator/native-linux-x64-gnu": "1.59.0",
194
- "@ata-validator/native-linux-x64-musl": "1.59.0",
195
- "@ata-validator/native-win32-x64": "1.59.0"
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"