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/validator-core.js
CHANGED
|
@@ -19,6 +19,14 @@ const { normalizeKeywords, schemaUsesKeywords } = require('./keywords');
|
|
|
19
19
|
// bundle built from lite.js never contains it. Until something registers, the
|
|
20
20
|
// core behaves exactly as it does where `new Function` is blocked: every
|
|
21
21
|
// schema runs on the interpreted engine, which passes the same test suite.
|
|
22
|
+
// Whether this process runs code compiled ahead of time, where generated
|
|
23
|
+
// code falls to an interpreter: Bun's flag for its compiler, or the
|
|
24
|
+
// environment saying so (an executable built before the flag existed).
|
|
25
|
+
function aotHost() {
|
|
26
|
+
if (env('ATA_ENGINE') === 'interpreter') return true;
|
|
27
|
+
const b = typeof Bun !== 'undefined' ? Bun : undefined;
|
|
28
|
+
return !!(b && b.isAOT === true);
|
|
29
|
+
}
|
|
22
30
|
let _codegen = null;
|
|
23
31
|
let compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined;
|
|
24
32
|
function _registerCodegen(engine) {
|
|
@@ -37,6 +45,10 @@ let _aotLoader = null;
|
|
|
37
45
|
// index.browser.mjs, null in every other entry, which then carry none of it.
|
|
38
46
|
let _tiering = null;
|
|
39
47
|
function _registerTiering(t) { _tiering = t; }
|
|
48
|
+
// Called with each validator the constructor finishes. The Node entry uses it
|
|
49
|
+
// to warm the engine once the event loop is free (see index.js).
|
|
50
|
+
let _onBuilt = null;
|
|
51
|
+
function _registerBuiltHook(f) { _onBuilt = f; }
|
|
40
52
|
function _registerAot(load) {
|
|
41
53
|
_aotLoader = load;
|
|
42
54
|
}
|
|
@@ -358,64 +370,6 @@ function _jsonSyntaxRejection(e) {
|
|
|
358
370
|
|
|
359
371
|
|
|
360
372
|
|
|
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
373
|
function createPaddedBuffer(jsonStr) {
|
|
420
374
|
if (typeof Buffer === 'undefined') throw new Error('createPaddedBuffer requires Node.js Buffer');
|
|
421
375
|
const jsonBuf = Buffer.from(jsonStr);
|
|
@@ -830,7 +784,13 @@ class Validator {
|
|
|
830
784
|
if (options.engine !== undefined && options.engine !== 'auto' && options.engine !== 'interpreter') {
|
|
831
785
|
throw new TypeError("engine must be 'auto' or 'interpreter', got " + JSON.stringify(options.engine));
|
|
832
786
|
}
|
|
833
|
-
|
|
787
|
+
// Under an ahead-of-time compiler the generated code is interpreted and
|
|
788
|
+
// the interpreted engine, plain JavaScript, is compiled: measured on
|
|
789
|
+
// Bun's compiler, 80 against 530 ns a document. So the default flips
|
|
790
|
+
// there: Bun.isAOT once Bun ships it, ATA_ENGINE=interpreter until then
|
|
791
|
+
// and for any other host. `engine: 'auto'` asked for by name keeps the
|
|
792
|
+
// generated code.
|
|
793
|
+
this._interpretOnly = options.engine === 'interpreter' || (options.engine === undefined && aotHost());
|
|
834
794
|
this._initialized = false;
|
|
835
795
|
this._nativeReady = false;
|
|
836
796
|
this._compiled = null;
|
|
@@ -919,8 +879,10 @@ class Validator {
|
|
|
919
879
|
this._verdictTail = null;
|
|
920
880
|
this._validateTail = null;
|
|
921
881
|
this._entryExt = null;
|
|
922
|
-
// The browser build warms the engine once, when the page is idle (lib/tiering.js)
|
|
882
|
+
// The browser build warms the engine once, when the page is idle (lib/tiering.js);
|
|
883
|
+
// the Node entry does the same once the event loop is free (index.js).
|
|
923
884
|
if (_tiering !== null) _tiering.built(this);
|
|
885
|
+
if (_onBuilt !== null) _onBuilt(this);
|
|
924
886
|
|
|
925
887
|
// Public methods start as memoized accessors on the prototype; nothing is
|
|
926
888
|
// allocated per instance until one is first read. See _defineLazyMethod
|
|
@@ -1008,7 +970,10 @@ class Validator {
|
|
|
1008
970
|
|
|
1009
971
|
// Check cache first -- reuse compiled functions for same schema
|
|
1010
972
|
const sm = this._schemaMap.size > 0 ? this._schemaMap : null;
|
|
1011
|
-
|
|
973
|
+
// The generated code takes the form of the target engine (js-compiler's
|
|
974
|
+
// setTargetEngine), so a function written for one engine is never handed
|
|
975
|
+
// to a compile asked for the other in the same process.
|
|
976
|
+
const mapKey = compileCacheKey(this._schemaStr, this._schemaMap) + '\u0000' + (_codegen && _codegen.jsCompiler && typeof _codegen.jsCompiler.targetEngine === 'function' ? _codegen.jsCompiler.targetEngine() : 'v8');
|
|
1012
977
|
var _forceNapi = this._interpretOnly || env('ATA_FORCE_NAPI');
|
|
1013
978
|
// Custom formats are JS functions: bypass the compile cache since they can
|
|
1014
979
|
// differ between validators that share the same schema string. An
|
|
@@ -1838,23 +1803,9 @@ class Validator {
|
|
|
1838
1803
|
}
|
|
1839
1804
|
};
|
|
1840
1805
|
|
|
1841
|
-
// Standard Schema V1
|
|
1806
|
+
// Standard Schema V1, the same object a Validator hands out.
|
|
1842
1807
|
Object.defineProperty(v, "~standard", {
|
|
1843
|
-
value:
|
|
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
|
-
}),
|
|
1808
|
+
value: require('./standard-schema').standardSchemaFor(v, () => schemaObj, (self, value) => self.validate(value)),
|
|
1858
1809
|
writable: false,
|
|
1859
1810
|
enumerable: false,
|
|
1860
1811
|
configurable: false,
|
|
@@ -1985,6 +1936,14 @@ function compile(schema, opts) {
|
|
|
1985
1936
|
// JSONSchema type (see index.d.ts) to an inline schema object so TypeScript
|
|
1986
1937
|
// gives autocomplete and value checking while authoring. Returns the schema
|
|
1987
1938
|
// untouched so it can be passed straight to Validator, toStandaloneModule, etc.
|
|
1939
|
+
// `schemaOf<T>()`: the JSON Schema of a TypeScript type, written in by
|
|
1940
|
+
// @ata-project/unplugin at build time from the type and its JSDoc tags. It
|
|
1941
|
+
// has no run-time form: a call that reaches here was not replaced, and the
|
|
1942
|
+
// loud error is better than an empty schema that accepts everything.
|
|
1943
|
+
function schemaOf () {
|
|
1944
|
+
throw new Error('schemaOf<T>() is replaced at build time by @ata-project/unplugin (with ts-json-schema-generator installed); it has no run-time form. Write the JSON Schema, or build with the plugin.');
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1988
1947
|
function defineSchema (schema) {
|
|
1989
1948
|
return schema;
|
|
1990
1949
|
}
|
|
@@ -1994,74 +1953,19 @@ function defineSchema (schema) {
|
|
|
1994
1953
|
// bound closure, stores it on the instance as an ordinary writable property
|
|
1995
1954
|
// and returns it. The setter keeps the compile step's plain assignments
|
|
1996
1955
|
// (`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
1956
|
// use (`const f = v.validate`) keeps working because the closure binds the
|
|
2023
1957
|
// instance.
|
|
2024
1958
|
// Standard Schema V1. Built on first read, then pinned to the instance with
|
|
2025
1959
|
// the same descriptor the constructor used to install eagerly.
|
|
1960
|
+
// Lazy on the prototype, pinned to the instance on first read; the object
|
|
1961
|
+
// comes from lib/standard-schema.js, shared with the wrapper around a module
|
|
1962
|
+
// written ahead of time, and loaded only when something reads `~standard`.
|
|
2026
1963
|
Object.defineProperty(Validator.prototype, "~standard", {
|
|
2027
1964
|
configurable: true,
|
|
1965
|
+
enumerable: false,
|
|
2028
1966
|
get() {
|
|
2029
|
-
const
|
|
2030
|
-
|
|
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
|
-
});
|
|
1967
|
+
const std = require('./standard-schema').standardSchemaFor(this, (self) => self._rawSchema, (self, value) => self.validate(value));
|
|
1968
|
+
Object.defineProperty(this, "~standard", { value: std, writable: false, enumerable: false, configurable: false });
|
|
2065
1969
|
return std;
|
|
2066
1970
|
},
|
|
2067
1971
|
});
|
|
@@ -2529,12 +2433,14 @@ module.exports = {
|
|
|
2529
2433
|
SIMDJSON_PADDING,
|
|
2530
2434
|
parseJSON,
|
|
2531
2435
|
defineSchema,
|
|
2436
|
+
schemaOf,
|
|
2532
2437
|
};
|
|
2533
2438
|
// How an entry registers the code generator. Not enumerable, so the exported
|
|
2534
2439
|
// surface of `require('ata-validator')` is what it was.
|
|
2535
2440
|
Object.defineProperty(module.exports, '_registerCodegen', { value: _registerCodegen, enumerable: false });
|
|
2536
2441
|
Object.defineProperty(module.exports, '_setCodegenLoader', { value: (load) => { _loadCodegen = load; }, enumerable: false });
|
|
2537
2442
|
Object.defineProperty(module.exports, '_registerTiering', { value: _registerTiering, enumerable: false });
|
|
2443
|
+
Object.defineProperty(module.exports, '_registerBuiltHook', { value: _registerBuiltHook, enumerable: false });
|
|
2538
2444
|
Object.defineProperty(module.exports, '_codegenRegistered', { value: () => _codegen !== null, enumerable: false });
|
|
2539
2445
|
Object.defineProperty(module.exports, '_tiering', { get: () => _tiering, enumerable: false });
|
|
2540
2446
|
Object.defineProperty(module.exports, '_registerAot', { value: _registerAot, enumerable: false });
|
package/lib/verify.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// `ata verify`: the proof that a compiled module answers as the runtime
|
|
3
|
+
// Validator does, run against a project's own schemas. For every schema the
|
|
4
|
+
// module written by `ata build` is loaded, documents are made from the schema
|
|
5
|
+
// (valid ones, and ones with one rule broken) and read from the project's
|
|
6
|
+
// samples, and both the module and a Validator built from the schema at
|
|
7
|
+
// verification time answer each one; the verdicts and the error lists have to
|
|
8
|
+
// agree. The module's schema hash is checked against the schema as it is now,
|
|
9
|
+
// so a module built from an older schema is reported stale.
|
|
10
|
+
//
|
|
11
|
+
// A validator that wrongly rejects gets a bug report; one that wrongly accepts
|
|
12
|
+
// does not. This command exists so a build-time module gets checked in CI the
|
|
13
|
+
// way the engine is checked here, on every change.
|
|
14
|
+
|
|
15
|
+
const fs = require('fs')
|
|
16
|
+
const path = require('path')
|
|
17
|
+
const { pathToFileURL } = require('url')
|
|
18
|
+
|
|
19
|
+
// A small deterministic generator (xorshift32), so a run can be repeated.
|
|
20
|
+
function rng (seed) {
|
|
21
|
+
let x = (seed >>> 0) || 0x9e3779b9
|
|
22
|
+
const next = () => { x ^= x << 13; x ^= x >>> 17; x ^= x << 5; return (x >>> 0) / 4294967296 }
|
|
23
|
+
return { next, int: (n) => Math.floor(next() * n), pick: (a) => a[Math.floor(next() * a.length)], chance: (p) => next() < p }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const WORDS = ['ada', 'Zoë', 'order', 'x', 'north-west', 'a b', 'ÄÖÜ', 'sku-1001', 'TENOFF', 'hello world', '', '0', 'null', '😀', 'line\nbreak', ' ']
|
|
27
|
+
// Strings tried against a `pattern` the generator cannot read: the common
|
|
28
|
+
// shapes of identifiers, codes, addresses, dates and numbers.
|
|
29
|
+
const PATTERN_CANDIDATES = [
|
|
30
|
+
'a', 'ab', 'abc', 'abc-123', 'ABC', 'A1', 'x_y', 'hello', 'Hello World', 'sku-1001', 'SKU-1001', 'TENOFF',
|
|
31
|
+
'zoe@example.com', 'a@b.co', 'user.name+tag@example.org', '2026-10-25', '2026-10-25T10:00:00Z', '10:00:00',
|
|
32
|
+
'0', '1', '42', '-7', '3.14', '1e3', '0001', '+905551112233', '555-1234', '#fff', '#a1b2c3',
|
|
33
|
+
'https://example.com/path', 'http://x.io', 'example.com', '/a/b/c', 'v1.2.3', '1.2.3', 'en-US', 'TR',
|
|
34
|
+
'123e4567-e89b-12d3-a456-426614174000', '192.168.0.1', '::1', 'true', 'null', 'aaaaaaaaaaaaaaaa', 'ÄÖÜ', 'çş'
|
|
35
|
+
]
|
|
36
|
+
const FORMAT_EXAMPLES = {
|
|
37
|
+
email: ['zoe@example.com', 'a@b.co', 'not an email', 'x@', '@y.com'],
|
|
38
|
+
'date-time': ['2026-10-25T10:00:00Z', '2026-10-25T10:00:00+03:00', '2026-10-25 10:00', 'yesterday'],
|
|
39
|
+
date: ['2026-10-25', '2026-13-01', '2026-2-5'],
|
|
40
|
+
time: ['10:00:00Z', '25:00:00Z', '10:00'],
|
|
41
|
+
uuid: ['123e4567-e89b-12d3-a456-426614174000', '123e4567-e89b-12d3-a456', 'not-a-uuid'],
|
|
42
|
+
uri: ['https://example.com/a?b=1', 'example.com', '/relative', 'mailto:a@b.co'],
|
|
43
|
+
'uri-reference': ['https://example.com', '/path', '#frag', ' spaced'],
|
|
44
|
+
ipv4: ['192.168.0.1', '256.1.1.1', '1.2.3'],
|
|
45
|
+
ipv6: ['::1', '2001:db8::1', '2001:db8:::1', '1.2.3.4'],
|
|
46
|
+
hostname: ['example.com', 'a-b.c', '-bad.com', 'x'.repeat(70)],
|
|
47
|
+
regex: ['^a+$', '([', '\\d+'],
|
|
48
|
+
'json-pointer': ['/a/b', 'a/b', '/a~1b', '/a~2'],
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// A value meant to satisfy `schema`, built from its keywords; where the
|
|
52
|
+
// schema says little, something plausible of the kind.
|
|
53
|
+
function makeValid (schema, r, defs, depth) {
|
|
54
|
+
if (schema === true || schema === undefined) return r.pick([1, 'x', true, null, {}, []])
|
|
55
|
+
if (schema === false) return undefined
|
|
56
|
+
if (typeof schema !== 'object' || schema === null) return null
|
|
57
|
+
if (schema.$ref !== undefined && typeof schema.$ref === 'string') {
|
|
58
|
+
const t = resolveLocal(schema.$ref, defs)
|
|
59
|
+
if (t !== undefined && depth < 6) return makeValid(t, r, defs, depth + 1)
|
|
60
|
+
}
|
|
61
|
+
if (schema.const !== undefined) return clone(schema.const)
|
|
62
|
+
if (Array.isArray(schema.enum) && schema.enum.length) return clone(r.pick(schema.enum))
|
|
63
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length) return makeValid(r.pick(schema.oneOf), r, defs, depth + 1)
|
|
64
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.length) return makeValid(r.pick(schema.anyOf), r, defs, depth + 1)
|
|
65
|
+
if (Array.isArray(schema.allOf) && schema.allOf.length) return makeValid(mergeAllOf(schema), r, defs, depth + 1)
|
|
66
|
+
let type = schema.type
|
|
67
|
+
if (Array.isArray(type)) type = r.pick(type)
|
|
68
|
+
if (type === undefined) {
|
|
69
|
+
if (schema.properties || schema.required || schema.additionalProperties !== undefined) type = 'object'
|
|
70
|
+
else if (schema.items || schema.prefixItems || schema.minItems !== undefined) type = 'array'
|
|
71
|
+
else if (schema.minLength !== undefined || schema.maxLength !== undefined || schema.pattern !== undefined || schema.format !== undefined) type = 'string'
|
|
72
|
+
else if (schema.minimum !== undefined || schema.maximum !== undefined || schema.multipleOf !== undefined) type = 'number'
|
|
73
|
+
else type = r.pick(['string', 'number', 'object', 'array', 'boolean', 'null'])
|
|
74
|
+
}
|
|
75
|
+
switch (type) {
|
|
76
|
+
case 'null': return null
|
|
77
|
+
case 'boolean': return r.chance(0.5)
|
|
78
|
+
case 'integer':
|
|
79
|
+
case 'number': {
|
|
80
|
+
let lo = schema.minimum !== undefined ? schema.minimum : schema.exclusiveMinimum !== undefined && typeof schema.exclusiveMinimum === 'number' ? schema.exclusiveMinimum + 1 : -1000
|
|
81
|
+
let hi = schema.maximum !== undefined ? schema.maximum : schema.exclusiveMaximum !== undefined && typeof schema.exclusiveMaximum === 'number' ? schema.exclusiveMaximum - 1 : 1000
|
|
82
|
+
if (hi < lo) hi = lo
|
|
83
|
+
let v = lo + r.next() * (hi - lo)
|
|
84
|
+
if (type === 'integer' || r.chance(0.6)) v = Math.round(v)
|
|
85
|
+
if (typeof schema.multipleOf === 'number' && schema.multipleOf > 0) v = Math.round(v / schema.multipleOf) * schema.multipleOf
|
|
86
|
+
if (v < lo) v = lo
|
|
87
|
+
if (v > hi) v = hi
|
|
88
|
+
return v
|
|
89
|
+
}
|
|
90
|
+
case 'string': {
|
|
91
|
+
if (schema.format && FORMAT_EXAMPLES[schema.format]) return r.pick(FORMAT_EXAMPLES[schema.format])
|
|
92
|
+
const min = schema.minLength || 0
|
|
93
|
+
const max = schema.maxLength !== undefined ? schema.maxLength : Math.max(min, 12)
|
|
94
|
+
if (schema.pattern) {
|
|
95
|
+
const simple = /^\^?\[([a-zA-Z0-9-]+)\]([+*])?\$?$/.exec(schema.pattern)
|
|
96
|
+
if (simple) {
|
|
97
|
+
const set = simple[1].includes('a-z') ? 'abcdefgh' : simple[1].includes('0-9') ? '0123456789' : simple[1].includes('A-Z') ? 'ABCDEFGH' : simple[1].replace(/-/g, '')
|
|
98
|
+
let s = ''
|
|
99
|
+
const n = Math.max(min, Math.min(max, 1 + r.int(8)))
|
|
100
|
+
for (let i = 0; i < n; i++) s += set[r.int(set.length)]
|
|
101
|
+
return s
|
|
102
|
+
}
|
|
103
|
+
// Any other pattern: a candidate that matches, when one does, so the
|
|
104
|
+
// generated documents reach past the pattern; else a word, which the
|
|
105
|
+
// schema then refuses, and that is a document too.
|
|
106
|
+
let re = null
|
|
107
|
+
try { re = new RegExp(schema.pattern, 'u') } catch {}
|
|
108
|
+
if (re) {
|
|
109
|
+
const matching = PATTERN_CANDIDATES.filter((c) => re.test(c) && [...c].length >= min && [...c].length <= max)
|
|
110
|
+
if (matching.length) return r.pick(matching)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
let s = r.pick(WORDS)
|
|
114
|
+
while ([...s].length < min) s += 'a'
|
|
115
|
+
if ([...s].length > max) s = [...s].slice(0, max).join('')
|
|
116
|
+
return s
|
|
117
|
+
}
|
|
118
|
+
case 'array': {
|
|
119
|
+
const min = schema.minItems || 0
|
|
120
|
+
const max = schema.maxItems !== undefined ? schema.maxItems : Math.max(min, 4)
|
|
121
|
+
const n = depth > 4 ? min : min + r.int(Math.max(1, max - min + 1))
|
|
122
|
+
const out = []
|
|
123
|
+
for (let i = 0; i < n; i++) {
|
|
124
|
+
const sub = Array.isArray(schema.prefixItems) && i < schema.prefixItems.length ? schema.prefixItems[i] : Array.isArray(schema.items) ? (schema.items[i] !== undefined ? schema.items[i] : schema.additionalItems) : schema.items
|
|
125
|
+
if (sub === false) break
|
|
126
|
+
out.push(makeValid(sub === undefined ? true : sub, r, defs, depth + 1))
|
|
127
|
+
}
|
|
128
|
+
if (schema.uniqueItems) return out.filter((v, i, a) => a.findIndex((w) => JSON.stringify(w) === JSON.stringify(v)) === i)
|
|
129
|
+
return out
|
|
130
|
+
}
|
|
131
|
+
case 'object':
|
|
132
|
+
default: {
|
|
133
|
+
const out = {}
|
|
134
|
+
const props = schema.properties && typeof schema.properties === 'object' ? schema.properties : {}
|
|
135
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : [])
|
|
136
|
+
for (const k of Object.keys(props)) {
|
|
137
|
+
if (k === '__proto__') continue
|
|
138
|
+
if (required.has(k) || r.chance(0.7)) out[k] = makeValid(props[k], r, defs, depth + 1)
|
|
139
|
+
}
|
|
140
|
+
for (const k of required) if (!(k in out) && k !== '__proto__') out[k] = makeValid(props[k] === undefined ? true : props[k], r, defs, depth + 1)
|
|
141
|
+
if (schema.additionalProperties !== false && !schema.patternProperties && r.chance(0.2) && depth < 4) out[`extra${r.int(3)}`] = makeValid(typeof schema.additionalProperties === 'object' ? schema.additionalProperties : true, r, defs, depth + 1)
|
|
142
|
+
if (schema.patternProperties && r.chance(0.5)) {
|
|
143
|
+
for (const [p, sub] of Object.entries(schema.patternProperties)) {
|
|
144
|
+
const m = /^\^?([a-zA-Z_]+)/.exec(p)
|
|
145
|
+
if (m) out[m[1] + r.int(9)] = makeValid(sub, r, defs, depth + 1)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return out
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function mergeAllOf (schema) {
|
|
154
|
+
const merged = {}
|
|
155
|
+
for (const b of schema.allOf) if (typeof b === 'object' && b !== null) Object.assign(merged, b, { properties: { ...(merged.properties || {}), ...(b.properties || {}) }, required: [...(merged.required || []), ...(b.required || [])] })
|
|
156
|
+
for (const k of Object.keys(schema)) if (k !== 'allOf' && merged[k] === undefined) merged[k] = schema[k]
|
|
157
|
+
return merged
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function resolveLocal (ref, defs) {
|
|
161
|
+
const m = /^#\/(\$defs|definitions)\/([^/]+)$/.exec(ref)
|
|
162
|
+
if (!m || !defs[m[1]]) return undefined
|
|
163
|
+
const key = decodeURIComponent(m[2].replace(/~1/g, '/').replace(/~0/g, '~'))
|
|
164
|
+
return Object.prototype.hasOwnProperty.call(defs[m[1]], key) ? defs[m[1]][key] : undefined
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function clone (v) { return v === undefined ? v : JSON.parse(JSON.stringify(v)) }
|
|
168
|
+
|
|
169
|
+
// One rule broken somewhere in `doc`: a kind swapped, a key dropped or added,
|
|
170
|
+
// a number pushed out of range, a string lengthened or emptied, an array
|
|
171
|
+
// emptied or an element duplicated. The point is a document the schema is
|
|
172
|
+
// likely to refuse, so both engines get to disagree on refusals too.
|
|
173
|
+
function mutate (doc, r) {
|
|
174
|
+
const paths = []
|
|
175
|
+
const walk = (v, p) => {
|
|
176
|
+
paths.push(p)
|
|
177
|
+
if (Array.isArray(v)) v.forEach((x, i) => walk(x, [...p, i]))
|
|
178
|
+
else if (v && typeof v === 'object') for (const k of Object.keys(v)) walk(v[k], [...p, k])
|
|
179
|
+
}
|
|
180
|
+
walk(doc, [])
|
|
181
|
+
const p = r.pick(paths)
|
|
182
|
+
const get = (o, q) => q.reduce((a, k) => (a == null ? a : a[k]), o)
|
|
183
|
+
const set = (o, q, val) => { if (q.length === 0) return val; const parent = get(o, q.slice(0, -1)); if (parent != null) parent[q[q.length - 1]] = val; return o }
|
|
184
|
+
const del = (o, q) => { const parent = get(o, q.slice(0, -1)); if (Array.isArray(parent)) parent.splice(q[q.length - 1], 1); else if (parent) delete parent[q[q.length - 1]]; return o }
|
|
185
|
+
const v = get(doc, p)
|
|
186
|
+
switch (r.int(8)) {
|
|
187
|
+
case 0: return set(doc, p, r.pick([null, true, 'str', 0, [], {}]))
|
|
188
|
+
case 1: return p.length ? del(doc, p) : set(doc, p, null)
|
|
189
|
+
case 2: if (v && typeof v === 'object' && !Array.isArray(v)) { v[`unexpected${r.int(5)}`] = r.pick([1, 'x', null]); return doc } return set(doc, p, 'unexpected')
|
|
190
|
+
case 3: return set(doc, p, typeof v === 'number' ? r.pick([v * 1000 + 1, -v - 1, v + 0.5, Number.MAX_SAFE_INTEGER, -1]) : -1)
|
|
191
|
+
case 4: return set(doc, p, typeof v === 'string' ? r.pick(['', v.repeat(40), v + ' ', '😀' + v, v.toUpperCase()]) : '')
|
|
192
|
+
case 5: if (Array.isArray(v)) { if (v.length && r.chance(0.5)) v.push(clone(v[0])); else v.length = 0; return doc } return set(doc, p, [])
|
|
193
|
+
case 6: return set(doc, p, typeof v === 'boolean' ? !v : typeof v === 'number' ? String(v) : typeof v === 'string' ? Number(v) || 7 : v)
|
|
194
|
+
default: return JSON.parse(JSON.stringify(doc).replace(/"([a-zA-Z]+)":/, '"$1_":'))
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Documents for `schema`: `count` made, half of them with one rule broken.
|
|
199
|
+
function generate (schema, count, seed) {
|
|
200
|
+
const r = rng(seed)
|
|
201
|
+
const defs = { $defs: schema && schema.$defs, definitions: schema && schema.definitions }
|
|
202
|
+
const docs = []
|
|
203
|
+
for (let i = 0; i < count; i++) {
|
|
204
|
+
let d = makeValid(schema, r, defs, 0)
|
|
205
|
+
if (i % 2 === 1 && d !== undefined) d = mutate(clone(d), r)
|
|
206
|
+
docs.push(d)
|
|
207
|
+
}
|
|
208
|
+
return docs
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// The project's own documents: a JSON file is one document, a JSON Lines
|
|
212
|
+
// file one per line, a JSON array is taken as one document, not several.
|
|
213
|
+
function readSamples (paths) {
|
|
214
|
+
const docs = []
|
|
215
|
+
const files = []
|
|
216
|
+
for (const p of paths) {
|
|
217
|
+
if (!fs.existsSync(p)) continue
|
|
218
|
+
if (fs.statSync(p).isDirectory()) {
|
|
219
|
+
for (const f of fs.readdirSync(p).sort()) if (/\.(json|jsonl|ndjson)$/.test(f)) files.push(path.join(p, f))
|
|
220
|
+
} else files.push(p)
|
|
221
|
+
}
|
|
222
|
+
for (const f of files) {
|
|
223
|
+
const text = fs.readFileSync(f, 'utf8')
|
|
224
|
+
if (/\.(jsonl|ndjson)$/.test(f)) {
|
|
225
|
+
text.split('\n').forEach((line, i) => { if (line.trim()) { try { docs.push({ source: `${f}:${i + 1}`, doc: JSON.parse(line) }) } catch {} } })
|
|
226
|
+
} else {
|
|
227
|
+
try { docs.push({ source: f, doc: JSON.parse(text) }) } catch { docs.push({ source: f, doc: undefined, bad: true }) }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return docs
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function errorKey (e) { return `${e.keyword || ''}@${e.instancePath || ''}` }
|
|
234
|
+
function summarize (result) {
|
|
235
|
+
if (!result || typeof result !== 'object') return 'no result'
|
|
236
|
+
const errs = Array.isArray(result.errors) ? result.errors.map(errorKey).sort() : []
|
|
237
|
+
return `${result.valid ? 'valid' : 'invalid'} ${errs.join(' ')}`
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function loadModule (file) {
|
|
241
|
+
if (/\.cjs$/.test(file)) { delete require.cache[require.resolve(file)]; return require(file) }
|
|
242
|
+
return import(pathToFileURL(file).href + `?t=${Date.now()}`)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Verifies one schema against its module. Returns a report object.
|
|
246
|
+
async function verifyOne ({ schemaFile, moduleFile, schema, generate: count = 200, seed = 1, samples = [] }) {
|
|
247
|
+
const { Validator } = require('..')
|
|
248
|
+
const { schemaHash } = require('./schema-hash')
|
|
249
|
+
const report = { schema: schemaFile, module: moduleFile, documents: 0, generated: 0, samples: 0, valid: 0, agree: 0, disagreements: [], stale: false, missing: false, ataVersion: null, error: null }
|
|
250
|
+
if (!fs.existsSync(moduleFile)) { report.missing = true; return report }
|
|
251
|
+
let mod
|
|
252
|
+
try { mod = await loadModule(moduleFile) } catch (e) { report.error = `module does not load: ${e.message}`; return report }
|
|
253
|
+
if (typeof mod.isValid !== 'function' || typeof mod.validate !== 'function') { report.error = 'module exports no isValid and validate'; return report }
|
|
254
|
+
report.ataVersion = mod.ataVersion || null
|
|
255
|
+
const hash = schemaHash(schema)
|
|
256
|
+
if (mod.schemaHash !== undefined && mod.schemaHash !== hash) report.stale = true
|
|
257
|
+
let runtime
|
|
258
|
+
try { runtime = new Validator(schema, {}) } catch (e) { report.error = `schema does not compile: ${e.message}`; return report }
|
|
259
|
+
const docs = generate(schema, count, seed).map((d, i) => ({ source: `generated #${i + 1}`, doc: d }))
|
|
260
|
+
report.generated = docs.length
|
|
261
|
+
const sampleDocs = readSamples(samples)
|
|
262
|
+
report.samples = sampleDocs.length
|
|
263
|
+
for (const { source, doc, bad } of [...docs, ...sampleDocs]) {
|
|
264
|
+
if (bad || doc === undefined) continue
|
|
265
|
+
report.documents++
|
|
266
|
+
const text = JSON.stringify(doc)
|
|
267
|
+
const a = runtime.validate(JSON.parse(text))
|
|
268
|
+
const b = mod.validate(JSON.parse(text))
|
|
269
|
+
const aIs = runtime.isValidObject(JSON.parse(text))
|
|
270
|
+
const bIs = mod.isValid(JSON.parse(text))
|
|
271
|
+
if (a.valid) report.valid++
|
|
272
|
+
const sa = summarize(a), sb = summarize(b)
|
|
273
|
+
if (sa === sb && aIs === bIs && aIs === a.valid) { report.agree++; continue }
|
|
274
|
+
if (report.disagreements.length < 20) report.disagreements.push({ source, document: text.length > 400 ? text.slice(0, 400) + '…' : text, runtime: sa, module: sb, runtimeIsValid: aIs, moduleIsValid: bIs })
|
|
275
|
+
else report.disagreements.push(null)
|
|
276
|
+
}
|
|
277
|
+
report.disagreements = report.disagreements.filter(Boolean).concat(report.documents - report.agree > 20 ? [{ more: report.documents - report.agree - 20 }] : [])
|
|
278
|
+
return report
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function renderReport (reports, { json = false } = {}) {
|
|
282
|
+
if (json) return JSON.stringify({ ok: reports.every(ok), schemas: reports }, null, 2)
|
|
283
|
+
const lines = []
|
|
284
|
+
for (const r of reports) {
|
|
285
|
+
const name = path.relative(process.cwd(), r.schema) || r.schema
|
|
286
|
+
if (r.missing) { lines.push(`${name}: no module at ${path.relative(process.cwd(), r.module)}; run ata build first`); continue }
|
|
287
|
+
if (r.error) { lines.push(`${name}: ${r.error}`); continue }
|
|
288
|
+
const dis = r.documents - r.agree
|
|
289
|
+
lines.push(`${name}: ${r.documents} documents (${r.generated} generated, ${r.samples} from samples), ${r.valid} valid by the runtime, ${r.agree} agree, ${dis} disagree${r.stale ? '; STALE: the module was built from a different schema' : ''}${r.ataVersion ? ` (module from ata ${r.ataVersion})` : ''}`)
|
|
290
|
+
for (const d of r.disagreements) {
|
|
291
|
+
if (d.more) { lines.push(` ... and ${d.more} more`); continue }
|
|
292
|
+
lines.push(` ${d.source}`)
|
|
293
|
+
lines.push(` document ${d.document}`)
|
|
294
|
+
lines.push(` runtime ${d.runtime}${d.runtimeIsValid !== (d.runtime.startsWith('valid')) ? ` (isValidObject ${d.runtimeIsValid})` : ''}`)
|
|
295
|
+
lines.push(` module ${d.module}${d.moduleIsValid !== (d.module.startsWith('valid')) ? ` (isValid ${d.moduleIsValid})` : ''}`)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const bad = reports.filter((r) => !ok(r))
|
|
299
|
+
lines.push(bad.length === 0
|
|
300
|
+
? `ata verify: ${reports.length} schema${reports.length === 1 ? '' : 's'}, every module answers as the runtime does`
|
|
301
|
+
: `ata verify: ${bad.length} of ${reports.length} schema${reports.length === 1 ? '' : 's'} failed`)
|
|
302
|
+
return lines.join('\n')
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function ok (r) { return !r.missing && !r.error && !r.stale && r.documents === r.agree }
|
|
306
|
+
|
|
307
|
+
// A module given as source text (a plugin's inlined module) held against the
|
|
308
|
+
// runtime the same way: written to a temporary file, loaded, verified.
|
|
309
|
+
async function verifySource (schema, src, opts = {}) {
|
|
310
|
+
const os = require('os')
|
|
311
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ata-verify-src-'))
|
|
312
|
+
const file = path.join(dir, `m.${opts.format === 'cjs' ? 'cjs' : 'mjs'}`)
|
|
313
|
+
fs.writeFileSync(file, src)
|
|
314
|
+
try {
|
|
315
|
+
return await verifyOne({ schemaFile: opts.name || 'schema', moduleFile: file, schema, generate: opts.generate, seed: opts.seed, samples: opts.samples || [] })
|
|
316
|
+
} finally {
|
|
317
|
+
fs.rmSync(dir, { recursive: true, force: true })
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
module.exports = { verifyOne, verifySource, renderReport, generate, mutate, readSamples, ok }
|
package/lib/version.js
CHANGED
package/lite.js
CHANGED
package/lite.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.61.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",
|
|
@@ -88,7 +88,8 @@
|
|
|
88
88
|
"./lib/native-load.js": "./lib/native-load.browser.js",
|
|
89
89
|
"./lib/aot.js": "./lib/aot.browser.js",
|
|
90
90
|
"./lib/buffer-gate.js": "./lib/buffer-gate.browser.js",
|
|
91
|
-
"./lib/interpreter.js": "./lib/interpreter.browser.js"
|
|
91
|
+
"./lib/interpreter.js": "./lib/interpreter.browser.js",
|
|
92
|
+
"./lib/aot-order.js": false
|
|
92
93
|
},
|
|
93
94
|
"scripts": {
|
|
94
95
|
"prepublishOnly": "node tests/test_pack_purity.js && ATA_ALLOW_DOC_PLACEHOLDERS=1 node scripts/check-doc-coverage.js && node tests/test_error_codes_lock.js && node tests/test_safe_regex_embed.js && node tests/test_version_sync.js",
|
|
@@ -186,13 +187,13 @@
|
|
|
186
187
|
"codegen.d.ts"
|
|
187
188
|
],
|
|
188
189
|
"optionalDependencies": {
|
|
189
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
190
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
191
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
192
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
193
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
194
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
195
|
-
"@ata-validator/native-win32-x64": "1.
|
|
190
|
+
"@ata-validator/native-darwin-arm64": "1.61.0",
|
|
191
|
+
"@ata-validator/native-darwin-x64": "1.61.0",
|
|
192
|
+
"@ata-validator/native-linux-arm64-gnu": "1.61.0",
|
|
193
|
+
"@ata-validator/native-linux-arm64-musl": "1.61.0",
|
|
194
|
+
"@ata-validator/native-linux-x64-gnu": "1.61.0",
|
|
195
|
+
"@ata-validator/native-linux-x64-musl": "1.61.0",
|
|
196
|
+
"@ata-validator/native-win32-x64": "1.61.0"
|
|
196
197
|
},
|
|
197
198
|
"peerDependencies": {
|
|
198
199
|
"yaml": "^2.0.0"
|