ata-validator 1.36.0 → 1.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -101,16 +101,15 @@ file.
101
101
  | Bundle (gzipped) | simple | 1.3 KB | 58.1 KB | 43.9x smaller |
102
102
  | Bundle (gzipped) | complex | 7.8 KB | 58.1 KB | 7.4x smaller |
103
103
  | Bundle (gzipped) | nested | 4.5 KB | 58.1 KB | 12.9x smaller |
104
- | Cold start | simple | 22 ms | 44 ms | 2.0x faster |
105
- | Throughput (1M ops) | simple | 266 Mops/s | 109 Mops/s | 2.4x faster |
106
- | Compile time | simple | 19 µs | 1.67 ms | 87x faster |
104
+ | Cold start | simple | 22 ms | 40 ms | 1.8x faster |
105
+ | Throughput (1M ops) | simple | 269 Mops/s | 116 Mops/s | 2.3x faster |
106
+ | Compile time | simple | 19 µs | 1.63 ms | 85x faster |
107
107
 
108
108
  The runtime column is the default validator most frameworks ship. Reproduce on your machine
109
109
  with `npm run bench:aot-vs-ajv`. Numbers from one run on Apple M4 Pro, Node 25.2.1, 2026-09-28,
110
- on ata-validator 1.33.1. The runtime column's bundle measured 52.7 KB on the previous run and
111
- 58.1 KB now, with ata's modules unchanged in size. Across three runs throughput moved
112
- between 231 and 280 Mops/s against 97 to 109, cold start between 21 and 22 ms against 40 to 44,
113
- and the compile ratio between 80x and 87x. The throughput row times a single one-million-call
110
+ on ata-validator 1.36.0. Across four runs throughput moved between 213 and 269 Mops/s against
111
+ 94 to 116, cold start between 21 and 22 ms against 40 to 42, and the compile ratio between 79x
112
+ and 90x. The throughput row times a single one-million-call
114
113
  loop of a few milliseconds, so it moves the most from run to run; treat the last three rows as
115
114
  an order of magnitude rather than a constant.
116
115
 
@@ -240,21 +239,21 @@ is the most common way to get a misleading number out of this library.
240
239
 
241
240
  | | compiled with `ata build` | runtime `new Validator(schema)` |
242
241
  |---|---|---|
243
- | In a bundle, gzipped | **2.2 KB** | 93.2 KB |
244
- | Time to a served request | **3.5 ms** | 7.7 ms |
242
+ | In a bundle, gzipped | **2.2 KB** | 95.6 KB |
243
+ | Time to a served request | **3.5 ms** | 7.8 ms |
245
244
  | Schema known when | build time | any time |
246
245
 
247
246
  The bundle row is the ten-field user schema in
248
247
  `tests/fixtures/error-dx/user.schema.json`, every export of the compiled module against
249
- `new Validator(schema)`, built with `bun build --minify --target=browser` on ata 1.35.0.
250
- The startup row is a Hono route on Bun 1.4, best of seven, median of three rounds, from
248
+ `new Validator(schema)`, built with `bun build --minify --target=browser` on ata 1.36.0.
249
+ The startup row is a Hono route on Bun 1.4, median of nine interleaved runs, from
251
250
  `benchmark/bundle`, against 3.5 ms for the same app doing no validation at all, so the
252
251
  compiled path costs nothing measurable to start. The runtime figure is what it is because
253
252
  a schema that arrives at run time can use any keyword, so the whole engine has to be
254
253
  there. The compiled module imports nothing and contains only the checks your schema asks
255
254
  for.
256
255
 
257
- **On a server, use whichever fits your schemas.** 93 KB of JavaScript on a Node or Bun
256
+ **On a server, use whichever fits your schemas.** 96 KB of JavaScript on a Node or Bun
258
257
  process is not a cost anyone notices, and the runtime API is the simpler thing to reach
259
258
  for. Speed is the same either way once warm.
260
259
 
