ata-validator 1.2.2 → 1.3.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 +13 -0
- package/index.d.ts +13 -0
- package/index.js +23 -9
- package/lib/draft7.js +43 -1
- package/lib/js-compiler.js +174 -10
- package/lib/version.js +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
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.3.0 - 2026-08-02
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `useDefaults` and `assertFormat` validator options, both defaulting to `true` so existing behavior is unchanged. `useDefaults: false` stops missing properties being filled in from their `default` before validation, which matters because a `default` that does not satisfy its own subschema currently makes an otherwise valid instance fail. `assertFormat: false` treats `format` as an annotation rather than an assertion, the Draft 2020-12 reading when the format-assertion vocabulary is not in use. With both off, ata passes every case in the suite's `format` and `default` files.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- A `$ref` that could not be resolved validated everything instead of failing. The JS compiler emitted no check at all for an unresolved reference, so a typo in a `$ref`, or a reference into a schema that was never registered, silently turned off every constraint behind it rather than reporting an error. Both compiler paths now decline to compile such a schema and it validates on the interpreted engine, which reports `ATA5001` naming the reference it could not resolve.
|
|
14
|
+
- References that resolve relative to an enclosing base URI are no longer compiled by the JS paths, which match registry entries by exact key or path suffix and have no notion of a base. Nested `$id` scopes, relative references, and documents registered under a URI different from the `$id` they declare now route to the interpreted engine, which tracks the base properly. Schemas that reference a flat registry of ids, the common `$ref: 'shared#'` shape, still take the compiled fast path.
|
|
15
|
+
- A schema supplied through the `schemas` option as a URI-keyed record was registered only under the `$id` it declared, so references to the URI it was registered under could not resolve. It is now addressable by both.
|
|
16
|
+
- Official draft 2020-12 remote-reference suite: 30 of 31 cases, up from 21. Pure-JS configuration on the full suite: 1189 of 1190, up from 1188. The native configuration stays at 1190 of 1190.
|
|
17
|
+
|
|
5
18
|
## 1.2.2 - 2026-07-26
|
|
6
19
|
|
|
7
20
|
### Fixed
|
package/index.d.ts
CHANGED
|
@@ -317,6 +317,19 @@ export type ValidateAndParseResult<T = unknown> =
|
|
|
317
317
|
export interface ValidatorOptions {
|
|
318
318
|
coerceTypes?: boolean;
|
|
319
319
|
removeAdditional?: boolean;
|
|
320
|
+
/**
|
|
321
|
+
* When true (default), missing properties are filled in from their `default`
|
|
322
|
+
* before validation. Set to false to leave the instance untouched, which is
|
|
323
|
+
* what the specification describes: `default` is an annotation and never
|
|
324
|
+
* affects whether an instance is valid.
|
|
325
|
+
*/
|
|
326
|
+
useDefaults?: boolean;
|
|
327
|
+
/**
|
|
328
|
+
* When true (default), `format` is asserted and a value that does not match
|
|
329
|
+
* its format fails. Set to false to treat `format` as an annotation, the
|
|
330
|
+
* Draft 2020-12 behavior when the format-assertion vocabulary is not in use.
|
|
331
|
+
*/
|
|
332
|
+
assertFormat?: boolean;
|
|
320
333
|
schemas?: Record<string, object> | object[];
|
|
321
334
|
/**
|
|
322
335
|
* Custom format checkers. Keys are format names referenced from `format` in
|
package/index.js
CHANGED
|
@@ -9,7 +9,7 @@ const {
|
|
|
9
9
|
compileToJSCodegenWithErrors,
|
|
10
10
|
compileToJSCombined,
|
|
11
11
|
} = require("./lib/js-compiler");
|
|
12
|
-
const { normalizeDraft7, normalizeNullable } = require("./lib/draft7");
|
|
12
|
+
const { normalizeDraft7, normalizeNullable, stripFormatAssertions } = require("./lib/draft7");
|
|
13
13
|
const { classify } = require("./lib/shape-classifier");
|
|
14
14
|
const { buildTier0Plan, tier0Validate } = require("./lib/tier0");
|
|
15
15
|
|
|
@@ -250,11 +250,13 @@ function buildPreprocessCodegen(schema, options) {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
// defaults: inline per property
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
253
|
+
if (options.useDefaults !== false) {
|
|
254
|
+
for (const [key, prop] of Object.entries(props)) {
|
|
255
|
+
if (prop && typeof prop === 'object' && prop.default !== undefined) {
|
|
256
|
+
const k = JSON.stringify(key);
|
|
257
|
+
const def = JSON.stringify(prop.default);
|
|
258
|
+
lines.push(`if(!(${k} in d))d[${k}]=${def}`);
|
|
259
|
+
}
|
|
258
260
|
}
|
|
259
261
|
}
|
|
260
262
|
|
|
@@ -435,7 +437,11 @@ function buildSchemaMap(schemas) {
|
|
|
435
437
|
} else {
|
|
436
438
|
for (const [key, s] of Object.entries(schemas)) {
|
|
437
439
|
const normalized = _normalizeCallerSchema(s)
|
|
438
|
-
|
|
440
|
+
// A retrieved document is addressable both by the URI it was registered
|
|
441
|
+
// under and by the $id it declares. Registering only the $id makes
|
|
442
|
+
// references to the retrieval URI unresolvable.
|
|
443
|
+
map.set(key, normalized)
|
|
444
|
+
if (normalized.$id && normalized.$id !== key) map.set(normalized.$id, normalized)
|
|
439
445
|
}
|
|
440
446
|
}
|
|
441
447
|
return map
|
|
@@ -537,10 +543,18 @@ class Validator {
|
|
|
537
543
|
// When schema is a string, JSON.parse already produces a fresh object.
|
|
538
544
|
// When schema is an object, normalization runs on a clone so the caller's
|
|
539
545
|
// object is never touched.
|
|
540
|
-
|
|
546
|
+
let schemaObj = typeof schema === "string"
|
|
541
547
|
? JSON.parse(schema)
|
|
542
548
|
: _normalizeCallerSchema(schema);
|
|
543
549
|
|
|
550
|
+
// assertFormat: false makes `format` annotation-only. Strip it on a clone
|
|
551
|
+
// so the caller's schema keeps the keyword.
|
|
552
|
+
if (options.assertFormat === false) {
|
|
553
|
+
schemaObj = stripFormatAssertions(
|
|
554
|
+
schemaObj === schema ? _deepCloneWithSymbols(schemaObj) : schemaObj,
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
544
558
|
this._schemaStr = null; // lazy: computed on first use
|
|
545
559
|
this._schemaObj = schemaObj;
|
|
546
560
|
this._options = options;
|
|
@@ -722,7 +736,7 @@ class Validator {
|
|
|
722
736
|
const preprocessSchema = resolveSchemaForPreprocess(schemaObj, this._schemaMap);
|
|
723
737
|
let preprocess = buildPreprocessCodegen(preprocessSchema, options);
|
|
724
738
|
if (!preprocess) {
|
|
725
|
-
const applyDefaults = buildDefaultsApplier(preprocessSchema);
|
|
739
|
+
const applyDefaults = options.useDefaults === false ? null : buildDefaultsApplier(preprocessSchema);
|
|
726
740
|
const applyCoerce = options.coerceTypes ? buildCoercer(preprocessSchema) : null;
|
|
727
741
|
const applyRemove = options.removeAdditional
|
|
728
742
|
? buildRemover(preprocessSchema)
|
package/lib/draft7.js
CHANGED
|
@@ -126,4 +126,46 @@ function _normalizeNullable(schema) {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
// Draft 2020-12 treats `format` as an annotation unless the assertion
|
|
130
|
+
// vocabulary is in use. Removing the keyword is exactly that reading, and it
|
|
131
|
+
// keeps every engine in agreement without each one growing its own switch.
|
|
132
|
+
// Walks schema-bearing keywords only, so a property or definition named
|
|
133
|
+
// "format" is untouched.
|
|
134
|
+
function stripFormatAssertions(schema) {
|
|
135
|
+
if (typeof schema !== 'object' || schema === null) return schema
|
|
136
|
+
_stripFormat(schema, new Set())
|
|
137
|
+
return schema
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function _stripFormat(schema, seen) {
|
|
141
|
+
if (typeof schema !== 'object' || schema === null || Array.isArray(schema)) return
|
|
142
|
+
if (seen.has(schema)) return
|
|
143
|
+
seen.add(schema)
|
|
144
|
+
|
|
145
|
+
if (typeof schema.format === 'string') delete schema.format
|
|
146
|
+
|
|
147
|
+
const objSubs = ['properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas']
|
|
148
|
+
for (const key of objSubs) {
|
|
149
|
+
if (schema[key] && typeof schema[key] === 'object' && !Array.isArray(schema[key])) {
|
|
150
|
+
for (const v of Object.values(schema[key])) _stripFormat(v, seen)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const arrSubs = ['allOf', 'anyOf', 'oneOf', 'prefixItems']
|
|
154
|
+
for (const key of arrSubs) {
|
|
155
|
+
if (Array.isArray(schema[key])) {
|
|
156
|
+
for (const s of schema[key]) _stripFormat(s, seen)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const singleSubs = ['items', 'additionalItems', 'contains', 'not', 'if', 'then', 'else',
|
|
160
|
+
'additionalProperties', 'propertyNames', 'unevaluatedItems',
|
|
161
|
+
'unevaluatedProperties', 'contentSchema']
|
|
162
|
+
for (const key of singleSubs) {
|
|
163
|
+
if (Array.isArray(schema[key])) {
|
|
164
|
+
for (const s of schema[key]) _stripFormat(s, seen)
|
|
165
|
+
} else {
|
|
166
|
+
_stripFormat(schema[key], seen)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = { isDraft7, normalizeDraft7, normalizeNullable, stripFormatAssertions }
|
package/lib/js-compiler.js
CHANGED
|
@@ -107,6 +107,8 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
107
107
|
if (!hasDynamic && hasNestedIdScope(schema)) return null
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
if (!defs && needsBaseTracking(schema, schemaMap, new Set())) return null
|
|
111
|
+
|
|
110
112
|
// Collect $defs early so sub-schemas can resolve $ref
|
|
111
113
|
const rootDefs = defs || collectDefs(schema)
|
|
112
114
|
|
|
@@ -611,27 +613,23 @@ function resolveRef(ref, defs, schemaMap) {
|
|
|
611
613
|
if (entry) return (d) => { const fn = entry.fn; return fn ? fn(d) : true }
|
|
612
614
|
}
|
|
613
615
|
}
|
|
616
|
+
// A referenced schema that cannot itself be compiled must propagate the bail.
|
|
617
|
+
// Substituting an always-true function here would make the reference vacuous
|
|
618
|
+
// and silently drop every constraint behind it.
|
|
614
619
|
// 2. Cross-schema ref (exact match)
|
|
615
620
|
if (schemaMap && schemaMap.has(ref)) {
|
|
616
|
-
|
|
617
|
-
const fn = compileToJS(resolved, null, schemaMap)
|
|
618
|
-
return fn || (() => true)
|
|
621
|
+
return compileToJS(schemaMap.get(ref), null, schemaMap)
|
|
619
622
|
}
|
|
620
623
|
// 3. Cross-schema ref with JSON pointer fragment ("<id>#/<path>")
|
|
621
624
|
if (schemaMap && ref.includes('#')) {
|
|
622
625
|
const r = resolveCrossSchemaRef(ref, schemaMap)
|
|
623
|
-
if (r)
|
|
624
|
-
const fn = compileToJS(r.schema, null, schemaMap)
|
|
625
|
-
return fn || (() => true)
|
|
626
|
-
}
|
|
626
|
+
if (r) return compileToJS(r.schema, null, schemaMap)
|
|
627
627
|
}
|
|
628
628
|
// 4. Cross-schema ref (relative URI resolution, no fragment)
|
|
629
629
|
if (schemaMap && !ref.includes('://') && !ref.startsWith('#')) {
|
|
630
630
|
for (const [id] of schemaMap) {
|
|
631
631
|
if (id.endsWith('/' + ref)) {
|
|
632
|
-
|
|
633
|
-
const fn = compileToJS(resolved, null, schemaMap)
|
|
634
|
-
return fn || (() => true)
|
|
632
|
+
return compileToJS(schemaMap.get(id), null, schemaMap)
|
|
635
633
|
}
|
|
636
634
|
}
|
|
637
635
|
}
|
|
@@ -933,6 +931,169 @@ function hasAdditionalPropertiesSchema(schema) {
|
|
|
933
931
|
return false
|
|
934
932
|
}
|
|
935
933
|
|
|
934
|
+
// Schema-bearing keywords, so the walk below never mistakes a property named
|
|
935
|
+
// "$ref" for an actual reference.
|
|
936
|
+
const SUBSCHEMA_MAPS = ['properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas']
|
|
937
|
+
const SUBSCHEMA_LISTS = ['allOf', 'anyOf', 'oneOf', 'prefixItems']
|
|
938
|
+
const SUBSCHEMA_SINGLES = ['items', 'additionalItems', 'contains', 'not', 'if', 'then', 'else',
|
|
939
|
+
'additionalProperties', 'propertyNames', 'unevaluatedItems', 'unevaluatedProperties', 'contentSchema']
|
|
940
|
+
|
|
941
|
+
// genCode emits nothing for a $ref it cannot resolve, which makes the reference
|
|
942
|
+
// silently vacuous — a typo in a $ref would turn validation off rather than
|
|
943
|
+
// fail. Codegen bails instead, so the schema routes to the interpreted engine,
|
|
944
|
+
// which reports the unresolved reference as an error. Mirrors genCode's own
|
|
945
|
+
// resolution rules; anything genCode cannot resolve is treated as unresolvable.
|
|
946
|
+
function refResolves(ref, rootDefs, anchors, schemaMap) {
|
|
947
|
+
if (ref === '#') return true
|
|
948
|
+
const local = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
|
|
949
|
+
if (local) return !!(rootDefs && rootDefs[local[1]])
|
|
950
|
+
if (ref.startsWith('#') && !ref.startsWith('#/')) {
|
|
951
|
+
const entry = rootDefs && rootDefs[ref]
|
|
952
|
+
return !!((entry && entry.raw) || (anchors && anchors[ref]))
|
|
953
|
+
}
|
|
954
|
+
if (ref.startsWith('#')) return false
|
|
955
|
+
if (!schemaMap) return false
|
|
956
|
+
if (schemaMap.has(ref)) return true
|
|
957
|
+
if (!ref.includes('://')) {
|
|
958
|
+
for (const [id] of schemaMap) if (id.endsWith('/' + ref)) return true
|
|
959
|
+
}
|
|
960
|
+
if (ref.includes('#')) return !!resolveCrossSchemaRef(ref, schemaMap)
|
|
961
|
+
return false
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// Resolve a $ref that crosses into another document, mirroring genCode's rules.
|
|
965
|
+
function crossDocTarget(ref, schemaMap) {
|
|
966
|
+
if (!schemaMap || ref.startsWith('#')) return null
|
|
967
|
+
if (schemaMap.has(ref)) return schemaMap.get(ref)
|
|
968
|
+
if (!ref.includes('://')) {
|
|
969
|
+
for (const [id, s] of schemaMap) if (id.endsWith('/' + ref)) return s
|
|
970
|
+
}
|
|
971
|
+
if (ref.includes('#')) {
|
|
972
|
+
const r = resolveCrossSchemaRef(ref, schemaMap)
|
|
973
|
+
if (r) return r.schema
|
|
974
|
+
}
|
|
975
|
+
return null
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// True when a subtree contains a fragment-only reference ("#", "#/$defs/x",
|
|
979
|
+
// "#anchor"). Such a reference is relative to the document that holds it.
|
|
980
|
+
function subtreeHasLocalRef(node, seen) {
|
|
981
|
+
if (typeof node !== 'object' || node === null || Array.isArray(node)) return false
|
|
982
|
+
if (seen.has(node)) return false
|
|
983
|
+
seen.add(node)
|
|
984
|
+
if (typeof node.$ref === 'string' && node.$ref.startsWith('#')) return true
|
|
985
|
+
for (const key of SUBSCHEMA_MAPS) {
|
|
986
|
+
const group = node[key]
|
|
987
|
+
if (group && typeof group === 'object' && !Array.isArray(group)) {
|
|
988
|
+
for (const sub of Object.values(group)) if (subtreeHasLocalRef(sub, seen)) return true
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
for (const key of SUBSCHEMA_LISTS) {
|
|
992
|
+
if (Array.isArray(node[key])) {
|
|
993
|
+
for (const sub of node[key]) if (subtreeHasLocalRef(sub, seen)) return true
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
for (const key of SUBSCHEMA_SINGLES) {
|
|
997
|
+
if (Array.isArray(node[key])) {
|
|
998
|
+
for (const sub of node[key]) if (subtreeHasLocalRef(sub, seen)) return true
|
|
999
|
+
} else if (subtreeHasLocalRef(node[key], seen)) return true
|
|
1000
|
+
}
|
|
1001
|
+
return false
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Neither JS path tracks the base URI a reference is resolved against: they
|
|
1005
|
+
// match registry keys by exact string or by path suffix. That is fine for a
|
|
1006
|
+
// flat registry of absolute ids, but wrong as soon as resolution depends on an
|
|
1007
|
+
// enclosing scope. These shapes go to the interpreted engine, which resolves
|
|
1008
|
+
// against a real base.
|
|
1009
|
+
function needsBaseTracking(schema, schemaMap, seen) {
|
|
1010
|
+
if (!schemaMap || schemaMap.size === 0) return false
|
|
1011
|
+
// A nested $id opens a new scope that later relative references resolve
|
|
1012
|
+
// against.
|
|
1013
|
+
if (hasNestedIdScope(schema)) return true
|
|
1014
|
+
return refsAreScopeSensitive(schema, schemaMap, seen)
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function refsAreScopeSensitive(node, schemaMap, seen) {
|
|
1018
|
+
if (typeof node !== 'object' || node === null || Array.isArray(node)) return false
|
|
1019
|
+
if (seen.has(node)) return false
|
|
1020
|
+
seen.add(node)
|
|
1021
|
+
|
|
1022
|
+
const ref = node.$ref
|
|
1023
|
+
if (typeof ref === 'string' && !ref.startsWith('#')) {
|
|
1024
|
+
const key = ref.split('#')[0]
|
|
1025
|
+
// A key the registry holds verbatim needs no base to resolve — this is the
|
|
1026
|
+
// flat-registry case ($ref: 'shared#'). Anything else that is not absolute
|
|
1027
|
+
// only resolves by walking up from an enclosing base.
|
|
1028
|
+
if (!schemaMap.has(key) && !ref.includes('://')) return true
|
|
1029
|
+
const target = crossDocTarget(ref, schemaMap)
|
|
1030
|
+
if (target && typeof target === 'object') {
|
|
1031
|
+
// Registered under a key that differs from the $id it declares, so its
|
|
1032
|
+
// own references resolve against a different base than its retrieval URI.
|
|
1033
|
+
if (typeof target.$id === 'string' && target.$id !== key) return true
|
|
1034
|
+
if (subtreeHasLocalRef(target, new Set()) && refsAreScopeSensitive(target, schemaMap, seen)) return true
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
for (const key of SUBSCHEMA_MAPS) {
|
|
1039
|
+
const group = node[key]
|
|
1040
|
+
if (group && typeof group === 'object' && !Array.isArray(group)) {
|
|
1041
|
+
for (const sub of Object.values(group)) if (refsAreScopeSensitive(sub, schemaMap, seen)) return true
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
for (const key of SUBSCHEMA_LISTS) {
|
|
1045
|
+
if (Array.isArray(node[key])) {
|
|
1046
|
+
for (const sub of node[key]) if (refsAreScopeSensitive(sub, schemaMap, seen)) return true
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
for (const key of SUBSCHEMA_SINGLES) {
|
|
1050
|
+
if (Array.isArray(node[key])) {
|
|
1051
|
+
for (const sub of node[key]) if (refsAreScopeSensitive(sub, schemaMap, seen)) return true
|
|
1052
|
+
} else if (refsAreScopeSensitive(node[key], schemaMap, seen)) return true
|
|
1053
|
+
}
|
|
1054
|
+
return false
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function hasUnresolvableRef(node, rootDefs, anchors, schemaMap, seen) {
|
|
1058
|
+
if (typeof node !== 'object' || node === null || Array.isArray(node)) return false
|
|
1059
|
+
if (seen.has(node)) return false
|
|
1060
|
+
seen.add(node)
|
|
1061
|
+
|
|
1062
|
+
if (typeof node.$ref === 'string') {
|
|
1063
|
+
if (!refResolves(node.$ref, rootDefs, anchors, schemaMap)) return true
|
|
1064
|
+
// Following a reference into another document changes the base URI, which
|
|
1065
|
+
// codegen does not track: it would resolve the target's own fragment-only
|
|
1066
|
+
// references against the entry schema instead. Bail so the interpreted
|
|
1067
|
+
// engine, which does track the base, handles it.
|
|
1068
|
+
const target = crossDocTarget(node.$ref, schemaMap)
|
|
1069
|
+
if (target && subtreeHasLocalRef(target, new Set())) return true
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
for (const key of SUBSCHEMA_MAPS) {
|
|
1073
|
+
const group = node[key]
|
|
1074
|
+
if (group && typeof group === 'object' && !Array.isArray(group)) {
|
|
1075
|
+
for (const sub of Object.values(group)) {
|
|
1076
|
+
if (hasUnresolvableRef(sub, rootDefs, anchors, schemaMap, seen)) return true
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
for (const key of SUBSCHEMA_LISTS) {
|
|
1081
|
+
if (Array.isArray(node[key])) {
|
|
1082
|
+
for (const sub of node[key]) {
|
|
1083
|
+
if (hasUnresolvableRef(sub, rootDefs, anchors, schemaMap, seen)) return true
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
for (const key of SUBSCHEMA_SINGLES) {
|
|
1088
|
+
if (Array.isArray(node[key])) {
|
|
1089
|
+
for (const sub of node[key]) {
|
|
1090
|
+
if (hasUnresolvableRef(sub, rootDefs, anchors, schemaMap, seen)) return true
|
|
1091
|
+
}
|
|
1092
|
+
} else if (hasUnresolvableRef(node[key], rootDefs, anchors, schemaMap, seen)) return true
|
|
1093
|
+
}
|
|
1094
|
+
return false
|
|
1095
|
+
}
|
|
1096
|
+
|
|
936
1097
|
// --- Codegen mode: generates a single Function (NOT CSP-safe) ---
|
|
937
1098
|
// This matches ajv's approach: one monolithic function, V8 JIT fully inlines it
|
|
938
1099
|
function compileToJSCodegen(schema, schemaMap, userFormats) {
|
|
@@ -998,6 +1159,9 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
|
|
|
998
1159
|
}
|
|
999
1160
|
}
|
|
1000
1161
|
|
|
1162
|
+
if (hasUnresolvableRef(schema, rootDefs, anchors, schemaMap, new Set())) return null
|
|
1163
|
+
if (needsBaseTracking(schema, schemaMap, new Set())) return null
|
|
1164
|
+
|
|
1001
1165
|
const ctx = { varCounter: 0, helpers: [], helperCode: [], preamble: [], closureVars: ['_cpLen'], closureVals: [_cpLen], rootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors, rootSchema: schema, userFormats: userFormats || null }
|
|
1002
1166
|
const lines = []
|
|
1003
1167
|
genCode(schema, 'd', lines, ctx)
|
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.3.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",
|
|
@@ -112,12 +112,12 @@
|
|
|
112
112
|
"LICENSE"
|
|
113
113
|
],
|
|
114
114
|
"optionalDependencies": {
|
|
115
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
116
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
117
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
118
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
119
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
120
|
-
"@ata-validator/native-win32-x64": "1.
|
|
115
|
+
"@ata-validator/native-darwin-arm64": "1.3.0",
|
|
116
|
+
"@ata-validator/native-linux-x64-gnu": "1.3.0",
|
|
117
|
+
"@ata-validator/native-linux-arm64-gnu": "1.3.0",
|
|
118
|
+
"@ata-validator/native-linux-x64-musl": "1.3.0",
|
|
119
|
+
"@ata-validator/native-linux-arm64-musl": "1.3.0",
|
|
120
|
+
"@ata-validator/native-win32-x64": "1.3.0"
|
|
121
121
|
},
|
|
122
122
|
"peerDependencies": {
|
|
123
123
|
"yaml": "^2.0.0"
|