ata-validator 1.10.0 → 1.11.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/CHANGELOG.md +23 -0
- package/index.js +180 -101
- package/lib/enrich-error.js +10 -3
- package/lib/error-codes.js +54 -1
- package/lib/formats.js +209 -4
- package/lib/interpreter.js +3 -3
- package/lib/js-compiler.js +112 -28
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to ata-validator are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/), and this project adheres to semantic versioning.
|
|
4
4
|
|
|
5
|
+
## 1.11.0 - 2026-08-31
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- The verdict methods answer the same question as `validate()` again. With `coerceTypes`, `removeAdditional` or a schema `default` in play, `validate()` ran the preprocess pass and `isValidObject()`, `isValidJSON()` and `validateJSON()` did not, so the same validator answered `true` from one and `false` from another for the same document: `validate({ age: '26' })` accepted where `isValidObject({ age: '26' })` rejected. Every path now runs the same pass. Verdict methods on a validator configured this way rewrite the input in place, as `validate()` already did, and the cost of the correction is 0.5 ns on `isValidObject` and 2.7 ns on `isValidJSON`, measured interleaved; validators without those options are unchanged. `tests/test_verdict_preprocess.js` holds all four methods to the same answers.
|
|
10
|
+
|
|
11
|
+
- The native engine's error codes no longer reach callers untranslated. A type failure answered by the addon came back as `code: 3` with no `keyword` and a `docUrl` pointing at a page that does not exist, while the same failure from the JavaScript engines came back as `ATA1001`; both now report the documented code, keyword and link. `tests/test_native_error_codes.js` holds the table against the enum in `include/ata.h`, so the two cannot drift apart silently.
|
|
12
|
+
- The error generator declined self-referencing schemas by emitting nothing for the reference, which accepted whatever that reference guarded: `{ properties: { foo: { $ref: "#" } }, additionalProperties: false }` accepted `{ foo: { bar: false } }` on that path. It now declines the schema outright and the validator falls back to an engine that answers it correctly. The entry-point agreement test covers the shape.
|
|
13
|
+
|
|
14
|
+
- `ipv6` gave two different answers depending on which engine ran it, and neither was right. The compiled path refused an IPv4-mapped address such as `::ffff:192.168.1.1`, the interpreted path accepted `::ffff:1.2.3.4.5`, and both accepted a group of five hex digits like `12345::1`. One implementation now answers for every engine, following RFC 4291, checked against Node's own `net.isIPv6` and the suite's corpus.
|
|
15
|
+
|
|
16
|
+
- `date-time` refuses dates that do not exist. The check ran a regular expression for the shape and then handed the string to `Date.parse`, which rolls an out-of-range day into the next month, so `2026-02-30T00:00:00Z`, `2026-02-29T00:00:00Z` and `2026-12-31T24:00:00Z` were all accepted. The month, the day count for that month in that year, the clock and the offset are now checked directly, per RFC 3339. Schemas that relied on the old leniency will see those values rejected.
|
|
17
|
+
|
|
18
|
+
- Data that points back at itself no longer exhausts the stack. A document with a cycle, which JSON text cannot express but an in-memory object graph can, threw `RangeError: Maximum call stack size exceeded` on the compiled path while the interpreted engine settled on an answer, so the two engines disagreed. Both now follow the same rule: a value already being checked against a schema is a fixed point and counts as satisfied, and a cycle no longer hides a real violation elsewhere in the document. Validation runs a fast pass that only counts depth and a guarded pass that runs when that depth is exceeded, so ordinary documents pay one integer operation per recursive call. Measured interleaved on a self-referencing schema: a four-node document 22.2 to 30.6 ns, a 200-node document 2077 to 1178 ns, non-recursive schemas unchanged at 3.8 ns. `tests/test_cyclic_input.js` holds all three engines to the same answers.
|
|
19
|
+
|
|
20
|
+
### Performance
|
|
21
|
+
|
|
22
|
+
- `ipv6` and `hostname` read the string once as well: 54.7 to 29.6 ns and 45.5 to 28.2 ns, interleaved medians. `ipv6` no longer allocates two arrays per check; `hostname` keeps the answers of the expression it replaces, fuzzed over 300k strings with 0 mismatches.
|
|
23
|
+
|
|
24
|
+
- `date-time` reads the string once, with no regular expression, no date object and no allocation: 95.0 to 39.4 ns on a valid value with a `Z`, 103.8 to 45.3 ns with a numeric offset, interleaved medians. Fuzzed against a reference that spells out RFC 3339, with 0 mismatches over 300k strings; `tests/test_formats_single_pass.js` keeps both the predicate and the generated form on it.
|
|
25
|
+
|
|
26
|
+
- A constructed Validator is roughly three times smaller on the heap until it is used. The public methods and the Standard Schema entry moved from per-instance closures built in the constructor to memoized prototype accessors, and the JSON position cache is only allocated when the JSON text path first needs it. Measured per instance on a 10-key object schema, double-gc deltas over 2000 instances: 1.61 KB to 0.43 KB with a shared schema object, 2.33 KB to 1.12 KB when each instance owns its schema, 3.93 KB to 3.30 KB once compiled and used. Construction alone went from 1504 to 855 ns; construction plus first validate pays about 0.9 microseconds more, once, because the compile step's method assignments now go through a defining setter. The hot validate() path is unchanged, measured interleaved. Detached method references (`const f = v.validate`) still work; `tests/test_lazy_instance.js` pins the shape.
|
|
27
|
+
|
|
5
28
|
## 1.10.0 - 2026-08-30
|
|
6
29
|
|
|
7
30
|
### Performance
|
package/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const { needsNormalization } = require("./lib/schema-scan");
|
|
|
15
15
|
const { isV1Dialect } = require("./lib/dialect");
|
|
16
16
|
const { classify } = require("./lib/shape-classifier");
|
|
17
17
|
const { buildTier0Plan, tier0Validate } = require("./lib/tier0");
|
|
18
|
+
const { createCache: _createPosCache } = require("./lib/data-position-cache");
|
|
18
19
|
|
|
19
20
|
// Extract default values from a schema tree. Returns a function that applies
|
|
20
21
|
// defaults to an object in-place (mutates), or null if no defaults exist.
|
|
@@ -788,89 +789,15 @@ class Validator {
|
|
|
788
789
|
// Per-validate data position cache. Populated by validateJSON before
|
|
789
790
|
// dispatching to inner validate(); consulted by the rich-error wrap
|
|
790
791
|
// to attach dataFrame entries to each enriched error.
|
|
791
|
-
this._posCache =
|
|
792
|
+
this._posCache = null; // created by _pos() on first use, only the JSON text path needs it
|
|
792
793
|
this._lastRawInput = null;
|
|
793
794
|
|
|
794
|
-
//
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
return this.validate(data);
|
|
798
|
-
};
|
|
799
|
-
this.isValidObject = (data) => {
|
|
800
|
-
// Lazy: classify + build tier 0 plan on first call, not in constructor.
|
|
801
|
-
const _tier = classify(this._schemaObj);
|
|
802
|
-
if (_tier.tier === 0) {
|
|
803
|
-
const _plan = buildTier0Plan(this._schemaObj);
|
|
804
|
-
let _n = 0;
|
|
805
|
-
this.isValidObject = (d) => {
|
|
806
|
-
const r = tier0Validate(_plan, d);
|
|
807
|
-
if (++_n === 2) {
|
|
808
|
-
try { this._ensureCodegen(); } catch {}
|
|
809
|
-
}
|
|
810
|
-
return r;
|
|
811
|
-
};
|
|
812
|
-
} else {
|
|
813
|
-
this._ensureCodegen();
|
|
814
|
-
// Codegen can bail on shapes it cannot represent; the full compile
|
|
815
|
-
// binds the native path or the unsupported thrower instead of
|
|
816
|
-
// leaving this stub to re-dispatch to itself.
|
|
817
|
-
if (!this._jsFn) this._ensureCompiled();
|
|
818
|
-
}
|
|
819
|
-
return this.isValidObject(data);
|
|
820
|
-
};
|
|
821
|
-
this.validateJSON = (jsonStr) => {
|
|
822
|
-
this._ensureCompiled();
|
|
823
|
-
return this.validateJSON(jsonStr);
|
|
824
|
-
};
|
|
825
|
-
this.isValidJSON = (jsonStr) => {
|
|
826
|
-
this._ensureCompiled();
|
|
827
|
-
return this.isValidJSON(jsonStr);
|
|
828
|
-
};
|
|
829
|
-
this.validateAndParse = (jsonStr) => {
|
|
830
|
-
if (!native) throw new Error('Native addon required for validateAndParse()');
|
|
831
|
-
this._ensureCompiled();
|
|
832
|
-
return this.validateAndParse(jsonStr);
|
|
833
|
-
};
|
|
834
|
-
this.isValid = (buf) => {
|
|
835
|
-
if (!native) throw new Error('Native addon required for isValid() — use validate() or isValidObject() instead');
|
|
836
|
-
this._ensureCompiled();
|
|
837
|
-
return this.isValid(buf);
|
|
838
|
-
};
|
|
839
|
-
this.countValid = (ndjsonBuf) => {
|
|
840
|
-
if (!native) throw new Error('Native addon required for countValid()');
|
|
841
|
-
this._ensureCompiled();
|
|
842
|
-
return this.countValid(ndjsonBuf);
|
|
843
|
-
};
|
|
844
|
-
this.batchIsValid = (buffers) => {
|
|
845
|
-
if (!native) throw new Error('Native addon required for batchIsValid()');
|
|
846
|
-
this._ensureCompiled();
|
|
847
|
-
return this.batchIsValid(buffers);
|
|
848
|
-
};
|
|
795
|
+
// Public methods start as memoized accessors on the prototype; nothing is
|
|
796
|
+
// allocated per instance until one is first read. See _defineLazyMethod
|
|
797
|
+
// below the class.
|
|
849
798
|
|
|
850
|
-
// ~standard
|
|
851
|
-
// the
|
|
852
|
-
const self = this;
|
|
853
|
-
Object.defineProperty(this, "~standard", {
|
|
854
|
-
value: Object.freeze({
|
|
855
|
-
version: 1,
|
|
856
|
-
vendor: "ata-validator",
|
|
857
|
-
validate(value) {
|
|
858
|
-
const result = self.validate(value);
|
|
859
|
-
if (result.valid) {
|
|
860
|
-
return { value };
|
|
861
|
-
}
|
|
862
|
-
return {
|
|
863
|
-
issues: result.errors.map((err) => ({
|
|
864
|
-
message: err.message,
|
|
865
|
-
path: parsePointerPath(err.instancePath),
|
|
866
|
-
})),
|
|
867
|
-
};
|
|
868
|
-
},
|
|
869
|
-
}),
|
|
870
|
-
writable: false,
|
|
871
|
-
enumerable: false,
|
|
872
|
-
configurable: false,
|
|
873
|
-
});
|
|
799
|
+
// "~standard" (Standard Schema V1) is a lazy prototype accessor too;
|
|
800
|
+
// see below the class. Consumers only pay for it if they read it.
|
|
874
801
|
|
|
875
802
|
// Populate identity cache so repeated `new Validator(sameSchema)` short-circuits.
|
|
876
803
|
if (!opts && typeof schema === "object" && schema !== null) {
|
|
@@ -882,6 +809,22 @@ class Validator {
|
|
|
882
809
|
// meta-schema, which addSchema() may only have registered just now. Run once,
|
|
883
810
|
// before anything reads the schema, and before `_schemaStr` is computed from
|
|
884
811
|
// it. After this addSchema() is refused, so the answer cannot go stale.
|
|
812
|
+
// Whether validation is preceded by a pass that rewrites the input:
|
|
813
|
+
// coercion, removal of undeclared keys, or filling in defaults. The verdict
|
|
814
|
+
// methods have to take the same path when it is, so the quick bindings that
|
|
815
|
+
// answer from the compiled function alone are not used for these validators.
|
|
816
|
+
_needsPreprocess() {
|
|
817
|
+
const o = this._options;
|
|
818
|
+
if (o.coerceTypes || o.removeAdditional) return true;
|
|
819
|
+
if (o.useDefaults === false) return false;
|
|
820
|
+
if (!this._schemaStr) this._schemaStr = JSON.stringify(this._schemaObj);
|
|
821
|
+
return this._schemaStr.includes('"default"');
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
_pos() {
|
|
825
|
+
return this._posCache || (this._posCache = _createPosCache());
|
|
826
|
+
}
|
|
827
|
+
|
|
885
828
|
_ensureVocabularies() {
|
|
886
829
|
if (this._vocabulariesApplied) return;
|
|
887
830
|
this._vocabulariesApplied = true;
|
|
@@ -1180,14 +1123,26 @@ class Validator {
|
|
|
1180
1123
|
return result;
|
|
1181
1124
|
};
|
|
1182
1125
|
}
|
|
1183
|
-
|
|
1126
|
+
// The verdict methods answer validate()'s question without building the
|
|
1127
|
+
// error list, so they run the same preprocess pass. Skipping it made the
|
|
1128
|
+
// two disagree on input that coercion or a default would have fixed.
|
|
1129
|
+
this.isValidObject = preprocess
|
|
1130
|
+
? (data) => { preprocess(data); return jsFn(data) }
|
|
1131
|
+
: jsFn;
|
|
1184
1132
|
const hybridFn = jsFn._hybridFactory
|
|
1185
1133
|
? jsFn._hybridFactory(VALID_RESULT, errFn)
|
|
1186
1134
|
: null;
|
|
1187
|
-
const
|
|
1135
|
+
const jsonValidateInner = safeCombinedFn
|
|
1188
1136
|
|| hybridFn
|
|
1189
1137
|
|| ((obj) => (jsFn(obj) ? VALID_RESULT : errFn(obj)));
|
|
1190
|
-
|
|
1138
|
+
// Parsed text takes the same preprocess pass as a parsed object, so
|
|
1139
|
+
// validate(obj) and validateJSON(text) answer the same for the same
|
|
1140
|
+
// document. Without it, coercion, removal and defaults applied on one
|
|
1141
|
+
// path and not the other.
|
|
1142
|
+
const jsonValidateFn = preprocess
|
|
1143
|
+
? (obj) => { preprocess(obj); return jsonValidateInner(obj) }
|
|
1144
|
+
: jsonValidateInner;
|
|
1145
|
+
this.validateJSON = useSimdjsonForLarge && native && !preprocess
|
|
1191
1146
|
? (jsonStr) => {
|
|
1192
1147
|
if (jsonStr.length >= SIMDJSON_THRESHOLD) {
|
|
1193
1148
|
this._ensureNative();
|
|
@@ -1214,7 +1169,22 @@ class Validator {
|
|
|
1214
1169
|
this._ensureNative();
|
|
1215
1170
|
return this._compiled.validateJSON(jsonStr);
|
|
1216
1171
|
};
|
|
1217
|
-
|
|
1172
|
+
// The addon validates the bytes as they are, which is the wrong answer
|
|
1173
|
+
// when the schema asks for coercion, removal or defaults: those change
|
|
1174
|
+
// what counts as valid. With a preprocess pass configured the text is
|
|
1175
|
+
// parsed and run through the same path validate() takes.
|
|
1176
|
+
const verdictFromText = (jsonStr) => {
|
|
1177
|
+
let parsed;
|
|
1178
|
+
try {
|
|
1179
|
+
parsed = JSON.parse(jsonStr);
|
|
1180
|
+
} catch (e) {
|
|
1181
|
+
if (!(e instanceof SyntaxError)) throw e;
|
|
1182
|
+
return false;
|
|
1183
|
+
}
|
|
1184
|
+
if (preprocess) preprocess(parsed);
|
|
1185
|
+
return jsFn(parsed);
|
|
1186
|
+
};
|
|
1187
|
+
this.isValidJSON = useSimdjsonForLarge && native && !preprocess
|
|
1218
1188
|
? (jsonStr) => {
|
|
1219
1189
|
if (jsonStr.length >= SIMDJSON_THRESHOLD) {
|
|
1220
1190
|
this._ensureNative();
|
|
@@ -1223,21 +1193,9 @@ class Validator {
|
|
|
1223
1193
|
Buffer.from(jsonStr),
|
|
1224
1194
|
);
|
|
1225
1195
|
}
|
|
1226
|
-
|
|
1227
|
-
return jsFn(JSON.parse(jsonStr));
|
|
1228
|
-
} catch (e) {
|
|
1229
|
-
if (!(e instanceof SyntaxError)) throw e;
|
|
1230
|
-
return false;
|
|
1231
|
-
}
|
|
1196
|
+
return verdictFromText(jsonStr);
|
|
1232
1197
|
}
|
|
1233
|
-
:
|
|
1234
|
-
try {
|
|
1235
|
-
return jsFn(JSON.parse(jsonStr));
|
|
1236
|
-
} catch (e) {
|
|
1237
|
-
if (!(e instanceof SyntaxError)) throw e;
|
|
1238
|
-
return false;
|
|
1239
|
-
}
|
|
1240
|
-
};
|
|
1198
|
+
: verdictFromText;
|
|
1241
1199
|
// validateAndParse: parse the JSON, then validate. Pure JS (JSON.parse +
|
|
1242
1200
|
// validate) so it works with or without the native addon and in browsers.
|
|
1243
1201
|
{
|
|
@@ -1416,7 +1374,7 @@ class Validator {
|
|
|
1416
1374
|
if (result && result.valid === false && result !== ABORT_EARLY_RESULT) {
|
|
1417
1375
|
// Positions come from the raw input when validateJSON set one;
|
|
1418
1376
|
// resolved eagerly since the cache is reset per call.
|
|
1419
|
-
const positions = (enrich && self._lastRawInput != null) ? self.
|
|
1377
|
+
const positions = (enrich && self._lastRawInput != null) ? self._pos().get(self._lastRawInput) : null;
|
|
1420
1378
|
if (positions) self._posCache.reset();
|
|
1421
1379
|
let cached = null;
|
|
1422
1380
|
return {
|
|
@@ -1481,7 +1439,7 @@ class Validator {
|
|
|
1481
1439
|
let parsedData;
|
|
1482
1440
|
try { parsedData = JSON.parse(jsonStr); } catch { parsedData = undefined; }
|
|
1483
1441
|
if (!first || !first.docUrl) {
|
|
1484
|
-
const positions = (this._lastRawInput != null) ? this.
|
|
1442
|
+
const positions = (this._lastRawInput != null) ? this._pos().get(this._lastRawInput) : null;
|
|
1485
1443
|
const enriched = result.errors.map((e) => enrich(e, {
|
|
1486
1444
|
data: parsedData,
|
|
1487
1445
|
positions,
|
|
@@ -1501,7 +1459,7 @@ class Validator {
|
|
|
1501
1459
|
return { valid: false, errors: enriched };
|
|
1502
1460
|
}
|
|
1503
1461
|
// Already-enriched path: still attach dataFrame if missing.
|
|
1504
|
-
const positions = (this._lastRawInput != null) ? this.
|
|
1462
|
+
const positions = (this._lastRawInput != null) ? this._pos().get(this._lastRawInput) : null;
|
|
1505
1463
|
if (positions) {
|
|
1506
1464
|
for (const e of result.errors) {
|
|
1507
1465
|
if (e && !e.dataFrame) {
|
|
@@ -1673,6 +1631,13 @@ class Validator {
|
|
|
1673
1631
|
|
|
1674
1632
|
_ensureCodegen() {
|
|
1675
1633
|
if (this._jsFn) return;
|
|
1634
|
+
// A validator that rewrites its input cannot use the binding below: that
|
|
1635
|
+
// one answers from the compiled function alone and would skip the rewrite,
|
|
1636
|
+
// so isValidObject() and validate() would disagree.
|
|
1637
|
+
if (this._needsPreprocess()) {
|
|
1638
|
+
this._ensureCompiled();
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1676
1641
|
this._ensureVocabularies();
|
|
1677
1642
|
if (typeof process !== 'undefined' && process.env && process.env.ATA_FORCE_NAPI) return;
|
|
1678
1643
|
if (!this._schemaStr) this._schemaStr = JSON.stringify(this._schemaObj);
|
|
@@ -2032,6 +1997,120 @@ function defineSchema (schema) {
|
|
|
2032
1997
|
return schema;
|
|
2033
1998
|
}
|
|
2034
1999
|
|
|
2000
|
+
// Public methods start as memoized accessors on the prototype. A fresh
|
|
2001
|
+
// Validator allocates none of them; the first read of a method builds the
|
|
2002
|
+
// bound closure, stores it on the instance as an ordinary writable property
|
|
2003
|
+
// and returns it. The setter keeps the compile step's plain assignments
|
|
2004
|
+
// (`this.validate = fn`) working before the getter has ever run. Detached
|
|
2005
|
+
// use (`const f = v.validate`) keeps working because the closure binds the
|
|
2006
|
+
// instance.
|
|
2007
|
+
// Standard Schema V1. Built on first read, then pinned to the instance with
|
|
2008
|
+
// the same descriptor the constructor used to install eagerly.
|
|
2009
|
+
Object.defineProperty(Validator.prototype, "~standard", {
|
|
2010
|
+
configurable: true,
|
|
2011
|
+
get() {
|
|
2012
|
+
const self = this;
|
|
2013
|
+
const std = Object.freeze({
|
|
2014
|
+
version: 1,
|
|
2015
|
+
vendor: "ata-validator",
|
|
2016
|
+
validate(value) {
|
|
2017
|
+
const result = self.validate(value);
|
|
2018
|
+
if (result.valid) {
|
|
2019
|
+
return { value };
|
|
2020
|
+
}
|
|
2021
|
+
return {
|
|
2022
|
+
issues: result.errors.map((err) => ({
|
|
2023
|
+
message: err.message,
|
|
2024
|
+
path: parsePointerPath(err.instancePath),
|
|
2025
|
+
})),
|
|
2026
|
+
};
|
|
2027
|
+
},
|
|
2028
|
+
});
|
|
2029
|
+
Object.defineProperty(this, "~standard", {
|
|
2030
|
+
value: std,
|
|
2031
|
+
writable: false,
|
|
2032
|
+
enumerable: false,
|
|
2033
|
+
configurable: false,
|
|
2034
|
+
});
|
|
2035
|
+
return std;
|
|
2036
|
+
},
|
|
2037
|
+
});
|
|
2038
|
+
|
|
2039
|
+
function _defineLazyMethod(name, maker) {
|
|
2040
|
+
Object.defineProperty(Validator.prototype, name, {
|
|
2041
|
+
configurable: true,
|
|
2042
|
+
get() {
|
|
2043
|
+
const fn = maker(this);
|
|
2044
|
+
Object.defineProperty(this, name, { value: fn, writable: true, configurable: true, enumerable: true });
|
|
2045
|
+
return fn;
|
|
2046
|
+
},
|
|
2047
|
+
set(fn) {
|
|
2048
|
+
Object.defineProperty(this, name, { value: fn, writable: true, configurable: true, enumerable: true });
|
|
2049
|
+
},
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
_defineLazyMethod('validate', (self) => (data) => {
|
|
2054
|
+
self._ensureCompiled();
|
|
2055
|
+
return self.validate(data);
|
|
2056
|
+
});
|
|
2057
|
+
_defineLazyMethod('isValidObject', (self) => (data) => {
|
|
2058
|
+
// A validator that rewrites its input goes through the full compile, which
|
|
2059
|
+
// binds a verdict method that runs the rewrite first.
|
|
2060
|
+
if (self._needsPreprocess()) {
|
|
2061
|
+
self._ensureCompiled();
|
|
2062
|
+
return self.isValidObject(data);
|
|
2063
|
+
}
|
|
2064
|
+
// Lazy: classify + build tier 0 plan on first call, not in constructor.
|
|
2065
|
+
const _tier = classify(self._schemaObj);
|
|
2066
|
+
if (_tier.tier === 0) {
|
|
2067
|
+
const _plan = buildTier0Plan(self._schemaObj);
|
|
2068
|
+
let _n = 0;
|
|
2069
|
+
self.isValidObject = (d) => {
|
|
2070
|
+
const r = tier0Validate(_plan, d);
|
|
2071
|
+
if (++_n === 2) {
|
|
2072
|
+
try { self._ensureCodegen(); } catch {}
|
|
2073
|
+
}
|
|
2074
|
+
return r;
|
|
2075
|
+
};
|
|
2076
|
+
} else {
|
|
2077
|
+
self._ensureCodegen();
|
|
2078
|
+
// Codegen can bail on shapes it cannot represent; the full compile
|
|
2079
|
+
// binds the native path or the unsupported thrower instead of
|
|
2080
|
+
// leaving this stub to re-dispatch to itself.
|
|
2081
|
+
if (!self._jsFn) self._ensureCompiled();
|
|
2082
|
+
}
|
|
2083
|
+
return self.isValidObject(data);
|
|
2084
|
+
});
|
|
2085
|
+
_defineLazyMethod('validateJSON', (self) => (jsonStr) => {
|
|
2086
|
+
self._ensureCompiled();
|
|
2087
|
+
return self.validateJSON(jsonStr);
|
|
2088
|
+
});
|
|
2089
|
+
_defineLazyMethod('isValidJSON', (self) => (jsonStr) => {
|
|
2090
|
+
self._ensureCompiled();
|
|
2091
|
+
return self.isValidJSON(jsonStr);
|
|
2092
|
+
});
|
|
2093
|
+
_defineLazyMethod('validateAndParse', (self) => (jsonStr) => {
|
|
2094
|
+
if (!native) throw new Error('Native addon required for validateAndParse()');
|
|
2095
|
+
self._ensureCompiled();
|
|
2096
|
+
return self.validateAndParse(jsonStr);
|
|
2097
|
+
});
|
|
2098
|
+
_defineLazyMethod('isValid', (self) => (buf) => {
|
|
2099
|
+
if (!native) throw new Error('Native addon required for isValid() — use validate() or isValidObject() instead');
|
|
2100
|
+
self._ensureCompiled();
|
|
2101
|
+
return self.isValid(buf);
|
|
2102
|
+
});
|
|
2103
|
+
_defineLazyMethod('countValid', (self) => (ndjsonBuf) => {
|
|
2104
|
+
if (!native) throw new Error('Native addon required for countValid()');
|
|
2105
|
+
self._ensureCompiled();
|
|
2106
|
+
return self.countValid(ndjsonBuf);
|
|
2107
|
+
});
|
|
2108
|
+
_defineLazyMethod('batchIsValid', (self) => (buffers) => {
|
|
2109
|
+
if (!native) throw new Error('Native addon required for batchIsValid()');
|
|
2110
|
+
self._ensureCompiled();
|
|
2111
|
+
return self.batchIsValid(buffers);
|
|
2112
|
+
});
|
|
2113
|
+
|
|
2035
2114
|
module.exports = {
|
|
2036
2115
|
Validator,
|
|
2037
2116
|
compile,
|
package/lib/enrich-error.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { CODES, codeFor } = require('./error-codes');
|
|
3
|
+
const { CODES, codeFor, fromNative } = require('./error-codes');
|
|
4
4
|
const { suggestFor } = require('./suggestions');
|
|
5
5
|
|
|
6
6
|
const DOC_BASE = 'https://ata-validator.com/e/';
|
|
@@ -130,12 +130,19 @@ function rankFor (keyword) {
|
|
|
130
130
|
function enrich (rawErr, opts) {
|
|
131
131
|
const data = opts && opts.data;
|
|
132
132
|
const positions = opts && opts.positions;
|
|
133
|
-
const keyword = rawErr.keyword;
|
|
134
133
|
const format = rawErr.params && rawErr.params.format;
|
|
134
|
+
// The native engine reports its own enum as a number. Translate it to the
|
|
135
|
+
// keyword and public code the JavaScript engines use, so an error means the
|
|
136
|
+
// same thing whichever engine produced it.
|
|
137
|
+
const fromAddon = typeof rawErr.code === 'number' ? fromNative(rawErr.code, format) : null;
|
|
138
|
+
const keyword = rawErr.keyword || (fromAddon && fromAddon.keyword);
|
|
135
139
|
// Prefer a code the codegen already attached (e.g. branch-collapse emits
|
|
136
140
|
// ATA4001/4002/4003 distinguishing zero/multi/anyOf failure modes). The
|
|
137
141
|
// keyword-derived lookup only finds the first match for `keyword: 'oneOf'`.
|
|
138
|
-
const code =
|
|
142
|
+
const code = (fromAddon && fromAddon.code) ||
|
|
143
|
+
(typeof rawErr.code === 'string' && rawErr.code) ||
|
|
144
|
+
codeFor(keyword, format) ||
|
|
145
|
+
'ATA9001';
|
|
139
146
|
const meta = CODES[code];
|
|
140
147
|
const path = rawErr.instancePath != null ? rawErr.instancePath : (rawErr.path || '');
|
|
141
148
|
|
package/lib/error-codes.js
CHANGED
|
@@ -100,4 +100,57 @@ function codeFor (keyword, format) {
|
|
|
100
100
|
return hit === undefined ? null : hit;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
|
|
104
|
+
// The native engine reports its own `error_code` enum from include/ata.h as an
|
|
105
|
+
// ordinal. Nothing outside the addon should ever see one: an error that came
|
|
106
|
+
// from there carries the same public code and keyword as the same failure from
|
|
107
|
+
// the JavaScript engines. Ordinals are positional, so this table follows the
|
|
108
|
+
// enum's declaration order and `tests/test_native_error_codes.js` fails if the
|
|
109
|
+
// two drift apart.
|
|
110
|
+
const NATIVE_KEYWORDS = [
|
|
111
|
+
null, // 0 ok
|
|
112
|
+
'__parse__', // 1 invalid_json
|
|
113
|
+
'__compile__', // 2 invalid_schema
|
|
114
|
+
'type', // 3 type_mismatch
|
|
115
|
+
'required', // 4 required_property_missing
|
|
116
|
+
'additionalProperties', // 5 additional_property_not_allowed
|
|
117
|
+
'enum', // 6 enum_mismatch
|
|
118
|
+
'const', // 7 const_mismatch
|
|
119
|
+
'minimum', // 8 minimum_violation
|
|
120
|
+
'maximum', // 9 maximum_violation
|
|
121
|
+
'exclusiveMinimum', // 10 exclusive_minimum_violation
|
|
122
|
+
'exclusiveMaximum', // 11 exclusive_maximum_violation
|
|
123
|
+
'minLength', // 12 min_length_violation
|
|
124
|
+
'maxLength', // 13 max_length_violation
|
|
125
|
+
'pattern', // 14 pattern_mismatch
|
|
126
|
+
'format', // 15 format_mismatch
|
|
127
|
+
'minItems', // 16 min_items_violation
|
|
128
|
+
'maxItems', // 17 max_items_violation
|
|
129
|
+
'uniqueItems', // 18 unique_items_violation
|
|
130
|
+
'minProperties', // 19 min_properties_violation
|
|
131
|
+
'maxProperties', // 20 max_properties_violation
|
|
132
|
+
'multipleOf', // 21 multiple_of_violation
|
|
133
|
+
'allOf', // 22 all_of_failed
|
|
134
|
+
'anyOf', // 23 any_of_failed
|
|
135
|
+
'oneOf', // 24 one_of_failed
|
|
136
|
+
'not', // 25 not_failed
|
|
137
|
+
'$ref', // 26 ref_not_found
|
|
138
|
+
'if', // 27 if_then_else_failed
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
// Keyword and public code for a native ordinal, or null when the ordinal is
|
|
142
|
+
// outside the enum, which would mean the addon is newer than this table.
|
|
143
|
+
function fromNative (ordinal, format) {
|
|
144
|
+
if (typeof ordinal !== 'number' || !Number.isInteger(ordinal)) return null
|
|
145
|
+
const keyword = NATIVE_KEYWORDS[ordinal]
|
|
146
|
+
if (!keyword) return null
|
|
147
|
+
// codeFor('format') answers with the first format code, which names a
|
|
148
|
+
// specific format. Without knowing which format failed, the generic one is
|
|
149
|
+
// the honest answer.
|
|
150
|
+
const code = keyword === 'format'
|
|
151
|
+
? (format ? codeFor('format', format) || 'ATA3099' : 'ATA3099')
|
|
152
|
+
: codeFor(keyword) || 'ATA9001'
|
|
153
|
+
return { keyword, code }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = { CODES, get, all, codeFor, fromNative, NATIVE_KEYWORDS };
|
package/lib/formats.js
CHANGED
|
@@ -21,13 +21,83 @@ function date (s) {
|
|
|
21
21
|
return m >= 1 && m <= 12 && d >= 1 && d <= 31;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// RFC 3339 date-time, read once. The form this replaces ran a regular
|
|
25
|
+
// expression for the shape and then `Date.parse` for the calendar, which
|
|
26
|
+
// builds a date object to answer a question about the string. Everything the
|
|
27
|
+
// parse rejected is checked here directly: month, the day count for that month
|
|
28
|
+
// in that year, and the ranges of the clock and the offset.
|
|
29
|
+
function daysInMonth (year, month) {
|
|
30
|
+
if (month === 2) {
|
|
31
|
+
const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
32
|
+
return leap ? 29 : 28;
|
|
33
|
+
}
|
|
34
|
+
return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function twoDigits (s, i) {
|
|
38
|
+
return (s.charCodeAt(i) - 48) * 10 + (s.charCodeAt(i + 1) - 48);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function dateTime (s) {
|
|
42
|
+
const n = s.length;
|
|
43
|
+
// The shortest accepted form is 2026-08-31T12:00:00Z
|
|
44
|
+
if (n < 20) return false;
|
|
45
|
+
for (let i = 0; i < 19; i++) {
|
|
46
|
+
const c = s.charCodeAt(i);
|
|
47
|
+
if (i === 4 || i === 7) { if (c !== 45) return false; continue; }
|
|
48
|
+
if (i === 10) { if (c !== 84 && c !== 116) return false; continue; }
|
|
49
|
+
if (i === 13 || i === 16) { if (c !== 58) return false; continue; }
|
|
50
|
+
if (c < 48 || c > 57) return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const year = (s.charCodeAt(0) - 48) * 1000 + (s.charCodeAt(1) - 48) * 100 +
|
|
54
|
+
(s.charCodeAt(2) - 48) * 10 + (s.charCodeAt(3) - 48);
|
|
55
|
+
const month = twoDigits(s, 5);
|
|
56
|
+
if (month < 1 || month > 12) return false;
|
|
57
|
+
const day = twoDigits(s, 8);
|
|
58
|
+
if (day < 1 || day > daysInMonth(year, month)) return false;
|
|
59
|
+
if (twoDigits(s, 11) > 23) return false;
|
|
60
|
+
if (twoDigits(s, 14) > 59) return false;
|
|
61
|
+
if (twoDigits(s, 17) > 59) return false;
|
|
62
|
+
|
|
63
|
+
let i = 19;
|
|
64
|
+
if (s.charCodeAt(i) === 46) {
|
|
65
|
+
i++;
|
|
66
|
+
const start = i;
|
|
67
|
+
while (i < n) {
|
|
68
|
+
const c = s.charCodeAt(i);
|
|
69
|
+
if (c < 48 || c > 57) break;
|
|
70
|
+
i++;
|
|
71
|
+
}
|
|
72
|
+
if (i === start) return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const c = s.charCodeAt(i);
|
|
76
|
+
if (c === 90 || c === 122) return i === n - 1;
|
|
77
|
+
if (c !== 43 && c !== 45) return false;
|
|
78
|
+
if (n - i !== 6) return false;
|
|
79
|
+
if (!isDigit(s.charCodeAt(i + 1)) || !isDigit(s.charCodeAt(i + 2))) return false;
|
|
80
|
+
if (s.charCodeAt(i + 3) !== 58) return false;
|
|
81
|
+
if (!isDigit(s.charCodeAt(i + 4)) || !isDigit(s.charCodeAt(i + 5))) return false;
|
|
82
|
+
if (twoDigits(s, i + 1) > 23) return false;
|
|
83
|
+
if (twoDigits(s, i + 4) > 59) return false;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
24
87
|
// Four decimal octets, no leading zeros, no empty octets, nothing else.
|
|
88
|
+
// Also used for the dotted tail of an IPv4-mapped IPv6 address, which is why
|
|
89
|
+
// the work is done over a range rather than a whole string: taking a slice for
|
|
90
|
+
// it would allocate on a path that exists to avoid allocating.
|
|
25
91
|
function ipv4 (s) {
|
|
26
|
-
|
|
92
|
+
return ipv4Range(s, 0, s.length);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ipv4Range (s, from, to) {
|
|
96
|
+
const n = to - from;
|
|
27
97
|
if (n < 7 || n > 15) return false;
|
|
28
98
|
let octets = 0, value = 0, digits = 0;
|
|
29
|
-
for (let i =
|
|
30
|
-
const c = i <
|
|
99
|
+
for (let i = from; i <= to; i++) {
|
|
100
|
+
const c = i < to ? s.charCodeAt(i) : 46;
|
|
31
101
|
if (c === 46) {
|
|
32
102
|
if (digits === 0 || value > 255) return false;
|
|
33
103
|
octets++; value = 0; digits = 0;
|
|
@@ -41,6 +111,90 @@ function ipv4 (s) {
|
|
|
41
111
|
return octets === 4;
|
|
42
112
|
}
|
|
43
113
|
|
|
114
|
+
// RFC 4291 address: up to eight groups of one to four hex digits, at most one
|
|
115
|
+
// run of "::" standing in for the zero groups, and an optional dotted IPv4
|
|
116
|
+
// tail in the last position, which counts as two groups. The forms this
|
|
117
|
+
// replaces were a character-class test plus two `split(':')` calls, which
|
|
118
|
+
// allocated twice and accepted things like "12345::1"; they also disagreed
|
|
119
|
+
// with each other about an IPv4 tail. Answers match Node's own `net.isIPv6`,
|
|
120
|
+
// which the test uses as its oracle.
|
|
121
|
+
function ipv6 (s) {
|
|
122
|
+
const n = s.length;
|
|
123
|
+
if (n < 2 || n > 45) return false;
|
|
124
|
+
|
|
125
|
+
let end = n;
|
|
126
|
+
let groups = 0;
|
|
127
|
+
// A dot can only belong to a trailing IPv4 address, which fills two groups.
|
|
128
|
+
const dot = s.indexOf('.');
|
|
129
|
+
if (dot !== -1) {
|
|
130
|
+
const lastColon = s.lastIndexOf(':', dot);
|
|
131
|
+
if (lastColon === -1) return false;
|
|
132
|
+
if (!ipv4Range(s, lastColon + 1, n)) return false;
|
|
133
|
+
end = lastColon + 1; // keep the colon: the group scan ends on it
|
|
134
|
+
groups = 2;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let compressed = false;
|
|
138
|
+
let digits = 0;
|
|
139
|
+
let i = 0;
|
|
140
|
+
|
|
141
|
+
if (s.charCodeAt(0) === 58 && s.charCodeAt(1) !== 58) return false;
|
|
142
|
+
|
|
143
|
+
while (i < end) {
|
|
144
|
+
const c = s.charCodeAt(i);
|
|
145
|
+
if (c === 58) {
|
|
146
|
+
if (digits > 0) { groups++; digits = 0; }
|
|
147
|
+
if (i + 1 < n && s.charCodeAt(i + 1) === 58) {
|
|
148
|
+
if (compressed) return false;
|
|
149
|
+
compressed = true;
|
|
150
|
+
i += 2;
|
|
151
|
+
if (i < n && s.charCodeAt(i) === 58) return false;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
i++;
|
|
155
|
+
// A single colon must have something on both sides, and the group scan
|
|
156
|
+
// ending on a colon only happens when an IPv4 tail follows it.
|
|
157
|
+
if (i === end && end === n) return false;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (isDigit(c) || (c >= 97 && c <= 102) || (c >= 65 && c <= 70)) {
|
|
161
|
+
if (++digits > 4) return false;
|
|
162
|
+
i++;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (digits > 0) groups++;
|
|
168
|
+
|
|
169
|
+
if (groups > 8) return false;
|
|
170
|
+
return compressed ? groups < 8 : groups === 8;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Labels of letters, digits and hyphens, each 1 to 63 characters, none
|
|
174
|
+
// starting or ending with a hyphen, joined by single dots, 253 characters at
|
|
175
|
+
// most. Same answers as the expression it replaces, read once.
|
|
176
|
+
function hostname (s) {
|
|
177
|
+
const n = s.length;
|
|
178
|
+
if (n === 0 || n > 253) return false;
|
|
179
|
+
let labelLength = 0;
|
|
180
|
+
let previous = 46; // a dot, so a leading hyphen is refused like a leading dot
|
|
181
|
+
for (let i = 0; i < n; i++) {
|
|
182
|
+
const c = s.charCodeAt(i);
|
|
183
|
+
if (c === 46) {
|
|
184
|
+
if (labelLength === 0 || previous === 45) return false;
|
|
185
|
+
labelLength = 0;
|
|
186
|
+
previous = c;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const alnum = isDigit(c) || (c >= 97 && c <= 122) || (c >= 65 && c <= 90);
|
|
190
|
+
if (!alnum && c !== 45) return false;
|
|
191
|
+
if (c === 45 && previous === 46) return false; // label starts with a hyphen
|
|
192
|
+
if (++labelLength > 63) return false;
|
|
193
|
+
previous = c;
|
|
194
|
+
}
|
|
195
|
+
return labelLength !== 0 && previous !== 45;
|
|
196
|
+
}
|
|
197
|
+
|
|
44
198
|
// Source twins. `v` is the expression holding the string; when `isStr` is
|
|
45
199
|
// false the check is wrapped in a typeof guard, matching FORMAT_CODEGEN's
|
|
46
200
|
// convention. Each returns a statement that `return false`s on mismatch.
|
|
@@ -50,8 +204,59 @@ function dateSource (v, isStr) {
|
|
|
50
204
|
return guard(v, isStr, `if(${v}.length!==10)return false;for(let _i=0;_i<10;_i++){const _c=${v}.charCodeAt(_i);if(_i===4||_i===7){if(_c!==45)return false}else if(_c<48||_c>57)return false}const _m=(${v}.charCodeAt(5)-48)*10+(${v}.charCodeAt(6)-48),_d=(${v}.charCodeAt(8)-48)*10+(${v}.charCodeAt(9)-48);if(_m<1||_m>12||_d<1||_d>31)return false`);
|
|
51
205
|
}
|
|
52
206
|
|
|
207
|
+
function dateTimeSource (v, isStr) {
|
|
208
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n<20)return false;for(let _i=0;_i<19;_i++){const _c=${v}.charCodeAt(_i);if(_i===4||_i===7){if(_c!==45)return false;continue}if(_i===10){if(_c!==84&&_c!==116)return false;continue}if(_i===13||_i===16){if(_c!==58)return false;continue}if(_c<48||_c>57)return false}` +
|
|
209
|
+
`const _y=(${v}.charCodeAt(0)-48)*1000+(${v}.charCodeAt(1)-48)*100+(${v}.charCodeAt(2)-48)*10+(${v}.charCodeAt(3)-48);` +
|
|
210
|
+
`const _mo=(${v}.charCodeAt(5)-48)*10+(${v}.charCodeAt(6)-48);if(_mo<1||_mo>12)return false;` +
|
|
211
|
+
`const _dm=_mo===2?(((_y%4===0&&_y%100!==0)||_y%400===0)?29:28):(_mo===4||_mo===6||_mo===9||_mo===11?30:31);` +
|
|
212
|
+
`const _d=(${v}.charCodeAt(8)-48)*10+(${v}.charCodeAt(9)-48);if(_d<1||_d>_dm)return false;` +
|
|
213
|
+
`if((${v}.charCodeAt(11)-48)*10+(${v}.charCodeAt(12)-48)>23)return false;` +
|
|
214
|
+
`if((${v}.charCodeAt(14)-48)*10+(${v}.charCodeAt(15)-48)>59)return false;` +
|
|
215
|
+
`if((${v}.charCodeAt(17)-48)*10+(${v}.charCodeAt(18)-48)>59)return false;` +
|
|
216
|
+
`let _i2=19;if(${v}.charCodeAt(19)===46){_i2=20;const _st=_i2;while(_i2<_n){const _c2=${v}.charCodeAt(_i2);if(_c2<48||_c2>57)break;_i2++}if(_i2===_st)return false}` +
|
|
217
|
+
`const _tz=${v}.charCodeAt(_i2);` +
|
|
218
|
+
`if(_tz===90||_tz===122){if(_i2!==_n-1)return false}` +
|
|
219
|
+
`else{if(_tz!==43&&_tz!==45)return false;if(_n-_i2!==6)return false;` +
|
|
220
|
+
`const _oh=${v}.charCodeAt(_i2+1),_oh2=${v}.charCodeAt(_i2+2),_om=${v}.charCodeAt(_i2+4),_om2=${v}.charCodeAt(_i2+5);` +
|
|
221
|
+
`if(_oh<48||_oh>57||_oh2<48||_oh2>57||_om<48||_om>57||_om2<48||_om2>57)return false;` +
|
|
222
|
+
`if(${v}.charCodeAt(_i2+3)!==58)return false;` +
|
|
223
|
+
`if((_oh-48)*10+(_oh2-48)>23)return false;if((_om-48)*10+(_om2-48)>59)return false}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function hostnameSource (v, isStr) {
|
|
227
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n===0||_n>253)return false;let _ll=0,_pv=46;` +
|
|
228
|
+
`for(let _i=0;_i<_n;_i++){const _c=${v}.charCodeAt(_i);` +
|
|
229
|
+
`if(_c===46){if(_ll===0||_pv===45)return false;_ll=0;_pv=_c;continue}` +
|
|
230
|
+
`const _an=(_c>=48&&_c<=57)||(_c>=97&&_c<=122)||(_c>=65&&_c<=90);` +
|
|
231
|
+
`if(!_an&&_c!==45)return false;` +
|
|
232
|
+
`if(_c===45&&_pv===46)return false;` +
|
|
233
|
+
`if(++_ll>63)return false;_pv=_c}` +
|
|
234
|
+
`if(_ll===0||_pv===45)return false`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function ipv6Source (v, isStr) {
|
|
238
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n<2||_n>45)return false;let _end=_n,_g=0;` +
|
|
239
|
+
`const _dot=${v}.indexOf('.');` +
|
|
240
|
+
`if(_dot!==-1){const _lc=${v}.lastIndexOf(':',_dot);if(_lc===-1)return false;` +
|
|
241
|
+
`{const _f=_lc+1,_t=_n,_ln=_t-_f;if(_ln<7||_ln>15)return false;let _o=0,_val=0,_dg=0;` +
|
|
242
|
+
`for(let _i=_f;_i<=_t;_i++){const _c=_i<_t?${v}.charCodeAt(_i):46;` +
|
|
243
|
+
`if(_c===46){if(_dg===0||_val>255)return false;_o++;_val=0;_dg=0;if(_o>4)return false}` +
|
|
244
|
+
`else if(_c>=48&&_c<=57){if(_dg===1&&_val===0)return false;_val=_val*10+(_c-48);_dg++;if(_dg>3)return false}` +
|
|
245
|
+
`else return false}if(_o!==4)return false}` +
|
|
246
|
+
`_end=_lc+1;_g=2}` +
|
|
247
|
+
`let _cp=false,_dg2=0,_i2=0;` +
|
|
248
|
+
`if(${v}.charCodeAt(0)===58&&${v}.charCodeAt(1)!==58)return false;` +
|
|
249
|
+
`while(_i2<_end){const _c2=${v}.charCodeAt(_i2);` +
|
|
250
|
+
`if(_c2===58){if(_dg2>0){_g++;_dg2=0}` +
|
|
251
|
+
`if(_i2+1<_n&&${v}.charCodeAt(_i2+1)===58){if(_cp)return false;_cp=true;_i2+=2;if(_i2<_n&&${v}.charCodeAt(_i2)===58)return false;continue}` +
|
|
252
|
+
`_i2++;if(_i2===_end&&_end===_n)return false;continue}` +
|
|
253
|
+
`if((_c2>=48&&_c2<=57)||(_c2>=97&&_c2<=102)||(_c2>=65&&_c2<=70)){if(++_dg2>4)return false;_i2++;continue}` +
|
|
254
|
+
`return false}` +
|
|
255
|
+
`if(_dg2>0)_g++;if(_g>8)return false;if(_cp){if(_g>=8)return false}else if(_g!==8)return false`);
|
|
256
|
+
}
|
|
257
|
+
|
|
53
258
|
function ipv4Source (v, isStr) {
|
|
54
259
|
return guard(v, isStr, `const _n=${v}.length;if(_n<7||_n>15)return false;let _o=0,_val=0,_dg=0;for(let _i=0;_i<=_n;_i++){const _c=_i<_n?${v}.charCodeAt(_i):46;if(_c===46){if(_dg===0||_val>255)return false;_o++;_val=0;_dg=0;if(_o>4)return false}else if(_c>=48&&_c<=57){if(_dg===1&&_val===0)return false;_val=_val*10+(_c-48);_dg++;if(_dg>3)return false}else return false}if(_o!==4)return false`);
|
|
55
260
|
}
|
|
56
261
|
|
|
57
|
-
module.exports = { date, ipv4, dateSource, ipv4Source };
|
|
262
|
+
module.exports = { date, ipv4, dateTime, ipv6, hostname, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource };
|
package/lib/interpreter.js
CHANGED
|
@@ -251,15 +251,15 @@ const _formats = require('./formats');
|
|
|
251
251
|
const FORMAT_CHECKS = {
|
|
252
252
|
email: (s) => { const at = s.indexOf('@'); return at > 0 && at < s.length - 1 && s.indexOf('.', at) > at + 1; },
|
|
253
253
|
date: _formats.date,
|
|
254
|
-
'date-time':
|
|
254
|
+
'date-time': _formats.dateTime,
|
|
255
255
|
time: (s) => /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.\d+)?([Zz]|[+-]\d{2}:\d{2})?$/.test(s),
|
|
256
256
|
duration: (s) => /^P(?:\d+Y)?(?:\d+M)?(?:\d+W)?(?:\d+D)?(?:T(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$/.test(s) && s !== 'P' && !s.endsWith('T'),
|
|
257
257
|
uuid: (s) => s.length === 36 && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s),
|
|
258
258
|
uri: (s) => /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(s) && !/[\s\u0000-\u001f\u007f]/.test(s),
|
|
259
259
|
'uri-reference': (s) => !/[\s\u0000-\u001f\u007f]/.test(s),
|
|
260
260
|
ipv4: _formats.ipv4,
|
|
261
|
-
ipv6:
|
|
262
|
-
hostname:
|
|
261
|
+
ipv6: _formats.ipv6,
|
|
262
|
+
hostname: _formats.hostname,
|
|
263
263
|
regex: (s) => { try { new RegExp(s, 'u'); return true; } catch { try { new RegExp(s); return true; } catch { return false; } } },
|
|
264
264
|
'json-pointer': (s) => s === '' || (/^\//.test(s) && !/~(?![01])/.test(s)),
|
|
265
265
|
};
|
package/lib/js-compiler.js
CHANGED
|
@@ -764,7 +764,7 @@ const FORMAT_CHECKS = {
|
|
|
764
764
|
date: _formats.date,
|
|
765
765
|
uuid: (s) => s.length === 36 && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s),
|
|
766
766
|
ipv4: _formats.ipv4,
|
|
767
|
-
hostname:
|
|
767
|
+
hostname: _formats.hostname,
|
|
768
768
|
}
|
|
769
769
|
|
|
770
770
|
// Dangerous JS property names that exist on Object.prototype
|
|
@@ -850,6 +850,49 @@ function hasNestedIdScope(node, isRoot = true) {
|
|
|
850
850
|
// the generated code the same way `$ref: "#"` does at the root. A plain
|
|
851
851
|
// cross-reference (A -> B where B does not refer back) is not on a cycle and
|
|
852
852
|
// still inlines.
|
|
853
|
+
// Data can point back at itself, and a schema that references itself would
|
|
854
|
+
// then recurse until the stack runs out. Validation runs in two passes: the
|
|
855
|
+
// fast one counts depth and gives up past CYCLE_DEPTH, the second re-runs with
|
|
856
|
+
// a set of the values currently under check, where a value met twice is a
|
|
857
|
+
// fixed point and counts as satisfied. That is what the interpreted engine
|
|
858
|
+
// does, so both engines answer the same on cyclic input. Legitimate documents
|
|
859
|
+
// never reach the threshold, so they only pay the counter.
|
|
860
|
+
// Raised by a generator that meets a construct it cannot express. The entry
|
|
861
|
+
// point turns it into a decline, so the caller falls back to an engine that
|
|
862
|
+
// can. Emitting nothing would mean "everything is valid", which is the failure
|
|
863
|
+
// mode this project has shipped three times in $ref handling; declining is the
|
|
864
|
+
// only safe way out.
|
|
865
|
+
const DECLINE = Symbol('ata.decline')
|
|
866
|
+
|
|
867
|
+
const CYCLE_DEPTH = 512
|
|
868
|
+
|
|
869
|
+
// Declarations the guarded functions close over. Emitted once, ahead of them.
|
|
870
|
+
function emitGuardState () {
|
|
871
|
+
return 'const _CYC={};const _stk=new Set();let _sd=0,_sg=false\n '
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Wraps a self-recursive root: `name` is the guarded entry, `name`_b the body.
|
|
875
|
+
function emitRootGuard (name, args, onRepeat) {
|
|
876
|
+
const call = `${name}_b(${args})`
|
|
877
|
+
return (
|
|
878
|
+
`function ${name}(${args}){\n ` +
|
|
879
|
+
`if(_sg){if(typeof d!=='object'||d===null)return ${call};if(_stk.has(d))return ${onRepeat};_stk.add(d);try{return ${call}}finally{_stk.delete(d)}}\n ` +
|
|
880
|
+
`if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${call}\n _sd--\n return _r\n }\n `
|
|
881
|
+
)
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Runs `attempt`, and on the depth signal runs it again under the guard.
|
|
885
|
+
// `reset` clears anything the abandoned pass left behind.
|
|
886
|
+
function emitGuardedRun (attempt, reset, defSets) {
|
|
887
|
+
const clears = ['_stk.clear()'].concat(defSets.map((n) => `${n}_s.clear()`)).join(';')
|
|
888
|
+
return (
|
|
889
|
+
`_sd=0\n try{${attempt}}catch(_err){\n ` +
|
|
890
|
+
`if(_err!==_CYC)throw _err\n ` +
|
|
891
|
+
`${reset}_sg=true;${clears}\n ` +
|
|
892
|
+
`try{${attempt}}finally{_sg=false}\n }\n `
|
|
893
|
+
)
|
|
894
|
+
}
|
|
895
|
+
|
|
853
896
|
function cyclicDefNames (defs) {
|
|
854
897
|
const out = new Set()
|
|
855
898
|
if (!defs || typeof defs !== 'object') return out
|
|
@@ -1417,18 +1460,33 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
|
|
|
1417
1460
|
}
|
|
1418
1461
|
}
|
|
1419
1462
|
|
|
1463
|
+
const defSets = ctx.defFns ? Array.from(ctx.defFns.values()) : []
|
|
1464
|
+
const needsGuard = ctx.usesRecursion || defSets.length > 0
|
|
1465
|
+
|
|
1420
1466
|
let body, hybridBody
|
|
1421
1467
|
if (ctx.usesRecursion) {
|
|
1422
|
-
// Self-recursive:
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1468
|
+
// Self-recursive: a named body reached through the cycle guard, so data
|
|
1469
|
+
// that points back at itself settles instead of exhausting the stack.
|
|
1470
|
+
const decl = emitRootGuard('_validate', 'd', 'true') +
|
|
1471
|
+
`function _validate_b(d){\n ${checkStr}\n return true\n }\n `
|
|
1472
|
+
const run = emitGuardedRun('return _validate(d)', '', defSets)
|
|
1473
|
+
body = `${decl}function _run(d){\n ${run}\n }\n return _run(d)`
|
|
1474
|
+
// Hybrid: keep the boolean answer, wrap only the outer call
|
|
1475
|
+
hybridBody = `${decl}function _run(d){\n ${run}\n }\n return _run(d)?R:E(d)`
|
|
1476
|
+
} else if (defSets.length > 0) {
|
|
1477
|
+
// No self-reference at the root, but a def on a cycle can still be
|
|
1478
|
+
// re-entered with the same value; the guarded functions need their state.
|
|
1479
|
+
const run = emitGuardedRun(`return _body(d)`, '', defSets)
|
|
1480
|
+
body = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)`
|
|
1481
|
+
hybridBody = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)?R:E(d)`
|
|
1426
1482
|
} else {
|
|
1427
1483
|
body = checkStr + '\n return true'
|
|
1428
1484
|
hybridBody = replaceTopLevel(checkStr + '\n return R')
|
|
1429
1485
|
}
|
|
1430
1486
|
|
|
1431
|
-
const
|
|
1487
|
+
const guardStr = needsGuard ? emitGuardState() : ''
|
|
1488
|
+
const preambleStr = guardStr +
|
|
1489
|
+
(ctx.preamble && ctx.preamble.length ? ctx.preamble.join('\n ') + '\n ' : '')
|
|
1432
1490
|
|
|
1433
1491
|
try {
|
|
1434
1492
|
let boolFn
|
|
@@ -1695,7 +1753,16 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1695
1753
|
ctx.defFns.set(defName, fnName)
|
|
1696
1754
|
const bodyLines = []
|
|
1697
1755
|
genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)
|
|
1698
|
-
|
|
1756
|
+
// Entered through the cycle guard: see emitGuardState. In the fast
|
|
1757
|
+
// pass the wrapper only counts depth; the set is used by the second,
|
|
1758
|
+
// guarded pass that runs when data turns out to point back at
|
|
1759
|
+
// itself.
|
|
1760
|
+
ctx.preamble.push(
|
|
1761
|
+
`const ${fnName}_s=new Set()\n function ${fnName}(d){\n ` +
|
|
1762
|
+
`if(_sg){if(typeof d!=='object'||d===null)return ${fnName}_b(d);if(${fnName}_s.has(d))return true;${fnName}_s.add(d);try{return ${fnName}_b(d)}finally{${fnName}_s.delete(d)}}\n ` +
|
|
1763
|
+
`if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d)\n _sd--\n return _r\n }\n ` +
|
|
1764
|
+
`function ${fnName}_b(d){${bodyLines.join('\n ')}\n return true}`,
|
|
1765
|
+
)
|
|
1699
1766
|
}
|
|
1700
1767
|
lines.push(`if(!${fnName}(${v}))return false`)
|
|
1701
1768
|
if (!hasSiblings) return
|
|
@@ -2903,9 +2970,7 @@ const FORMAT_CODEGEN = {
|
|
|
2903
2970
|
uuid: (v, isStr) => isStr
|
|
2904
2971
|
? `if(${v}.length!==36||!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(${v}))return false`
|
|
2905
2972
|
: `if(typeof ${v}==='string'&&(${v}.length!==36||!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(${v})))return false`,
|
|
2906
|
-
'date-time':
|
|
2907
|
-
? `if(!/^\\d{4}-\\d{2}-\\d{2}[Tt]\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?([Zz]|[+-]\\d{2}:\\d{2})$/.test(${v})||isNaN(Date.parse(${v})))return false`
|
|
2908
|
-
: `if(typeof ${v}==='string'&&(!/^\\d{4}-\\d{2}-\\d{2}[Tt]\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?([Zz]|[+-]\\d{2}:\\d{2})$/.test(${v})||isNaN(Date.parse(${v}))))return false`,
|
|
2973
|
+
'date-time': _formats.dateTimeSource,
|
|
2909
2974
|
time: (v, isStr) => isStr
|
|
2910
2975
|
? `if(!/^([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})?$/.test(${v}))return false`
|
|
2911
2976
|
: `if(typeof ${v}==='string'&&!/^([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})?$/.test(${v}))return false`,
|
|
@@ -2919,12 +2984,8 @@ const FORMAT_CODEGEN = {
|
|
|
2919
2984
|
? `if(/[\\s\\u0000-\\u001f\\u007f]/.test(${v}))return false`
|
|
2920
2985
|
: `if(typeof ${v}==='string'&&/[\\s\\u0000-\\u001f\\u007f]/.test(${v}))return false`,
|
|
2921
2986
|
ipv4: _formats.ipv4Source,
|
|
2922
|
-
ipv6:
|
|
2923
|
-
|
|
2924
|
-
: `if(typeof ${v}==='string'){const _s=${v};if(_s===''||!/^[0-9a-fA-F:]+$/.test(_s)||_s.split(':').length<3||_s.split(':').length>8)return false}`,
|
|
2925
|
-
hostname: (v, isStr) => isStr
|
|
2926
|
-
? `if(!/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(${v}))return false`
|
|
2927
|
-
: `if(typeof ${v}==='string'&&!/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(${v}))return false`,
|
|
2987
|
+
ipv6: _formats.ipv6Source,
|
|
2988
|
+
hostname: _formats.hostnameSource,
|
|
2928
2989
|
}
|
|
2929
2990
|
|
|
2930
2991
|
// Safe key escaping: use JSON.stringify to handle all special chars (newlines, null bytes, etc.)
|
|
@@ -3069,8 +3130,6 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
3069
3130
|
if (typeof schema === 'object' && schema !== null) {
|
|
3070
3131
|
const s = JSON.stringify(schema)
|
|
3071
3132
|
if (s.includes('unevaluatedProperties') || s.includes('unevaluatedItems')) return null
|
|
3072
|
-
// Bail on self-referencing schemas — error codegen doesn't support recursion
|
|
3073
|
-
if (s.includes('"$ref":"#"')) return null
|
|
3074
3133
|
}
|
|
3075
3134
|
if (typeof schema === 'boolean') {
|
|
3076
3135
|
return schema
|
|
@@ -3133,7 +3192,12 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
3133
3192
|
: null }
|
|
3134
3193
|
ctx.helperCode.push('const _cpLen=s=>{let n=0;for(const _ of s)n++;return n}')
|
|
3135
3194
|
const lines = []
|
|
3136
|
-
|
|
3195
|
+
try {
|
|
3196
|
+
genCodeE(schema, 'd', '', lines, ctx, '#')
|
|
3197
|
+
} catch (e) {
|
|
3198
|
+
if (e === DECLINE) return null
|
|
3199
|
+
throw e
|
|
3200
|
+
}
|
|
3137
3201
|
// Inline branch-collapse helper only when the schema actually used a
|
|
3138
3202
|
// oneOf/anyOf branch path (tracked via ctx.usesBranchCollapse). Keeps the
|
|
3139
3203
|
// AOT output small for schemas that don't compose. Standalone modules can't
|
|
@@ -3144,16 +3208,28 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
3144
3208
|
if (lines.length === 0) return (d) => ({ valid: true, errors: [] })
|
|
3145
3209
|
|
|
3146
3210
|
const checkStr = lines.join('\n ')
|
|
3211
|
+
const defSetsE = ctx.defFns ? Array.from(ctx.defFns.values()) : []
|
|
3212
|
+
const needsGuardE = ctx.usesRecursion || defSetsE.length > 0
|
|
3213
|
+
const guardStrE = needsGuardE ? emitGuardState() : ''
|
|
3214
|
+
const helpersE = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
|
|
3215
|
+
|
|
3147
3216
|
let body
|
|
3148
3217
|
if (ctx.usesRecursion) {
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3218
|
+
// The abandoned first pass may have pushed errors, so the retry starts
|
|
3219
|
+
// from an empty list and the reported errors come from the guarded run
|
|
3220
|
+
// alone.
|
|
3221
|
+
body = `const _e=[];\n ` + guardStrE + helpersE +
|
|
3222
|
+
emitRootGuard('_validateE', 'd,_all,_e', '') +
|
|
3223
|
+
`function _validateE_b(d,_all,_e){\n ${checkStr}\n }\n ` +
|
|
3224
|
+
emitGuardedRun('_validateE(d,_all,_e)', '_e.length=0;', defSetsE) +
|
|
3225
|
+
`return{valid:_e.length===0,errors:_e}`
|
|
3226
|
+
} else if (defSetsE.length > 0) {
|
|
3227
|
+
body = `const _e=[];\n ` + guardStrE + helpersE +
|
|
3228
|
+
`function _bodyE(d,_all,_e){\n ${checkStr}\n }\n ` +
|
|
3229
|
+
emitGuardedRun('_bodyE(d,_all,_e)', '_e.length=0;', defSetsE) +
|
|
3152
3230
|
`return{valid:_e.length===0,errors:_e}`
|
|
3153
3231
|
} else {
|
|
3154
|
-
body = `const _e=[];\n ` +
|
|
3155
|
-
(ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : '') +
|
|
3156
|
-
checkStr +
|
|
3232
|
+
body = `const _e=[];\n ` + helpersE + checkStr +
|
|
3157
3233
|
`\n return{valid:_e.length===0,errors:_e}`
|
|
3158
3234
|
}
|
|
3159
3235
|
try {
|
|
@@ -3232,8 +3308,11 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3232
3308
|
}
|
|
3233
3309
|
// $ref — resolve local and cross-schema refs
|
|
3234
3310
|
if (schema.$ref) {
|
|
3235
|
-
//
|
|
3236
|
-
|
|
3311
|
+
// A self-reference cannot be expressed here yet: the root helper takes no
|
|
3312
|
+
// path argument, so errors raised through it would carry the path of the
|
|
3313
|
+
// root rather than of the value that failed. This used to fall through as
|
|
3314
|
+
// a permissive no-op, which accepted whatever the reference guarded.
|
|
3315
|
+
if (schema.$ref === '#') throw DECLINE
|
|
3237
3316
|
const m = schema.$ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
|
|
3238
3317
|
if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
|
|
3239
3318
|
const defName = m[1]
|
|
@@ -3248,7 +3327,12 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3248
3327
|
ctx.defFns.set(defName, fnName)
|
|
3249
3328
|
const bodyLines = []
|
|
3250
3329
|
genCodeE(ctx.rootDefs[defName], 'd', '_p', bodyLines, ctx, '#/$defs/' + defName)
|
|
3251
|
-
ctx.helperCode.push(
|
|
3330
|
+
ctx.helperCode.push(
|
|
3331
|
+
`const ${fnName}_s=new Set()\n function ${fnName}(d,_p,_all,_e){\n ` +
|
|
3332
|
+
`if(_sg){if(typeof d!=='object'||d===null)return ${fnName}_b(d,_p,_all,_e);if(${fnName}_s.has(d))return;${fnName}_s.add(d);try{return ${fnName}_b(d,_p,_all,_e)}finally{${fnName}_s.delete(d)}}\n ` +
|
|
3333
|
+
`if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d,_p,_all,_e)\n _sd--\n return _r\n }\n ` +
|
|
3334
|
+
`function ${fnName}_b(d,_p,_all,_e){${bodyLines.join('\n ')}}`,
|
|
3335
|
+
)
|
|
3252
3336
|
}
|
|
3253
3337
|
lines.push(`${fnName}(${v},${pathExpr || '""'},_all,_e);if(!_all&&_e.length)return{valid:false,errors:_e}`)
|
|
3254
3338
|
return
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "JSON Schema validation with first-class TypeScript and zero runtime cost. AOT compile to per-schema ESM modules with zero validator dependency. Generic Validator<T> for TypeBox/Zod/Valibot composition. Optional runtime API. Standard Schema V1 compatible.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"release:check": "node scripts/regen-safe-regex-source.js && node tests/test_pack_purity.js && node scripts/check-doc-coverage.js && node tests/test_error_codes_lock.js && node tests/test_safe_regex_source_sync.js && node tests/test_version_sync.js",
|
|
45
45
|
"build": "cmake-js build --target ata",
|
|
46
46
|
"rebuild": "cmake-js rebuild --target ata",
|
|
47
|
-
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_formats_single_pass.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_ajv_errors.js && node tests/test_pattern_properties_errors.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_data_positions.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
|
|
47
|
+
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_formats_single_pass.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_ajv_errors.js && node tests/test_pattern_properties_errors.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_data_positions.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
|
|
48
48
|
"bench:size": "node benchmark/bench_aot_size.mjs",
|
|
49
49
|
"test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
|
|
50
50
|
"test:compat": "node tests/test_compat.js",
|
|
@@ -112,13 +112,13 @@
|
|
|
112
112
|
"LICENSE"
|
|
113
113
|
],
|
|
114
114
|
"optionalDependencies": {
|
|
115
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
116
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
117
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
118
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
119
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
120
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
121
|
-
"@ata-validator/native-win32-x64": "1.
|
|
115
|
+
"@ata-validator/native-darwin-arm64": "1.11.0",
|
|
116
|
+
"@ata-validator/native-darwin-x64": "1.11.0",
|
|
117
|
+
"@ata-validator/native-linux-x64-gnu": "1.11.0",
|
|
118
|
+
"@ata-validator/native-linux-arm64-gnu": "1.11.0",
|
|
119
|
+
"@ata-validator/native-linux-x64-musl": "1.11.0",
|
|
120
|
+
"@ata-validator/native-linux-arm64-musl": "1.11.0",
|
|
121
|
+
"@ata-validator/native-win32-x64": "1.11.0"
|
|
122
122
|
},
|
|
123
123
|
"peerDependencies": {
|
|
124
124
|
"yaml": "^2.0.0"
|