@@ -486,7 +485,7 @@ const v = new Validator(schema, {
486
485
 
487
486
  ### Build-time compile (`ata compile`)
488
487
 
489
- The `ata` CLI turns a JSON Schema file into a self-contained JavaScript module. No runtime dependency on `ata-validator`, so only the generated validator ships to the browser. For the ten-field user schema in `tests/fixtures/error-dx/user.schema.json` the module is 2.2 KB gzipped, full error detail included, against 93.2 KB for the runtime bundled for the browser.
488
+ The `ata` CLI turns a JSON Schema file into a self-contained JavaScript module. No runtime dependency on `ata-validator`, so only the generated validator ships to the browser. For the ten-field user schema in `tests/fixtures/error-dx/user.schema.json` the module is 2.2 KB gzipped, full error detail included, against 95.6 KB for the runtime bundled for the browser.
490
489
 
491
490
  ```bash
492
491
  npx ata compile schemas/user.json -o src/generated/user.validator.mjs
@@ -528,11 +527,11 @@ npx ata build 'schemas/*.json' --out-dir build/validators --check
528
527
  Run with `--watch` during development for incremental rebuilds.
529
528
 
530
529
  Bundle sizes for the 10-field user schema in `tests/fixtures/error-dx/user.schema.json`,
531
- minified and gzipped, measured with `bun build --minify --target=browser` on ata 1.35.0:
530
+ minified and gzipped, measured with `bun build --minify --target=browser` on ata 1.36.0:
532
531
 
533
532
  | What the app imports | Size | Notes |
534
533
  |---|---|---|
535
- | `Validator` from `ata-validator` | 93.2 KB | The compiler ships with it, because a runtime schema can use any keyword |
534
+ | `Validator` from `ata-validator` | 95.6 KB | The compiler ships with it, because a runtime schema can use any keyword |
536
535
  | `isValid` from the compiled module | **1.3 KB** | Nothing else is reachable, so the error collector is dropped |
537
536
  | `validate` from the compiled module | **2.0 KB** | Adds the detailed error collector |
538
537
 
@@ -541,6 +540,16 @@ bundling it makes no difference: importing only `isValid` already leaves the err
541
540
  collector unreachable, and a bundler drops it. Use the flag to cut the file on disk,
542
541
  not to cut what ships.
543
542
 
543
+ To get the compiled module without changing code written against the runtime API, use
544
+ `compileAway` in [`@ata-project/unplugin`](https://github.com/ata-core/unplugin-ata): a
545
+ `new Validator(schema)` whose schema is known at build time is replaced with the compiled
546
+ module wrapped by `fromCompiled()` from `ata-validator/compiled`, which answers `validate()`,
547
+ `isValidObject()`, `validateJSON()` and `isValidJSON()` as the runtime does, defaults and errors
548
+ included. For the plugin's three-schema test entry a minified Vite build goes from 115.7 KB to
549
+ 15.5 KB gzipped. In Node, loading the wrapper and a compiled module and answering the first two
550
+ checks takes 1.12 ms where the runtime takes 6.63 ms (median of 15 fresh processes). Across
551
+ SchemaStore's 977 schemas, 725 can be compiled away; the rest stay on the runtime.
552
+
544
553
  Programmatic API if you prefer to script it:
545
554
 
546
555
  ```javascript
@@ -557,7 +566,7 @@ through a `setFormats()` export the module carries. `docs/API.md` has the
557
566
  details.
558
567
 
559
568
  **Fastify startup, 10 route schemas, from a cold process to the first validated request:
560
- ajv 20.2 ms, ata 2.7 ms, no build step required.** ata registers in 0.2 ms of that and
569
+ ajv 19.9 ms, ata 2.9 ms, no build step required.** ata registers in 0.5 ms of that and
561
570
  compiles on the first request, so counting only registration would overstate the gap.
562
571
  Reproduce with `node benchmark/bench_fastify_boot.mjs`.
563
572
 
package/build.d.ts CHANGED
@@ -178,3 +178,10 @@ export function compiledSchemaFor(schema: unknown): object;
178
178
  * whose detailed errors the generator cannot produce.
179
179
  */
180
180
  export function compiledModuleFor(schema: unknown, opts?: { format?: 'esm' | 'cjs' }): string | null;
181
+
182
+ /**
183
+ * The Validator options `fromCompiled()` reproduces, so a plugin can tell
184
+ * whether `new Validator(schema, options)` can be replaced. Absent before
185
+ * ata-validator 1.37.0, where only calls without options can be.
186
+ */
187
+ export const compiledOptions: readonly string[];
package/build.mjs CHANGED
@@ -12,4 +12,5 @@ export const schemaHash = mod.schemaHash;
12
12
  export const compiledEligible = mod.compiledEligible;
13
13
  export const compiledSchemaFor = mod.compiledSchemaFor;
14
14
  export const compiledModuleFor = mod.compiledModuleFor;
15
+ export const compiledOptions = mod.compiledOptions;
15
16
  export default mod;
package/compiled.d.ts CHANGED
@@ -14,4 +14,12 @@ export interface CompiledValidator<T = unknown> {
14
14
  isValidJSON(json: string): boolean;
15
15
  }
16
16
 
17
- export function fromCompiled<T = unknown>(mod: CompiledModule, schema: object): CompiledValidator<T>;
17
+ /** The Validator options `fromCompiled` reproduces. Any other option throws. */
18
+ export const COMPILED_OPTIONS: readonly ['useDefaults'];
19
+
20
+ export interface CompiledOptions {
21
+ /** `false` leaves the input unchanged, as `new Validator(schema, { useDefaults: false })` does. */
22
+ useDefaults?: boolean;
23
+ }
24
+
25
+ export function fromCompiled<T = unknown>(mod: CompiledModule, schema: object, options?: CompiledOptions): CompiledValidator<T>;
package/compiled.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import mod from './compiled.js';
2
- export const { fromCompiled } = mod;
2
+ export const { fromCompiled, COMPILED_OPTIONS } = mod;
3
3
  export default mod;
package/index.d.ts CHANGED
@@ -452,6 +452,17 @@ export interface BundleStandaloneOptions extends ValidatorOptions {
452
452
  export interface StandardSchemaV1Props<Output = unknown, Input = unknown> {
453
453
  version: 1;
454
454
  vendor: "ata-validator";
455
+ /**
456
+ * Standard JSON Schema: the schema this validator checks, as a copy, for a
457
+ * consumer that publishes it (the MCP SDK's `inputSchema`, for one). The
458
+ * target must be the dialect the schema declares, and a schema without
459
+ * `$schema` is 2020-12. Any other target throws; ata does not convert
460
+ * between dialects.
461
+ */
462
+ jsonSchema: {
463
+ input(options: { target: "draft-2020-12" | "draft-07" | (string & {}); libraryOptions?: Record<string, unknown> }): Record<string, unknown>;
464
+ output(options: { target: "draft-2020-12" | "draft-07" | (string & {}); libraryOptions?: Record<string, unknown> }): Record<string, unknown>;
465
+ };
455
466
  validate(
456
467
  value: unknown
457
468
  ):
package/lib/aot-build.js CHANGED
@@ -295,8 +295,8 @@ const { schemaHash } = require('./schema-hash');
295
295
  // tests/test_compiled_parity.js holds that. Declined: custom error messages,
296
296
  // which the core applies in a layer the wrapper does not carry. A caller must also get a
297
297
  // module back from toStandaloneModule, which returns null for a schema it
298
- // cannot compile. The text checks are deliberately coarse: a property named
299
- // `default` declines too, and declining costs only the saving.
298
+ // cannot compile. The text check is deliberately coarse: a property named
299
+ // `errorMessage` declines too, and declining costs only the saving.
300
300
  function compiledEligible(schema) {
301
301
  if (typeof schema !== 'object' || schema === null || Array.isArray(schema)) return false;
302
302
  return !JSON.stringify(schema).includes('"errorMessage"');
@@ -331,7 +331,13 @@ function compiledModuleFor(schema, opts) {
331
331
  return src && !degraded ? src : null;
332
332
  }
333
333
 
334
+ // The Validator options fromCompiled() reproduces, for a plugin deciding whether
335
+ // `new Validator(schema, options)` can be replaced. An older ata-validator does
336
+ // not export this, and a plugin should then replace only calls without options.
337
+ const { COMPILED_OPTIONS: compiledOptions } = require('./compiled');
338
+
334
339
  module.exports = {
340
+ compiledOptions,
335
341
  compiledEligible,
336
342
  compiledSchemaFor,
337
343
  compiledModuleFor,
package/lib/compiled.js CHANGED
@@ -10,14 +10,20 @@
10
10
  //
11
11
  // Only for a schema compiledEligible() accepts, and with the schema
12
12
  // compiledSchemaFor() returns: the one the runtime reads, after normalization.
13
- // Custom error messages and every option other than the defaults stay on the
14
- // runtime. Defaults are applied as a default Validator applies them, before
15
- // any check, with the same code.
13
+ // Custom error messages stay on the runtime, and so does every option except
14
+ // the ones COMPILED_OPTIONS lists. Defaults are applied as a default Validator
15
+ // applies them, before any check, with the same code; `useDefaults: false`
16
+ // leaves the input as it is, as the runtime then does.
16
17
 
17
18
  const { LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy } = require('./rejections');
18
19
  const { buildDefaultsApplier } = require('./defaults');
19
20
 
20
21
  const VALID_RESULT = Object.freeze({ valid: true, errors: Object.freeze([]) });
22
+
23
+ // The Validator options the wrapper answers the same way as the runtime.
24
+ // Anything else changes what the runtime does in a way the wrapper does not
25
+ // reproduce, so it is refused rather than ignored.
26
+ const COMPILED_OPTIONS = Object.freeze(['useDefaults']);
21
27
  const EMPTY_ERRORS = Object.freeze([]);
22
28
  const VERDICT_DISAGREES = Object.freeze({
23
29
  valid: false,
@@ -43,9 +49,17 @@ class CompiledState {
43
49
  }
44
50
  }
45
51
 
46
- function fromCompiled(mod, schema) {
52
+ function fromCompiled(mod, schema, options) {
53
+ if (options !== undefined && (options === null || typeof options !== 'object')) {
54
+ throw new TypeError('fromCompiled options must be an object');
55
+ }
56
+ for (const key of Object.keys(options || {})) {
57
+ if (!COMPILED_OPTIONS.includes(key)) {
58
+ throw new TypeError(`fromCompiled does not support the ${key} option; a Validator with it has to stay on the runtime`);
59
+ }
60
+ }
47
61
  const self = new CompiledState(schema);
48
- const fill = buildDefaultsApplier(schema);
62
+ const fill = options && options.useDefaults === false ? null : buildDefaultsApplier(schema);
49
63
  if (fill) {
50
64
  self._mutatesInput = true;
51
65
  self._preprocess = fill;
@@ -93,4 +107,4 @@ function fromCompiled(mod, schema) {
93
107
  };
94
108
  }
95
109
 
96
- module.exports = { fromCompiled };
110
+ module.exports = { fromCompiled, COMPILED_OPTIONS };
@@ -1237,6 +1237,30 @@ const REF_NEUTRAL_SIBLINGS = new Set([
1237
1237
  'markdownEnumDescriptions', 'defaultSnippets', 'doNotSuggest', 'suggestSortText',
1238
1238
  ])
1239
1239
 
1240
+ // Stands for the schema path of a call to a recursive definition's error
1241
+ // helper while its body is generated; see genCodeENode. Not a pointer, so no
1242
+ // ordinal or source frame is looked up for it at compile time.
1243
+ const SP_PLACEHOLDER = '@@ata-sp@@'
1244
+
1245
+ // Follows a definition that is a local $ref to another definition until it
1246
+ // reaches one that is not. False for a chain that loops, and for one whose
1247
+ // next hop is not a definition in this document; codegenSafe decides whether
1248
+ // a non-local reference resolves.
1249
+ function aliasChainEnds(def, defs) {
1250
+ const seen = new Set()
1251
+ let node = def
1252
+ while (node && typeof node === 'object' && typeof node.$ref === 'string') {
1253
+ const m = node.$ref.match(/^#\/(?:\$defs|definitions)\/([^/]+)$/)
1254
+ if (!m) return !node.$ref.startsWith('#/')
1255
+ const name = m[1].replace(/~1/g, '/').replace(/~0/g, '~')
1256
+ if (seen.has(name)) return false
1257
+ seen.add(name)
1258
+ node = defs[name]
1259
+ if (node === undefined) return false
1260
+ }
1261
+ return true
1262
+ }
1263
+
1240
1264
  function codegenSafe(schema, schemaMap) {
1241
1265
  if (typeof schema === 'boolean') return true
1242
1266
  if (typeof schema !== 'object' || schema === null) return true
@@ -1381,7 +1405,11 @@ function codegenSafe(schema, schemaMap) {
1381
1405
  // codegen anchor maps register it (see "Build anchors map"), so anchor
1382
1406
  // refs (`$ref: '#name'`) resolve to it on all paths.
1383
1407
  if (def.$id && !def.$id.startsWith('#')) return false
1384
- if (def.$ref) return false // nested ref chain — bail
1408
+ // A definition that is only a reference to another one is an alias,
1409
+ // the shape TypeScript-to-schema generators emit for a named union.
1410
+ // The generators resolve it like any other $ref; a chain of aliases
1411
+ // that loops back on itself has no schema at its end and declines.
1412
+ if (def.$ref && !aliasChainEnds(def, defs)) return false
1385
1413
  if (!codegenSafe(def, schemaMap)) return false
1386
1414
  }
1387
1415
  }
@@ -1697,7 +1725,103 @@ function sharedCodegenGate(schema, schemaMap) {
1697
1725
 
1698
1726
  // --- Codegen mode: generates a single Function (NOT CSP-safe) ---
1699
1727
  // This matches ajv's approach: one monolithic function, V8 JIT fully inlines it
1728
+ // `propertyNames` as the generators read it. They express maxLength,
1729
+ // minLength, pattern, const and enum, and the gate declines anything else, so
1730
+ // a schema that writes `propertyNames: { $ref: '#/definitions/Key' }` or adds
1731
+ // `type: 'string'` went to the interpreted engine. Both are rewritten here, on
1732
+ // a copy of the nodes that change: a local reference, through aliases, is
1733
+ // replaced by what it names, `type: 'string'` is dropped since every property
1734
+ // name is a string, and annotations go. Where what is left is still not all
1735
+ // supported keys, the node is left as written and the gate declines as before.
1736
+ // Errors keep the path the interpreted engine reports, which runs through the
1737
+ // reference without naming it.
1738
+ const PN_SUPPORTED = new Set(['maxLength', 'minLength', 'pattern', 'const', 'enum'])
1739
+ const PN_ANNOTATIONS = new Set(['$schema', '$comment', 'title', 'description', 'examples', 'default', 'deprecated', 'readOnly', 'writeOnly'])
1740
+ function simplePropertyNames(pn, defs) {
1741
+ const seen = new Set()
1742
+ while (pn && typeof pn === 'object' && typeof pn.$ref === 'string') {
1743
+ if (Object.keys(pn).some((k) => k !== '$ref' && !PN_ANNOTATIONS.has(k) && !k.startsWith('x-'))) return null
1744
+ const m = pn.$ref.match(/^#\/(?:\$defs|definitions)\/([^/]+)$/)
1745
+ if (!m || !defs) return null
1746
+ const name = m[1].replace(/~1/g, '/').replace(/~0/g, '~')
1747
+ if (seen.has(name)) return null
1748
+ seen.add(name)
1749
+ pn = defs[name]
1750
+ }
1751
+ if (!pn || typeof pn !== 'object' || Array.isArray(pn)) return null
1752
+ const out = {}
1753
+ for (const [k, val] of Object.entries(pn)) {
1754
+ if (PN_ANNOTATIONS.has(k) || k.startsWith('x-')) continue
1755
+ if (k === 'type') {
1756
+ if (val === 'string' || (Array.isArray(val) && val.includes('string'))) continue
1757
+ return null
1758
+ }
1759
+ if (!PN_SUPPORTED.has(k)) return null
1760
+ out[k] = val
1761
+ }
1762
+ return out
1763
+ }
1764
+ // The three generators are handed the same schema object for one validator,
1765
+ // so the rewrite is kept per object and done once. It walks the schema rather
1766
+ // than serializing it to look for the keyword: on a 7.6 MB schema the
1767
+ // serialization, three times over, was 3% of the first answer.
1768
+ const _pnNormalized = new WeakMap()
1769
+ function normalizePropertyNames(root) {
1770
+ if (!root || typeof root !== 'object') return root
1771
+ const hit = _pnNormalized.get(root)
1772
+ if (hit !== undefined) return hit
1773
+ const out = normalizePropertyNamesWalk(root)
1774
+ _pnNormalized.set(root, out)
1775
+ return out
1776
+ }
1777
+ function normalizePropertyNamesWalk(root) {
1778
+ const defs = root.$defs || root.definitions || null
1779
+ const seen = new Map()
1780
+ const walk = (node) => {
1781
+ if (node === null || typeof node !== 'object') return node
1782
+ if (seen.has(node)) return seen.get(node)
1783
+ seen.set(node, node)
1784
+ if (Array.isArray(node)) {
1785
+ let out = node
1786
+ node.forEach((n, i) => { const w = walk(n); if (w !== n) { if (out === node) out = node.slice(); out[i] = w } })
1787
+ seen.set(node, out)
1788
+ return out
1789
+ }
1790
+ let out = node
1791
+ const set = (k, v) => { if (out === node) out = { ...node }; out[k] = v }
1792
+ for (const k of SUBSCHEMA_MAPS) {
1793
+ const map = node[k]
1794
+ if (!map || typeof map !== 'object') continue
1795
+ let copy = map
1796
+ for (const [name, sub] of Object.entries(map)) {
1797
+ const w = walk(sub)
1798
+ if (w !== sub) { if (copy === map) copy = { ...map }; copy[name] = w }
1799
+ }
1800
+ if (copy !== map) set(k, copy)
1801
+ }
1802
+ for (const k of SUBSCHEMA_LISTS) {
1803
+ if (!Array.isArray(node[k])) continue
1804
+ const w = walk(node[k])
1805
+ if (w !== node[k]) set(k, w)
1806
+ }
1807
+ for (const k of SUBSCHEMA_SINGLES) {
1808
+ if (k === 'propertyNames' || node[k] === null || typeof node[k] !== 'object') continue
1809
+ const w = walk(node[k])
1810
+ if (w !== node[k]) set(k, w)
1811
+ }
1812
+ if (node.propertyNames && typeof node.propertyNames === 'object') {
1813
+ const simple = simplePropertyNames(node.propertyNames, defs)
1814
+ if (simple && JSON.stringify(simple) !== JSON.stringify(node.propertyNames)) set('propertyNames', simple)
1815
+ }
1816
+ seen.set(node, out)
1817
+ return out
1818
+ }
1819
+ return walk(root)
1820
+ }
1821
+
1700
1822
  function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
1823
+ const inputSchema = schema
1824
+ schema = normalizePropertyNames(schema)
1701
1825
  if (typeof schema === 'boolean') return schema ? () => true : () => false
1702
1826
  if (typeof schema !== 'object' || schema === null) return null
1703
1827
  // removeAdditional: a verdict function that deletes the keys the remover
@@ -1768,7 +1892,7 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
1768
1892
  if (hasUnresolvableRef(schema, rootDefs, anchors, schemaMap, new Set())) return null
1769
1893
  if (needsBaseTracking(schema, schemaMap, new Set())) return null
1770
1894
 
1771
- const ctx = { varCounter: 0, helpers: [], helperCode: [], preamble: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen], rootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors, rootSchema: schema, userFormats: userFormats || null, removeNodes }
1895
+ const ctx = { varCounter: 0, helpers: [], helperCode: [], preamble: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen], rootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors, rootSchema: inputSchema, userFormats: userFormats || null, removeNodes }
1772
1896
  const lines = []
1773
1897
  try {
1774
1898
  genCode(schema, 'd', lines, ctx)
@@ -3912,6 +4036,8 @@ function genCharCodeSwitch(keys, v) {
3912
4036
  // Returns a function: (data, allErrors) => { valid, errors }
3913
4037
  // Valid path is still fast — only error path does extra work.
3914
4038
  function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts) {
4039
+ const inputSchema = schema
4040
+ schema = normalizePropertyNames(schema)
3915
4041
  // unevaluated* is generated only where it is provably its plain counterpart
3916
4042
  // (see unevalLocalOk); any occurrence that is not keeps the whole schema on
3917
4043
  // the interpreted engine's error path, exactly as before.
@@ -3974,7 +4100,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
3974
4100
  if (hasUnresolvableRef(schema, eRootDefs, eAnchors, schemaMap, new Set())) return null
3975
4101
  if (needsBaseTracking(schema, schemaMap, new Set())) return null
3976
4102
 
3977
- const ctx = { varCounter: 0, helperCode: [], rootDefs: eRootDefs, shared: [], refStack: new Set(), schemaMap: schemaMap || null, anchors: eAnchors, rootSchema: schema, userFormats: userFormats || null,
4103
+ const ctx = { varCounter: 0, helperCode: [], rootDefs: eRootDefs, shared: [], refStack: new Set(), schemaMap: schemaMap || null, anchors: eAnchors, rootSchema: inputSchema, userFormats: userFormats || null,
3978
4104
  // Custom format checkers referenced by the body, bound as
3979
4105
  // closure parameters below the way the other entry points do.
3980
4106
  closureVars: [], closureVals: [],
@@ -4159,16 +4285,26 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
4159
4285
  if (!fnName) {
4160
4286
  fnName = '_defE' + ctx.defFns.size + '_' + defName.replace(/[^A-Za-z0-9_]/g, '_')
4161
4287
  ctx.defFns.set(defName, fnName)
4288
+ // The schema path comes in as an argument too, `_sp`, so an error
4289
+ // names the path it was reached by, as it does when the definition
4290
+ // is inlined and as the interpreted engine reports it; a fixed
4291
+ // `#/$defs/<name>` named the definition instead. The body is
4292
+ // generated against a placeholder prefix that becomes `_sp` in every
4293
+ // string it starts. One left anywhere else declines, rather than
4294
+ // emitting a path the placeholder stands in for.
4162
4295
  const bodyLines = []
4163
- genCodeE(ctx.rootDefs[defName], 'd', '_p', bodyLines, ctx, '#/$defs/' + defName)
4296
+ genCodeE(ctx.rootDefs[defName], 'd', '_p', bodyLines, ctx, SP_PLACEHOLDER)
4297
+ let body = bodyLines.join('\n ')
4298
+ body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
4299
+ if (body.includes(SP_PLACEHOLDER)) throw DECLINE
4164
4300
  ctx.helperCode.push(
4165
- `const ${fnName}_s=new Set()\n function ${fnName}(d,_p,_all,_e){\n ` +
4166
- `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 ` +
4167
- `if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d,_p,_all,_e)\n _sd--\n return _r\n }\n ` +
4168
- `function ${fnName}_b(d,_p,_all,_e){${bodyLines.join('\n ')}}`,
4301
+ `const ${fnName}_s=new Set()\n function ${fnName}(d,_p,_all,_e,_sp){\n ` +
4302
+ `if(_sg){if(typeof d!=='object'||d===null)return ${fnName}_b(d,_p,_all,_e,_sp);if(${fnName}_s.has(d))return;${fnName}_s.add(d);try{return ${fnName}_b(d,_p,_all,_e,_sp)}finally{${fnName}_s.delete(d)}}\n ` +
4303
+ `if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d,_p,_all,_e,_sp)\n _sd--\n return _r\n }\n ` +
4304
+ `function ${fnName}_b(d,_p,_all,_e,_sp){${body}}`,
4169
4305
  )
4170
4306
  }
4171
- lines.push(`${fnName}(${v},${pathExpr || '""'},_all,_e);if(!_all&&_e.length)return{valid:false,errors:_e}`)
4307
+ lines.push(`${fnName}(${v},${pathExpr || '""'},_all,_e,'${schemaPrefix}');if(!_all&&_e.length)return{valid:false,errors:_e}`)
4172
4308
  return
4173
4309
  }
4174
4310
  if (ctx.refStack.has(schema.$ref)) return
@@ -4760,6 +4896,8 @@ function genIfE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
4760
4896
  // Avoids double-pass (jsFn → false → errFn runs same checks again).
4761
4897
  // Uses type-aware optimizations: after type check passes, skip guards.
4762
4898
  function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats) {
4899
+ const inputSchema = schema
4900
+ schema = normalizePropertyNames(schema)
4763
4901
  // Same provably-local rule as the error generator above.
4764
4902
  if (typeof schema === 'object' && schema !== null) {
4765
4903
  const s = JSON.stringify(schema)
@@ -4838,7 +4976,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats) {
4838
4976
  if (needsBaseTracking(schema, schemaMap, new Set())) return null
4839
4977
 
4840
4978
  const ctx = { varCounter: 0, helperCode: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen],
4841
- rootDefs: cRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: cAnchors, rootSchema: schema, userFormats: userFormats || null }
4979
+ rootDefs: cRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: cAnchors, rootSchema: inputSchema, userFormats: userFormats || null }
4842
4980
  const lines = []
4843
4981
  try {
4844
4982
  genCodeC(schema, 'd', '', lines, ctx, '#')
@@ -218,7 +218,9 @@ function codegenAvailable() {
218
218
  // Schema compilation cache: same schema string -> reuse compiled functions
219
219
  const _compileCache = new Map();
220
220
  // Generated preprocess passes, keyed like _compileCache plus the options that
221
- // shape the pass. `null` records that the generator produced none.
221
+ // shape the pass. Only passes that exist are kept: a schema with nothing to
222
+ // rewrite answers `null` quickly, and keeping that answer held its key, the
223
+ // whole schema text, for every such validator, 0.2 KB each.
222
224
  const _preprocessCache = new Map();
223
225
 
224
226
  // Object identity cache: same schema object reference -> reuse entire compiled state
@@ -908,7 +910,7 @@ class Validator {
908
910
  if (hit !== undefined) preprocess = hit;
909
911
  else {
910
912
  preprocess = _codegen.buildPreprocess(preprocessSchema, options) || null;
911
- if (ppKey !== null) _preprocessCache.set(ppKey, preprocess);
913
+ if (ppKey !== null && preprocess !== null) _preprocessCache.set(ppKey, preprocess);
912
914
  }
913
915
  }
914
916
  if (!preprocess) {
@@ -1469,6 +1471,7 @@ class Validator {
1469
1471
  value: Object.freeze({
1470
1472
  version: 1,
1471
1473
  vendor: "ata-validator",
1474
+ jsonSchema: standardJsonSchema(() => schemaObj),
1472
1475
  validate(value) {
1473
1476
  const result = v.validate(value);
1474
1477
  if (result.valid) return { value };
@@ -1610,6 +1613,31 @@ function defineSchema (schema) {
1610
1613
  // bound closure, stores it on the instance as an ordinary writable property
1611
1614
  // and returns it. The setter keeps the compile step's plain assignments
1612
1615
  // (`this.validate = fn`) working before the getter has ever run. Detached
1616
+ // Standard JSON Schema, the `jsonSchema` member of `~standard`: what a consumer
1617
+ // such as the MCP SDK reads to publish a schema, from `input({ target })`.
1618
+ // ata validates the JSON Schema it was given and does not transform values, so
1619
+ // input and output are the same document, handed back as a copy when the
1620
+ // target is the schema's dialect: draft-07 when it declares draft-07, 2020-12
1621
+ // when it declares 2020-12 or nothing, as ata reads it. Any other target
1622
+ // throws, as the specification asks, rather than returning a document that
1623
+ // would mean something else there.
1624
+ function standardJsonSchema(getSchema) {
1625
+ const convert = (options) => {
1626
+ const target = options && options.target;
1627
+ const schema = getSchema();
1628
+ if (schema === true) return {};
1629
+ if (schema === false) return { not: {} };
1630
+ const declared = typeof schema.$schema === 'string' ? schema.$schema : '';
1631
+ const dialect = declared === '' || declared.includes('draft/2020-12/schema') ? 'draft-2020-12'
1632
+ : declared.includes('draft-07/schema') ? 'draft-07' : declared;
1633
+ if (target !== dialect) {
1634
+ throw new TypeError(`ata-validator cannot provide a ${dialect} schema as ${String(target)}; it does not convert between dialects`);
1635
+ }
1636
+ return JSON.parse(JSON.stringify(schema));
1637
+ };
1638
+ return Object.freeze({ input: convert, output: convert });
1639
+ }
1640
+
1613
1641
  // use (`const f = v.validate`) keeps working because the closure binds the
1614
1642
  // instance.
1615
1643
  // Standard Schema V1. Built on first read, then pinned to the instance with
@@ -1621,6 +1649,7 @@ Object.defineProperty(Validator.prototype, "~standard", {
1621
1649
  const std = Object.freeze({
1622
1650
  version: 1,
1623
1651
  vendor: "ata-validator",
1652
+ jsonSchema: standardJsonSchema(() => self._rawSchema),
1624
1653
  validate(value) {
1625
1654
  const result = self.validate(value);
1626
1655
  if (result.valid) {
package/lib/version.js CHANGED
@@ -7,4 +7,4 @@
7
7
  //
8
8
  // Kept in lockstep with package.json by `tests/test_version_sync.js`.
9
9
 
10
- module.exports = '1.36.0';
10
+ module.exports = '1.37.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.36.0",
3
+ "version": "1.37.0",
4
4
  "description": "JSON Schema validation that compiles for speed and still runs where code generation is blocked. Compiled and interpreted engines answer identically at 100% of the official suite. TypeScript inference, Standard Schema V1, and a build step that emits dependency-free modules.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -59,7 +59,7 @@
59
59
  "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",
60
60
  "build": "cmake-js build --target ata",
61
61
  "rebuild": "cmake-js rebuild --target ata",
62
- "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_text_path_errors.js && node tests/test_compiled_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_validate_json_scanner_cost.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.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_esm_exports.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_native_lazy.js && node tests/test_cold_start_modules.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_option.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_error_accumulator_collision.js && node tests/test_uri_helper_parity.js && node tests/test_uri_fast_path.js && node tests/test_extend_verdict.js && node tests/test_ipv4_regex.js && node tests/test_ipv6_fast_path.js && node tests/test_nested_recursion_decline.js && node tests/test_generator_error_content.js && node tests/test_inherited_key_presence.js && node tests/test_proto_accessor.js && node tests/test_hybrid_tier.js && node tests/test_first_rejection_builds_one.js && node tests/test_container_local.js && node tests/test_one_shot_validate.js && node tests/test_fixed_pattern_inline.js && node tests/test_plan_source.js && node tests/test_fused_remove_additional.js && node tests/test_runtime_parse.js && node tests/test_formats_single_pass.js && node tests/test_email_format_cost.js && node tests/test_format_single_error.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_ref_annotation_siblings.js && node tests/test_gate_wrapped_schemas.js && node tests/test_pattern_message_escape.js && node tests/test_nested_defaults_engines.js && node tests/test_nested_coercion_parity.js && node tests/test_preprocess_cache.js && node tests/test_large_schema_codegen.js && node tests/test_base_uri.js && node tests/test_hoisted_def_recursion.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_deferred_def_scope.js && node tests/test_node_scoped_ctx_flags.js && node tests/test_fast_slot_exhaustion.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_additional_props_combined_cost.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_error_order_ordinal.js && node tests/test_ref_error_order.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.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_single_pass_errors.js && node tests/test_lazy_json_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_lite_parity.js && node tests/test_lite_bundle_size.js && node tests/test_defaults_own_keys.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_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_targeted_positions.js && node tests/test_position_map_cost.js && node tests/test_targeted_positions_cost.js && node tests/test_aot_staleness.js && node tests/test_file_positions.js && node tests/test_aot_positions_cost.js && node tests/test_schema_hash.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",
62
+ "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_text_path_errors.js && node tests/test_compiled_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_validate_json_scanner_cost.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_standard_json_schema.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.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_esm_exports.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_native_lazy.js && node tests/test_cold_start_modules.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_option.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_error_accumulator_collision.js && node tests/test_uri_helper_parity.js && node tests/test_uri_fast_path.js && node tests/test_extend_verdict.js && node tests/test_ipv4_regex.js && node tests/test_ipv6_fast_path.js && node tests/test_nested_recursion_decline.js && node tests/test_generator_error_content.js && node tests/test_inherited_key_presence.js && node tests/test_proto_accessor.js && node tests/test_hybrid_tier.js && node tests/test_first_rejection_builds_one.js && node tests/test_container_local.js && node tests/test_one_shot_validate.js && node tests/test_fixed_pattern_inline.js && node tests/test_plan_source.js && node tests/test_fused_remove_additional.js && node tests/test_runtime_parse.js && node tests/test_formats_single_pass.js && node tests/test_email_format_cost.js && node tests/test_format_single_error.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_ref_annotation_siblings.js && node tests/test_gate_wrapped_schemas.js && node tests/test_pattern_message_escape.js && node tests/test_nested_defaults_engines.js && node tests/test_nested_coercion_parity.js && node tests/test_preprocess_cache.js && node tests/test_large_schema_codegen.js && node tests/test_base_uri.js && node tests/test_hoisted_def_recursion.js && node tests/test_alias_definitions.js && node tests/test_property_names_ref.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_deferred_def_scope.js && node tests/test_node_scoped_ctx_flags.js && node tests/test_fast_slot_exhaustion.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_additional_props_combined_cost.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_error_order_ordinal.js && node tests/test_ref_error_order.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.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_single_pass_errors.js && node tests/test_lazy_json_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_lite_parity.js && node tests/test_lite_bundle_size.js && node tests/test_defaults_own_keys.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_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_targeted_positions.js && node tests/test_position_map_cost.js && node tests/test_targeted_positions_cost.js && node tests/test_aot_staleness.js && node tests/test_file_positions.js && node tests/test_aot_positions_cost.js && node tests/test_schema_hash.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",
63
63
  "bench:size": "node benchmark/bench_aot_size.mjs",
64
64
  "test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
65
65
  "test:compat": "node tests/test_compat.js",
@@ -117,6 +117,7 @@
117
117
  "aot.mjs",
118
118
  "aot.d.ts",
119
119
  "lib/",
120
+ "!lib/plan-source.js",
120
121
  "compat.js",
121
122
  "compat.mjs",
122
123
  "compat.d.ts",
@@ -134,13 +135,13 @@
134
135
  "LICENSE"
135
136
  ],
136
137
  "optionalDependencies": {
137
- "@ata-validator/native-darwin-arm64": "1.36.0",
138
- "@ata-validator/native-darwin-x64": "1.36.0",
139
- "@ata-validator/native-linux-arm64-gnu": "1.36.0",
140
- "@ata-validator/native-linux-arm64-musl": "1.36.0",
141
- "@ata-validator/native-linux-x64-gnu": "1.36.0",
142
- "@ata-validator/native-linux-x64-musl": "1.36.0",
143
- "@ata-validator/native-win32-x64": "1.36.0"
138
+ "@ata-validator/native-darwin-arm64": "1.37.0",
139
+ "@ata-validator/native-darwin-x64": "1.37.0",
140
+ "@ata-validator/native-linux-arm64-gnu": "1.37.0",
141
+ "@ata-validator/native-linux-arm64-musl": "1.37.0",
142
+ "@ata-validator/native-linux-x64-gnu": "1.37.0",
143
+ "@ata-validator/native-linux-x64-musl": "1.37.0",
144
+ "@ata-validator/native-win32-x64": "1.37.0"
144
145
  },
145
146
  "peerDependencies": {
146
147
  "yaml": "^2.0.0"
@@ -1,281 +0,0 @@
1
- 'use strict';
2
-
3
- // Generated source from interpreter plans: the verdict function.
4
- //
5
- // The interpreted engine is the reference every other engine is tested
6
- // against, and its Plan is a schema node with every keyword resolved once.
7
- // lib/plan-compiler.js turns plans into closures; this turns them into source,
8
- // deciding at compile time exactly what plan-compiler decides, and calling the
9
- // same helpers (deepEqual, the code point bounds, multipleOfOk, the format
10
- // functions and compiled patterns the plan already holds), so the answers
11
- // cannot drift the way three hand-written generators did.
12
- //
13
- // The scope is deliberately narrow for now: value-level keywords, properties
14
- // and additionalProperties, prefixItems and items, and the in-place
15
- // applicators. Everything else, references, unevaluated*, contains,
16
- // propertyNames, patternProperties, dependentSchemas, custom keywords, makes
17
- // the compiler return null. It never emits a function with a keyword missing.
18
-
19
- const { createInterpreter, _planInternals: I } = require('./interpreter');
20
- const { Plan, deepEqual, cpAtLeast, cpAtMost, multipleOfOk, T_STRING, T_NUMBER, T_INTEGER, T_BOOLEAN, T_NULL, T_OBJECT, T_ARRAY } = I;
21
-
22
- const DECLINE = Symbol('plan-source.decline');
23
- const ALL_TYPES = T_STRING | T_NUMBER | T_INTEGER | T_BOOLEAN | T_NULL | T_OBJECT | T_ARRAY;
24
-
25
- // The own-property test the generators use; see ownKeyExpr in js-compiler.js.
26
- const HELPERS = 'const _hop=Object.prototype.hasOwnProperty;';
27
-
28
- const PROTO_NAMES = new Set([...Object.getOwnPropertyNames(Object.prototype), '__proto__']);
29
-
30
- function isPrimitive(x) {
31
- return x === null || typeof x === 'string' || typeof x === 'boolean' || Number.isFinite(x);
32
- }
33
-
34
- function lit(x) {
35
- return JSON.stringify(x);
36
- }
37
-
38
- function createCtx() {
39
- return { n: 0, argNames: [], argVals: [], fns: [], fnFor: new Map() };
40
- }
41
-
42
- // A value the emitted code needs by reference: a function, a RegExp, a list.
43
- function arg(ctx, value) {
44
- const i = ctx.argVals.indexOf(value);
45
- if (i !== -1) return ctx.argNames[i];
46
- const name = '_a' + ctx.argVals.length;
47
- ctx.argNames.push(name);
48
- ctx.argVals.push(value);
49
- return name;
50
- }
51
-
52
- function local(ctx) {
53
- return '_v' + ctx.n++;
54
- }
55
-
56
- // JSON Schema type bits, exactly as dataBits() assigns them: a non-finite
57
- // number has no type, so it matches neither number nor integer.
58
- function typeTest(mask, v) {
59
- const parts = [];
60
- if (mask & T_STRING) parts.push(`typeof ${v}==='string'`);
61
- if (mask & T_NUMBER) parts.push(`Number.isFinite(${v})`);
62
- else if (mask & T_INTEGER) parts.push(`Number.isInteger(${v})`);
63
- if (mask & T_BOOLEAN) parts.push(`typeof ${v}==='boolean'`);
64
- if (mask & T_NULL) parts.push(`${v}===null`);
65
- if ((mask & T_OBJECT) && (mask & T_ARRAY)) parts.push(`(typeof ${v}==='object'&&${v}!==null)`);
66
- else if (mask & T_OBJECT) parts.push(`(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`);
67
- else if (mask & T_ARRAY) parts.push(`Array.isArray(${v})`);
68
- return parts.length ? parts.join('||') : 'false';
69
- }
70
-
71
- // `plain` names a local holding `v.__proto__===Object.prototype`, read once per
72
- // object. With the key a constant at the `in`, the engine keeps the check
73
- // monomorphic; through a shared helper the key is a parameter and it is not.
74
- function ownTest(v, key, plain) {
75
- const k = lit(key);
76
- return PROTO_NAMES.has(key) ? `_hop.call(${v},${k})` : `(${plain}?${k} in ${v}:_hop.call(${v},${k}))`;
77
- }
78
-
79
- const K_NUM = T_NUMBER | T_INTEGER;
80
- // The body under a guard for one kind of value, given what the type check
81
- // already established: unconditional when the value can only be that kind,
82
- // nothing at all when it cannot be.
83
- function guarded(known, kind, cond, body) {
84
- if (!body) return '';
85
- if ((known & kind) === 0) return '';
86
- if ((known & ~kind) === 0) return body;
87
- return `if(${cond}){${body}}`;
88
- }
89
-
90
- // Value-level keywords, in compileLeafV's order and with its meaning.
91
- function emitLeaf(ctx, P, v, known) {
92
- let out = '';
93
- if (P.hasType) out += `if(!(${typeTest(P.typeMask, v)}))return false;`;
94
- if (P.enum !== null) {
95
- const vals = P.enum;
96
- if (vals.length === 0) out += 'return false;';
97
- else if (vals.every(isPrimitive)) out += `if(!(${vals.map((x) => `${v}===${lit(x)}`).join('||')}))return false;`;
98
- else {
99
- const a = arg(ctx, vals);
100
- const de = arg(ctx, deepEqual);
101
- const i = local(ctx);
102
- out += `{let ${i}=0;for(;${i}<${a}.length;${i}++)if(${de}(${a}[${i}],${v}))break;if(${i}===${a}.length)return false}`;
103
- }
104
- }
105
- if (P.hasConst) {
106
- out += isPrimitive(P.const)
107
- ? `if(${v}!==${lit(P.const)})return false;`
108
- : `if(!${arg(ctx, deepEqual)}(${arg(ctx, P.const)},${v}))return false;`;
109
- }
110
- if (P.hasNumber) {
111
- let n = '';
112
- if (P.minimum !== undefined) n += `if(${v}<${lit(P.minimum)})return false;`;
113
- if (P.maximum !== undefined) n += `if(${v}>${lit(P.maximum)})return false;`;
114
- if (P.exclusiveMinimum !== undefined) n += `if(${v}<=${lit(P.exclusiveMinimum)})return false;`;
115
- if (P.exclusiveMaximum !== undefined) n += `if(${v}>=${lit(P.exclusiveMaximum)})return false;`;
116
- if (P.multipleOf !== undefined) n += `if(!${arg(ctx, multipleOfOk)}(${v},${lit(P.multipleOf)}))return false;`;
117
- out += guarded(known, K_NUM, `Number.isFinite(${v})`, n);
118
- }
119
- if (P.hasString) {
120
- let s = '';
121
- if (P.minLength !== undefined) s += `if(!${arg(ctx, cpAtLeast)}(${v},${lit(P.minLength)}))return false;`;
122
- if (P.maxLength !== undefined) s += `if(!${arg(ctx, cpAtMost)}(${v},${lit(P.maxLength)}))return false;`;
123
- if (P.pattern !== null) s += `if(!${arg(ctx, P.pattern)}.test(${v}))return false;`;
124
- if (P.formatFn !== null) s += `if(!${arg(ctx, P.formatFn)}(${v}))return false;`;
125
- out += guarded(known, T_STRING, `typeof ${v}==='string'`, s);
126
- }
127
- if (P.minItems !== undefined || P.maxItems !== undefined || P.uniqueItems) {
128
- let a = '';
129
- if (P.minItems !== undefined) a += `if(${v}.length<${lit(P.minItems)})return false;`;
130
- if (P.maxItems !== undefined) a += `if(${v}.length>${lit(P.maxItems)})return false;`;
131
- if (P.uniqueItems) {
132
- const de = arg(ctx, deepEqual);
133
- const i = local(ctx), j = local(ctx);
134
- a += `for(let ${i}=0;${i}<${v}.length;${i}++)for(let ${j}=${i}+1;${j}<${v}.length;${j}++)if(${de}(${v}[${i}],${v}[${j}]))return false;`;
135
- }
136
- out += guarded(known, T_ARRAY, `Array.isArray(${v})`, a);
137
- }
138
- return out;
139
- }
140
-
141
- // The object-level keywords of a node, value-level and structural together, so
142
- // the prototype of the object is read once for all of them.
143
- function emitObjectLeaf(ctx, P, v, plain) {
144
- let o = '';
145
- if (P.required !== null || P.minProperties !== undefined || P.maxProperties !== undefined || P.dependentRequired !== null) {
146
- if (P.required !== null) for (const k of P.required) o += `if(!${ownTest(v, k, plain)})return false;`;
147
- if (P.minProperties !== undefined) o += `if(Object.keys(${v}).length<${lit(P.minProperties)})return false;`;
148
- if (P.maxProperties !== undefined) o += `if(Object.keys(${v}).length>${lit(P.maxProperties)})return false;`;
149
- if (P.dependentRequired !== null) {
150
- for (const [key, deps] of P.dependentRequired) {
151
- o += `if(${ownTest(v, key, plain)}){${deps.map((dep) => `if(!${ownTest(v, dep, plain)})return false;`).join('')}}`;
152
- }
153
- }
154
- }
155
- return o;
156
- }
157
-
158
- // A subschema asked for a verdict of its own (a branch of anyOf/oneOf, the
159
- // subject of not/if) becomes a named function, emitted once per plan.
160
- function branchFn(ctx, node) {
161
- if (node === true) return '_t';
162
- if (node === false) return '_f';
163
- let name = ctx.fnFor.get(node);
164
- if (name !== undefined) return name;
165
- name = '_b' + ctx.fnFor.size;
166
- ctx.fnFor.set(node, name);
167
- const body = emitNode(ctx, node, 'd');
168
- ctx.fns.push(`function ${name}(d){${body}return true}`);
169
- return name;
170
- }
171
-
172
- function emitNode(ctx, node, v) {
173
- if (node === true) return '';
174
- if (node === false) return 'return false;';
175
- if (!(node instanceof Plan)) return '';
176
- const P = node;
177
- if (P.tracked || P.hasUnevaluated || P.hasCustom || P.macros !== null ||
178
- P.contains !== undefined || P.propertyNames !== undefined || P.patternProperties !== null ||
179
- P.dependentSchemas !== null || P.propertyDependencies !== null) {
180
- throw DECLINE;
181
- }
182
- const known = P.hasType ? P.typeMask : ALL_TYPES;
183
- let out = emitLeaf(ctx, P, v, known);
184
-
185
- if (P.prefixItems !== null || P.items !== undefined) {
186
- let a = '';
187
- if (P.prefixItems !== null) {
188
- P.prefixItems.forEach((child, i) => {
189
- const x = local(ctx);
190
- const body = emitNode(ctx, child, x);
191
- if (body) a += `if(${v}.length>${i}){const ${x}=${v}[${i}];${body}}`;
192
- });
193
- }
194
- if (P.items !== undefined) {
195
- const start = P.prefixItems !== null ? P.prefixItems.length : 0;
196
- const i = local(ctx);
197
- const x = local(ctx);
198
- const body = emitNode(ctx, P.items, x);
199
- if (body) a += `for(let ${i}=${start};${i}<${v}.length;${i}++){const ${x}=${v}[${i}];${body}}`;
200
- }
201
- out += guarded(known, T_ARRAY, `Array.isArray(${v})`, a);
202
- }
203
-
204
- const plain = local(ctx);
205
- let o = emitObjectLeaf(ctx, P, v, plain);
206
- if (P.properties !== null || P.additionalProperties !== undefined) {
207
- const declared = P.properties !== null ? [...P.properties.keys()] : [];
208
- if (P.properties !== null) {
209
- for (const [key, entry] of P.properties) {
210
- const x = local(ctx);
211
- const body = emitNode(ctx, entry.node, x);
212
- if (body) o += `if(${ownTest(v, key, plain)}){const ${x}=${v}[${lit(key)}];${body}}`;
213
- }
214
- }
215
- const ap = P.additionalProperties;
216
- if (ap !== undefined && ap !== true) {
217
- const k = local(ctx);
218
- let isDeclared = 'false';
219
- if (declared.length > 8) {
220
- const set = Object.create(null);
221
- for (const key of declared) set[key] = 1;
222
- isDeclared = `${arg(ctx, set)}[${k}]===1`;
223
- } else if (declared.length > 0) {
224
- isDeclared = declared.map((key) => `${k}===${lit(key)}`).join('||');
225
- }
226
- let apBody;
227
- if (ap === false) apBody = 'return false;';
228
- else {
229
- const x = local(ctx);
230
- const body = emitNode(ctx, ap, x);
231
- apBody = body ? `const ${x}=${v}[${k}];${body}` : '';
232
- }
233
- if (apBody) o += `for(const ${k} in ${v}){if(!${plain}&&!_hop.call(${v},${k}))continue;if(${isDeclared})continue;${apBody}}`;
234
- }
235
- }
236
- if (o) out += guarded(known, T_OBJECT, `typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})`, `const ${plain}=${v}.__proto__===Object.prototype;${o}`);
237
-
238
- if (P.allOf !== null) for (const child of P.allOf) out += emitNode(ctx, child, v);
239
- if (P.anyOf !== null) out += `if(!(${P.anyOf.map((c) => `${branchFn(ctx, c)}(${v})`).join('||')}))return false;`;
240
- if (P.oneOf !== null) {
241
- const c = local(ctx);
242
- out += `{let ${c}=0;${P.oneOf.map((b) => `if(${branchFn(ctx, b)}(${v})&&++${c}>1)return false;`).join('')}if(${c}!==1)return false}`;
243
- }
244
- if (P.not !== undefined) out += `if(${branchFn(ctx, P.not)}(${v}))return false;`;
245
- if (P.if !== undefined) {
246
- const t = P.then !== undefined ? emitNode(ctx, P.then, v) : '';
247
- const e = P.else !== undefined ? emitNode(ctx, P.else, v) : '';
248
- if (t || e) out += `if(${branchFn(ctx, P.if)}(${v})){${t}}else{${e}}`;
249
- }
250
- return out;
251
- }
252
-
253
- // Returns { fn, source } or null when the schema is outside what this emits.
254
- function compileVerdict(schema, options) {
255
- let interp;
256
- try {
257
- interp = createInterpreter(schema, options || {});
258
- } catch {
259
- return null;
260
- }
261
- const ctx = createCtx();
262
- let body;
263
- try {
264
- body = emitNode(ctx, interp.rootNode, 'd');
265
- } catch (e) {
266
- if (e === DECLINE) return null;
267
- throw e;
268
- }
269
- const source = HELPERS + 'function _t(){return true}function _f(){return false}' +
270
- ctx.fns.join('') + `return function(d){${body}return true}`;
271
- let fn;
272
- try {
273
- // eslint-disable-next-line no-new-func
274
- fn = new Function(...ctx.argNames, source)(...ctx.argVals);
275
- } catch {
276
- return null;
277
- }
278
- return { fn, source };
279
- }
280
-
281
- module.exports = { compileVerdict };