ata-validator 1.61.3 → 1.62.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 +67 -10
- package/bin/ata.js +3 -2
- package/compat.js +15 -2
- package/index.d.ts +7 -0
- package/index.js +10 -0
- package/lib/aot-build.js +27 -1
- package/lib/aot-impl.js +108 -17
- package/lib/buffer-gate.js +25 -14
- package/lib/bun-target.js +21 -5
- package/lib/clone-emit.js +115 -10
- package/lib/codegen-engine.js +94 -18
- package/lib/compiled-common.js +11 -4
- package/lib/compiled-verdict.js +2 -2
- package/lib/compiled.js +1 -1
- package/lib/data-positions.js +18 -0
- package/lib/defaults.js +101 -23
- package/lib/describe-schema.js +38 -5
- package/lib/diagnose.js +51 -29
- package/lib/enrich-error.js +23 -1
- package/lib/error-sort-rank.js +12 -0
- package/lib/formats-source.js +12 -1
- package/lib/formats.js +19 -19
- package/lib/interpreter-core.js +23 -4
- package/lib/js-compiler-errors.js +70 -72
- package/lib/js-compiler.js +212 -72
- package/lib/json-rejections.js +1 -1
- package/lib/migrate.js +24 -5
- package/lib/native-attach.browser.js +7 -0
- package/lib/native-attach.js +76 -0
- package/lib/native-positions.js +7 -0
- package/lib/plan-compiler.js +7 -1
- package/lib/regex-linear.js +88 -0
- package/lib/rejections.js +13 -3
- package/lib/render-compact.js +5 -5
- package/lib/render-pretty.js +23 -10
- package/lib/safe-regex.js +29 -4
- package/lib/scan-compiler.js +16 -4
- package/lib/stream.js +293 -0
- package/lib/tier0.js +6 -2
- package/lib/ts-gen.js +25 -12
- package/lib/unique-items.js +11 -3
- package/lib/validator-core.js +61 -66
- package/lib/verify.js +25 -6
- package/lib/version.js +1 -1
- package/package.json +17 -8
- package/stream.d.ts +51 -0
- package/stream.js +3 -0
- package/stream.mjs +3 -0
package/README.md
CHANGED
|
@@ -201,16 +201,24 @@ documents, Node 24:
|
|
|
201
201
|
|
|
202
202
|
| | documents a second, the verdict | same, `new Function` refused |
|
|
203
203
|
|---|---:|---:|
|
|
204
|
-
| ata | 3,
|
|
205
|
-
| ata, compiled module | 3,
|
|
206
|
-
|
|
|
207
|
-
|
|
|
208
|
-
|
|
|
209
|
-
| zod |
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
204
|
+
| ata | 3,274,803 | 1,109,881 |
|
|
205
|
+
| ata, compiled module | 3,270,947 | 3,353,351 |
|
|
206
|
+
| typia | 1,957,392 | 1,931,134 |
|
|
207
|
+
| typebox | 1,615,239 | EvalError |
|
|
208
|
+
| ajv | 1,245,508 | EvalError |
|
|
209
|
+
| zod | 436,632 | 230,808 |
|
|
210
|
+
|
|
211
|
+
Run of 2026-10-30 with typia 15.2.2, which reuses a pattern's RegExp and shortcuts length
|
|
212
|
+
checks after a report from this benchmark; its order figure doubled.
|
|
213
|
+
|
|
214
|
+
The same repository runs every SchemaStore schema that ships sample documents, 446 of them,
|
|
215
|
+
against the default validator on those samples: ata ahead on 430, median 1.83x, 2.82x on
|
|
216
|
+
schemas of 50 to 200 KB, and never below 0.74x on the 16 where it is behind
|
|
217
|
+
([RESULTS-schemastore.md](https://github.com/ata-core/validator-benchmarks/blob/main/RESULTS-schemastore.md),
|
|
218
|
+
Node 24, ata 1.61.3). Where ata is behind is
|
|
219
|
+
in the same tables: on a four-field body that fails, ajv answers the verdict faster (15.5M
|
|
220
|
+
against 10.4M), and with every error read it does 7.1M a second against ata's 3.6M with rich
|
|
221
|
+
errors, 5.2M with `richErrors: false`.
|
|
214
222
|
|
|
215
223
|
## Why AOT
|
|
216
224
|
|
|
@@ -793,6 +801,55 @@ is rejected by the new one, so a pull request that tightens a schema fails
|
|
|
793
801
|
the check until someone looks. Generated documents are a sample, not a proof:
|
|
794
802
|
a change this reports is real, one it does not report may still exist.
|
|
795
803
|
|
|
804
|
+
### Validation as the body arrives
|
|
805
|
+
|
|
806
|
+
A request that carries many documents, a JSON array or NDJSON, is usually read to the end,
|
|
807
|
+
parsed as a whole and then validated, so a bad third record is reported after the last byte.
|
|
808
|
+
`ata-validator/stream` validates each record the moment its last byte lands and stops reading
|
|
809
|
+
at the first failure, with memory that does not grow with the body.
|
|
810
|
+
|
|
811
|
+
```js
|
|
812
|
+
import { validateStream } from 'ata-validator/stream'
|
|
813
|
+
|
|
814
|
+
app.post('/orders/import', async (c) => {
|
|
815
|
+
const r = await validateStream(orders, c.req.raw.body) // a Web ReadableStream, or a Node Readable
|
|
816
|
+
if (!r.valid) return c.json({ record: r.errors[0].record, errors: r.errors[0].errors }, 400)
|
|
817
|
+
return c.json({ imported: r.records })
|
|
818
|
+
})
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
`v.stream(opts)` on Node, or `streamValidator(v, opts)` from `ata-validator/stream` anywhere (the lite, edge and browser entries do not carry the method, so their bundles stay what they were), is the same thing fed by hand: `push(chunk)` takes strings or bytes and returns
|
|
822
|
+
the first failure as soon as its record is complete, `end()` returns `{ valid, records, errors }`.
|
|
823
|
+
`onRecord(doc, index)` hands over each record that passed, parsed, so an import does not parse
|
|
824
|
+
twice; `stopOnFirst: false` lists every failing record instead of stopping. The format is read
|
|
825
|
+
from the first character (`[` is an array, anything else NDJSON) or set with `format`. A JSON
|
|
826
|
+
array is split by a scan that tracks strings and nesting, not by parsing it, so the elements are
|
|
827
|
+
parsed one at a time.
|
|
828
|
+
|
|
829
|
+
Measured on a 100 MB body of 128,419 order documents, read in 64 KB chunks, Node 25, Apple M4
|
|
830
|
+
Pro, one process per row:
|
|
831
|
+
|
|
832
|
+
| | invalid record 3 | invalid last record |
|
|
833
|
+
|---|---|---|
|
|
834
|
+
| read it all, `JSON.parse`, validate the array | 517 ms, 783 MB | 571 ms, 845 MB |
|
|
835
|
+
| read it all, split lines, parse and validate each | 237 ms, 553 MB | 462 ms, 660 MB |
|
|
836
|
+
| `ata-validator/stream`, JSON array | 13.5 ms, 122 MB, 0.1 MB read | 577 ms, 195 MB |
|
|
837
|
+
| `ata-validator/stream`, NDJSON | 19.6 ms, 122 MB, 0.1 MB read | 461 ms, 197 MB |
|
|
838
|
+
|
|
839
|
+
Peak RSS; the 100 MB figure is the process without the body. A body that is valid to the end
|
|
840
|
+
costs the same time as before and a third of the memory. `tests/test_stream.js` holds the stream
|
|
841
|
+
to whole-document validation under every way of cutting the bytes into chunks, including inside
|
|
842
|
+
multi-byte characters, strings with brackets and escaped quotes, nested arrays, scalars, CRLF
|
|
843
|
+
and blank lines, and checks that a failing source is destroyed or cancelled rather than read on.
|
|
844
|
+
|
|
845
|
+
A Node http request handed to `validateStream` is not destroyed at the failure: closing the
|
|
846
|
+
socket under the response turns the answer into a network error at the client (Node's `fetch`
|
|
847
|
+
uploading 100 MB got `write EPIPE`, Chrome "Failed to fetch", neither the 400). Reading stops and
|
|
848
|
+
the rest of the body is read and dropped, so the response goes out and memory stays flat. Node's
|
|
849
|
+
`fetch` then had the 400 after 24 ms and 1 MB sent; Chrome, which reads only after its upload,
|
|
850
|
+
after 58 ms for a 50 MB file over loopback; the Hono example above, whose Node adapter cancels
|
|
851
|
+
the stream without closing the socket, after 28 ms. A plain `Readable` is still destroyed.
|
|
852
|
+
|
|
796
853
|
### Bun, compiled ahead of time
|
|
797
854
|
|
|
798
855
|
Bun's ahead-of-time compiler (`bun build --compile --aot`, an open pull
|
package/bin/ata.js
CHANGED
|
@@ -533,7 +533,8 @@ function cmdValidate (args) {
|
|
|
533
533
|
try {
|
|
534
534
|
schema = JSON.parse(schemaContent);
|
|
535
535
|
} catch (e) {
|
|
536
|
-
|
|
536
|
+
// The parser quotes the text it stopped at, and the text is the file's.
|
|
537
|
+
process.stderr.write(`error: ${schemaPath} is not valid JSON: ${require('../lib/diagnose').term(e.message)}\n`);
|
|
537
538
|
process.exit(2);
|
|
538
539
|
}
|
|
539
540
|
|
|
@@ -550,7 +551,7 @@ function cmdValidate (args) {
|
|
|
550
551
|
try {
|
|
551
552
|
v = new Validator(schema, { source: { path: schemaPath, content: schemaContent } });
|
|
552
553
|
} catch (e) {
|
|
553
|
-
process.stderr.write(`error: schema compile failed: ${e.message}\n`);
|
|
554
|
+
process.stderr.write(`error: schema compile failed: ${require('../lib/diagnose').term(e.message)}\n`);
|
|
554
555
|
process.exit(2);
|
|
555
556
|
}
|
|
556
557
|
|
package/compat.js
CHANGED
|
@@ -129,7 +129,9 @@ class Ata {
|
|
|
129
129
|
const formatNames = Object.keys(this._formats);
|
|
130
130
|
if (formatNames.length > 0) out.formats = { ...this._formats };
|
|
131
131
|
const keywordNames = Object.keys(this._keywords);
|
|
132
|
-
|
|
132
|
+
// Null prototype: a keyword named __proto__ written into a plain object
|
|
133
|
+
// would set its prototype and never reach the validator.
|
|
134
|
+
const kws = Object.create(null);
|
|
133
135
|
for (const name of keywordNames) if (this._keywords[name] !== true) kws[name] = this._keywords[name];
|
|
134
136
|
if (Object.keys(kws).length > 0) out.keywords = kws;
|
|
135
137
|
const registry = this._registrySnapshot();
|
|
@@ -646,8 +648,16 @@ function referencedDefaults(rootDoc, registry) {
|
|
|
646
648
|
}
|
|
647
649
|
return node;
|
|
648
650
|
};
|
|
651
|
+
// `done`, per call: the schemas already applied to each object. A second
|
|
652
|
+
// application fills nothing new, and following every edge of a chain such
|
|
653
|
+
// as n_i = allOf [n_(i-1), n_(i-1)] applied the bottom schema 2^n times.
|
|
654
|
+
let done = null;
|
|
649
655
|
const fill = (schema, data, active) => {
|
|
650
656
|
if (schema === null || typeof schema !== 'object' || data === null || typeof data !== 'object' || active.has(schema)) return;
|
|
657
|
+
let applied = done.get(data);
|
|
658
|
+
if (applied === undefined) done.set(data, (applied = new Set()));
|
|
659
|
+
if (applied.has(schema)) return;
|
|
660
|
+
applied.add(schema);
|
|
651
661
|
active.add(schema);
|
|
652
662
|
if (typeof schema.$ref === 'string') fill(resolve(schema.$ref), data, active);
|
|
653
663
|
if (Array.isArray(schema.allOf)) for (const sub of schema.allOf) fill(sub, data, active);
|
|
@@ -670,7 +680,10 @@ function referencedDefaults(rootDoc, registry) {
|
|
|
670
680
|
}
|
|
671
681
|
active.delete(schema);
|
|
672
682
|
};
|
|
673
|
-
return (data) =>
|
|
683
|
+
return (data) => {
|
|
684
|
+
done = new Map();
|
|
685
|
+
try { fill(rootDoc, data, new Set()); } finally { done = null; }
|
|
686
|
+
};
|
|
674
687
|
}
|
|
675
688
|
|
|
676
689
|
// The registry id a fragment-only key is kept under: a `#...` string is not an
|
package/index.d.ts
CHANGED
|
@@ -509,6 +509,13 @@ export interface Validator<T = unknown> {
|
|
|
509
509
|
/** Validate a JSON string. Uses simdjson fast path for large documents. */
|
|
510
510
|
validateJSON(jsonString: string): ValidationResult<T>;
|
|
511
511
|
|
|
512
|
+
/**
|
|
513
|
+
* Validation as the body arrives: a record stream over this validator for a
|
|
514
|
+
* JSON array or NDJSON fed in chunks, reporting the first failing record the
|
|
515
|
+
* moment it is complete. See `ata-validator/stream` for the types.
|
|
516
|
+
*/
|
|
517
|
+
stream(opts?: import('./stream').StreamOptions): import('./stream').RecordStream;
|
|
518
|
+
|
|
512
519
|
/** Fast boolean check for a JSON string */
|
|
513
520
|
isValidJSON(jsonString: string): boolean;
|
|
514
521
|
|
package/index.js
CHANGED
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
// lib/codegen-engine.js, so that the edge entry (index.edge.js) and
|
|
7
7
|
// ata-validator/lite can load the core without it.
|
|
8
8
|
const core = require('./lib/validator-core.js');
|
|
9
|
+
|
|
10
|
+
// Validation as the body arrives: `v.stream(opts)` is a record stream over the
|
|
11
|
+
// validator (lib/stream.js), loaded on first use. Defined here, on the Node
|
|
12
|
+
// entry, and not in the core: the lite, edge and browser builds are measured
|
|
13
|
+
// to the byte and a static require from the core would put the stream into
|
|
14
|
+
// every one of them; there `ata-validator/stream` is imported where it is used.
|
|
15
|
+
Object.defineProperty(core.Validator.prototype, 'stream', {
|
|
16
|
+
configurable: true, writable: true,
|
|
17
|
+
value: function stream (opts) { return require('./lib/stream.js').streamValidator(this, opts); },
|
|
18
|
+
});
|
|
9
19
|
// The code generator loads on first need, or once the event loop is free,
|
|
10
20
|
// not with the package: it is most of what requiring the package parsed. The
|
|
11
21
|
// timer is unref'd, so a script that has finished does not stay to load it.
|
package/lib/aot-build.js
CHANGED
|
@@ -95,7 +95,13 @@ function runtimePathFor(outputs, opts, format) {
|
|
|
95
95
|
let dir = path.resolve(path.dirname(outputs[0]));
|
|
96
96
|
for (const o of outputs) {
|
|
97
97
|
const d = path.resolve(path.dirname(o));
|
|
98
|
-
|
|
98
|
+
// The root already ends in a separator ('/' or a drive root), and dirname of the
|
|
99
|
+
// root is the root: compared against root + sep the walk never ended.
|
|
100
|
+
while (dir !== d && !d.startsWith(dir.endsWith(path.sep) ? dir : dir + path.sep)) {
|
|
101
|
+
const up = path.dirname(dir);
|
|
102
|
+
if (up === dir) break;
|
|
103
|
+
dir = up;
|
|
104
|
+
}
|
|
99
105
|
}
|
|
100
106
|
return path.join(dir, name);
|
|
101
107
|
}
|
|
@@ -245,6 +251,15 @@ async function buildWith(opts) {
|
|
|
245
251
|
// check once warm.
|
|
246
252
|
if (opts.mode !== undefined && opts.mode !== 'code' && opts.mode !== 'schema') throw new Error(`build: mode must be code or schema (got "${opts.mode}")`);
|
|
247
253
|
const schemaMode = opts.mode === 'schema' && !opts.abortEarly;
|
|
254
|
+
// The options that change what a module holds, recorded with each cache
|
|
255
|
+
// entry and required to match. Without them a module built for production
|
|
256
|
+
// (no schema-source frames) was reported up to date by a development build
|
|
257
|
+
// and kept, since the input and the output had not changed.
|
|
258
|
+
const optionsKey = JSON.stringify([
|
|
259
|
+
!!opts.abortEarly, opts.onePass === true, !!opts.plainErrors, !!opts.verdictOnly,
|
|
260
|
+
opts.order === undefined ? null : opts.order, opts.formatMode === undefined ? null : opts.formatMode,
|
|
261
|
+
opts.baseUri === undefined ? null : opts.baseUri, opts.target === undefined ? null : opts.target, !!opts.types,
|
|
262
|
+
]);
|
|
248
263
|
|
|
249
264
|
const compiled = [];
|
|
250
265
|
const cached = [];
|
|
@@ -367,6 +382,8 @@ async function buildWith(opts) {
|
|
|
367
382
|
cacheEntry.output === output &&
|
|
368
383
|
!!cacheEntry.sharable === sharable &&
|
|
369
384
|
!!cacheEntry.schemaMode === schemaMode &&
|
|
385
|
+
cacheEntry.source === source &&
|
|
386
|
+
cacheEntry.options === optionsKey &&
|
|
370
387
|
// A module built against the other inputs depends on them too, and
|
|
371
388
|
// the registry is put together from such modules: always rebuilt.
|
|
372
389
|
!cacheEntry.usesSiblings &&
|
|
@@ -522,6 +539,8 @@ async function buildWith(opts) {
|
|
|
522
539
|
outputHash: hashContent(Buffer.from(src, 'utf8')),
|
|
523
540
|
sharable,
|
|
524
541
|
schemaMode,
|
|
542
|
+
source,
|
|
543
|
+
options: optionsKey,
|
|
525
544
|
mode: e.mode,
|
|
526
545
|
runtime: e.mode !== 'inline' ? runtime.path : undefined,
|
|
527
546
|
fullGz: e.fullGz,
|
|
@@ -577,8 +596,13 @@ async function watch(opts, onReport) {
|
|
|
577
596
|
}
|
|
578
597
|
};
|
|
579
598
|
|
|
599
|
+
// The build writes its cache file, JSON and often beside the schemas; a
|
|
600
|
+
// change to it is the build's own and starts nothing, or every build woke
|
|
601
|
+
// the watcher for the next one.
|
|
602
|
+
const cachePath = opts.cacheFile ? path.resolve(opts.cacheFile) : null;
|
|
580
603
|
const watchers = dirs.map((d) => fs.watch(d, (_event, filename) => {
|
|
581
604
|
if (!filename) return;
|
|
605
|
+
if (cachePath !== null && path.resolve(d, filename) === cachePath) return;
|
|
582
606
|
const ext = path.extname(filename).toLowerCase();
|
|
583
607
|
if (ext !== '.json' && ext !== '.yaml' && ext !== '.yml') return;
|
|
584
608
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -754,3 +778,5 @@ module.exports = {
|
|
|
754
778
|
toStandaloneModule,
|
|
755
779
|
schemaHash,
|
|
756
780
|
};
|
|
781
|
+
// For tests only, not part of ata-validator/build.
|
|
782
|
+
Object.defineProperty(module.exports, '_runtimePathFor', { value: runtimePathFor, enumerable: false });
|
package/lib/aot-impl.js
CHANGED
|
@@ -160,13 +160,19 @@ module.exports = { boolFn, hybridFactory, errFn };
|
|
|
160
160
|
// 'inject' writes no function source. The module exports `setFormats(map)`
|
|
161
161
|
// and each format is looked up from that registry when a value is checked,
|
|
162
162
|
// so the caller supplies the functions at load time.
|
|
163
|
+
// The format name a helper name was made from (js-compiler.js ufName):
|
|
164
|
+
// `_uf_` and the name with every character outside [A-Za-z0-9_] written as
|
|
165
|
+
// `$` and four hex digits.
|
|
166
|
+
function formatFromName(name) {
|
|
167
|
+
return name.slice(4).replace(/\$([0-9a-f]{4})/g, (m, h) => String.fromCharCode(parseInt(h, 16)));
|
|
168
|
+
}
|
|
163
169
|
function emitFormatDecls(closures, mode, declKW) {
|
|
164
170
|
if (!closures || closures.length === 0) return { decls: '', exportsSetFormats: false };
|
|
165
171
|
if (mode === 'inject') {
|
|
166
172
|
let out = `${declKW} __formats = Object.create(null);\n`;
|
|
167
173
|
out += `function setFormats(map) { for (const k in map) __formats[k] = map[k]; }\n`;
|
|
168
174
|
for (const { name, format } of closures) {
|
|
169
|
-
const key = JSON.stringify(format === null ? name
|
|
175
|
+
const key = JSON.stringify(format === null ? formatFromName(name) : format);
|
|
170
176
|
out += `${declKW} ${name} = function (v) { const f = __formats[${key}]; if (typeof f !== 'function') throw new Error('ata: format ' + ${key} + ' is not registered; call setFormats({ [' + ${key} + ']: fn }) before validating'); return f(v); };\n`;
|
|
171
177
|
}
|
|
172
178
|
return { decls: out, exportsSetFormats: true };
|
|
@@ -199,10 +205,15 @@ function sourceFrameDecls(frames, schemaFile) {
|
|
|
199
205
|
const texts = [];
|
|
200
206
|
const textIndex = new Map();
|
|
201
207
|
const rows = [];
|
|
208
|
+
// The rows come from the caller's `sourceMap` option. A line or column that
|
|
209
|
+
// is not an integer is written as 0: written as given, a string in one ran
|
|
210
|
+
// as code when the module loaded.
|
|
211
|
+
const int = (n) => (Number.isInteger(n) ? n : 0);
|
|
202
212
|
for (const f of frames.values()) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
213
|
+
const text = typeof f.text === 'string' ? f.text : '';
|
|
214
|
+
let i = textIndex.get(text);
|
|
215
|
+
if (i === undefined) { i = texts.length; texts.push(text); textIndex.set(text, i); }
|
|
216
|
+
rows[f.index] = `[${int(f.line)},${int(f.col)},${i}]`;
|
|
206
217
|
}
|
|
207
218
|
return `const __ataSF = ${JSON.stringify(schemaFile)};\n` +
|
|
208
219
|
`const __ataSL = ${JSON.stringify(texts)};\n` +
|
|
@@ -367,7 +378,27 @@ function defaultsDecl(validator) {
|
|
|
367
378
|
if (defaults.buildDefaultsApplier(schema) === null) return '';
|
|
368
379
|
const embedded = require('./schema-strip').stripAnnotationsKeepingDefaults(schema);
|
|
369
380
|
const text = JSON.stringify(JSON.stringify(embedded)).replace(/</g, '\\u003c');
|
|
370
|
-
|
|
381
|
+
// patternProperties keys are tested as validation tests them: on the
|
|
382
|
+
// linear engine where validation uses it, the platform RegExp otherwise.
|
|
383
|
+
// Called without a matcher, the embedded pass took the RegExp for every
|
|
384
|
+
// pattern, and a key that backtracks cost seconds there. A module whose
|
|
385
|
+
// declaration names __ataSafeRe carries the engine (callers check).
|
|
386
|
+
const { useSafeEngine } = require('./js-compiler')._internal;
|
|
387
|
+
const safe = new Set();
|
|
388
|
+
const seen = new Set();
|
|
389
|
+
const walk = (n) => {
|
|
390
|
+
if (n === null || typeof n !== 'object' || seen.has(n)) return;
|
|
391
|
+
seen.add(n);
|
|
392
|
+
if (!Array.isArray(n) && n.patternProperties && typeof n.patternProperties === 'object' && !Array.isArray(n.patternProperties)) {
|
|
393
|
+
for (const k of Object.keys(n.patternProperties)) if (useSafeEngine(k)) safe.add(k);
|
|
394
|
+
}
|
|
395
|
+
for (const k of Object.keys(n)) walk(n[k]);
|
|
396
|
+
};
|
|
397
|
+
walk(embedded);
|
|
398
|
+
const build = safe.size === 0
|
|
399
|
+
? 'buildDefaultsApplier'
|
|
400
|
+
: `(function () { const SAFE = new Set(${JSON.stringify([...safe])}); return (s) => buildDefaultsApplier(s, (p) => SAFE.has(p) ? __ataSafeRe(p) : keyPattern(p)); })()`;
|
|
401
|
+
return `const _ataDefaults = (function () {\nconst MAX_DEPTH = ${defaults.MAX_DEPTH};\n${loadAotOrder().take('defaults.js', defaults.EMBED)}\nreturn ${build};\n})();\n` +
|
|
371
402
|
`let _ataFill;\nfunction applyDefaults(d) { (_ataFill || (_ataFill = _ataDefaults(JSON.parse(${text}))))(d); }\n`;
|
|
372
403
|
}
|
|
373
404
|
|
|
@@ -577,7 +608,7 @@ function toStandaloneModule(validator, opts) {
|
|
|
577
608
|
// which carries it anyway, instead of embedding a copy (2.9 KB gzipped,
|
|
578
609
|
// minified) in every module that has a pattern the platform RegExp does not
|
|
579
610
|
// run.
|
|
580
|
-
|
|
611
|
+
let usesSafeRe = errorRuntime !== null && ((jsFn && jsFn._usesSafeRe) || (jsErrFn && jsErrFn._usesSafeRe));
|
|
581
612
|
let compactCore = '';
|
|
582
613
|
if (errorRuntime !== null) {
|
|
583
614
|
const { stripAnnotations } = require('./schema-strip');
|
|
@@ -649,9 +680,33 @@ function toStandaloneModule(validator, opts) {
|
|
|
649
680
|
// safe-regex prelude, which has a module-scope parse() of its own for
|
|
650
681
|
// reading patterns. A second declaration of that name shadowed it and the
|
|
651
682
|
// prelude called this one instead, reaching _fn before its initialiser ran.
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
683
|
+
// With a pass before the check (the validator's coerceTypes or
|
|
684
|
+
// removeAdditional, and its defaults unless useDefaults is off), the
|
|
685
|
+
// runtime's parse() takes its verdict on a filled copy of the input and
|
|
686
|
+
// checks the copy it hands back too (buildParse in lib/codegen-engine.js).
|
|
687
|
+
// Checked on the raw input, a default that makes a conditional apply was
|
|
688
|
+
// missed and parse() returned what validate() refuses after the fill.
|
|
689
|
+
const fillDecl = defaultsDecl(validator);
|
|
690
|
+
const fills = !!fillDecl && vopts.useDefaults !== false;
|
|
691
|
+
// The defaults pass tests patternProperties keys on the linear engine
|
|
692
|
+
// where validation does; the module then carries the engine.
|
|
693
|
+
const fillSafeRe = fillDecl.includes('__ataSafeRe(');
|
|
694
|
+
if (fillSafeRe && errorRuntime !== null) usesSafeRe = true;
|
|
695
|
+
const parseCore = !cloneExpr
|
|
696
|
+
? ''
|
|
697
|
+
: !fills && pre.decl === ''
|
|
698
|
+
? `function _ataParse(data) {\n if (!_fn(data)) { const e = new Error('validation failed'); e.name = 'AtaValidationError'; throw e; }\n return ${cloneExpr};\n}\n`
|
|
699
|
+
: `function _ataParse(data) {
|
|
700
|
+
let doc;
|
|
701
|
+
try { doc = structuredClone(data); } catch (_) { doc = undefined; }
|
|
702
|
+
if (doc === undefined && data !== undefined) throw new TypeError('parse() is not available for this value: it cannot be copied to fill its defaults before the check. Use validate() instead.');
|
|
703
|
+
if (doc !== null && typeof doc === 'object') { ${pre.decl !== '' ? '_ataPre(doc); ' : ''}${fills ? 'applyDefaults(doc); ' : ''}}
|
|
704
|
+
if (!_fn(doc)) { const e = new Error('validation failed'); e.name = 'AtaValidationError'; throw e; }
|
|
705
|
+
const out = ${cloneExpr};
|
|
706
|
+
if (!_fn(out)) throw new TypeError('parse() is not available for this value: the copy that keeps the declared properties is not one the schema accepts. Use validate() instead.');
|
|
707
|
+
return out;
|
|
708
|
+
}
|
|
709
|
+
`;
|
|
655
710
|
|
|
656
711
|
// validateJSON(): parse the text, validate, and on failure attach a
|
|
657
712
|
// dataFrame (byte offset, line, col, source line) to every error by
|
|
@@ -666,7 +721,7 @@ function toStandaloneModule(validator, opts) {
|
|
|
666
721
|
// the worst case of a pointer at the very end. It is also 125 characters
|
|
667
722
|
// smaller than the full-map builder it replaces.
|
|
668
723
|
const positionsCore = !(opts && opts.positions) ? '' : `const _ataPosMap = ${buildTargetedPositionMap.toString()};
|
|
669
|
-
function _ataFrame(p) {
|
|
724
|
+
function _ataFrame(p) { const f = { byteOffset: p.byteOffset, length: p.length, line: p.line, col: p.col, text: p.text }; if (p.textCol !== undefined) f.textCol = p.textCol; return f; }
|
|
670
725
|
function validateJSON(text) {
|
|
671
726
|
const s = String(text);
|
|
672
727
|
let data;
|
|
@@ -705,7 +760,6 @@ function validateJSON(text) {
|
|
|
705
760
|
const hashDecl = `const schemaHash = ${JSON.stringify(hashSrc)};\nconst ataVersion = ${JSON.stringify(ATA_VERSION)};\n`;
|
|
706
761
|
|
|
707
762
|
let names = fmt.exportsSetFormats ? baseNames + ', setFormats' : baseNames;
|
|
708
|
-
const fillDecl = defaultsDecl(validator);
|
|
709
763
|
if (fillDecl) names += ', applyDefaults';
|
|
710
764
|
names += ', schemaHash, ataVersion';
|
|
711
765
|
if (positionsCore) names += ', validateJSON';
|
|
@@ -726,7 +780,7 @@ ${errorRuntime === null ? '// Schema is embedded; runtime has zero dependency on
|
|
|
726
780
|
// Re-run the build after upgrading ata: the version above is what wrote this.
|
|
727
781
|
${degradedNote}'use strict';
|
|
728
782
|
${errorRuntime === null ? '' : format === 'esm' ? `import { createErrors as _ataCreateErrors${usesSafeRe ? ', compileSafe as _ataCompileSafe' : ''}${usesUriWalk ? ', uriWalk as _ataUriWalk' : ''} } from ${JSON.stringify(errorRuntime)};\n` : `const { createErrors: _ataCreateErrors${usesSafeRe ? ', compileSafe: _ataCompileSafe' : ''}${usesUriWalk ? ', uriWalk: _ataUriWalk' : ''} } = require(${JSON.stringify(errorRuntime)});\n`}${_CP_LEN_SOURCE}
|
|
729
|
-
${errorRuntime !== null ? (usesSafeRe ? 'const __ataSafeRe = _ataCompileSafe;\n' : '') : safeRePrelude(jsFn, jsErrFn, onePass !== null && onePass.usesSafeRe ? { _usesSafeRe: true } : null)}${schemaSourceConst}const VALID = Object.freeze({ valid: true, errors: Object.freeze([]) });
|
|
783
|
+
${errorRuntime !== null ? (usesSafeRe ? 'const __ataSafeRe = _ataCompileSafe;\n' : '') : safeRePrelude(jsFn, jsErrFn, (onePass !== null && onePass.usesSafeRe) || fillSafeRe ? { _usesSafeRe: true } : null)}${schemaSourceConst}const VALID = Object.freeze({ valid: true, errors: Object.freeze([]) });
|
|
730
784
|
const ABORT = Object.freeze({
|
|
731
785
|
valid: false,
|
|
732
786
|
errors: Object.freeze([Object.freeze({
|
|
@@ -885,7 +939,10 @@ function toSchemaModule(validator, opts) {
|
|
|
885
939
|
? `import { createValidator as _ataCreateValidator } from ${spec};\n`
|
|
886
940
|
: `const { createValidator: _ataCreateValidator } = require(${spec});\n`)
|
|
887
941
|
+ (regSpec === null ? '' : format === 'esm' ? `import _ataRegistry from ${regSpec};\n` : `const _ataRegistry = require(${regSpec});\n`);
|
|
888
|
-
|
|
942
|
+
let fillDecl = defaultsDecl(validator);
|
|
943
|
+
// No verdict code here to carry the linear engine; the defaults pass
|
|
944
|
+
// brings its own when it tests keys on it.
|
|
945
|
+
if (fillDecl.includes('__ataSafeRe(')) fillDecl = getSafeRegexEmbed() + '\n' + fillDecl;
|
|
889
946
|
const expNames = 'validate, isValid' + (fillDecl ? ', applyDefaults' : '') + ', schemaHash, ataVersion';
|
|
890
947
|
const exp = format === 'esm'
|
|
891
948
|
? `export { ${expNames} };\nexport default { ${expNames} };\n`
|
|
@@ -1035,6 +1092,7 @@ function bundleStandalone(Validator, schemas, opts) {
|
|
|
1035
1092
|
// The runtime's passes over the data, for the options asked for, and its
|
|
1036
1093
|
// defaults pass as the validator's applyDefaults.
|
|
1037
1094
|
const fillDecl = defaultsDecl(v);
|
|
1095
|
+
if (fillDecl.includes('__ataSafeRe(')) bundleUsesSafeRe = true;
|
|
1038
1096
|
const pre = bundlePreprocess(v, opts, fillDecl, 'bundleStandalone');
|
|
1039
1097
|
preamble = preamble ? `${preamble}\n${fillDecl}${pre.decl}` : fillDecl + pre.decl;
|
|
1040
1098
|
const ret = (body) => fillDecl
|
|
@@ -1188,13 +1246,21 @@ function bundleCompact(Validator, schemas, opts) {
|
|
|
1188
1246
|
// User format functions are referenced as _uf_<name> by the hybrid and error
|
|
1189
1247
|
// bodies. Collect them across all schemas (deduped by name) and embed via
|
|
1190
1248
|
// Function#toString so the bundle stays self-contained.
|
|
1191
|
-
|
|
1249
|
+
// One declaration per helper name. The name is one to one with the
|
|
1250
|
+
// format name, so two entries under one name are the same format; were
|
|
1251
|
+
// they bound to different functions the bundle could not serve both, so
|
|
1252
|
+
// that is refused rather than letting one schema run another's checker.
|
|
1253
|
+
const fmtSeen = new Map();
|
|
1192
1254
|
const fmtAll = [];
|
|
1193
1255
|
for (const e of entries) {
|
|
1194
1256
|
if (!e || !e.fmt) continue;
|
|
1195
1257
|
for (const entry of e.fmt) {
|
|
1196
|
-
|
|
1197
|
-
|
|
1258
|
+
const prev = fmtSeen.get(entry.name);
|
|
1259
|
+
if (prev !== undefined) {
|
|
1260
|
+
if (prev !== entry.fn) throw new Error(`ata: custom format "${entry.format === null ? formatFromName(entry.name) : entry.format}" is bound to two different functions in this bundle`);
|
|
1261
|
+
continue;
|
|
1262
|
+
}
|
|
1263
|
+
fmtSeen.set(entry.name, entry.fn);
|
|
1198
1264
|
fmtAll.push(entry);
|
|
1199
1265
|
}
|
|
1200
1266
|
}
|
|
@@ -1255,9 +1321,24 @@ function loadBundle(Validator, mods, schemas, opts) {
|
|
|
1255
1321
|
// do not write but a pattern could carry, has its backslash dropped, since
|
|
1256
1322
|
// `\x3C` already is the escape. A module with neither sequence, which is
|
|
1257
1323
|
// nearly every one, comes back as the same string.
|
|
1324
|
+
// The match is the `<` and what follows; the backslashes before it are
|
|
1325
|
+
// counted from the match offset. Matching them as part of the pattern made
|
|
1326
|
+
// the scan re-read a run of backslashes at every `<` after it, so an enum
|
|
1327
|
+
// value of 40000 backslashes and `<a` took 6.5 s. One pass, whatever the run.
|
|
1258
1328
|
function htmlSafe(src) {
|
|
1259
1329
|
if (typeof src !== 'string' || src.indexOf('<') === -1) return src;
|
|
1260
|
-
|
|
1330
|
+
let out = '';
|
|
1331
|
+
let last = 0;
|
|
1332
|
+
const re = /<(\/script|!--)/gi;
|
|
1333
|
+
let m;
|
|
1334
|
+
while ((m = re.exec(src)) !== null) {
|
|
1335
|
+
const at = m.index;
|
|
1336
|
+
let bs = 0;
|
|
1337
|
+
while (at - bs - 1 >= last && src.charCodeAt(at - bs - 1) === 92) bs++;
|
|
1338
|
+
out += src.slice(last, at - (bs % 2)) + '\\x3C' + m[1];
|
|
1339
|
+
last = at + m[0].length;
|
|
1340
|
+
}
|
|
1341
|
+
return last === 0 ? src : out + src.slice(last);
|
|
1261
1342
|
}
|
|
1262
1343
|
const htmlSafeResult = (fn) => function () { return htmlSafe(fn.apply(this, arguments)); };
|
|
1263
1344
|
|
|
@@ -1278,6 +1359,16 @@ module.exports = {
|
|
|
1278
1359
|
if (!Array.isArray(schemas)) return er.errorRuntimeSource(format, ATA_VERSION, undefined, !!schemaMode);
|
|
1279
1360
|
const { stripAnnotations } = require('./schema-strip');
|
|
1280
1361
|
const embedded = schemas.map((x) => stripAnnotations(x && typeof x === 'object' && x._schemaObj !== undefined ? x._schemaObj : x));
|
|
1362
|
+
// A module reaches the registered schemas (`schemas` option) through
|
|
1363
|
+
// $ref, so what they use the runtime must carry too. Read from the
|
|
1364
|
+
// validator's map, since the validator may not have compiled yet: a
|
|
1365
|
+
// pattern that lived only in a registered schema was missed, the runtime
|
|
1366
|
+
// went out without the pattern engine, and the module matched on the
|
|
1367
|
+
// platform RegExp.
|
|
1368
|
+
for (const x of schemas) {
|
|
1369
|
+
if (!x || typeof x !== 'object' || !(x._schemaMap instanceof Map)) continue;
|
|
1370
|
+
for (const ext of x._schemaMap.values()) if (ext && typeof ext === 'object') embedded.push(stripAnnotations(ext));
|
|
1371
|
+
}
|
|
1281
1372
|
const features = er.runtimeFeatures(embedded);
|
|
1282
1373
|
for (const x of schemas) if (x && x._jsFn && x._jsFn._usesSafeRe) features['safe-regex'] = true;
|
|
1283
1374
|
return er.errorRuntimeSource(format, ATA_VERSION, features, !!schemaMode);
|
package/lib/buffer-gate.js
CHANGED
|
@@ -24,11 +24,16 @@ const UNSUPPORTED_KEYWORDS = new Set([
|
|
|
24
24
|
'contains', 'minContains', 'maxContains',
|
|
25
25
|
'unevaluatedProperties', 'unevaluatedItems',
|
|
26
26
|
'dependencies', 'dependentSchemas', 'dependentRequired',
|
|
27
|
-
'propertyNames', 'patternProperties',
|
|
27
|
+
'propertyNames', 'patternProperties', 'propertyDependencies',
|
|
28
28
|
]);
|
|
29
29
|
|
|
30
|
-
// Formats the native
|
|
31
|
-
|
|
30
|
+
// Formats the native engine checks by the same rules as lib/formats.js,
|
|
31
|
+
// held to the same answers by tests/test_native_formats.js. Every other
|
|
32
|
+
// format name goes through validate(): the native engine has no check for
|
|
33
|
+
// it and would let any string pass. This was a list of the divergent ones,
|
|
34
|
+
// which missed every format the engine does not know (json-pointer, iri,
|
|
35
|
+
// uri-template, regex and the rest) and a caller's own formats.
|
|
36
|
+
const NATIVE_FORMATS = new Set(['email', 'date', 'uri', 'uuid', 'ipv4', 'ipv6', 'hostname']);
|
|
32
37
|
|
|
33
38
|
const SUBSCHEMA_MAPS = ['properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas', 'dependencies'];
|
|
34
39
|
const SUBSCHEMA_NODES = [
|
|
@@ -92,7 +97,7 @@ function refersBack(root) {
|
|
|
92
97
|
return visit(root);
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
function walk(schema, depth) {
|
|
100
|
+
function walk(schema, depth, userFormats) {
|
|
96
101
|
if (depth > NATIVE_DEPTH) return true;
|
|
97
102
|
if (schema === true || schema === false) {
|
|
98
103
|
// A boolean root is answered wrong by the walker; nested booleans are
|
|
@@ -101,7 +106,7 @@ function walk(schema, depth) {
|
|
|
101
106
|
}
|
|
102
107
|
if (schema === null || typeof schema !== 'object') return false;
|
|
103
108
|
if (Array.isArray(schema)) {
|
|
104
|
-
for (const s of schema) if (walk(s, depth + 1)) return true;
|
|
109
|
+
for (const s of schema) if (walk(s, depth + 1, userFormats)) return true;
|
|
105
110
|
return false;
|
|
106
111
|
}
|
|
107
112
|
for (const key of Object.keys(schema)) {
|
|
@@ -114,7 +119,7 @@ function walk(schema, depth) {
|
|
|
114
119
|
if (key === '$id' && depth > 0) return true;
|
|
115
120
|
// An empty enum rejects everything; the walker accepts everything.
|
|
116
121
|
if (key === 'enum' && Array.isArray(v) && v.length === 0) return true;
|
|
117
|
-
if (key === 'format' &&
|
|
122
|
+
if (key === 'format' && (!NATIVE_FORMATS.has(v) || (userFormats && Object.prototype.hasOwnProperty.call(userFormats, v)))) return true;
|
|
118
123
|
// Unicode property escapes: RE2 cannot parse them and the walker then
|
|
119
124
|
// skips the pattern instead of failing.
|
|
120
125
|
if (key === 'pattern' && typeof v === 'string' && /\\[pP]\{/.test(v)) return true;
|
|
@@ -126,10 +131,10 @@ function walk(schema, depth) {
|
|
|
126
131
|
|
|
127
132
|
if (SUBSCHEMA_MAPS.includes(key)) {
|
|
128
133
|
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
129
|
-
for (const k of Object.keys(v)) if (walk(v[k], depth + 1)) return true;
|
|
134
|
+
for (const k of Object.keys(v)) if (walk(v[k], depth + 1, userFormats)) return true;
|
|
130
135
|
}
|
|
131
136
|
} else if (SUBSCHEMA_NODES.includes(key)) {
|
|
132
|
-
if (walk(v, depth + 1)) return true;
|
|
137
|
+
if (walk(v, depth + 1, userFormats)) return true;
|
|
133
138
|
}
|
|
134
139
|
}
|
|
135
140
|
return false;
|
|
@@ -138,13 +143,15 @@ function walk(schema, depth) {
|
|
|
138
143
|
// True when the buffer APIs must answer through validate() for this schema.
|
|
139
144
|
// `keywords` is the validator's normalized custom keyword map, if any: the
|
|
140
145
|
// native walker knows nothing about those, so a schema using one is routed
|
|
141
|
-
// through validate() like the other shapes here.
|
|
142
|
-
|
|
146
|
+
// through validate() like the other shapes here. `userFormats` is the
|
|
147
|
+
// validator's `formats` option: a format it defines, even under a built-in
|
|
148
|
+
// name, is a check only the JS engines run.
|
|
149
|
+
function bufferNeedsSlowPath(schema, schemaMap, keywords, userFormats) {
|
|
143
150
|
if (keywords && schemaUsesKeywords(schema, keywords)) return true;
|
|
144
|
-
if (walk(schema, 0)) return true;
|
|
151
|
+
if (walk(schema, 0, userFormats)) return true;
|
|
145
152
|
if (refersBack(schema)) return true;
|
|
146
153
|
if (schemaMap && schemaMap.size > 0) {
|
|
147
|
-
for (const s of schemaMap.values()) if (walk(s, 1)) return true;
|
|
154
|
+
for (const s of schemaMap.values()) if (walk(s, 1, userFormats)) return true;
|
|
148
155
|
}
|
|
149
156
|
return false;
|
|
150
157
|
}
|
|
@@ -163,8 +170,12 @@ function toText(input, name) {
|
|
|
163
170
|
function installSlowBufferApis(validator) {
|
|
164
171
|
const isValidText = (text) => validator.isValidJSON(text);
|
|
165
172
|
validator.isValid = (input) => isValidText(toText(input, 'isValid'));
|
|
166
|
-
|
|
167
|
-
|
|
173
|
+
// The length is the caller's, so it is held to the buffer: a view past its
|
|
174
|
+
// end read the bytes of whatever shared the pool, and answered on them.
|
|
175
|
+
validator.isValidPrepadded = (paddedBuffer, jsonLength) => {
|
|
176
|
+
if (!(paddedBuffer instanceof Uint8Array) || !Number.isInteger(jsonLength) || jsonLength < 0 || jsonLength > paddedBuffer.byteLength) return false;
|
|
177
|
+
return isValidText(Buffer.from(paddedBuffer.buffer, paddedBuffer.byteOffset, jsonLength).toString('utf8'));
|
|
178
|
+
};
|
|
168
179
|
const ndjson = (input, name) => {
|
|
169
180
|
const lines = toText(input, name).split('\n');
|
|
170
181
|
const out = [];
|
package/lib/bun-target.js
CHANGED
|
@@ -79,15 +79,20 @@ function empty () { return { string: [], number: [], boolean: [], null: [], obje
|
|
|
79
79
|
function universal () { const t = empty(); for (const k of KINDS) t[k].push(UNIVERSAL[k]); return t }
|
|
80
80
|
function single (kind, alt) { const t = empty(); t[kind].push(alt); return t }
|
|
81
81
|
function union (a, b) { const t = empty(); for (const k of [...KINDS, 'alias']) t[k] = dedupe([...a[k], ...b[k]]); return t }
|
|
82
|
-
function dedupe (xs) { return xs.
|
|
82
|
+
function dedupe (xs) { return xs.length < 2 ? xs : [...new Set(xs)] }
|
|
83
83
|
function isUniversal (t) { return t.alias.length === 0 && KINDS.every((k) => t[k].length === 1 && t[k][0] === UNIVERSAL[k]) }
|
|
84
84
|
|
|
85
85
|
// Intersection, or null where the two cannot be written as one type; the
|
|
86
|
-
// caller then keeps one side in the residual.
|
|
86
|
+
// caller then keeps one side in the residual. The product of two unions
|
|
87
|
+
// distributes, so allOf over n branches of two shapes each is 2^n
|
|
88
|
+
// alternatives: 14 branches took 2.3 s and 16 did not finish. Past this many
|
|
89
|
+
// alternatives for one kind the branch stays in the residual.
|
|
90
|
+
const MAX_ALTERNATIVES = 256
|
|
87
91
|
function intersect (a, b) {
|
|
88
92
|
if (a.alias.length || b.alias.length) return null
|
|
89
93
|
const t = empty()
|
|
90
94
|
for (const k of KINDS) {
|
|
95
|
+
if (a[k].length * b[k].length > MAX_ALTERNATIVES) return null
|
|
91
96
|
for (const x of a[k]) {
|
|
92
97
|
for (const y of b[k]) {
|
|
93
98
|
const z = meet(k, x, y)
|
|
@@ -548,17 +553,28 @@ function schemaToBunType (root, opts = {}) {
|
|
|
548
553
|
// the type, and the module's functions with the type on them. `parse`
|
|
549
554
|
// declares the type where it parses, which is what the compiler reads to make
|
|
550
555
|
// the objects with that layout; the module then checks the content.
|
|
556
|
+
// A string as a JavaScript literal with every line terminator escaped.
|
|
557
|
+
// JSON.stringify leaves U+2028 and U+2029 as they are, and inside a `//`
|
|
558
|
+
// comment those end the comment the way a line feed does.
|
|
559
|
+
function jsString (s) {
|
|
560
|
+
return JSON.stringify(String(s)).replace(/[\u2028\u2029]/g, (c) => '\\u' + c.charCodeAt(0).toString(16))
|
|
561
|
+
}
|
|
562
|
+
// The same without the quotes, for text that goes into a // comment: a file
|
|
563
|
+
// name holding a line break ended the comment and the rest of the name was
|
|
564
|
+
// source.
|
|
565
|
+
function commentText (s) { return jsString(s).slice(1, -1) }
|
|
566
|
+
|
|
551
567
|
function toBunModule ({ schemaFile, moduleImport, typeName: tn, typeSource, aliases }) {
|
|
552
568
|
const lines = []
|
|
553
|
-
lines.push(`// Generated by ata build --target bun from ${schemaFile}. Do not edit.`)
|
|
569
|
+
lines.push(`// Generated by ata build --target bun from ${commentText(schemaFile)}. Do not edit.`)
|
|
554
570
|
lines.push('//')
|
|
555
571
|
lines.push('// For executables built with `bun build --compile --aot`: the schema as a type')
|
|
556
|
-
lines.push(`// for the program, with the plain module (${moduleImport}) answering every`)
|
|
572
|
+
lines.push(`// for the program, with the plain module (${commentText(moduleImport)}) answering every`)
|
|
557
573
|
lines.push('// check, so the answers and the errors are the module\'s. `parse(text)` declares')
|
|
558
574
|
lines.push('// the type where it parses and the module checks the content; it is not faster')
|
|
559
575
|
lines.push('// than a plain parse and the module. Everywhere else, including plain `bun run`,')
|
|
560
576
|
lines.push('// the plain module does the same without this file.')
|
|
561
|
-
lines.push(`import * as _m from ${
|
|
577
|
+
lines.push(`import * as _m from ${jsString(moduleImport)}`)
|
|
562
578
|
lines.push('')
|
|
563
579
|
for (const [n, src] of aliases) lines.push(`type ${n} = ${src}`)
|
|
564
580
|
lines.push(`export type ${tn} = ${typeSource}`)
|