ata-validator 1.50.0 → 1.52.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 +26 -20
- package/aot.d.ts +11 -1
- package/aot.mjs +1 -1
- package/bin/ata.js +21 -0
- package/index.bundle.mjs +15 -0
- package/lib/aot-build.js +183 -9
- package/lib/aot-impl.js +49 -16
- package/lib/combined-runtime.js +4 -1
- package/lib/error-runtime.js +5 -1
- package/lib/js-compiler-errors.js +15 -15
- package/lib/js-compiler.js +193 -44
- package/lib/safe-regex.js +33 -14
- package/lib/schema-order.js +64 -2
- package/lib/schema-runtime-entry.js +25 -1
- package/lib/validator-core.js +3 -0
- package/lib/version.js +1 -1
- package/package.json +10 -8
package/README.md
CHANGED
|
@@ -138,18 +138,19 @@ errors (7.6M with `richErrors: false`).
|
|
|
138
138
|
|
|
139
139
|
| Dimension | Schema | ata-AOT | runtime validator | Difference |
|
|
140
140
|
|---|---|---|---|---|
|
|
141
|
-
| Bundle (gzipped) | simple | 1.
|
|
142
|
-
| Bundle (gzipped) | complex | 3.0 KB | 52.7 KB | 17.
|
|
143
|
-
| Bundle (gzipped) | nested | 4.5 KB | 52.7 KB | 11.
|
|
141
|
+
| Bundle (gzipped) | simple | 1.3 KB | 52.7 KB | 39.0x smaller |
|
|
142
|
+
| Bundle (gzipped) | complex | 3.0 KB | 52.7 KB | 17.7x smaller |
|
|
143
|
+
| Bundle (gzipped) | nested | 4.5 KB | 52.7 KB | 11.8x smaller |
|
|
144
144
|
| Cold start | simple | 20 ms | 37 ms | 1.9x faster |
|
|
145
|
-
| Throughput (1M ops) | simple |
|
|
146
|
-
| Compile time | simple |
|
|
145
|
+
| Throughput (1M ops) | simple | 315 Mops/s | 99 Mops/s | 3.2x faster |
|
|
146
|
+
| Compile time | simple | 24 µs | 1.57 ms | 65x faster |
|
|
147
147
|
|
|
148
148
|
The runtime column is the default validator most frameworks ship. Reproduce on your machine
|
|
149
|
-
with `npm run bench:aot-vs-ajv`. Numbers from one run on Apple M4 Pro, Node 25.2.1, 2026-10-
|
|
150
|
-
on ata-validator 1.
|
|
151
|
-
|
|
152
|
-
and
|
|
149
|
+
with `npm run bench:aot-vs-ajv`. Numbers from one run on Apple M4 Pro, Node 25.2.1, 2026-10-11,
|
|
150
|
+
on ata-validator 1.51.0. Across five runs throughput moved between 263 and 325 Mops/s against
|
|
151
|
+
97 to 110, cold start between 20 and 22 ms against 37 to 40, and the compile ratio between 61x
|
|
152
|
+
and 65x. The compile row is the time to write a module, a build-time cost; it was 18 µs on
|
|
153
|
+
1.46.0, and modules have carried the one-pass error function since 1.47.0. The throughput row times a single one-million-call
|
|
153
154
|
loop of a few milliseconds, so it moves the most from run to run; treat the last three rows as
|
|
154
155
|
an order of magnitude rather than a constant.
|
|
155
156
|
|
|
@@ -279,13 +280,13 @@ is the most common way to get a misleading number out of this library.
|
|
|
279
280
|
|
|
280
281
|
| | compiled with `ata build` | runtime `new Validator(schema)` |
|
|
281
282
|
|---|---|---|
|
|
282
|
-
| In a bundle, gzipped | **2.1 KB** |
|
|
283
|
+
| In a bundle, gzipped | **2.1 KB** | 123.9 KB |
|
|
283
284
|
| Time to a served request | **3.5 ms** | 7.6 ms |
|
|
284
285
|
| Schema known when | build time | any time |
|
|
285
286
|
|
|
286
287
|
The bundle row is the ten-field user schema in
|
|
287
288
|
`tests/fixtures/error-dx/user.schema.json`, every export of the compiled module against
|
|
288
|
-
`new Validator(schema)`, built with `bun build --minify --target=browser` on ata 1.
|
|
289
|
+
`new Validator(schema)`, built with `bun build --minify --target=browser` on ata 1.51.0.
|
|
289
290
|
The startup row is a Hono route on Bun 1.4, the median of three rounds of best-of-seven, from
|
|
290
291
|
`benchmark/bundle`, against 3.5 ms for the same app doing no validation at all, so the
|
|
291
292
|
compiled path costs nothing measurable to start. The runtime figure is what it is because
|
|
@@ -293,7 +294,7 @@ a schema that arrives at run time can use any keyword, so the whole engine has t
|
|
|
293
294
|
there. The compiled module imports nothing and contains only the checks your schema asks
|
|
294
295
|
for.
|
|
295
296
|
|
|
296
|
-
**On a server, use whichever fits your schemas.**
|
|
297
|
+
**On a server, use whichever fits your schemas.** 124 KB of JavaScript on a Node or Bun
|
|
297
298
|
process is not a cost anyone notices, and the runtime API is the simpler thing to reach
|
|
298
299
|
for. Speed is the same either way once warm.
|
|
299
300
|
|
|
@@ -535,7 +536,7 @@ const v = new Validator(schema, {
|
|
|
535
536
|
|
|
536
537
|
### Build-time compile (`ata compile`)
|
|
537
538
|
|
|
538
|
-
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.1 KB gzipped, full error detail included, against
|
|
539
|
+
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.1 KB gzipped, full error detail included, against 123.9 KB for the runtime bundled for the browser (ata 1.51.0).
|
|
539
540
|
|
|
540
541
|
```bash
|
|
541
542
|
npx ata compile schemas/user.json -o src/generated/user.validator.mjs
|
|
@@ -581,20 +582,25 @@ error collector out of the modules: each one carries its verdict and its schema,
|
|
|
581
582
|
its errors from one `ata-errors.mjs` the build writes beside them. The build does this only
|
|
582
583
|
where it makes the output smaller, so a single small schema keeps its self-contained module.
|
|
583
584
|
The errors are the ones `new Validator(schema, { richErrors: false })` reports, in the same
|
|
584
|
-
order.
|
|
585
|
-
to
|
|
585
|
+
order. Forty SchemaStore schemas imported into one app, minified with esbuild, go from 513.8 KB
|
|
586
|
+
to 213.3 KB gzipped, and from 60.4 ms to 28.4 ms to import with code generation blocked. A
|
|
586
587
|
rejected document costs more this way, since its errors come from an interpreted walk:
|
|
587
588
|
`--errors inline` keeps every module self-contained. `--mode schema` writes each module as
|
|
588
589
|
its schema and takes the verdict from the runtime too: forty SchemaStore schemas in one app
|
|
589
|
-
come to
|
|
590
|
-
23.
|
|
590
|
+
come to 102.4 KB gzipped against 213.3 KB as compact modules, and load and check each once in
|
|
591
|
+
23.9 ms against 34.5, while a check once warm costs about five times what generated code costs.
|
|
592
|
+
Schemas that reference each other by `$id` are built against each other, and in schema mode
|
|
593
|
+
they are written once to a shared registry that keeps only the definitions the modules reach;
|
|
594
|
+
`--base-uri <uri>` names files that declare no `$id`. Eight Kubernetes kinds, whose schemas
|
|
595
|
+
share one 1.1 MB definitions file, come to 32.6 KB gzipped as schema modules against 68.6 KB
|
|
596
|
+
self-contained, on ata 1.51.0.
|
|
591
597
|
|
|
592
598
|
Bundle sizes for the 10-field user schema in `tests/fixtures/error-dx/user.schema.json`,
|
|
593
|
-
minified and gzipped, measured with `bun build --minify --target=browser` on ata 1.
|
|
599
|
+
minified and gzipped, measured with `bun build --minify --target=browser` on ata 1.51.0:
|
|
594
600
|
|
|
595
601
|
| What the app imports | Size | Notes |
|
|
596
602
|
|---|---|---|
|
|
597
|
-
| `Validator` from `ata-validator` |
|
|
603
|
+
| `Validator` from `ata-validator` | 123.9 KB | The compiler ships with it, because a runtime schema can use any keyword |
|
|
598
604
|
| `isValid` from the compiled module | **1.3 KB** | Nothing else is reachable, so the error collector is dropped |
|
|
599
605
|
| `validate` from the compiled module | **1.9 KB** | Adds the detailed error collector |
|
|
600
606
|
|
|
@@ -632,7 +638,7 @@ through a `setFormats()` export the module carries. `docs/API.md` has the
|
|
|
632
638
|
details.
|
|
633
639
|
|
|
634
640
|
**Fastify startup, 10 route schemas, from a cold process to the first validated request:
|
|
635
|
-
ajv 19.
|
|
641
|
+
ajv 19.0 ms, ata 2.81 ms, no build step required.** ata registers in 0.20 ms of that and
|
|
636
642
|
compiles on the first request, so counting only registration would overstate the gap.
|
|
637
643
|
Reproduce with `node benchmark/bench_fastify_boot.mjs`.
|
|
638
644
|
|
package/aot.d.ts
CHANGED
|
@@ -56,7 +56,17 @@ export function compactDecline(validator: Validator<unknown>): string | null;
|
|
|
56
56
|
* runtime Validator does with `richErrors: false`. Null where
|
|
57
57
|
* toCompactModule would be null. `ata build --mode schema` writes these.
|
|
58
58
|
*/
|
|
59
|
-
export function toSchemaModule(validator: Validator<unknown>, opts: { runtime: string; format?: 'esm' | 'cjs' }): string | null;
|
|
59
|
+
export function toSchemaModule(validator: Validator<unknown>, opts: { runtime: string; registry?: string; format?: 'esm' | 'cjs' }): string | null;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Source of the registry module schema modules import when their schemas
|
|
63
|
+
* reference each other by `$id`: the schemas, without descriptive keywords,
|
|
64
|
+
* as one array. Pass its import specifier as `registry` to toSchemaModule,
|
|
65
|
+
* for a validator built with those schemas in its `schemas` option; the module
|
|
66
|
+
* then names its schema by `$id` and carries no copy of it. `ata build --mode
|
|
67
|
+
* schema` does this for inputs that reference each other.
|
|
68
|
+
*/
|
|
69
|
+
export function registrySource(schemas: ReadonlyArray<object | Validator<unknown>>, format?: 'esm' | 'cjs'): string;
|
|
60
70
|
|
|
61
71
|
/** Source of the runtime compact modules (and, with `schemaMode`, schema
|
|
62
72
|
* modules) import. With `schemas`, the validators or schemas whose modules
|
package/aot.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import mod from './lib/aot-impl.js';
|
|
2
|
-
export const { toStandalone, toStandaloneModule, toCompactModule, toSchemaModule, compactDecline, errorRuntimeSource, bundle, bundleStandalone, bundleCompact, loadBundle } = mod;
|
|
2
|
+
export const { toStandalone, toStandaloneModule, toCompactModule, toSchemaModule, registrySource, compactDecline, errorRuntimeSource, bundle, bundleStandalone, bundleCompact, loadBundle } = mod;
|
|
3
3
|
export default mod;
|
package/bin/ata.js
CHANGED
|
@@ -51,6 +51,9 @@ Build options:
|
|
|
51
51
|
--mode <mode> code (default) | schema. schema writes each module as its
|
|
52
52
|
schema and takes the verdict and errors from one runtime
|
|
53
53
|
file: far smaller and quicker to load, slower per check
|
|
54
|
+
--base-uri <uri> The URI an input without $id is known by: <uri> plus its
|
|
55
|
+
file name, for schemas that reference a shared file by URL
|
|
56
|
+
or relative path
|
|
54
57
|
--check Check (don't write); exit 1 if any output is stale
|
|
55
58
|
--cache-file <path> Cache file for incremental builds (default: cache disabled)
|
|
56
59
|
--max-size <bytes> Fail build if any compiled module exceeds this gzipped size
|
|
@@ -102,6 +105,7 @@ function parseArgs(argv) {
|
|
|
102
105
|
if (a === '--one-pass') { out.opts.onePass = true; continue; }
|
|
103
106
|
if (a === '--errors') { out.opts.errors = argv[++i]; continue; }
|
|
104
107
|
if (a === '--mode') { out.opts.mode = argv[++i]; continue; }
|
|
108
|
+
if (a === '--base-uri') { out.opts.baseUri = argv[++i]; continue; }
|
|
105
109
|
if (a === '--check') { out.opts.check = true; continue; }
|
|
106
110
|
if (a === '--strict') { out.opts.strict = true; continue; }
|
|
107
111
|
if (a === '--strict-schema') { out.opts.strictSchema = true; continue; }
|
|
@@ -306,6 +310,7 @@ function cmdBuild(args) {
|
|
|
306
310
|
onePass: args.opts.onePass === true ? true : undefined,
|
|
307
311
|
errors: args.opts.errors,
|
|
308
312
|
mode: args.opts.mode,
|
|
313
|
+
baseUri: args.opts.baseUri,
|
|
309
314
|
check: !!args.opts.check,
|
|
310
315
|
maxSize: args.opts.maxSize,
|
|
311
316
|
strict: !!args.opts.strict,
|
|
@@ -326,6 +331,22 @@ function cmdBuild(args) {
|
|
|
326
331
|
}
|
|
327
332
|
if (report.runtime) {
|
|
328
333
|
process.stdout.write(`ata: error runtime -> ${report.runtime.output} (${report.runtime.bytes.toLocaleString()} bytes, shared by the modules above)\n`);
|
|
334
|
+
if (report.runtime.registry) process.stdout.write(`ata: schema registry -> ${report.runtime.registry.output} (${report.runtime.registry.bytes.toLocaleString()} bytes, the schemas the modules above reference)\n`);
|
|
335
|
+
}
|
|
336
|
+
// A module the error generator declined still has an exact verdict, but
|
|
337
|
+
// every failure is the one ATA9000 error. Said once per module, short,
|
|
338
|
+
// and once with what to do about it.
|
|
339
|
+
let noDetail = 0;
|
|
340
|
+
for (const w of report.warnings || []) {
|
|
341
|
+
if (w.kind === 'error-detail') {
|
|
342
|
+
noDetail++;
|
|
343
|
+
process.stderr.write(`ata: warning ${w.input}: no error detail, failures report the single ATA9000 error (the verdict is exact)\n`);
|
|
344
|
+
} else {
|
|
345
|
+
process.stderr.write(`ata: warning ${w.input}: ${w.warning}\n`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (noDetail > 0 && args.opts.mode !== 'schema') {
|
|
349
|
+
process.stderr.write(`ata: ${noDetail} module${noDetail === 1 ? '' : 's'} without error detail; with --mode schema a module takes its errors from the interpreted engine instead\n`);
|
|
329
350
|
}
|
|
330
351
|
for (const c of report.cached) {
|
|
331
352
|
process.stdout.write(`ata: cached ${c.input}\n`);
|
package/index.bundle.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// ESM entry for bundlers that target a server (Bun, esbuild or Rollup for
|
|
2
|
+
// Node) and for Node before 22.10, which does not match `module-sync`: the
|
|
3
|
+
// validator core with the code generator registered, as index.js, but with
|
|
4
|
+
// each tool imported on its own (lib/tools.mjs), so the tools an application
|
|
5
|
+
// does not import stay out of its bundle. Through index.js a bundler sees one
|
|
6
|
+
// CommonJS module and keeps all of them.
|
|
7
|
+
import core from './lib/validator-core.js';
|
|
8
|
+
import engine from './lib/codegen-engine.js';
|
|
9
|
+
import { toTypeScript, renderPretty, renderCompact, toOutput, toRetryMessage, describeSchema, renderJSON } from './lib/tools.mjs';
|
|
10
|
+
|
|
11
|
+
core._registerCodegen(engine);
|
|
12
|
+
|
|
13
|
+
export const { Validator, compile, validate, validateAsync, parseAsync, version, createPaddedBuffer, SIMDJSON_PADDING, parseJSON, defineSchema } = core;
|
|
14
|
+
export { toTypeScript, renderPretty, renderCompact, toOutput, toRetryMessage, describeSchema, renderJSON };
|
|
15
|
+
export default { Validator, compile, validate, validateAsync, parseAsync, version, createPaddedBuffer, SIMDJSON_PADDING, parseJSON, toTypeScript, defineSchema, renderPretty, renderCompact, toOutput, toRetryMessage, describeSchema, renderJSON };
|
package/lib/aot-build.js
CHANGED
|
@@ -109,6 +109,99 @@ function importSpecifier(fromFile, toFile) {
|
|
|
109
109
|
const RUNTIME_PLACEHOLDER = '__ATA_ERROR_RUNTIME__';
|
|
110
110
|
const ERRORS_MODES = new Set(['auto', 'inline', 'shared']);
|
|
111
111
|
|
|
112
|
+
// The registry without the definitions no module reaches. A shared
|
|
113
|
+
// definitions file (Kubernetes ships one with hundreds) is mostly what a
|
|
114
|
+
// given set of schemas never uses. Only `definitions` and `$defs` entries are
|
|
115
|
+
// dropped, and only from a document every reference into which is a plain
|
|
116
|
+
// pointer into them; a document reached as a whole, through an anchor, or
|
|
117
|
+
// holding an embedded $id is kept as it is. The result is checked: every
|
|
118
|
+
// root's references must still resolve against it, or the whole registry is
|
|
119
|
+
// kept.
|
|
120
|
+
function pruneRegistry(roots, byId) {
|
|
121
|
+
const used = new Map();
|
|
122
|
+
const keepWhole = new Set();
|
|
123
|
+
const seen = new Set();
|
|
124
|
+
const queue = [];
|
|
125
|
+
const hasInnerId = new Map();
|
|
126
|
+
const innerId = (doc) => {
|
|
127
|
+
let hit = hasInnerId.get(doc);
|
|
128
|
+
if (hit !== undefined) return hit;
|
|
129
|
+
hit = false;
|
|
130
|
+
const stack = [doc];
|
|
131
|
+
const visited = new Set();
|
|
132
|
+
while (stack.length && !hit) {
|
|
133
|
+
const n = stack.pop();
|
|
134
|
+
if (n === null || typeof n !== 'object' || visited.has(n)) continue;
|
|
135
|
+
visited.add(n);
|
|
136
|
+
if (n !== doc && !Array.isArray(n) && typeof n.$id === 'string') hit = true;
|
|
137
|
+
for (const k of Object.keys(n)) stack.push(n[k]);
|
|
138
|
+
}
|
|
139
|
+
hasInnerId.set(doc, hit);
|
|
140
|
+
return hit;
|
|
141
|
+
};
|
|
142
|
+
const docOf = new Map();
|
|
143
|
+
for (const [id, doc] of byId) docOf.set(id.replace(/#$/, ''), doc);
|
|
144
|
+
const reach = (doc, base, node) => {
|
|
145
|
+
if (node === null || typeof node !== 'object' || seen.has(node)) return;
|
|
146
|
+
seen.add(node);
|
|
147
|
+
queue.push([doc, base, node]);
|
|
148
|
+
};
|
|
149
|
+
const defsOf = (doc) => (doc.$defs && typeof doc.$defs === 'object' ? '$defs' : doc.definitions && typeof doc.definitions === 'object' ? 'definitions' : null);
|
|
150
|
+
for (const root of roots) reach(root, typeof root.$id === 'string' ? root.$id : null, root);
|
|
151
|
+
while (queue.length) {
|
|
152
|
+
const [doc, base, node] = queue.pop();
|
|
153
|
+
if (Array.isArray(node)) { for (const x of node) reach(doc, base, x); continue; }
|
|
154
|
+
for (const k of Object.keys(node)) {
|
|
155
|
+
const v = node[k];
|
|
156
|
+
if ((k === '$ref' || k === '$dynamicRef' || k === '$recursiveRef') && typeof v === 'string') {
|
|
157
|
+
let abs;
|
|
158
|
+
try { abs = base ? new URL(v, base).href : v.charCodeAt(0) === 35 ? null : new URL(v).href; } catch { abs = null; }
|
|
159
|
+
const local = v.charCodeAt(0) === 35;
|
|
160
|
+
const hash = abs === null ? -1 : abs.indexOf('#');
|
|
161
|
+
const uri = abs === null ? null : hash < 0 ? abs : abs.slice(0, hash);
|
|
162
|
+
const target = local ? doc : uri !== null ? docOf.get(uri) : undefined;
|
|
163
|
+
if (target === undefined) continue;
|
|
164
|
+
const frag = local ? v.slice(1) : hash < 0 ? '' : abs.slice(hash + 1);
|
|
165
|
+
const tbase = local ? base : uri;
|
|
166
|
+
const m = /^\/(\$defs|definitions)\/([^/]+)/.exec(frag);
|
|
167
|
+
const which = defsOf(target);
|
|
168
|
+
if (k !== '$ref' || m === null || m[1] !== which) {
|
|
169
|
+
keepWhole.add(target);
|
|
170
|
+
reach(target, tbase, target);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
let name = m[2];
|
|
174
|
+
try { name = decodeURIComponent(name); } catch {}
|
|
175
|
+
name = name.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
176
|
+
let set = used.get(target);
|
|
177
|
+
if (set === undefined) { set = new Set(); used.set(target, set); }
|
|
178
|
+
set.add(name);
|
|
179
|
+
// The rest of the document around its definitions is used too.
|
|
180
|
+
for (const key of Object.keys(target)) if (key !== which) reach(target, tbase, target[key]);
|
|
181
|
+
reach(target, tbase, target[which][name]);
|
|
182
|
+
} else if (v !== null && typeof v === 'object') {
|
|
183
|
+
reach(doc, base, v);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const out = new Map();
|
|
188
|
+
for (const [id, doc] of byId) {
|
|
189
|
+
const which = defsOf(doc);
|
|
190
|
+
if (which === null || keepWhole.has(doc) || innerId(doc) || roots.includes(doc)) { out.set(id, doc); continue; }
|
|
191
|
+
const names = used.get(doc) || new Set();
|
|
192
|
+
const defs = {};
|
|
193
|
+
for (const name of Object.keys(doc[which])) if (names.has(name)) Object.defineProperty(defs, name, { value: doc[which][name], enumerable: true, writable: true, configurable: true });
|
|
194
|
+
out.set(id, { ...doc, [which]: defs });
|
|
195
|
+
}
|
|
196
|
+
for (const root of roots) {
|
|
197
|
+
if (!aot._refsResolveWithin(root, out)) return byId;
|
|
198
|
+
}
|
|
199
|
+
// The registry documents' own references too, as the runtime may walk any
|
|
200
|
+
// part of a definition it keeps.
|
|
201
|
+
for (const doc of out.values()) if (!aot._refsResolveWithin(doc, out)) return byId;
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
|
|
112
205
|
async function build(opts) {
|
|
113
206
|
const globs = opts.globs || [];
|
|
114
207
|
if (globs.length === 0) throw new Error('build: at least one glob required');
|
|
@@ -138,6 +231,28 @@ async function build(opts) {
|
|
|
138
231
|
const warnings = [];
|
|
139
232
|
const entries = [];
|
|
140
233
|
|
|
234
|
+
// Schemas that reference each other: a schema whose references do not all
|
|
235
|
+
// land inside it gets the other inputs registered beside it, each under its
|
|
236
|
+
// $id, or, with `baseUri`, an input without one under the base URI and its
|
|
237
|
+
// file name. In schema mode its module then resolves against one registry
|
|
238
|
+
// module the build writes (registrySource); otherwise it is a
|
|
239
|
+
// self-contained module carrying what it references. A schema that
|
|
240
|
+
// resolves within itself is built exactly as it would be alone.
|
|
241
|
+
if (opts.baseUri !== undefined) {
|
|
242
|
+
try { new URL(opts.baseUri); } catch { throw new Error(`build: baseUri must be an absolute URI (got "${opts.baseUri}")`); }
|
|
243
|
+
}
|
|
244
|
+
let siblings = [];
|
|
245
|
+
for (const input of inputs) {
|
|
246
|
+
try {
|
|
247
|
+
const sch = parseSchemaFile(input);
|
|
248
|
+
if (sch === null || typeof sch !== 'object' || Array.isArray(sch)) continue;
|
|
249
|
+
if (typeof sch.$id === 'string' && sch.$id !== '') siblings.push({ input, schema: sch });
|
|
250
|
+
else if (opts.baseUri !== undefined) siblings.push({ input, schema: { $id: new URL(path.basename(input), opts.baseUri).href, ...sch } });
|
|
251
|
+
} catch {}
|
|
252
|
+
}
|
|
253
|
+
if (siblings.length === 0) siblings = null;
|
|
254
|
+
const REGISTRY_PLACEHOLDER = '__ATA_SCHEMA_REGISTRY__';
|
|
255
|
+
|
|
141
256
|
const compileOne = (input, raw) => {
|
|
142
257
|
const schema = parseSchemaFile(input);
|
|
143
258
|
// The build is where an authoring mistake is cheapest to stop: a typo
|
|
@@ -151,7 +266,13 @@ async function build(opts) {
|
|
|
151
266
|
return { error: problems.map((x) => `strict mode: ${x.message} at ${x.path}`).join('; ') };
|
|
152
267
|
}
|
|
153
268
|
}
|
|
154
|
-
const
|
|
269
|
+
const needs = siblings !== null && schema !== null && typeof schema === 'object' && !Array.isArray(schema) && !aot._refsResolveWithin(schema);
|
|
270
|
+
const self = needs ? siblings.find((x) => x.input === input) : undefined;
|
|
271
|
+
const others = needs ? siblings.filter((x) => x.input !== input && (self === undefined || x.schema.$id !== self.schema.$id)).map((x) => x.schema) : null;
|
|
272
|
+
// Known under the base URI too, so its own relative references resolve
|
|
273
|
+
// against it.
|
|
274
|
+
const own = self !== undefined ? self.schema : schema;
|
|
275
|
+
const v = others !== null && others.length > 0 ? new Validator(own, { schemas: others }) : new Validator(schema);
|
|
155
276
|
let sourceMap = null;
|
|
156
277
|
if (source) {
|
|
157
278
|
// Only attempt position map for JSON (the scanner is JSON-only).
|
|
@@ -174,14 +295,37 @@ async function build(opts) {
|
|
|
174
295
|
onWarning: (w, meta) => inputWarnings.push({ message: w, kind: (meta && meta.kind) || 'general' }),
|
|
175
296
|
});
|
|
176
297
|
const compact = schemaMode && src
|
|
177
|
-
? aot.toSchemaModule(v, { format, runtime: RUNTIME_PLACEHOLDER })
|
|
298
|
+
? aot.toSchemaModule(v, others !== null && others.length > 0 ? { format, runtime: RUNTIME_PLACEHOLDER, registry: REGISTRY_PLACEHOLDER } : { format, runtime: RUNTIME_PLACEHOLDER })
|
|
178
299
|
: sharable && src ? aot.toCompactModule(v, { format, formatMode: opts.formatMode, errorRuntime: RUNTIME_PLACEHOLDER }) : null;
|
|
179
300
|
// The runtime parts this module's schema needs (see runtimeFeatures).
|
|
180
|
-
|
|
301
|
+
// A registry module's schema reaches the others: what it needs from the
|
|
302
|
+
// registry, by $id, is every schema registered beside it. A key that is
|
|
303
|
+
// not its schema's $id cannot be found by the runtime, which keys the
|
|
304
|
+
// registry by $id: such a module is written self-contained.
|
|
305
|
+
let registryEntries = null;
|
|
306
|
+
let compactOut = compact;
|
|
307
|
+
if (compactOut && others !== null && others.length > 0) {
|
|
308
|
+
// A schema without an $id is not in the registry: its module carries it.
|
|
309
|
+
registryEntries = typeof v._schemaObj.$id === 'string' ? [v._schemaObj] : [];
|
|
310
|
+
for (const [id, sub] of v._schemaMap) {
|
|
311
|
+
if (!sub || typeof sub !== 'object' || sub.$id !== id) { compactOut = null; registryEntries = null; break; }
|
|
312
|
+
registryEntries.push(sub);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const { stripAnnotations: _strip } = require('./schema-strip');
|
|
316
|
+
const features = compactOut ? require('./error-runtime').runtimeFeatures((registryEntries || [v._schemaObj]).map(_strip)) : null;
|
|
181
317
|
// A module that imports the pattern engine needs it in the runtime,
|
|
182
318
|
// whatever the schema scan said.
|
|
183
|
-
if (features &&
|
|
184
|
-
|
|
319
|
+
if (features && compactOut.includes('_ataCompileSafe')) features['safe-regex'] = true;
|
|
320
|
+
// Why there is no module, when the reason is a reference: which one, and
|
|
321
|
+
// what would resolve it.
|
|
322
|
+
let unresolved = null;
|
|
323
|
+
if (!src) {
|
|
324
|
+
const miss = {};
|
|
325
|
+
const registry = v._schemaMap && v._schemaMap.size > 0 ? v._schemaMap : null;
|
|
326
|
+
if (!aot._refsResolveWithin(v._schemaObj, registry, miss) && typeof miss.ref === 'string') unresolved = miss.ref;
|
|
327
|
+
}
|
|
328
|
+
return { schema, src, compact: compactOut, features, registryEntries, root: v._schemaObj, usesSiblings: others !== null, unresolved, inputWarnings };
|
|
185
329
|
};
|
|
186
330
|
|
|
187
331
|
for (const input of inputs) {
|
|
@@ -196,6 +340,9 @@ async function build(opts) {
|
|
|
196
340
|
cacheEntry.output === output &&
|
|
197
341
|
!!cacheEntry.sharable === sharable &&
|
|
198
342
|
!!cacheEntry.schemaMode === schemaMode &&
|
|
343
|
+
// A module built against the other inputs depends on them too, and
|
|
344
|
+
// the registry is put together from such modules: always rebuilt.
|
|
345
|
+
!cacheEntry.usesSiblings &&
|
|
199
346
|
fs.existsSync(output) &&
|
|
200
347
|
cacheEntry.outputHash === hashContent(fs.readFileSync(output))
|
|
201
348
|
);
|
|
@@ -219,13 +366,15 @@ async function build(opts) {
|
|
|
219
366
|
for (const x of r.inputWarnings) warnings.push({ input, warning: x.message, kind: x.kind });
|
|
220
367
|
}
|
|
221
368
|
if (!r.src) {
|
|
222
|
-
const reason =
|
|
369
|
+
const reason = r.unresolved !== null
|
|
370
|
+
? `$ref "${r.unresolved}" does not resolve: no input has that $id. Add the schema it names as an input${opts.baseUri === undefined ? ', and if that file has no $id, give it one with --base-uri' : ''}`
|
|
371
|
+
: 'schema is not AOT-compatible (toStandaloneModule returned null)';
|
|
223
372
|
if (opts.strict) failed.push({ input, error: reason });
|
|
224
373
|
else skipped.push({ input, reason });
|
|
225
374
|
continue;
|
|
226
375
|
}
|
|
227
376
|
entries.push({
|
|
228
|
-
input, output, raw, inputHash, schema: r.schema, src: r.src, compact: r.compact, features: r.features,
|
|
377
|
+
input, output, raw, inputHash, schema: r.schema, src: r.src, compact: r.compact, features: r.features, registryEntries: r.registryEntries, root: r.root, usesSiblings: r.usesSiblings,
|
|
229
378
|
fullGz: zlib.gzipSync(r.src).length,
|
|
230
379
|
compactGz: r.compact ? zlib.gzipSync(r.compact).length : null,
|
|
231
380
|
});
|
|
@@ -265,6 +414,19 @@ async function build(opts) {
|
|
|
265
414
|
for (const e of entries) {
|
|
266
415
|
e.mode = runtime !== null && candidates.includes(e) ? (schemaMode ? 'schema' : 'compact') : 'inline';
|
|
267
416
|
}
|
|
417
|
+
// The registry the schema modules resolve against: every schema any of them
|
|
418
|
+
// needs, once, by $id.
|
|
419
|
+
let registryPath = null;
|
|
420
|
+
let registrySrc = null;
|
|
421
|
+
if (runtime !== null) {
|
|
422
|
+
const byId = new Map();
|
|
423
|
+
for (const e of entries) if (e.mode !== 'inline' && e.registryEntries) for (const sub of e.registryEntries) if (!byId.has(sub.$id)) byId.set(sub.$id, sub);
|
|
424
|
+
if (byId.size > 0) {
|
|
425
|
+
registryPath = path.join(path.dirname(runtime.path), 'ata-registry' + (format === 'cjs' ? '.cjs' : '.mjs'));
|
|
426
|
+
const roots = entries.filter((e) => e.mode !== 'inline' && e.registryEntries).map((e) => e.root);
|
|
427
|
+
registrySrc = aot.registrySource([...pruneRegistry(roots, byId).values()], format);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
268
430
|
|
|
269
431
|
for (const e of entries) {
|
|
270
432
|
const { input, output } = e;
|
|
@@ -282,9 +444,10 @@ async function build(opts) {
|
|
|
282
444
|
e.schema = r.schema; e.src = r.src; e.compact = r.compact; e.features = r.features;
|
|
283
445
|
if (e.mode !== 'inline' && !r.compact) e.mode = 'inline';
|
|
284
446
|
}
|
|
285
|
-
|
|
447
|
+
let src = e.mode !== 'inline'
|
|
286
448
|
? e.compact.split(JSON.stringify(RUNTIME_PLACEHOLDER)).join(JSON.stringify(importSpecifier(output, runtime.path)))
|
|
287
449
|
: e.src;
|
|
450
|
+
if (e.mode !== 'inline' && e.registryEntries) src = src.split(JSON.stringify(REGISTRY_PLACEHOLDER)).join(JSON.stringify(importSpecifier(output, registryPath)));
|
|
288
451
|
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
289
452
|
fs.writeFileSync(output, src);
|
|
290
453
|
const outBytes = Buffer.byteLength(src, 'utf8');
|
|
@@ -321,6 +484,7 @@ async function build(opts) {
|
|
|
321
484
|
fullGz: e.fullGz,
|
|
322
485
|
compactGz: e.compactGz,
|
|
323
486
|
features: e.features || undefined,
|
|
487
|
+
usesSiblings: e.usesSiblings || undefined,
|
|
324
488
|
};
|
|
325
489
|
} catch (err) {
|
|
326
490
|
failed.push({ input, error: err.message });
|
|
@@ -335,11 +499,21 @@ async function build(opts) {
|
|
|
335
499
|
fs.writeFileSync(runtime.path, runtime.src);
|
|
336
500
|
}
|
|
337
501
|
runtimeReport = { output: runtime.path, bytes: Buffer.byteLength(runtime.src, 'utf8'), gzipBytes: runtime.gz };
|
|
502
|
+
if (registrySrc !== null) {
|
|
503
|
+
const cur = fs.existsSync(registryPath) ? fs.readFileSync(registryPath, 'utf8') : null;
|
|
504
|
+
if (cur !== registrySrc) fs.writeFileSync(registryPath, registrySrc);
|
|
505
|
+
runtimeReport.registry = { output: registryPath, bytes: Buffer.byteLength(registrySrc, 'utf8'), gzipBytes: zlib.gzipSync(registrySrc).length };
|
|
506
|
+
}
|
|
338
507
|
}
|
|
339
508
|
|
|
340
509
|
writeCache(opts.cacheFile, newCache);
|
|
341
510
|
|
|
342
|
-
|
|
511
|
+
// The self-contained source is built for every input, so its error-detail
|
|
512
|
+
// decline is recorded even where the module written is a compact or schema
|
|
513
|
+
// module, whose errors come from the runtime. Only the written one counts.
|
|
514
|
+
const fromRuntime = new Set(entries.filter((e) => e.mode !== 'inline').map((e) => e.input));
|
|
515
|
+
const kept = warnings.filter((w) => !(w.kind === 'error-detail' && fromRuntime.has(w.input)));
|
|
516
|
+
return { compiled, cached, skipped, failed, warnings: kept, runtime: runtimeReport };
|
|
343
517
|
}
|
|
344
518
|
|
|
345
519
|
async function watch(opts, onReport) {
|
package/lib/aot-impl.js
CHANGED
|
@@ -251,7 +251,7 @@ function onePassCore(validator, opts) {
|
|
|
251
251
|
const decls = [];
|
|
252
252
|
const args = [];
|
|
253
253
|
const own = new Set();
|
|
254
|
-
const addOwn = (n) => { if (own.has(n)) return; own.add(n); if (n === '_h' || n === '_hall') addOwn('_hop'); if (n === '_ok') { addOwn('_PN'); addOwn('_hop'); } decls.push(OWN_HELPER_CODE[n]); };
|
|
254
|
+
const addOwn = (n) => { if (own.has(n)) return; own.add(n); if (n === '_h' || n === '_hall') addOwn('_hop'); if (n === '_ok') { addOwn('_PN'); addOwn('_hop'); } if (n === '_nk') addOwn('_PN'); decls.push(OWN_HELPER_CODE[n]); };
|
|
255
255
|
let usesSafeRe = emit.usesSafeRe, usesCollapse = false;
|
|
256
256
|
for (let i = 0; i < emit.names.length; i++) {
|
|
257
257
|
const name = emit.names[i], val = emit.values[i];
|
|
@@ -272,7 +272,7 @@ function onePassCore(validator, opts) {
|
|
|
272
272
|
if (name === '_VF') { args.push('null'); continue; }
|
|
273
273
|
if (name === '_PN') { addOwn('_PN'); args.push('_PN'); continue; }
|
|
274
274
|
if (name === '_OP' || name === '_gp') { addOwn('_PN'); args.push(name); continue; }
|
|
275
|
-
if (name === '_hop' || name === '_h' || name === '_ok' || name === '_hall') { addOwn(name); args.push(name); continue; }
|
|
275
|
+
if (name === '_hop' || name === '_h' || name === '_ok' || name === '_hall' || name === '_nk') { addOwn(name); args.push(name); continue; }
|
|
276
276
|
if (name === '_pe') { decls.push(PTR_ESC_HELPER); args.push('_pe'); continue; }
|
|
277
277
|
if (name === '_deq') { decls.push(DEQ_HELPER); args.push('_deq'); continue; }
|
|
278
278
|
if (name === '_anT') { decls.push('function _anT(){return true}'); args.push('_anT'); continue; }
|
|
@@ -687,13 +687,14 @@ function hasRefKey(n, seen) {
|
|
|
687
687
|
return false;
|
|
688
688
|
}
|
|
689
689
|
|
|
690
|
-
|
|
690
|
+
// miss: an object that gets `ref`, the first reference that does not resolve.
|
|
691
|
+
function refsResolveWithin(schema, registry, miss) {
|
|
691
692
|
// No reference anywhere (a key of that name inside a value counts too):
|
|
692
693
|
// nothing to resolve, and no index to build.
|
|
693
|
-
if (!hasRefKey(schema, new Set())) return true;
|
|
694
|
+
if (!registry && !hasRefKey(schema, new Set())) return true;
|
|
694
695
|
const { indexSchemas, resolveRef } = require('./interpreter-core')._refInternals;
|
|
695
696
|
let state;
|
|
696
|
-
try { state = indexSchemas(schema, null); } catch { return false; }
|
|
697
|
+
try { state = indexSchemas(schema, registry || null); } catch { return false; }
|
|
697
698
|
for (const [node, base] of state.nodeBase) {
|
|
698
699
|
if (node === null || typeof node !== 'object') continue;
|
|
699
700
|
for (const k of ['$ref', '$dynamicRef']) {
|
|
@@ -701,15 +702,19 @@ function refsResolveWithin(schema) {
|
|
|
701
702
|
if (r === undefined) continue;
|
|
702
703
|
if (typeof r !== 'string') return false;
|
|
703
704
|
let hit;
|
|
704
|
-
try { hit = resolveRef(r, base, state); } catch { return false; }
|
|
705
|
-
if (hit.node === undefined) return false;
|
|
705
|
+
try { hit = resolveRef(r, base, state); } catch { if (miss) miss.ref = r; return false; }
|
|
706
|
+
if (hit.node === undefined) { if (miss) miss.ref = r; return false; }
|
|
706
707
|
}
|
|
707
708
|
}
|
|
708
709
|
return true;
|
|
709
710
|
}
|
|
710
711
|
|
|
711
712
|
const COMPACT_OPTIONS = new Set(['engine', 'strictSchema', 'logger', 'source']);
|
|
712
|
-
|
|
713
|
+
// registryMode: for a schema module that resolves its references against the
|
|
714
|
+
// build's registry (toSchemaModule with `registry`): schemas registered beside
|
|
715
|
+
// the root are allowed, and every reference has to land in the schema or in
|
|
716
|
+
// one of them.
|
|
717
|
+
function compactDecline(validator, registryMode) {
|
|
713
718
|
// Options are allowed by name, not refused by name: one this list does not
|
|
714
719
|
// know (assertFormat: false, baseURI, a future one) may change what the
|
|
715
720
|
// verdict means, and the runtime would not know to follow it.
|
|
@@ -718,19 +723,24 @@ function compactDecline(validator) {
|
|
|
718
723
|
validator._ensureCompiled();
|
|
719
724
|
const o = validator._options || {};
|
|
720
725
|
for (const k of Object.keys(o)) {
|
|
721
|
-
if (o[k] === undefined || COMPACT_OPTIONS.has(k)) continue;
|
|
726
|
+
if (o[k] === undefined || COMPACT_OPTIONS.has(k) || (registryMode === true && k === 'schemas')) continue;
|
|
722
727
|
return 'option ' + k;
|
|
723
728
|
}
|
|
724
729
|
if (validator._usesKeywords || validator._externalChecks) return 'custom keywords';
|
|
725
730
|
if (validator._userFormats && Object.keys(validator._userFormats).length) return 'custom formats';
|
|
726
|
-
if (validator._schemaMap && validator._schemaMap.size > 0) return 'schemas registered beside the root';
|
|
731
|
+
if (registryMode !== true && validator._schemaMap && validator._schemaMap.size > 0) return 'schemas registered beside the root';
|
|
727
732
|
if (validator._schemaObj === null || typeof validator._schemaObj !== 'object') return 'boolean schema';
|
|
728
733
|
// The runtime has the embedded schema and nothing else: a reference that
|
|
729
734
|
// lands outside it (a meta-schema the runtime Validator resolves from its
|
|
730
735
|
// vendored copies, a schema registered later) would come back unresolved,
|
|
731
736
|
// and the errors with it. The check runs on the schema as embedded.
|
|
732
737
|
const { stripAnnotations } = require('./schema-strip');
|
|
733
|
-
|
|
738
|
+
let registry = null;
|
|
739
|
+
if (registryMode === true && validator._schemaMap && validator._schemaMap.size > 0) {
|
|
740
|
+
registry = new Map();
|
|
741
|
+
for (const [id, sub] of validator._schemaMap) registry.set(id, stripAnnotations(sub));
|
|
742
|
+
}
|
|
743
|
+
if (!refsResolveWithin(stripAnnotations(validator._schemaObj), registry)) return registry ? 'a reference outside the schema and the registry' : 'a reference outside the schema';
|
|
734
744
|
return null;
|
|
735
745
|
}
|
|
736
746
|
|
|
@@ -755,7 +765,12 @@ function toCompactModule(validator, opts) {
|
|
|
755
765
|
function toSchemaModule(validator, opts) {
|
|
756
766
|
assertEmittable(validator, 'toSchemaModule');
|
|
757
767
|
if (!opts || typeof opts.runtime !== 'string') throw new TypeError('toSchemaModule: opts.runtime (the import specifier of the schema runtime) is required');
|
|
758
|
-
|
|
768
|
+
// opts.registry: the import specifier of the build's registry module (the
|
|
769
|
+
// array of schemas, written by errorRegistrySource), for schemas that
|
|
770
|
+
// reference each other by $id. The module then names its schema by $id and
|
|
771
|
+
// carries no copy of it.
|
|
772
|
+
const registry = typeof opts.registry === 'string' ? opts.registry : null;
|
|
773
|
+
if (compactDecline(validator, registry !== null) !== null) return null;
|
|
759
774
|
const format = opts.format || 'esm';
|
|
760
775
|
const { stripAnnotations } = require('./schema-strip');
|
|
761
776
|
const { isV1Dialect } = require('./dialect');
|
|
@@ -770,12 +785,15 @@ function toSchemaModule(validator, opts) {
|
|
|
770
785
|
if ('$anchor' in n || '$dynamicAnchor' in n || (!root && '$id' in n)) return false;
|
|
771
786
|
return Object.keys(n).every((k) => flatWalk(n[k], false));
|
|
772
787
|
};
|
|
773
|
-
const
|
|
788
|
+
const ownId = registry !== null && typeof sch.$id === 'string' ? sch.$id : null;
|
|
789
|
+
const rtOpts = [isV1Dialect(sch) ? 'v1: true' : '', registry === null && flatWalk(stripped, true) ? 'flat: true' : '', registry !== null ? 'registry: _ataRegistry' : '', ownId !== null ? 'id: ' + JSON.stringify(ownId) : ''].filter(Boolean).join(', ');
|
|
774
790
|
const hashSrc = hashOf(validator);
|
|
775
791
|
const spec = JSON.stringify(opts.runtime);
|
|
776
|
-
const
|
|
792
|
+
const regSpec = registry !== null ? JSON.stringify(registry) : null;
|
|
793
|
+
const imp = (format === 'esm'
|
|
777
794
|
? `import { createValidator as _ataCreateValidator } from ${spec};\n`
|
|
778
|
-
: `const { createValidator: _ataCreateValidator } = require(${spec});\n
|
|
795
|
+
: `const { createValidator: _ataCreateValidator } = require(${spec});\n`)
|
|
796
|
+
+ (regSpec === null ? '' : format === 'esm' ? `import _ataRegistry from ${regSpec};\n` : `const _ataRegistry = require(${regSpec});\n`);
|
|
779
797
|
const exp = format === 'esm'
|
|
780
798
|
? 'export { validate, isValid, schemaHash, ataVersion };\nexport default { validate, isValid, schemaHash, ataVersion };\n'
|
|
781
799
|
: 'module.exports = { validate, isValid, schemaHash, ataVersion };\nmodule.exports.default = module.exports;\n';
|
|
@@ -786,7 +804,7 @@ function toSchemaModule(validator, opts) {
|
|
|
786
804
|
${imp}const VALID = Object.freeze({ valid: true, errors: Object.freeze([]) });
|
|
787
805
|
const ABORT = Object.freeze({ valid: false, errors: Object.freeze([Object.freeze({ code: 'ATA9000', message: 'validation failed', keyword: '__abort_early__', path: '' })]) });
|
|
788
806
|
let _ataV = null;
|
|
789
|
-
function _ataGet() { return _ataV !== null ? _ataV : (_ataV = _ataCreateValidator(JSON.parse(
|
|
807
|
+
function _ataGet() { return _ataV !== null ? _ataV : (_ataV = _ataCreateValidator(${ownId !== null ? 'null' : 'JSON.parse(' + text + ')'}${rtOpts ? ', { ' + rtOpts + ' }' : ''})); }
|
|
790
808
|
function isValid(data) { return _ataGet().isValid(data); }
|
|
791
809
|
function validate(data) { const v = _ataGet(); if (v.isValid(data)) return VALID; const e = v.errors(data); return e === null ? ABORT : { valid: false, errors: e }; }
|
|
792
810
|
const schemaHash = ${JSON.stringify(hashSrc)};
|
|
@@ -794,6 +812,18 @@ const ataVersion = ${JSON.stringify(ATA_VERSION)};
|
|
|
794
812
|
${exp}`;
|
|
795
813
|
}
|
|
796
814
|
|
|
815
|
+
// The registry module schema modules import when their schemas reference each
|
|
816
|
+
// other: every schema of the build, without its descriptive keywords, as one
|
|
817
|
+
// array the runtime turns into its id-to-schema table.
|
|
818
|
+
function registrySource(schemas, format) {
|
|
819
|
+
const { stripAnnotations } = require('./schema-strip');
|
|
820
|
+
const text = JSON.stringify(JSON.stringify(schemas.map((x) => stripAnnotations(x && x._schemaObj !== undefined ? x._schemaObj : x)))).replace(/</g, '\\u003c');
|
|
821
|
+
const head = `// Auto-generated by ata-validator ${ATA_VERSION}, do not edit.\n// The schemas of one build, which its schema modules resolve references against.\n'use strict';\n`;
|
|
822
|
+
return format === 'cjs'
|
|
823
|
+
? head + `module.exports = JSON.parse(${text});\n`
|
|
824
|
+
: head + `export default JSON.parse(${text});\n`;
|
|
825
|
+
}
|
|
826
|
+
|
|
797
827
|
// Bundle multiple validators into a single JS file for fast startup.
|
|
798
828
|
// Usage:
|
|
799
829
|
// const bundle = Validator.bundle([schema1, schema2, ...]);
|
|
@@ -1110,6 +1140,7 @@ module.exports = {
|
|
|
1110
1140
|
toStandaloneModule: htmlSafeResult(toStandaloneModule),
|
|
1111
1141
|
toCompactModule: htmlSafeResult(toCompactModule),
|
|
1112
1142
|
toSchemaModule: htmlSafeResult(toSchemaModule),
|
|
1143
|
+
registrySource: htmlSafeResult(registrySource),
|
|
1113
1144
|
compactDecline,
|
|
1114
1145
|
// `schemas`: the validators (or schemas) whose compact modules will import
|
|
1115
1146
|
// this runtime; parts none of them needs are left out. Omitted, the
|
|
@@ -1130,3 +1161,5 @@ module.exports = {
|
|
|
1130
1161
|
bundleCompact: htmlSafeResult(bundleCompact),
|
|
1131
1162
|
loadBundle,
|
|
1132
1163
|
};
|
|
1164
|
+
// For the build only (lib/aot-build.js), not part of ata-validator/aot.
|
|
1165
|
+
Object.defineProperty(module.exports, '_refsResolveWithin', { value: refsResolveWithin, enumerable: false });
|