ata-validator 1.12.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/aot.d.ts +37 -0
- package/aot.js +7 -0
- package/aot.mjs +3 -0
- package/index.js +5 -5
- package/lib/aot-impl.js +470 -0
- package/lib/aot.browser.js +33 -0
- package/lib/aot.js +6 -468
- package/lib/buffer-gate.browser.js +13 -0
- package/lib/version.js +1 -1
- package/package.json +19 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to ata-validator are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/), and this project adheres to semantic versioning.
|
|
4
4
|
|
|
5
|
+
## 1.13.0 - 2026-09-06
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- The default browser bundle stops carrying the AOT emitters. `lib/aot.js` has promised a browser stub via the package.json `browser` field since the fs-free build, but the mapping was never actually in the field, so every browser bundle shipped the emitters and the embedded safe-regex engine source they pull in, along with the native walker's routing table that only matters when the native addon loads. Both now map to stubs. Emitting validators in a browser still works, through the new `ata-validator/aot` entry, and the `Validator.bundle*` statics in a browser bundle throw a message that points there; `Validator.loadBundle` keeps working in the default bundle, since loading a bundle somebody already built is a runtime act. Measured with the schema-benchmarks rolldown pipeline: minified and gzipped 73,491 to 64,812 bytes, 11.8 percent smaller. The browser-imports guard now bans a token from each excluded file so the mapping cannot regress silently.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `ata-validator/aot`: the standalone emitters (`toStandalone`, `toStandaloneModule`, `bundle`, `bundleStandalone`, `bundleCompact`, `loadBundle`) as an explicit, typed import. In Node it is the same module the statics use; in a browser it is the way to opt back into emission without paying for it on pages that never emit.
|
|
14
|
+
|
|
5
15
|
## 1.12.1 - 2026-09-06
|
|
6
16
|
|
|
7
17
|
### Fixed
|
package/README.md
CHANGED
|
@@ -358,7 +358,7 @@ const v = new Validator(schema, {
|
|
|
358
358
|
|
|
359
359
|
### Build-time compile (`ata compile`)
|
|
360
360
|
|
|
361
|
-
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. Typical output is ~1 KB gzipped compared to ~
|
|
361
|
+
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. Typical output is ~1 KB gzipped compared to ~65 KB for the full runtime bundled for the browser.
|
|
362
362
|
|
|
363
363
|
```bash
|
|
364
364
|
npx ata compile schemas/user.json -o src/generated/user.validator.mjs
|
package/aot.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Validator, JSONSchema } from './index';
|
|
2
|
+
|
|
3
|
+
/** Options accepted by the standalone emitters. See docs/aot.md. */
|
|
4
|
+
export interface StandaloneOptions {
|
|
5
|
+
/** Module format for the emitted source. Default: 'esm'. */
|
|
6
|
+
format?: 'esm' | 'cjs';
|
|
7
|
+
/** Use stub error functions for the smallest output. Default: false. */
|
|
8
|
+
abortEarly?: boolean;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The constructor of Validator, as the emitters take it explicitly. */
|
|
13
|
+
export type ValidatorClass = typeof Validator;
|
|
14
|
+
|
|
15
|
+
/** Emit one standalone module's source for one validator, or null when the
|
|
16
|
+
* schema compiled through an engine that has no source form. */
|
|
17
|
+
export function toStandalone(validator: Validator<unknown>): string | null;
|
|
18
|
+
|
|
19
|
+
/** Emit one standalone module (no runtime dependency) for one validator. */
|
|
20
|
+
export function toStandaloneModule(validator: Validator<unknown>, opts?: StandaloneOptions): string;
|
|
21
|
+
|
|
22
|
+
/** Emit a module of per-schema compiled validators that `loadBundle` consumes. */
|
|
23
|
+
export function bundle(V: ValidatorClass, schemas: Array<JSONSchema | boolean>, opts?: StandaloneOptions): string;
|
|
24
|
+
|
|
25
|
+
/** Emit one self-contained module for many schemas, zero runtime dependency. */
|
|
26
|
+
export function bundleStandalone(V: ValidatorClass, schemas: Array<JSONSchema | boolean>, opts?: StandaloneOptions): string;
|
|
27
|
+
|
|
28
|
+
/** Like bundleStandalone with shared helpers deduplicated across schemas. */
|
|
29
|
+
export function bundleCompact(V: ValidatorClass, schemas: Array<JSONSchema | boolean>, opts?: StandaloneOptions): string;
|
|
30
|
+
|
|
31
|
+
/** Turn the modules a bundle produced back into Validator instances. */
|
|
32
|
+
export function loadBundle(
|
|
33
|
+
V: ValidatorClass,
|
|
34
|
+
mods: Array<unknown>,
|
|
35
|
+
schemas: Array<JSONSchema | boolean>,
|
|
36
|
+
opts?: object,
|
|
37
|
+
): Array<Validator<unknown>>;
|
package/aot.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Explicit entry for the AOT emitters, `require('ata-validator/aot')`. In
|
|
4
|
+
// Node this is the same module `Validator.bundleStandalone` and friends use.
|
|
5
|
+
// In a browser bundle it is the only way to get the emitters: the default
|
|
6
|
+
// entry maps them to a stub so pages that never emit code never carry it.
|
|
7
|
+
module.exports = require('./lib/aot-impl.js');
|
package/aot.mjs
ADDED
package/index.js
CHANGED
|
@@ -1602,7 +1602,7 @@ class Validator {
|
|
|
1602
1602
|
// validate() on shapes listed in lib/buffer-gate.js. For those schemas
|
|
1603
1603
|
// every buffer entry point goes through validate() instead.
|
|
1604
1604
|
if (native) {
|
|
1605
|
-
const { bufferNeedsSlowPath, installSlowBufferApis } = require('./lib/buffer-gate');
|
|
1605
|
+
const { bufferNeedsSlowPath, installSlowBufferApis } = require('./lib/buffer-gate.js');
|
|
1606
1606
|
if (bufferNeedsSlowPath(schemaObj, this._schemaMap)) installSlowBufferApis(this);
|
|
1607
1607
|
}
|
|
1608
1608
|
|
|
@@ -1925,19 +1925,19 @@ function version() {
|
|
|
1925
1925
|
// generated module shapes; browser bundles get `lib/aot.browser.js` (a stub
|
|
1926
1926
|
// that throws) via the package.json `browser` field.
|
|
1927
1927
|
Validator.bundle = function (schemas, opts) {
|
|
1928
|
-
return require('./lib/aot').bundle(Validator, schemas, opts);
|
|
1928
|
+
return require('./lib/aot.js').bundle(Validator, schemas, opts);
|
|
1929
1929
|
};
|
|
1930
1930
|
|
|
1931
1931
|
Validator.bundleStandalone = function (schemas, opts) {
|
|
1932
|
-
return require('./lib/aot').bundleStandalone(Validator, schemas, opts);
|
|
1932
|
+
return require('./lib/aot.js').bundleStandalone(Validator, schemas, opts);
|
|
1933
1933
|
};
|
|
1934
1934
|
|
|
1935
1935
|
Validator.bundleCompact = function (schemas, opts) {
|
|
1936
|
-
return require('./lib/aot').bundleCompact(Validator, schemas, opts);
|
|
1936
|
+
return require('./lib/aot.js').bundleCompact(Validator, schemas, opts);
|
|
1937
1937
|
};
|
|
1938
1938
|
|
|
1939
1939
|
Validator.loadBundle = function (mods, schemas, opts) {
|
|
1940
|
-
return require('./lib/aot').loadBundle(Validator, mods, schemas, opts);
|
|
1940
|
+
return require('./lib/aot.js').loadBundle(Validator, mods, schemas, opts);
|
|
1941
1941
|
};
|
|
1942
1942
|
|
|
1943
1943
|
const parseJSON = native ? native.parseJSON : JSON.parse;
|
package/lib/aot-impl.js
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// All ahead-of-time (AOT) code generation lives here so the default and browser
|
|
4
|
+
// entries can stay free of `fs`, `path`, and `__dirname`.
|
|
5
|
+
// Browser bundles get `lib/aot.browser.js` instead (see package.json `browser`
|
|
6
|
+
// field), which throws if anyone tries to call an AOT function from the browser.
|
|
7
|
+
//
|
|
8
|
+
// Public surface (called by index.js and ata-validator/build):
|
|
9
|
+
// toStandalone(validator)
|
|
10
|
+
// toStandaloneModule(validator, opts)
|
|
11
|
+
// bundle(Validator, schemas, opts)
|
|
12
|
+
// bundleStandalone(Validator, schemas, opts)
|
|
13
|
+
// bundleCompact(Validator, schemas, opts)
|
|
14
|
+
// loadBundle(Validator, mods, schemas, opts)
|
|
15
|
+
|
|
16
|
+
const { compileToJSCodegenWithErrors } = require('./js-compiler');
|
|
17
|
+
const SAFE_REGEX_SOURCE = require('./safe-regex-source');
|
|
18
|
+
|
|
19
|
+
// Embedded verbatim in standalone modules so the output file has no runtime
|
|
20
|
+
// dependency on ata-validator. ASCII fast-path plus surrogate-aware slow path.
|
|
21
|
+
const _CP_LEN_SOURCE = `function _cpLen(s) {
|
|
22
|
+
const len = s.length;
|
|
23
|
+
for (let i = 0; i < len; i++) {
|
|
24
|
+
if (((s.charCodeAt(i) - 0xD800) >>> 0) < 0x400) {
|
|
25
|
+
let n = 0; for (const _ of s) n++; return n;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return len;
|
|
29
|
+
}`;
|
|
30
|
+
|
|
31
|
+
// The linear-time regex engine, inlined verbatim into standalone output so a
|
|
32
|
+
// compiled module that uses safe `pattern` matchers has no runtime dependency
|
|
33
|
+
// on ata-validator. The engine source is baked into `lib/safe-regex-source.js`
|
|
34
|
+
// at build time (see `scripts/regen-safe-regex-source.js`), so this path has
|
|
35
|
+
// no `fs`/`path`/`__dirname` reads — safe in browser bundles too. The embed
|
|
36
|
+
// strips the strict directive and CommonJS exports and adds the `__ataSafeRe`
|
|
37
|
+
// alias the emitted code calls. The engine has no eval/new Function, so the
|
|
38
|
+
// embed is CSP-safe.
|
|
39
|
+
let _safeRegexEmbed = null;
|
|
40
|
+
function getSafeRegexEmbed() {
|
|
41
|
+
if (_safeRegexEmbed === null) {
|
|
42
|
+
const body = SAFE_REGEX_SOURCE
|
|
43
|
+
.replace(/^'use strict'\s*\n/, '')
|
|
44
|
+
.replace(/\nmodule\.exports[^\n]*\n?/, '\n');
|
|
45
|
+
_safeRegexEmbed = body.trimEnd() + '\nconst __ataSafeRe = compileSafe;';
|
|
46
|
+
}
|
|
47
|
+
return _safeRegexEmbed;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Returns the engine embed when any supplied compiled function references the
|
|
51
|
+
// safe matcher (jsFn._usesSafeRe), else an empty string so non-pattern modules
|
|
52
|
+
// pay zero bytes.
|
|
53
|
+
function safeRePrelude(...fns) {
|
|
54
|
+
return fns.some((f) => f && f._usesSafeRe) ? getSafeRegexEmbed() + '\n' : '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// --- Standalone pre-compilation ---
|
|
58
|
+
// Generate a JS module string that can be written to a file.
|
|
59
|
+
// On next startup, load with Validator.fromStandalone() -- zero compile time.
|
|
60
|
+
function toStandalone(validator) {
|
|
61
|
+
validator._ensureCompiled();
|
|
62
|
+
const jsFn = validator._jsFn;
|
|
63
|
+
if (!jsFn || !jsFn._source) return null;
|
|
64
|
+
const src = jsFn._source;
|
|
65
|
+
const hybridSrc = jsFn._hybridSource || '';
|
|
66
|
+
const preambleSrc = jsFn._preambleSource || '';
|
|
67
|
+
|
|
68
|
+
// Also capture error function source for zero-compile standalone load
|
|
69
|
+
const jsErrFn = compileToJSCodegenWithErrors(
|
|
70
|
+
typeof validator._schemaObj === 'object' ? validator._schemaObj : {},
|
|
71
|
+
);
|
|
72
|
+
const errSrc = jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : '';
|
|
73
|
+
|
|
74
|
+
return `// Auto-generated by ata-validator — do not edit
|
|
75
|
+
'use strict';
|
|
76
|
+
${_CP_LEN_SOURCE}
|
|
77
|
+
${safeRePrelude(jsFn, jsErrFn)}${preambleSrc}
|
|
78
|
+
const boolFn = function(d) {
|
|
79
|
+
${src}
|
|
80
|
+
};
|
|
81
|
+
const hybridFactory = function(R, E) {
|
|
82
|
+
return function(d) {
|
|
83
|
+
${hybridSrc}
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
${errSrc ? `const errFn = function(d, _all) {\n ${errSrc}\n};` : 'const errFn = null;'}
|
|
87
|
+
module.exports = { boolFn, hybridFactory, errFn };
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// --- Fully standalone module ---
|
|
92
|
+
// Generates a self-contained module that can be imported directly without
|
|
93
|
+
// pulling in ata-validator at runtime. Browser bundle gets only the
|
|
94
|
+
// generated validator (~2 KB typical) instead of the 165 KB compiler.
|
|
95
|
+
//
|
|
96
|
+
// import { validate, isValid } from './user-validator.mjs'
|
|
97
|
+
// if (isValid(data)) { ... }
|
|
98
|
+
//
|
|
99
|
+
// format: 'esm' | 'cjs'. Default 'esm'.
|
|
100
|
+
// abortEarly: if true, invalid result is a shared stub; smaller output.
|
|
101
|
+
|
|
102
|
+
// Custom format functions in standalone output.
|
|
103
|
+
//
|
|
104
|
+
// 'embed' (default) writes each function's source into the module through
|
|
105
|
+
// Function#toString. That only works for a plain function: one that closes
|
|
106
|
+
// over nothing and is not rewritten by a coverage or transpile step (istanbul
|
|
107
|
+
// injects `cov_` counters; a bundler may hoist helpers). The check below
|
|
108
|
+
// rejects those at build time with a named error instead of emitting a module
|
|
109
|
+
// that throws on first use.
|
|
110
|
+
//
|
|
111
|
+
// 'inject' writes no function source. The module exports `setFormats(map)`
|
|
112
|
+
// and each format is looked up from that registry when a value is checked,
|
|
113
|
+
// so the caller supplies the functions at load time.
|
|
114
|
+
function emitFormatDecls(closures, mode, declKW) {
|
|
115
|
+
if (!closures || closures.length === 0) return { decls: '', exportsSetFormats: false };
|
|
116
|
+
if (mode === 'inject') {
|
|
117
|
+
let out = `${declKW} __formats = Object.create(null);\n`;
|
|
118
|
+
out += `function setFormats(map) { for (const k in map) __formats[k] = map[k]; }\n`;
|
|
119
|
+
for (const { name, format } of closures) {
|
|
120
|
+
const key = JSON.stringify(format === null ? name.slice(4) : format);
|
|
121
|
+
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`;
|
|
122
|
+
}
|
|
123
|
+
return { decls: out, exportsSetFormats: true };
|
|
124
|
+
}
|
|
125
|
+
let out = '';
|
|
126
|
+
for (const { name, fn, format } of closures) {
|
|
127
|
+
const src = fn.toString();
|
|
128
|
+
const label = format === null ? name : format;
|
|
129
|
+
if (/\bcov_[A-Za-z0-9_$]+\b/.test(src)) {
|
|
130
|
+
throw new Error(`ata: custom format "${label}" is instrumented for coverage and cannot be embedded; use { formatMode: 'inject' } and register it with setFormats() at load time`);
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
// eslint-disable-next-line no-new-func
|
|
134
|
+
new Function('return (' + src + ')');
|
|
135
|
+
} catch {
|
|
136
|
+
throw new Error(`ata: custom format "${label}" has no standalone source (a bound function, a class method, or a native); use { formatMode: 'inject' } and register it with setFormats() at load time`);
|
|
137
|
+
}
|
|
138
|
+
out += `${declKW} ${name} = ${src};\n`;
|
|
139
|
+
}
|
|
140
|
+
return { decls: out, exportsSetFormats: false };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function toStandaloneModule(validator, opts) {
|
|
144
|
+
validator._ensureCompiled();
|
|
145
|
+
const jsFn = validator._jsFn;
|
|
146
|
+
if (!jsFn || !jsFn._source) return null;
|
|
147
|
+
const format = (opts && opts.format) || 'esm';
|
|
148
|
+
const abortEarly = !!(opts && opts.abortEarly);
|
|
149
|
+
const source = !!(opts && opts.source);
|
|
150
|
+
const sourceMap = opts && opts.sourceMap ? opts.sourceMap : null;
|
|
151
|
+
const schemaFile = opts && opts.schemaFile ? opts.schemaFile : null;
|
|
152
|
+
const src = jsFn._source;
|
|
153
|
+
|
|
154
|
+
let errCore = '';
|
|
155
|
+
let jsErrFn = null;
|
|
156
|
+
if (!abortEarly) {
|
|
157
|
+
jsErrFn = compileToJSCodegenWithErrors(
|
|
158
|
+
typeof validator._schemaObj === 'object' ? validator._schemaObj : {},
|
|
159
|
+
null,
|
|
160
|
+
validator._userFormats,
|
|
161
|
+
(source && sourceMap && schemaFile) ? { sourceMap, schemaFile } : null,
|
|
162
|
+
);
|
|
163
|
+
const errSrc = jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : '';
|
|
164
|
+
if (errSrc) {
|
|
165
|
+
errCore = `const errFn = function(d, _all) {\n ${errSrc}\n};\n`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Schema-source frames are baked as literals inside each emitted error so
|
|
170
|
+
// consumers don't need a runtime lookup. We still expose the schema file
|
|
171
|
+
// as a sentinel constant when --source is on — handy for introspection
|
|
172
|
+
// and visible in source graphs. With --no-source, the constant is omitted
|
|
173
|
+
// entirely so size budgets and grep-based "is this source-mapped?" checks
|
|
174
|
+
// both work.
|
|
175
|
+
const schemaSourceConst = (source && schemaFile)
|
|
176
|
+
? `const __ATA_SCHEMA_SOURCE__ = ${JSON.stringify({ file: schemaFile })};\n`
|
|
177
|
+
: '';
|
|
178
|
+
|
|
179
|
+
// Serialize closure vars referenced in _fn body: regex, sub-validators, sets.
|
|
180
|
+
let closureDecls = '';
|
|
181
|
+
if (jsFn._closures && jsFn._closures.length > 0) {
|
|
182
|
+
const lines = [];
|
|
183
|
+
for (const { name, val } of jsFn._closures) {
|
|
184
|
+
if (Array.isArray(val)) {
|
|
185
|
+
lines.push(`const ${name} = ${JSON.stringify(val)};`);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (val && val.__ataSafe) {
|
|
189
|
+
lines.push(`const ${name} = __ataSafeRe(${JSON.stringify(val.source)});`);
|
|
190
|
+
} else if (val instanceof RegExp) {
|
|
191
|
+
const flags = val.flags;
|
|
192
|
+
lines.push(`const ${name} = new RegExp(${JSON.stringify(val.source)}${flags ? ', ' + JSON.stringify(flags) : ''});`);
|
|
193
|
+
} else if (val instanceof Set) {
|
|
194
|
+
lines.push(`const ${name} = new Set(${JSON.stringify([...val])});`);
|
|
195
|
+
} else if (typeof val === 'function') {
|
|
196
|
+
// new Function('_ppv', body) — extract body from toString()
|
|
197
|
+
const str = val.toString();
|
|
198
|
+
// Matches: "function anonymous(_ppv\n) {\nbody\n}" or "function(_ppv){body}"
|
|
199
|
+
const m = str.match(/^function[^(]*\([^)]*\)\s*\{([\s\S]*)\}$/)
|
|
200
|
+
const body = m ? m[1].trim() : str;
|
|
201
|
+
lines.push(`const ${name} = function(_ppv) { ${body} };`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (lines.length) closureDecls = lines.join('\n') + '\n';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Hoisted oneOf/anyOf branch checks live in the boolean fn's preamble (the
|
|
208
|
+
// runtime emits them before the function). The standalone module must declare
|
|
209
|
+
// them at module scope too, or _fn references undefined names (e.g. _af1_b0).
|
|
210
|
+
const preambleDecls = jsFn._preambleSource ? jsFn._preambleSource + '\n' : '';
|
|
211
|
+
|
|
212
|
+
// User-supplied format functions are referenced as _uf_<name> by both the
|
|
213
|
+
// boolean (_fn) and error (errFn) bodies. Embed them via Function#toString
|
|
214
|
+
// so the standalone module stays self-contained.
|
|
215
|
+
const fmt = emitFormatDecls(jsFn._formatClosures, opts && opts.formatMode, 'const');
|
|
216
|
+
const formatDecls = fmt.decls;
|
|
217
|
+
|
|
218
|
+
const validBody = errCore
|
|
219
|
+
? 'return _fn(data) ? VALID : { valid: false, errors: errFn(data, true).errors }'
|
|
220
|
+
: 'return _fn(data) ? VALID : ABORT';
|
|
221
|
+
|
|
222
|
+
const names = fmt.exportsSetFormats ? 'validate, isValid, setFormats' : 'validate, isValid';
|
|
223
|
+
const exports = format === 'esm'
|
|
224
|
+
? `export { ${names} };\nexport default { ${names} };\n`
|
|
225
|
+
: `module.exports = { ${names} };\nmodule.exports.default = module.exports;\n`;
|
|
226
|
+
|
|
227
|
+
return `// Auto-generated by ata-validator — do not edit.
|
|
228
|
+
// Schema is embedded; runtime has zero dependency on ata-validator.
|
|
229
|
+
'use strict';
|
|
230
|
+
${_CP_LEN_SOURCE}
|
|
231
|
+
${safeRePrelude(jsFn, jsErrFn)}${schemaSourceConst}const VALID = Object.freeze({ valid: true, errors: Object.freeze([]) });
|
|
232
|
+
const ABORT = Object.freeze({
|
|
233
|
+
valid: false,
|
|
234
|
+
errors: Object.freeze([Object.freeze({
|
|
235
|
+
code: 'ATA9000',
|
|
236
|
+
message: 'validation failed',
|
|
237
|
+
keyword: '__abort_early__',
|
|
238
|
+
path: '',
|
|
239
|
+
})]),
|
|
240
|
+
});
|
|
241
|
+
${closureDecls}${preambleDecls}${formatDecls}const _fn = function(d) {
|
|
242
|
+
${src}
|
|
243
|
+
};
|
|
244
|
+
${errCore}function isValid(data) { return _fn(data); }
|
|
245
|
+
function validate(data) { ${validBody}; }
|
|
246
|
+
${exports}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Bundle multiple validators into a single JS file for fast startup.
|
|
250
|
+
// Usage:
|
|
251
|
+
// const bundle = Validator.bundle([schema1, schema2, ...]);
|
|
252
|
+
// fs.writeFileSync('validators.js', bundle);
|
|
253
|
+
// // On startup:
|
|
254
|
+
// const validators = Validator.loadBundle(require('./validators.js'), [schema1, schema2, ...]);
|
|
255
|
+
function bundle(Validator, schemas, opts) {
|
|
256
|
+
const parts = schemas.map((schema) => {
|
|
257
|
+
const v = new Validator(schema, opts);
|
|
258
|
+
const standalone = toStandalone(v);
|
|
259
|
+
if (!standalone) return 'null';
|
|
260
|
+
return (
|
|
261
|
+
'(function(){' +
|
|
262
|
+
standalone
|
|
263
|
+
.replace("'use strict';", '')
|
|
264
|
+
.replace('module.exports = ', 'return ') +
|
|
265
|
+
'})()'
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
return "'use strict';\nmodule.exports = [\n" + parts.join(',\n') + '\n];\n';
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Zero-dependency self-contained bundle — no require('ata-validator') needed at runtime.
|
|
272
|
+
// opts.format: 'cjs' (default) or 'esm'.
|
|
273
|
+
// opts.formats: { name: fn } — embedded in the output via Function#toString.
|
|
274
|
+
function bundleStandalone(Validator, schemas, opts) {
|
|
275
|
+
// Cross-schema $ref resolution: only meaningful when at least one schema has
|
|
276
|
+
// an $id. Skip the schemas-as-map plumbing when none of them do.
|
|
277
|
+
const haveIds = schemas.some((s) => s && typeof s === 'object' && s.$id);
|
|
278
|
+
const bundleOpts = haveIds ? { ...(opts || {}), schemas } : (opts || {});
|
|
279
|
+
const format = (opts && opts.format) || 'cjs';
|
|
280
|
+
const R = 'Object.freeze({valid:true,errors:Object.freeze([])})';
|
|
281
|
+
let bundleUsesSafeRe = false;
|
|
282
|
+
let bundleInjects = false;
|
|
283
|
+
const fns = schemas.map((schema) => {
|
|
284
|
+
const v = new Validator(schema, bundleOpts);
|
|
285
|
+
v._ensureCompiled();
|
|
286
|
+
const jsFn = v._jsFn;
|
|
287
|
+
if (!jsFn || !jsFn._hybridSource) return 'null';
|
|
288
|
+
// The schema the validator compiled, not the one the caller passed. They
|
|
289
|
+
// differ whenever anything prepared the schema: draft-07 normalization,
|
|
290
|
+
// `format` removed under assertFormat: false, keywords outside the
|
|
291
|
+
// dialect's vocabularies. Reading the original here would leave the
|
|
292
|
+
// boolean path and the error path disagreeing about the same document.
|
|
293
|
+
const jsErrFn = compileToJSCodegenWithErrors(
|
|
294
|
+
v._schemaObj,
|
|
295
|
+
v._schemaMap,
|
|
296
|
+
v._userFormats,
|
|
297
|
+
);
|
|
298
|
+
if (jsFn._usesSafeRe || (jsErrFn && jsErrFn._usesSafeRe)) bundleUsesSafeRe = true;
|
|
299
|
+
const errBody =
|
|
300
|
+
jsErrFn && jsErrFn._errSource
|
|
301
|
+
? jsErrFn._errSource
|
|
302
|
+
: "return{valid:false,errors:[{code:'error',path:'',message:'validation failed'}]}";
|
|
303
|
+
// Custom format closures: embedded, or bound to the bundle-level
|
|
304
|
+
// registry when opts.formats is 'inject'.
|
|
305
|
+
let preamble = '';
|
|
306
|
+
if (jsFn._formatClosures) {
|
|
307
|
+
const f = emitFormatDecls(jsFn._formatClosures, opts && opts.formatMode, 'var');
|
|
308
|
+
preamble = f.decls.replace(/^var __formats = [^\n]*\n|^function setFormats[^\n]*\n/gm, '');
|
|
309
|
+
if (f.exportsSetFormats) bundleInjects = true;
|
|
310
|
+
}
|
|
311
|
+
// Include hoisted anyOf/oneOf branch helpers (e.g. `_af1_b0`) so the
|
|
312
|
+
// bundle output is self-contained. `toStandalone` emits this same source
|
|
313
|
+
// for single-schema standalone output.
|
|
314
|
+
if (jsFn._preambleSource) {
|
|
315
|
+
preamble = preamble ? `${preamble}\n${jsFn._preambleSource}` : jsFn._preambleSource;
|
|
316
|
+
}
|
|
317
|
+
if (opts && opts.verbose) {
|
|
318
|
+
// Embed the schema and a small resolver so errors carry parentSchema.
|
|
319
|
+
const schemaLit = JSON.stringify(v._schemaObj);
|
|
320
|
+
return `(function(R){${preamble}var _S=${schemaLit};function _PS(p){if(!p||p[0]!=='#')return undefined;var s=p.slice(1);if(!s)return _S;var ps=s.split('/').filter(Boolean).map(function(x){return x.replace(/~1/g,'/').replace(/~0/g,'~')});var t=_S;for(var i=0;i<ps.length-1;i++){if(t==null||typeof t!=='object')return undefined;t=t[ps[i]]}return t}var E=function(d){var _all=true;${errBody}};var _v=function(d){${jsFn._hybridSource}};return function(d){var r=_v(d);if(r&&r.valid===false&&r.errors){var es=[];for(var i=0;i<r.errors.length;i++){var e=r.errors[i];es.push(Object.assign({},e,{parentSchema:_PS(e.schemaPath)}))}return{valid:false,errors:es}}return r}})(R)`;
|
|
321
|
+
}
|
|
322
|
+
return `(function(R){${preamble}var E=function(d){var _all=true;${errBody}};return function(d){${jsFn._hybridSource}}})(R)`;
|
|
323
|
+
});
|
|
324
|
+
const arr = `[${fns.join(',')}]`;
|
|
325
|
+
const safeEmbed = bundleUsesSafeRe ? getSafeRegexEmbed() + '\n' : '';
|
|
326
|
+
const registry = bundleInjects
|
|
327
|
+
? `var __formats=Object.create(null);\nfunction setFormats(map){for(var k in map)__formats[k]=map[k]}\n`
|
|
328
|
+
: '';
|
|
329
|
+
if (format === 'esm') {
|
|
330
|
+
const extra = bundleInjects ? 'export { validators, setFormats };' : 'export { validators };';
|
|
331
|
+
return `// Auto-generated by ata-validator — do not edit\n${safeEmbed}${registry}const R=${R};\nconst validators=${arr};\nexport default validators;\n${extra}\n`;
|
|
332
|
+
}
|
|
333
|
+
const attach = bundleInjects ? 'module.exports.setFormats=setFormats;\n' : '';
|
|
334
|
+
return `'use strict';\n${safeEmbed}${registry}var R=${R};\nmodule.exports=[${fns.join(',')}];\n${attach}`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Compact bundle: deduplicated code. Shared template functions + per-schema params.
|
|
338
|
+
// Much smaller file → faster V8 parse → faster startup.
|
|
339
|
+
// opts.format: 'cjs' (default) or 'esm'.
|
|
340
|
+
function bundleCompact(Validator, schemas, opts) {
|
|
341
|
+
const haveIds = schemas.some((s) => s && typeof s === 'object' && s.$id);
|
|
342
|
+
const bundleOpts = haveIds ? { ...(opts || {}), schemas } : (opts || {});
|
|
343
|
+
const format = (opts && opts.format) || 'cjs';
|
|
344
|
+
let bundleUsesSafeRe = false;
|
|
345
|
+
// Analyze schemas and group by structure
|
|
346
|
+
const entries = schemas.map((schema) => {
|
|
347
|
+
const v = new Validator(schema, bundleOpts);
|
|
348
|
+
v._ensureCompiled();
|
|
349
|
+
const jsFn = v._jsFn;
|
|
350
|
+
if (!jsFn || !jsFn._hybridSource) return null;
|
|
351
|
+
// The schema the validator compiled, not the one the caller passed. They
|
|
352
|
+
// differ whenever anything prepared the schema: draft-07 normalization,
|
|
353
|
+
// `format` removed under assertFormat: false, keywords outside the
|
|
354
|
+
// dialect's vocabularies. Reading the original here would leave the
|
|
355
|
+
// boolean path and the error path disagreeing about the same document.
|
|
356
|
+
const jsErrFn = compileToJSCodegenWithErrors(
|
|
357
|
+
v._schemaObj,
|
|
358
|
+
v._schemaMap,
|
|
359
|
+
v._userFormats,
|
|
360
|
+
);
|
|
361
|
+
if (jsFn._usesSafeRe || (jsErrFn && jsErrFn._usesSafeRe)) bundleUsesSafeRe = true;
|
|
362
|
+
// Hoisted anyOf/oneOf branch helpers (e.g. `_af1_b0`) must travel with the
|
|
363
|
+
// hybrid body or it references undefined names. Prepending keeps dedup honest:
|
|
364
|
+
// schemas with different branch sets no longer collide on body alone.
|
|
365
|
+
const hybrid = jsFn._preambleSource
|
|
366
|
+
? `${jsFn._preambleSource}\n${jsFn._hybridSource}`
|
|
367
|
+
: jsFn._hybridSource;
|
|
368
|
+
return {
|
|
369
|
+
hybrid,
|
|
370
|
+
err: jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : null,
|
|
371
|
+
fmt: jsFn._formatClosures || null,
|
|
372
|
+
};
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Deduplicate function bodies — many schemas produce identical or near-identical code
|
|
376
|
+
const bodyMap = new Map(); // body → index
|
|
377
|
+
const bodies = [];
|
|
378
|
+
const errMap = new Map();
|
|
379
|
+
const errBodies = [];
|
|
380
|
+
|
|
381
|
+
const indices = entries.map((e) => {
|
|
382
|
+
if (!e) return [-1, -1];
|
|
383
|
+
let hi = bodyMap.get(e.hybrid);
|
|
384
|
+
if (hi === undefined) {
|
|
385
|
+
hi = bodies.length;
|
|
386
|
+
bodies.push(e.hybrid);
|
|
387
|
+
bodyMap.set(e.hybrid, hi);
|
|
388
|
+
}
|
|
389
|
+
let ei = -1;
|
|
390
|
+
if (e.err) {
|
|
391
|
+
ei = errMap.get(e.err);
|
|
392
|
+
if (ei === undefined) {
|
|
393
|
+
ei = errBodies.length;
|
|
394
|
+
errBodies.push(e.err);
|
|
395
|
+
errMap.set(e.err, ei);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return [hi, ei];
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Generate compact bundle
|
|
402
|
+
const isEsm = format === 'esm';
|
|
403
|
+
let out = isEsm
|
|
404
|
+
? '// Auto-generated by ata-validator — do not edit\n'
|
|
405
|
+
: "'use strict';\n";
|
|
406
|
+
if (bundleUsesSafeRe) out += getSafeRegexEmbed() + '\n';
|
|
407
|
+
const declKW = isEsm ? 'const' : 'var';
|
|
408
|
+
out += `${declKW} R=Object.freeze({valid:true,errors:Object.freeze([])});\n`;
|
|
409
|
+
|
|
410
|
+
// User format functions are referenced as _uf_<name> by the hybrid and error
|
|
411
|
+
// bodies. Collect them across all schemas (deduped by name) and embed via
|
|
412
|
+
// Function#toString so the bundle stays self-contained.
|
|
413
|
+
const fmtSeen = new Set();
|
|
414
|
+
const fmtAll = [];
|
|
415
|
+
for (const e of entries) {
|
|
416
|
+
if (!e || !e.fmt) continue;
|
|
417
|
+
for (const entry of e.fmt) {
|
|
418
|
+
if (fmtSeen.has(entry.name)) continue;
|
|
419
|
+
fmtSeen.add(entry.name);
|
|
420
|
+
fmtAll.push(entry);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const fmtOut = emitFormatDecls(fmtAll, opts && opts.formatMode, declKW);
|
|
424
|
+
out += fmtOut.decls;
|
|
425
|
+
|
|
426
|
+
// Shared hybrid factories
|
|
427
|
+
out += `${declKW} H=[\n`;
|
|
428
|
+
out += bodies
|
|
429
|
+
.map((b) => `function(R,E){return function(d){${b}}}`)
|
|
430
|
+
.join(',\n');
|
|
431
|
+
out += '\n];\n';
|
|
432
|
+
|
|
433
|
+
// Shared error functions
|
|
434
|
+
out += `${declKW} EF=[\n`;
|
|
435
|
+
out += errBodies.map((b) => `function(d){var _all=true;${b}}`).join(',\n');
|
|
436
|
+
out += '\n];\n';
|
|
437
|
+
|
|
438
|
+
// Build validators from shared templates
|
|
439
|
+
const arrBody = indices
|
|
440
|
+
.map(([hi, ei]) => {
|
|
441
|
+
if (hi < 0) return 'null';
|
|
442
|
+
if (ei >= 0) return `H[${hi}](R,EF[${ei}])`;
|
|
443
|
+
return `H[${hi}](R,function(){return{valid:false,errors:[]}})`;
|
|
444
|
+
})
|
|
445
|
+
.join(',');
|
|
446
|
+
if (isEsm) {
|
|
447
|
+
out += `const validators=[${arrBody}];\nexport default validators;\nexport { validators${fmtOut.exportsSetFormats ? ', setFormats' : ''} };\n`;
|
|
448
|
+
} else {
|
|
449
|
+
out += `module.exports=[${arrBody}];\n`;
|
|
450
|
+
if (fmtOut.exportsSetFormats) out += 'module.exports.setFormats=setFormats;\n';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return out;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function loadBundle(Validator, mods, schemas, opts) {
|
|
457
|
+
return schemas.map((schema, i) => {
|
|
458
|
+
if (mods[i]) return Validator.fromStandalone(mods[i], schema, opts);
|
|
459
|
+
return new Validator(schema, opts);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
module.exports = {
|
|
464
|
+
toStandalone,
|
|
465
|
+
toStandaloneModule,
|
|
466
|
+
bundle,
|
|
467
|
+
bundleStandalone,
|
|
468
|
+
bundleCompact,
|
|
469
|
+
loadBundle,
|
|
470
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Browser twin of lib/aot.js, wired by the package.json `browser` field. The
|
|
4
|
+
// emitters write module source for a bundler or the filesystem, which is build
|
|
5
|
+
// tooling, not something a page does; leaving them out keeps their code and
|
|
6
|
+
// the embedded safe-regex engine source out of the browser bundle. Loading a
|
|
7
|
+
// bundle somebody already built is a runtime act, so that one works here too.
|
|
8
|
+
|
|
9
|
+
function unavailable(name) {
|
|
10
|
+
return function () {
|
|
11
|
+
throw new Error(
|
|
12
|
+
name + '() is not in the default browser bundle. ' +
|
|
13
|
+
"Import it from 'ata-validator/aot', which works in the browser too; " +
|
|
14
|
+
'pages that never emit code stay free of the emitters this way.'
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function loadBundle(Validator, mods, schemas, opts) {
|
|
20
|
+
return schemas.map((schema, i) => {
|
|
21
|
+
if (mods[i]) return Validator.fromStandalone(mods[i], schema, opts);
|
|
22
|
+
return new Validator(schema, opts);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
toStandalone: unavailable('toStandalone'),
|
|
28
|
+
toStandaloneModule: unavailable('toStandaloneModule'),
|
|
29
|
+
bundle: unavailable('bundle'),
|
|
30
|
+
bundleStandalone: unavailable('bundleStandalone'),
|
|
31
|
+
bundleCompact: unavailable('bundleCompact'),
|
|
32
|
+
loadBundle,
|
|
33
|
+
};
|
package/lib/aot.js
CHANGED
|
@@ -1,470 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
// toStandalone(validator)
|
|
10
|
-
// toStandaloneModule(validator, opts)
|
|
11
|
-
// bundle(Validator, schemas, opts)
|
|
12
|
-
// bundleStandalone(Validator, schemas, opts)
|
|
13
|
-
// bundleCompact(Validator, schemas, opts)
|
|
14
|
-
// loadBundle(Validator, mods, schemas, opts)
|
|
15
|
-
|
|
16
|
-
const { compileToJSCodegenWithErrors } = require('./js-compiler');
|
|
17
|
-
const SAFE_REGEX_SOURCE = require('./safe-regex-source');
|
|
18
|
-
|
|
19
|
-
// Embedded verbatim in standalone modules so the output file has no runtime
|
|
20
|
-
// dependency on ata-validator. ASCII fast-path plus surrogate-aware slow path.
|
|
21
|
-
const _CP_LEN_SOURCE = `function _cpLen(s) {
|
|
22
|
-
const len = s.length;
|
|
23
|
-
for (let i = 0; i < len; i++) {
|
|
24
|
-
if (((s.charCodeAt(i) - 0xD800) >>> 0) < 0x400) {
|
|
25
|
-
let n = 0; for (const _ of s) n++; return n;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return len;
|
|
29
|
-
}`;
|
|
30
|
-
|
|
31
|
-
// The linear-time regex engine, inlined verbatim into standalone output so a
|
|
32
|
-
// compiled module that uses safe `pattern` matchers has no runtime dependency
|
|
33
|
-
// on ata-validator. The engine source is baked into `lib/safe-regex-source.js`
|
|
34
|
-
// at build time (see `scripts/regen-safe-regex-source.js`), so this path has
|
|
35
|
-
// no `fs`/`path`/`__dirname` reads — safe in browser bundles too. The embed
|
|
36
|
-
// strips the strict directive and CommonJS exports and adds the `__ataSafeRe`
|
|
37
|
-
// alias the emitted code calls. The engine has no eval/new Function, so the
|
|
38
|
-
// embed is CSP-safe.
|
|
39
|
-
let _safeRegexEmbed = null;
|
|
40
|
-
function getSafeRegexEmbed() {
|
|
41
|
-
if (_safeRegexEmbed === null) {
|
|
42
|
-
const body = SAFE_REGEX_SOURCE
|
|
43
|
-
.replace(/^'use strict'\s*\n/, '')
|
|
44
|
-
.replace(/\nmodule\.exports[^\n]*\n?/, '\n');
|
|
45
|
-
_safeRegexEmbed = body.trimEnd() + '\nconst __ataSafeRe = compileSafe;';
|
|
46
|
-
}
|
|
47
|
-
return _safeRegexEmbed;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Returns the engine embed when any supplied compiled function references the
|
|
51
|
-
// safe matcher (jsFn._usesSafeRe), else an empty string so non-pattern modules
|
|
52
|
-
// pay zero bytes.
|
|
53
|
-
function safeRePrelude(...fns) {
|
|
54
|
-
return fns.some((f) => f && f._usesSafeRe) ? getSafeRegexEmbed() + '\n' : '';
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// --- Standalone pre-compilation ---
|
|
58
|
-
// Generate a JS module string that can be written to a file.
|
|
59
|
-
// On next startup, load with Validator.fromStandalone() -- zero compile time.
|
|
60
|
-
function toStandalone(validator) {
|
|
61
|
-
validator._ensureCompiled();
|
|
62
|
-
const jsFn = validator._jsFn;
|
|
63
|
-
if (!jsFn || !jsFn._source) return null;
|
|
64
|
-
const src = jsFn._source;
|
|
65
|
-
const hybridSrc = jsFn._hybridSource || '';
|
|
66
|
-
const preambleSrc = jsFn._preambleSource || '';
|
|
67
|
-
|
|
68
|
-
// Also capture error function source for zero-compile standalone load
|
|
69
|
-
const jsErrFn = compileToJSCodegenWithErrors(
|
|
70
|
-
typeof validator._schemaObj === 'object' ? validator._schemaObj : {},
|
|
71
|
-
);
|
|
72
|
-
const errSrc = jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : '';
|
|
73
|
-
|
|
74
|
-
return `// Auto-generated by ata-validator — do not edit
|
|
75
|
-
'use strict';
|
|
76
|
-
${_CP_LEN_SOURCE}
|
|
77
|
-
${safeRePrelude(jsFn, jsErrFn)}${preambleSrc}
|
|
78
|
-
const boolFn = function(d) {
|
|
79
|
-
${src}
|
|
80
|
-
};
|
|
81
|
-
const hybridFactory = function(R, E) {
|
|
82
|
-
return function(d) {
|
|
83
|
-
${hybridSrc}
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
${errSrc ? `const errFn = function(d, _all) {\n ${errSrc}\n};` : 'const errFn = null;'}
|
|
87
|
-
module.exports = { boolFn, hybridFactory, errFn };
|
|
88
|
-
`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// --- Fully standalone module ---
|
|
92
|
-
// Generates a self-contained module that can be imported directly without
|
|
93
|
-
// pulling in ata-validator at runtime. Browser bundle gets only the
|
|
94
|
-
// generated validator (~2 KB typical) instead of the 165 KB compiler.
|
|
95
|
-
//
|
|
96
|
-
// import { validate, isValid } from './user-validator.mjs'
|
|
97
|
-
// if (isValid(data)) { ... }
|
|
98
|
-
//
|
|
99
|
-
// format: 'esm' | 'cjs'. Default 'esm'.
|
|
100
|
-
// abortEarly: if true, invalid result is a shared stub; smaller output.
|
|
101
|
-
|
|
102
|
-
// Custom format functions in standalone output.
|
|
103
|
-
//
|
|
104
|
-
// 'embed' (default) writes each function's source into the module through
|
|
105
|
-
// Function#toString. That only works for a plain function: one that closes
|
|
106
|
-
// over nothing and is not rewritten by a coverage or transpile step (istanbul
|
|
107
|
-
// injects `cov_` counters; a bundler may hoist helpers). The check below
|
|
108
|
-
// rejects those at build time with a named error instead of emitting a module
|
|
109
|
-
// that throws on first use.
|
|
110
|
-
//
|
|
111
|
-
// 'inject' writes no function source. The module exports `setFormats(map)`
|
|
112
|
-
// and each format is looked up from that registry when a value is checked,
|
|
113
|
-
// so the caller supplies the functions at load time.
|
|
114
|
-
function emitFormatDecls(closures, mode, declKW) {
|
|
115
|
-
if (!closures || closures.length === 0) return { decls: '', exportsSetFormats: false };
|
|
116
|
-
if (mode === 'inject') {
|
|
117
|
-
let out = `${declKW} __formats = Object.create(null);\n`;
|
|
118
|
-
out += `function setFormats(map) { for (const k in map) __formats[k] = map[k]; }\n`;
|
|
119
|
-
for (const { name, format } of closures) {
|
|
120
|
-
const key = JSON.stringify(format === null ? name.slice(4) : format);
|
|
121
|
-
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`;
|
|
122
|
-
}
|
|
123
|
-
return { decls: out, exportsSetFormats: true };
|
|
124
|
-
}
|
|
125
|
-
let out = '';
|
|
126
|
-
for (const { name, fn, format } of closures) {
|
|
127
|
-
const src = fn.toString();
|
|
128
|
-
const label = format === null ? name : format;
|
|
129
|
-
if (/\bcov_[A-Za-z0-9_$]+\b/.test(src)) {
|
|
130
|
-
throw new Error(`ata: custom format "${label}" is instrumented for coverage and cannot be embedded; use { formatMode: 'inject' } and register it with setFormats() at load time`);
|
|
131
|
-
}
|
|
132
|
-
try {
|
|
133
|
-
// eslint-disable-next-line no-new-func
|
|
134
|
-
new Function('return (' + src + ')');
|
|
135
|
-
} catch {
|
|
136
|
-
throw new Error(`ata: custom format "${label}" has no standalone source (a bound function, a class method, or a native); use { formatMode: 'inject' } and register it with setFormats() at load time`);
|
|
137
|
-
}
|
|
138
|
-
out += `${declKW} ${name} = ${src};\n`;
|
|
139
|
-
}
|
|
140
|
-
return { decls: out, exportsSetFormats: false };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function toStandaloneModule(validator, opts) {
|
|
144
|
-
validator._ensureCompiled();
|
|
145
|
-
const jsFn = validator._jsFn;
|
|
146
|
-
if (!jsFn || !jsFn._source) return null;
|
|
147
|
-
const format = (opts && opts.format) || 'esm';
|
|
148
|
-
const abortEarly = !!(opts && opts.abortEarly);
|
|
149
|
-
const source = !!(opts && opts.source);
|
|
150
|
-
const sourceMap = opts && opts.sourceMap ? opts.sourceMap : null;
|
|
151
|
-
const schemaFile = opts && opts.schemaFile ? opts.schemaFile : null;
|
|
152
|
-
const src = jsFn._source;
|
|
153
|
-
|
|
154
|
-
let errCore = '';
|
|
155
|
-
let jsErrFn = null;
|
|
156
|
-
if (!abortEarly) {
|
|
157
|
-
jsErrFn = compileToJSCodegenWithErrors(
|
|
158
|
-
typeof validator._schemaObj === 'object' ? validator._schemaObj : {},
|
|
159
|
-
null,
|
|
160
|
-
validator._userFormats,
|
|
161
|
-
(source && sourceMap && schemaFile) ? { sourceMap, schemaFile } : null,
|
|
162
|
-
);
|
|
163
|
-
const errSrc = jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : '';
|
|
164
|
-
if (errSrc) {
|
|
165
|
-
errCore = `const errFn = function(d, _all) {\n ${errSrc}\n};\n`;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Schema-source frames are baked as literals inside each emitted error so
|
|
170
|
-
// consumers don't need a runtime lookup. We still expose the schema file
|
|
171
|
-
// as a sentinel constant when --source is on — handy for introspection
|
|
172
|
-
// and visible in source graphs. With --no-source, the constant is omitted
|
|
173
|
-
// entirely so size budgets and grep-based "is this source-mapped?" checks
|
|
174
|
-
// both work.
|
|
175
|
-
const schemaSourceConst = (source && schemaFile)
|
|
176
|
-
? `const __ATA_SCHEMA_SOURCE__ = ${JSON.stringify({ file: schemaFile })};\n`
|
|
177
|
-
: '';
|
|
178
|
-
|
|
179
|
-
// Serialize closure vars referenced in _fn body: regex, sub-validators, sets.
|
|
180
|
-
let closureDecls = '';
|
|
181
|
-
if (jsFn._closures && jsFn._closures.length > 0) {
|
|
182
|
-
const lines = [];
|
|
183
|
-
for (const { name, val } of jsFn._closures) {
|
|
184
|
-
if (Array.isArray(val)) {
|
|
185
|
-
lines.push(`const ${name} = ${JSON.stringify(val)};`);
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
if (val && val.__ataSafe) {
|
|
189
|
-
lines.push(`const ${name} = __ataSafeRe(${JSON.stringify(val.source)});`);
|
|
190
|
-
} else if (val instanceof RegExp) {
|
|
191
|
-
const flags = val.flags;
|
|
192
|
-
lines.push(`const ${name} = new RegExp(${JSON.stringify(val.source)}${flags ? ', ' + JSON.stringify(flags) : ''});`);
|
|
193
|
-
} else if (val instanceof Set) {
|
|
194
|
-
lines.push(`const ${name} = new Set(${JSON.stringify([...val])});`);
|
|
195
|
-
} else if (typeof val === 'function') {
|
|
196
|
-
// new Function('_ppv', body) — extract body from toString()
|
|
197
|
-
const str = val.toString();
|
|
198
|
-
// Matches: "function anonymous(_ppv\n) {\nbody\n}" or "function(_ppv){body}"
|
|
199
|
-
const m = str.match(/^function[^(]*\([^)]*\)\s*\{([\s\S]*)\}$/)
|
|
200
|
-
const body = m ? m[1].trim() : str;
|
|
201
|
-
lines.push(`const ${name} = function(_ppv) { ${body} };`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (lines.length) closureDecls = lines.join('\n') + '\n';
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Hoisted oneOf/anyOf branch checks live in the boolean fn's preamble (the
|
|
208
|
-
// runtime emits them before the function). The standalone module must declare
|
|
209
|
-
// them at module scope too, or _fn references undefined names (e.g. _af1_b0).
|
|
210
|
-
const preambleDecls = jsFn._preambleSource ? jsFn._preambleSource + '\n' : '';
|
|
211
|
-
|
|
212
|
-
// User-supplied format functions are referenced as _uf_<name> by both the
|
|
213
|
-
// boolean (_fn) and error (errFn) bodies. Embed them via Function#toString
|
|
214
|
-
// so the standalone module stays self-contained.
|
|
215
|
-
const fmt = emitFormatDecls(jsFn._formatClosures, opts && opts.formatMode, 'const');
|
|
216
|
-
const formatDecls = fmt.decls;
|
|
217
|
-
|
|
218
|
-
const validBody = errCore
|
|
219
|
-
? 'return _fn(data) ? VALID : { valid: false, errors: errFn(data, true).errors }'
|
|
220
|
-
: 'return _fn(data) ? VALID : ABORT';
|
|
221
|
-
|
|
222
|
-
const names = fmt.exportsSetFormats ? 'validate, isValid, setFormats' : 'validate, isValid';
|
|
223
|
-
const exports = format === 'esm'
|
|
224
|
-
? `export { ${names} };\nexport default { ${names} };\n`
|
|
225
|
-
: `module.exports = { ${names} };\nmodule.exports.default = module.exports;\n`;
|
|
226
|
-
|
|
227
|
-
return `// Auto-generated by ata-validator — do not edit.
|
|
228
|
-
// Schema is embedded; runtime has zero dependency on ata-validator.
|
|
229
|
-
'use strict';
|
|
230
|
-
${_CP_LEN_SOURCE}
|
|
231
|
-
${safeRePrelude(jsFn, jsErrFn)}${schemaSourceConst}const VALID = Object.freeze({ valid: true, errors: Object.freeze([]) });
|
|
232
|
-
const ABORT = Object.freeze({
|
|
233
|
-
valid: false,
|
|
234
|
-
errors: Object.freeze([Object.freeze({
|
|
235
|
-
code: 'ATA9000',
|
|
236
|
-
message: 'validation failed',
|
|
237
|
-
keyword: '__abort_early__',
|
|
238
|
-
path: '',
|
|
239
|
-
})]),
|
|
240
|
-
});
|
|
241
|
-
${closureDecls}${preambleDecls}${formatDecls}const _fn = function(d) {
|
|
242
|
-
${src}
|
|
243
|
-
};
|
|
244
|
-
${errCore}function isValid(data) { return _fn(data); }
|
|
245
|
-
function validate(data) { ${validBody}; }
|
|
246
|
-
${exports}`;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Bundle multiple validators into a single JS file for fast startup.
|
|
250
|
-
// Usage:
|
|
251
|
-
// const bundle = Validator.bundle([schema1, schema2, ...]);
|
|
252
|
-
// fs.writeFileSync('validators.js', bundle);
|
|
253
|
-
// // On startup:
|
|
254
|
-
// const validators = Validator.loadBundle(require('./validators.js'), [schema1, schema2, ...]);
|
|
255
|
-
function bundle(Validator, schemas, opts) {
|
|
256
|
-
const parts = schemas.map((schema) => {
|
|
257
|
-
const v = new Validator(schema, opts);
|
|
258
|
-
const standalone = toStandalone(v);
|
|
259
|
-
if (!standalone) return 'null';
|
|
260
|
-
return (
|
|
261
|
-
'(function(){' +
|
|
262
|
-
standalone
|
|
263
|
-
.replace("'use strict';", '')
|
|
264
|
-
.replace('module.exports = ', 'return ') +
|
|
265
|
-
'})()'
|
|
266
|
-
);
|
|
267
|
-
});
|
|
268
|
-
return "'use strict';\nmodule.exports = [\n" + parts.join(',\n') + '\n];\n';
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Zero-dependency self-contained bundle — no require('ata-validator') needed at runtime.
|
|
272
|
-
// opts.format: 'cjs' (default) or 'esm'.
|
|
273
|
-
// opts.formats: { name: fn } — embedded in the output via Function#toString.
|
|
274
|
-
function bundleStandalone(Validator, schemas, opts) {
|
|
275
|
-
// Cross-schema $ref resolution: only meaningful when at least one schema has
|
|
276
|
-
// an $id. Skip the schemas-as-map plumbing when none of them do.
|
|
277
|
-
const haveIds = schemas.some((s) => s && typeof s === 'object' && s.$id);
|
|
278
|
-
const bundleOpts = haveIds ? { ...(opts || {}), schemas } : (opts || {});
|
|
279
|
-
const format = (opts && opts.format) || 'cjs';
|
|
280
|
-
const R = 'Object.freeze({valid:true,errors:Object.freeze([])})';
|
|
281
|
-
let bundleUsesSafeRe = false;
|
|
282
|
-
let bundleInjects = false;
|
|
283
|
-
const fns = schemas.map((schema) => {
|
|
284
|
-
const v = new Validator(schema, bundleOpts);
|
|
285
|
-
v._ensureCompiled();
|
|
286
|
-
const jsFn = v._jsFn;
|
|
287
|
-
if (!jsFn || !jsFn._hybridSource) return 'null';
|
|
288
|
-
// The schema the validator compiled, not the one the caller passed. They
|
|
289
|
-
// differ whenever anything prepared the schema: draft-07 normalization,
|
|
290
|
-
// `format` removed under assertFormat: false, keywords outside the
|
|
291
|
-
// dialect's vocabularies. Reading the original here would leave the
|
|
292
|
-
// boolean path and the error path disagreeing about the same document.
|
|
293
|
-
const jsErrFn = compileToJSCodegenWithErrors(
|
|
294
|
-
v._schemaObj,
|
|
295
|
-
v._schemaMap,
|
|
296
|
-
v._userFormats,
|
|
297
|
-
);
|
|
298
|
-
if (jsFn._usesSafeRe || (jsErrFn && jsErrFn._usesSafeRe)) bundleUsesSafeRe = true;
|
|
299
|
-
const errBody =
|
|
300
|
-
jsErrFn && jsErrFn._errSource
|
|
301
|
-
? jsErrFn._errSource
|
|
302
|
-
: "return{valid:false,errors:[{code:'error',path:'',message:'validation failed'}]}";
|
|
303
|
-
// Custom format closures: embedded, or bound to the bundle-level
|
|
304
|
-
// registry when opts.formats is 'inject'.
|
|
305
|
-
let preamble = '';
|
|
306
|
-
if (jsFn._formatClosures) {
|
|
307
|
-
const f = emitFormatDecls(jsFn._formatClosures, opts && opts.formatMode, 'var');
|
|
308
|
-
preamble = f.decls.replace(/^var __formats = [^\n]*\n|^function setFormats[^\n]*\n/gm, '');
|
|
309
|
-
if (f.exportsSetFormats) bundleInjects = true;
|
|
310
|
-
}
|
|
311
|
-
// Include hoisted anyOf/oneOf branch helpers (e.g. `_af1_b0`) so the
|
|
312
|
-
// bundle output is self-contained. `toStandalone` emits this same source
|
|
313
|
-
// for single-schema standalone output.
|
|
314
|
-
if (jsFn._preambleSource) {
|
|
315
|
-
preamble = preamble ? `${preamble}\n${jsFn._preambleSource}` : jsFn._preambleSource;
|
|
316
|
-
}
|
|
317
|
-
if (opts && opts.verbose) {
|
|
318
|
-
// Embed the schema and a small resolver so errors carry parentSchema.
|
|
319
|
-
const schemaLit = JSON.stringify(v._schemaObj);
|
|
320
|
-
return `(function(R){${preamble}var _S=${schemaLit};function _PS(p){if(!p||p[0]!=='#')return undefined;var s=p.slice(1);if(!s)return _S;var ps=s.split('/').filter(Boolean).map(function(x){return x.replace(/~1/g,'/').replace(/~0/g,'~')});var t=_S;for(var i=0;i<ps.length-1;i++){if(t==null||typeof t!=='object')return undefined;t=t[ps[i]]}return t}var E=function(d){var _all=true;${errBody}};var _v=function(d){${jsFn._hybridSource}};return function(d){var r=_v(d);if(r&&r.valid===false&&r.errors){var es=[];for(var i=0;i<r.errors.length;i++){var e=r.errors[i];es.push(Object.assign({},e,{parentSchema:_PS(e.schemaPath)}))}return{valid:false,errors:es}}return r}})(R)`;
|
|
321
|
-
}
|
|
322
|
-
return `(function(R){${preamble}var E=function(d){var _all=true;${errBody}};return function(d){${jsFn._hybridSource}}})(R)`;
|
|
323
|
-
});
|
|
324
|
-
const arr = `[${fns.join(',')}]`;
|
|
325
|
-
const safeEmbed = bundleUsesSafeRe ? getSafeRegexEmbed() + '\n' : '';
|
|
326
|
-
const registry = bundleInjects
|
|
327
|
-
? `var __formats=Object.create(null);\nfunction setFormats(map){for(var k in map)__formats[k]=map[k]}\n`
|
|
328
|
-
: '';
|
|
329
|
-
if (format === 'esm') {
|
|
330
|
-
const extra = bundleInjects ? 'export { validators, setFormats };' : 'export { validators };';
|
|
331
|
-
return `// Auto-generated by ata-validator — do not edit\n${safeEmbed}${registry}const R=${R};\nconst validators=${arr};\nexport default validators;\n${extra}\n`;
|
|
332
|
-
}
|
|
333
|
-
const attach = bundleInjects ? 'module.exports.setFormats=setFormats;\n' : '';
|
|
334
|
-
return `'use strict';\n${safeEmbed}${registry}var R=${R};\nmodule.exports=[${fns.join(',')}];\n${attach}`;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Compact bundle: deduplicated code. Shared template functions + per-schema params.
|
|
338
|
-
// Much smaller file → faster V8 parse → faster startup.
|
|
339
|
-
// opts.format: 'cjs' (default) or 'esm'.
|
|
340
|
-
function bundleCompact(Validator, schemas, opts) {
|
|
341
|
-
const haveIds = schemas.some((s) => s && typeof s === 'object' && s.$id);
|
|
342
|
-
const bundleOpts = haveIds ? { ...(opts || {}), schemas } : (opts || {});
|
|
343
|
-
const format = (opts && opts.format) || 'cjs';
|
|
344
|
-
let bundleUsesSafeRe = false;
|
|
345
|
-
// Analyze schemas and group by structure
|
|
346
|
-
const entries = schemas.map((schema) => {
|
|
347
|
-
const v = new Validator(schema, bundleOpts);
|
|
348
|
-
v._ensureCompiled();
|
|
349
|
-
const jsFn = v._jsFn;
|
|
350
|
-
if (!jsFn || !jsFn._hybridSource) return null;
|
|
351
|
-
// The schema the validator compiled, not the one the caller passed. They
|
|
352
|
-
// differ whenever anything prepared the schema: draft-07 normalization,
|
|
353
|
-
// `format` removed under assertFormat: false, keywords outside the
|
|
354
|
-
// dialect's vocabularies. Reading the original here would leave the
|
|
355
|
-
// boolean path and the error path disagreeing about the same document.
|
|
356
|
-
const jsErrFn = compileToJSCodegenWithErrors(
|
|
357
|
-
v._schemaObj,
|
|
358
|
-
v._schemaMap,
|
|
359
|
-
v._userFormats,
|
|
360
|
-
);
|
|
361
|
-
if (jsFn._usesSafeRe || (jsErrFn && jsErrFn._usesSafeRe)) bundleUsesSafeRe = true;
|
|
362
|
-
// Hoisted anyOf/oneOf branch helpers (e.g. `_af1_b0`) must travel with the
|
|
363
|
-
// hybrid body or it references undefined names. Prepending keeps dedup honest:
|
|
364
|
-
// schemas with different branch sets no longer collide on body alone.
|
|
365
|
-
const hybrid = jsFn._preambleSource
|
|
366
|
-
? `${jsFn._preambleSource}\n${jsFn._hybridSource}`
|
|
367
|
-
: jsFn._hybridSource;
|
|
368
|
-
return {
|
|
369
|
-
hybrid,
|
|
370
|
-
err: jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : null,
|
|
371
|
-
fmt: jsFn._formatClosures || null,
|
|
372
|
-
};
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
// Deduplicate function bodies — many schemas produce identical or near-identical code
|
|
376
|
-
const bodyMap = new Map(); // body → index
|
|
377
|
-
const bodies = [];
|
|
378
|
-
const errMap = new Map();
|
|
379
|
-
const errBodies = [];
|
|
380
|
-
|
|
381
|
-
const indices = entries.map((e) => {
|
|
382
|
-
if (!e) return [-1, -1];
|
|
383
|
-
let hi = bodyMap.get(e.hybrid);
|
|
384
|
-
if (hi === undefined) {
|
|
385
|
-
hi = bodies.length;
|
|
386
|
-
bodies.push(e.hybrid);
|
|
387
|
-
bodyMap.set(e.hybrid, hi);
|
|
388
|
-
}
|
|
389
|
-
let ei = -1;
|
|
390
|
-
if (e.err) {
|
|
391
|
-
ei = errMap.get(e.err);
|
|
392
|
-
if (ei === undefined) {
|
|
393
|
-
ei = errBodies.length;
|
|
394
|
-
errBodies.push(e.err);
|
|
395
|
-
errMap.set(e.err, ei);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
return [hi, ei];
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
// Generate compact bundle
|
|
402
|
-
const isEsm = format === 'esm';
|
|
403
|
-
let out = isEsm
|
|
404
|
-
? '// Auto-generated by ata-validator — do not edit\n'
|
|
405
|
-
: "'use strict';\n";
|
|
406
|
-
if (bundleUsesSafeRe) out += getSafeRegexEmbed() + '\n';
|
|
407
|
-
const declKW = isEsm ? 'const' : 'var';
|
|
408
|
-
out += `${declKW} R=Object.freeze({valid:true,errors:Object.freeze([])});\n`;
|
|
409
|
-
|
|
410
|
-
// User format functions are referenced as _uf_<name> by the hybrid and error
|
|
411
|
-
// bodies. Collect them across all schemas (deduped by name) and embed via
|
|
412
|
-
// Function#toString so the bundle stays self-contained.
|
|
413
|
-
const fmtSeen = new Set();
|
|
414
|
-
const fmtAll = [];
|
|
415
|
-
for (const e of entries) {
|
|
416
|
-
if (!e || !e.fmt) continue;
|
|
417
|
-
for (const entry of e.fmt) {
|
|
418
|
-
if (fmtSeen.has(entry.name)) continue;
|
|
419
|
-
fmtSeen.add(entry.name);
|
|
420
|
-
fmtAll.push(entry);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
const fmtOut = emitFormatDecls(fmtAll, opts && opts.formatMode, declKW);
|
|
424
|
-
out += fmtOut.decls;
|
|
425
|
-
|
|
426
|
-
// Shared hybrid factories
|
|
427
|
-
out += `${declKW} H=[\n`;
|
|
428
|
-
out += bodies
|
|
429
|
-
.map((b) => `function(R,E){return function(d){${b}}}`)
|
|
430
|
-
.join(',\n');
|
|
431
|
-
out += '\n];\n';
|
|
432
|
-
|
|
433
|
-
// Shared error functions
|
|
434
|
-
out += `${declKW} EF=[\n`;
|
|
435
|
-
out += errBodies.map((b) => `function(d){var _all=true;${b}}`).join(',\n');
|
|
436
|
-
out += '\n];\n';
|
|
437
|
-
|
|
438
|
-
// Build validators from shared templates
|
|
439
|
-
const arrBody = indices
|
|
440
|
-
.map(([hi, ei]) => {
|
|
441
|
-
if (hi < 0) return 'null';
|
|
442
|
-
if (ei >= 0) return `H[${hi}](R,EF[${ei}])`;
|
|
443
|
-
return `H[${hi}](R,function(){return{valid:false,errors:[]}})`;
|
|
444
|
-
})
|
|
445
|
-
.join(',');
|
|
446
|
-
if (isEsm) {
|
|
447
|
-
out += `const validators=[${arrBody}];\nexport default validators;\nexport { validators${fmtOut.exportsSetFormats ? ', setFormats' : ''} };\n`;
|
|
448
|
-
} else {
|
|
449
|
-
out += `module.exports=[${arrBody}];\n`;
|
|
450
|
-
if (fmtOut.exportsSetFormats) out += 'module.exports.setFormats=setFormats;\n';
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
return out;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
function loadBundle(Validator, mods, schemas, opts) {
|
|
457
|
-
return schemas.map((schema, i) => {
|
|
458
|
-
if (mods[i]) return Validator.fromStandalone(mods[i], schema, opts);
|
|
459
|
-
return new Validator(schema, opts);
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
module.exports = {
|
|
464
|
-
toStandalone,
|
|
465
|
-
toStandaloneModule,
|
|
466
|
-
bundle,
|
|
467
|
-
bundleStandalone,
|
|
468
|
-
bundleCompact,
|
|
469
|
-
loadBundle,
|
|
470
|
-
};
|
|
3
|
+
// Node resolution of the AOT emitters. Browser bundles swap this file for
|
|
4
|
+
// `lib/aot.browser.js` via the package.json `browser` field, so the default
|
|
5
|
+
// browser bundle carries no emitters and no embedded safe-regex source; a
|
|
6
|
+
// page that wants to emit validators imports `ata-validator/aot`, which
|
|
7
|
+
// reaches `lib/aot-impl.js` directly and works without `fs`.
|
|
8
|
+
module.exports = require('./aot-impl.js');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Browser twin of lib/buffer-gate.js, wired by the package.json `browser`
|
|
4
|
+
// field. The gate exists to route schemas the native walker answers wrongly;
|
|
5
|
+
// a browser bundle never loads the native walker, so there is nothing to
|
|
6
|
+
// route and the shape list can stay out of the bundle. index.js only consults
|
|
7
|
+
// the gate when the native addon loaded, which it never does here, so these
|
|
8
|
+
// are unreachable, kept callable for safety.
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
bufferNeedsSlowPath: () => false,
|
|
12
|
+
installSlowBufferApis() {},
|
|
13
|
+
};
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "JSON Schema validation with first-class TypeScript and zero runtime cost. AOT compile to per-schema ESM modules with zero validator dependency. Generic Validator<T> for TypeBox/Zod/Valibot composition. Optional runtime API. Standard Schema V1 compatible.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
"import": "./compat.mjs",
|
|
21
21
|
"require": "./compat.js"
|
|
22
22
|
},
|
|
23
|
+
"./aot": {
|
|
24
|
+
"types": "./aot.d.ts",
|
|
25
|
+
"import": "./aot.mjs",
|
|
26
|
+
"require": "./aot.js"
|
|
27
|
+
},
|
|
23
28
|
"./build": {
|
|
24
29
|
"types": "./build.d.ts",
|
|
25
30
|
"import": "./build.mjs",
|
|
@@ -37,7 +42,9 @@
|
|
|
37
42
|
"ata": "bin/ata.js"
|
|
38
43
|
},
|
|
39
44
|
"browser": {
|
|
40
|
-
"./lib/native-load.js": "./lib/native-load.browser.js"
|
|
45
|
+
"./lib/native-load.js": "./lib/native-load.browser.js",
|
|
46
|
+
"./lib/aot.js": "./lib/aot.browser.js",
|
|
47
|
+
"./lib/buffer-gate.js": "./lib/buffer-gate.browser.js"
|
|
41
48
|
},
|
|
42
49
|
"scripts": {
|
|
43
50
|
"prepublishOnly": "node scripts/regen-safe-regex-source.js && node tests/test_pack_purity.js && ATA_ALLOW_DOC_PLACEHOLDERS=1 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",
|
|
@@ -96,6 +103,9 @@
|
|
|
96
103
|
"index.mjs",
|
|
97
104
|
"index.browser.mjs",
|
|
98
105
|
"index.d.ts",
|
|
106
|
+
"aot.js",
|
|
107
|
+
"aot.mjs",
|
|
108
|
+
"aot.d.ts",
|
|
99
109
|
"lib/",
|
|
100
110
|
"compat.js",
|
|
101
111
|
"compat.mjs",
|
|
@@ -112,13 +122,13 @@
|
|
|
112
122
|
"LICENSE"
|
|
113
123
|
],
|
|
114
124
|
"optionalDependencies": {
|
|
115
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
116
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
117
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
118
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
119
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
120
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
121
|
-
"@ata-validator/native-win32-x64": "1.
|
|
125
|
+
"@ata-validator/native-darwin-arm64": "1.13.0",
|
|
126
|
+
"@ata-validator/native-darwin-x64": "1.13.0",
|
|
127
|
+
"@ata-validator/native-linux-x64-gnu": "1.13.0",
|
|
128
|
+
"@ata-validator/native-linux-arm64-gnu": "1.13.0",
|
|
129
|
+
"@ata-validator/native-linux-x64-musl": "1.13.0",
|
|
130
|
+
"@ata-validator/native-linux-arm64-musl": "1.13.0",
|
|
131
|
+
"@ata-validator/native-win32-x64": "1.13.0"
|
|
122
132
|
},
|
|
123
133
|
"peerDependencies": {
|
|
124
134
|
"yaml": "^2.0.0"
|