ata-validator 0.18.2 → 0.21.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 +44 -0
- package/README.md +67 -0
- package/build.d.ts +28 -0
- package/build.mjs +3 -0
- package/index.browser.mjs +1 -1
- package/index.d.ts +58 -3
- package/index.js +101 -370
- package/index.mjs +1 -1
- package/lib/aot-build.js +30 -1
- package/lib/aot.js +410 -0
- package/lib/error-messages.js +88 -0
- package/lib/native-load.browser.js +8 -0
- package/lib/native-load.js +21 -0
- package/lib/refine.js +68 -0
- package/lib/safe-regex-source.js +8 -0
- package/lib/t.js +149 -0
- package/lib/version.js +10 -0
- package/package.json +14 -5
- package/scripts/regen-safe-regex-source.js +33 -0
- package/t.d.ts +205 -0
- package/t.js +9 -0
- package/t.mjs +6 -0
package/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Native addon: optional. Core validate() uses JS codegen and works without it.
|
|
2
2
|
// Buffer APIs (isValid, countValid, isValidParallel) require native.
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
// Loading is delegated so this file stays free of `pkg-prebuilds`/`__dirname`/
|
|
4
|
+
// `path` (the browser entry must not pull those in via the bundler).
|
|
5
|
+
const native = require("./lib/native-load")();
|
|
5
6
|
const {
|
|
6
7
|
compileToJS,
|
|
7
8
|
compileToJSCodegen,
|
|
@@ -287,45 +288,11 @@ const ABORT_EARLY_RESULT = Object.freeze({
|
|
|
287
288
|
})]),
|
|
288
289
|
});
|
|
289
290
|
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (s.charCodeAt(i) >= 0xD800 && s.charCodeAt(i) <= 0xDBFF) {
|
|
296
|
-
let n = 0; for (const _ of s) n++; return n;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return len;
|
|
300
|
-
}`;
|
|
301
|
-
|
|
302
|
-
// The linear-time regex engine, inlined verbatim into standalone output so a
|
|
303
|
-
// compiled module that uses safe `pattern` matchers has no runtime dependency on
|
|
304
|
-
// ata-validator. Read from the source file (one copy, no drift) with the strict
|
|
305
|
-
// directive and CommonJS exports stripped, plus the `__ataSafeRe` alias the
|
|
306
|
-
// emitted code calls. The engine has no eval/new Function, so the embed is
|
|
307
|
-
// CSP-safe.
|
|
308
|
-
// Lazy so merely importing ata never touches the filesystem. Bundlers that stub
|
|
309
|
-
// `fs`/`path` for the browser would otherwise crash at module load; the read is
|
|
310
|
-
// deferred to the first standalone compile that actually embeds the engine.
|
|
311
|
-
let _safeRegexEmbed = null;
|
|
312
|
-
function getSafeRegexEmbed() {
|
|
313
|
-
if (_safeRegexEmbed === null) {
|
|
314
|
-
const raw = require("fs").readFileSync(require("path").join(__dirname, "lib", "safe-regex.js"), "utf8");
|
|
315
|
-
const body = raw
|
|
316
|
-
.replace(/^'use strict'\s*\n/, "")
|
|
317
|
-
.replace(/\nmodule\.exports[^\n]*\n?/, "\n");
|
|
318
|
-
_safeRegexEmbed = body.trimEnd() + "\nconst __ataSafeRe = compileSafe;";
|
|
319
|
-
}
|
|
320
|
-
return _safeRegexEmbed;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Returns the engine embed when any supplied compiled function references the
|
|
324
|
-
// safe matcher (jsFn._usesSafeRe), else an empty string so non-pattern modules
|
|
325
|
-
// pay zero bytes.
|
|
326
|
-
function safeRePrelude(...fns) {
|
|
327
|
-
return fns.some((f) => f && f._usesSafeRe) ? getSafeRegexEmbed() + "\n" : "";
|
|
328
|
-
}
|
|
291
|
+
// `_CP_LEN_SOURCE`, the safe-regex embed, and the AOT helpers that consume them
|
|
292
|
+
// now live in `lib/aot.js` — keeping this file free of `fs`/`path`/`__dirname`
|
|
293
|
+
// references so a default import never touches disk. The instance and static
|
|
294
|
+
// AOT methods further down lazily require `./lib/aot`, so they pay nothing
|
|
295
|
+
// until a user calls `toStandaloneModule`/`bundleStandalone`/etc.
|
|
329
296
|
|
|
330
297
|
// Above this size, simdjson On Demand (selective field access) beats JSON.parse
|
|
331
298
|
// (which must materialize the full JS object tree). Buffer.from + NAPI ~2x faster.
|
|
@@ -1097,6 +1064,44 @@ class Validator {
|
|
|
1097
1064
|
};
|
|
1098
1065
|
}
|
|
1099
1066
|
|
|
1067
|
+
// Custom error messages: if any subschema declares an `errorMessage`
|
|
1068
|
+
// keyword, install an outermost decorator that overrides the `message`
|
|
1069
|
+
// field of the errors it owns. Gated on a one-time scan so schemas without
|
|
1070
|
+
// errorMessage keep the validate hot path untouched. Layered after rich
|
|
1071
|
+
// enrichment so `code`/`keyword`/`path` are already final and only the
|
|
1072
|
+
// human-facing message changes.
|
|
1073
|
+
{
|
|
1074
|
+
const emLib = require('./lib/error-messages');
|
|
1075
|
+
const schemaStr = this._schemaStr || (this._schemaObj ? JSON.stringify(this._schemaObj) : '');
|
|
1076
|
+
if (emLib.schemaHasErrorMessages(schemaStr)) {
|
|
1077
|
+
const root = this._schemaObj;
|
|
1078
|
+
const wrap = (inner) => (arg) => {
|
|
1079
|
+
const result = inner(arg);
|
|
1080
|
+
if (result && result.valid === false && result.errors && result.errors.length && result !== ABORT_EARLY_RESULT) {
|
|
1081
|
+
const overridden = emLib.applyErrorMessages(result.errors, root);
|
|
1082
|
+
if (overridden !== result.errors) return { valid: false, errors: overridden };
|
|
1083
|
+
}
|
|
1084
|
+
return result;
|
|
1085
|
+
};
|
|
1086
|
+
if (this.validate) this.validate = wrap(this.validate);
|
|
1087
|
+
if (this.validateJSON) this.validateJSON = wrap(this.validateJSON);
|
|
1088
|
+
// validateAndParse routes through self.validate on the codegen path, but
|
|
1089
|
+
// the native-only path returns directly from the addon — wrap it so both
|
|
1090
|
+
// paths get overrides. The result shape carries `value`, preserved here.
|
|
1091
|
+
if (this.validateAndParse) {
|
|
1092
|
+
const innerVP = this.validateAndParse;
|
|
1093
|
+
this.validateAndParse = (arg) => {
|
|
1094
|
+
const result = innerVP(arg);
|
|
1095
|
+
if (result && result.valid === false && result.errors && result.errors.length) {
|
|
1096
|
+
const overridden = emLib.applyErrorMessages(result.errors, root);
|
|
1097
|
+
if (overridden !== result.errors) return { valid: false, value: result.value, errors: overridden };
|
|
1098
|
+
}
|
|
1099
|
+
return result;
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1100
1105
|
// Save to identity cache for ultra-fast reuse with same schema object
|
|
1101
1106
|
if (this._schemaObj && typeof this._schemaObj === 'object') {
|
|
1102
1107
|
_identityCache.set(this._schemaObj, this);
|
|
@@ -1160,157 +1165,18 @@ class Validator {
|
|
|
1160
1165
|
}
|
|
1161
1166
|
}
|
|
1162
1167
|
|
|
1163
|
-
// ---
|
|
1164
|
-
//
|
|
1165
|
-
//
|
|
1168
|
+
// --- AOT pre-compilation ---
|
|
1169
|
+
// The bodies of `toStandalone` and `toStandaloneModule` live in `lib/aot.js`
|
|
1170
|
+
// so this entry stays free of `fs`/`path`/`__dirname`. The lazy require runs
|
|
1171
|
+
// only on first call, never during a plain `import 'ata-validator'`. Browser
|
|
1172
|
+
// bundlers swap `lib/aot.js` with `lib/aot.browser.js`, which throws a
|
|
1173
|
+
// pointed error instead of attempting to read source from disk.
|
|
1166
1174
|
toStandalone() {
|
|
1167
|
-
|
|
1168
|
-
const jsFn = this._jsFn;
|
|
1169
|
-
if (!jsFn || !jsFn._source) return null;
|
|
1170
|
-
const src = jsFn._source;
|
|
1171
|
-
const hybridSrc = jsFn._hybridSource || "";
|
|
1172
|
-
const preambleSrc = jsFn._preambleSource || "";
|
|
1173
|
-
|
|
1174
|
-
// Also capture error function source for zero-compile standalone load
|
|
1175
|
-
const jsErrFn = compileToJSCodegenWithErrors(
|
|
1176
|
-
typeof this._schemaObj === "object" ? this._schemaObj : {},
|
|
1177
|
-
);
|
|
1178
|
-
const errSrc = jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : "";
|
|
1179
|
-
|
|
1180
|
-
return `// Auto-generated by ata-validator — do not edit
|
|
1181
|
-
'use strict';
|
|
1182
|
-
${_CP_LEN_SOURCE}
|
|
1183
|
-
${safeRePrelude(jsFn, jsErrFn)}${preambleSrc}
|
|
1184
|
-
const boolFn = function(d) {
|
|
1185
|
-
${src}
|
|
1186
|
-
};
|
|
1187
|
-
const hybridFactory = function(R, E) {
|
|
1188
|
-
return function(d) {
|
|
1189
|
-
${hybridSrc}
|
|
1190
|
-
};
|
|
1191
|
-
};
|
|
1192
|
-
${errSrc ? `const errFn = function(d, _all) {\n ${errSrc}\n};` : "const errFn = null;"}
|
|
1193
|
-
module.exports = { boolFn, hybridFactory, errFn };
|
|
1194
|
-
`;
|
|
1175
|
+
return require('./lib/aot').toStandalone(this);
|
|
1195
1176
|
}
|
|
1196
1177
|
|
|
1197
|
-
// --- Fully standalone module ---
|
|
1198
|
-
// Generates a self-contained module that can be imported directly without
|
|
1199
|
-
// pulling in ata-validator at runtime. Browser bundle gets only the
|
|
1200
|
-
// generated validator (~2 KB typical) instead of the 165 KB compiler.
|
|
1201
|
-
//
|
|
1202
|
-
// import { validate, isValid } from './user-validator.mjs'
|
|
1203
|
-
// if (isValid(data)) { ... }
|
|
1204
|
-
//
|
|
1205
|
-
// format: 'esm' | 'cjs'. Default 'esm'.
|
|
1206
|
-
// abortEarly: if true, invalid result is a shared stub; smaller output.
|
|
1207
1178
|
toStandaloneModule(opts) {
|
|
1208
|
-
|
|
1209
|
-
const jsFn = this._jsFn;
|
|
1210
|
-
if (!jsFn || !jsFn._source) return null;
|
|
1211
|
-
const format = (opts && opts.format) || 'esm';
|
|
1212
|
-
const abortEarly = !!(opts && opts.abortEarly);
|
|
1213
|
-
const source = !!(opts && opts.source);
|
|
1214
|
-
const sourceMap = opts && opts.sourceMap ? opts.sourceMap : null;
|
|
1215
|
-
const schemaFile = opts && opts.schemaFile ? opts.schemaFile : null;
|
|
1216
|
-
const src = jsFn._source;
|
|
1217
|
-
|
|
1218
|
-
let errCore = '';
|
|
1219
|
-
let jsErrFn = null;
|
|
1220
|
-
if (!abortEarly) {
|
|
1221
|
-
jsErrFn = compileToJSCodegenWithErrors(
|
|
1222
|
-
typeof this._schemaObj === 'object' ? this._schemaObj : {},
|
|
1223
|
-
null,
|
|
1224
|
-
this._userFormats,
|
|
1225
|
-
(source && sourceMap && schemaFile) ? { sourceMap, schemaFile } : null,
|
|
1226
|
-
);
|
|
1227
|
-
const errSrc = jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : '';
|
|
1228
|
-
if (errSrc) {
|
|
1229
|
-
errCore = `const errFn = function(d, _all) {\n ${errSrc}\n};\n`;
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
// Schema-source frames are baked as literals inside each emitted error so
|
|
1234
|
-
// consumers don't need a runtime lookup. We still expose the schema file
|
|
1235
|
-
// as a sentinel constant when --source is on — handy for introspection
|
|
1236
|
-
// and visible in source graphs. With --no-source, the constant is omitted
|
|
1237
|
-
// entirely so size budgets and grep-based "is this source-mapped?" checks
|
|
1238
|
-
// both work.
|
|
1239
|
-
const schemaSourceConst = (source && schemaFile)
|
|
1240
|
-
? `const __ATA_SCHEMA_SOURCE__ = ${JSON.stringify({ file: schemaFile })};\n`
|
|
1241
|
-
: '';
|
|
1242
|
-
|
|
1243
|
-
// Serialize closure vars referenced in _fn body: regex, sub-validators, sets.
|
|
1244
|
-
let closureDecls = '';
|
|
1245
|
-
if (jsFn._closures && jsFn._closures.length > 0) {
|
|
1246
|
-
const lines = [];
|
|
1247
|
-
for (const { name, val } of jsFn._closures) {
|
|
1248
|
-
if (Array.isArray(val)) {
|
|
1249
|
-
lines.push(`const ${name} = ${JSON.stringify(val)};`);
|
|
1250
|
-
continue;
|
|
1251
|
-
}
|
|
1252
|
-
if (val && val.__ataSafe) {
|
|
1253
|
-
lines.push(`const ${name} = __ataSafeRe(${JSON.stringify(val.source)});`);
|
|
1254
|
-
} else if (val instanceof RegExp) {
|
|
1255
|
-
const flags = val.flags;
|
|
1256
|
-
lines.push(`const ${name} = new RegExp(${JSON.stringify(val.source)}${flags ? ', ' + JSON.stringify(flags) : ''});`);
|
|
1257
|
-
} else if (val instanceof Set) {
|
|
1258
|
-
lines.push(`const ${name} = new Set(${JSON.stringify([...val])});`);
|
|
1259
|
-
} else if (typeof val === 'function') {
|
|
1260
|
-
// new Function('_ppv', body) — extract body from toString()
|
|
1261
|
-
const str = val.toString();
|
|
1262
|
-
// Matches: "function anonymous(_ppv\n) {\nbody\n}" or "function(_ppv){body}"
|
|
1263
|
-
const m = str.match(/^function[^(]*\([^)]*\)\s*\{([\s\S]*)\}$/)
|
|
1264
|
-
const body = m ? m[1].trim() : str;
|
|
1265
|
-
lines.push(`const ${name} = function(_ppv) { ${body} };`);
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
if (lines.length) closureDecls = lines.join('\n') + '\n';
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
// Hoisted oneOf/anyOf branch checks live in the boolean fn's preamble (the
|
|
1272
|
-
// runtime emits them before the function). The standalone module must declare
|
|
1273
|
-
// them at module scope too, or _fn references undefined names (e.g. _af1_b0).
|
|
1274
|
-
const preambleDecls = jsFn._preambleSource ? jsFn._preambleSource + '\n' : '';
|
|
1275
|
-
|
|
1276
|
-
// User-supplied format functions are referenced as _uf_<name> by both the
|
|
1277
|
-
// boolean (_fn) and error (errFn) bodies. Embed them via Function#toString
|
|
1278
|
-
// so the standalone module stays self-contained.
|
|
1279
|
-
let formatDecls = '';
|
|
1280
|
-
if (jsFn._formatClosures && jsFn._formatClosures.length > 0) {
|
|
1281
|
-
formatDecls = jsFn._formatClosures
|
|
1282
|
-
.map(({ name, fn }) => `const ${name} = ${fn.toString()};`)
|
|
1283
|
-
.join('\n') + '\n';
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
const validBody = errCore
|
|
1287
|
-
? 'return _fn(data) ? VALID : { valid: false, errors: errFn(data, true).errors }'
|
|
1288
|
-
: 'return _fn(data) ? VALID : ABORT';
|
|
1289
|
-
|
|
1290
|
-
const exports = format === 'esm'
|
|
1291
|
-
? `export { validate, isValid };\nexport default { validate, isValid };\n`
|
|
1292
|
-
: `module.exports = { validate, isValid };\nmodule.exports.default = module.exports;\n`;
|
|
1293
|
-
|
|
1294
|
-
return `// Auto-generated by ata-validator — do not edit.
|
|
1295
|
-
// Schema is embedded; runtime has zero dependency on ata-validator.
|
|
1296
|
-
'use strict';
|
|
1297
|
-
${_CP_LEN_SOURCE}
|
|
1298
|
-
${safeRePrelude(jsFn, jsErrFn)}${schemaSourceConst}const VALID = Object.freeze({ valid: true, errors: Object.freeze([]) });
|
|
1299
|
-
const ABORT = Object.freeze({
|
|
1300
|
-
valid: false,
|
|
1301
|
-
errors: Object.freeze([Object.freeze({
|
|
1302
|
-
code: 'ATA9000',
|
|
1303
|
-
message: 'validation failed',
|
|
1304
|
-
keyword: '__abort_early__',
|
|
1305
|
-
path: '',
|
|
1306
|
-
})]),
|
|
1307
|
-
});
|
|
1308
|
-
${closureDecls}${preambleDecls}${formatDecls}const _fn = function(d) {
|
|
1309
|
-
${src}
|
|
1310
|
-
};
|
|
1311
|
-
${errCore}function isValid(data) { return _fn(data); }
|
|
1312
|
-
function validate(data) { ${validBody}; }
|
|
1313
|
-
${exports}`;
|
|
1179
|
+
return require('./lib/aot').toStandaloneModule(this, opts);
|
|
1314
1180
|
}
|
|
1315
1181
|
|
|
1316
1182
|
// Load a pre-compiled standalone module. Zero schema compilation.
|
|
@@ -1494,202 +1360,65 @@ function validate(schema, data) {
|
|
|
1494
1360
|
return v.validate(data);
|
|
1495
1361
|
}
|
|
1496
1362
|
|
|
1363
|
+
// Async validation for schemas built with `t.refine(...)`. Structural
|
|
1364
|
+
// validation runs synchronously first; refinements are awaited only when the
|
|
1365
|
+
// value is structurally valid (a refinement body may assume the right shape).
|
|
1366
|
+
// Accepts a schema literal or an existing Validator instance plus its schema.
|
|
1367
|
+
// Returns a Promise<ValidationResult>.
|
|
1368
|
+
async function validateAsync(schemaOrValidator, data) {
|
|
1369
|
+
const refineLib = require('./lib/refine');
|
|
1370
|
+
let validator, schema;
|
|
1371
|
+
if (schemaOrValidator instanceof Validator) {
|
|
1372
|
+
validator = schemaOrValidator;
|
|
1373
|
+
schema = validator._schemaObj;
|
|
1374
|
+
} else {
|
|
1375
|
+
schema = schemaOrValidator;
|
|
1376
|
+
validator = new Validator(schema);
|
|
1377
|
+
}
|
|
1378
|
+
const structural = validator.validate(data);
|
|
1379
|
+
if (!structural.valid) return structural;
|
|
1380
|
+
const refinements = refineLib.getRefinements(schema);
|
|
1381
|
+
if (!refinements) return structural;
|
|
1382
|
+
const issues = await refineLib.runRefinements(refinements, structural.data !== undefined ? structural.data : data);
|
|
1383
|
+
if (issues.length) return { valid: false, errors: issues };
|
|
1384
|
+
return structural;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// parseAsync resolves to the validated data, or rejects with an Error whose
|
|
1388
|
+
// `.errors` carries the ValidationError list. Mirrors the parse/validate split
|
|
1389
|
+
// used by Zod-style callers.
|
|
1390
|
+
async function parseAsync(schemaOrValidator, data) {
|
|
1391
|
+
const result = await validateAsync(schemaOrValidator, data);
|
|
1392
|
+
if (result.valid) return result.data !== undefined ? result.data : data;
|
|
1393
|
+
const err = new Error('ata: async validation failed');
|
|
1394
|
+
err.errors = result.errors;
|
|
1395
|
+
throw err;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1497
1398
|
function version() {
|
|
1498
1399
|
if (native) return native.version();
|
|
1499
|
-
try { return require("./
|
|
1400
|
+
try { return require("./lib/version"); } catch { return "unknown"; }
|
|
1500
1401
|
}
|
|
1501
1402
|
|
|
1502
|
-
//
|
|
1503
|
-
//
|
|
1504
|
-
//
|
|
1505
|
-
//
|
|
1506
|
-
//
|
|
1507
|
-
// const validators = Validator.loadBundle(require('./validators.js'), [schema1, schema2, ...]);
|
|
1403
|
+
// Static AOT entry points are thin lazy-loaders into `lib/aot.js`. The
|
|
1404
|
+
// implementation files (and the `fs`/`path` reads they perform) only enter
|
|
1405
|
+
// the process when one of these is actually called. See `lib/aot.js` for the
|
|
1406
|
+
// generated module shapes; browser bundles get `lib/aot.browser.js` (a stub
|
|
1407
|
+
// that throws) via the package.json `browser` field.
|
|
1508
1408
|
Validator.bundle = function (schemas, opts) {
|
|
1509
|
-
|
|
1510
|
-
const v = new Validator(schema, opts);
|
|
1511
|
-
const standalone = v.toStandalone();
|
|
1512
|
-
if (!standalone) return "null";
|
|
1513
|
-
return (
|
|
1514
|
-
"(function(){" +
|
|
1515
|
-
standalone
|
|
1516
|
-
.replace("'use strict';", "")
|
|
1517
|
-
.replace("module.exports = ", "return ") +
|
|
1518
|
-
"})()"
|
|
1519
|
-
);
|
|
1520
|
-
});
|
|
1521
|
-
return "'use strict';\nmodule.exports = [\n" + parts.join(",\n") + "\n];\n";
|
|
1409
|
+
return require('./lib/aot').bundle(Validator, schemas, opts);
|
|
1522
1410
|
};
|
|
1523
1411
|
|
|
1524
|
-
// Zero-dependency self-contained bundle — no require('ata-validator') needed at runtime.
|
|
1525
|
-
// opts.format: 'cjs' (default) or 'esm'.
|
|
1526
|
-
// opts.formats: { name: fn } — embedded in the output via Function#toString.
|
|
1527
1412
|
Validator.bundleStandalone = function (schemas, opts) {
|
|
1528
|
-
|
|
1529
|
-
// an $id. Skip the schemas-as-map plumbing when none of them do.
|
|
1530
|
-
const haveIds = schemas.some((s) => s && typeof s === 'object' && s.$id);
|
|
1531
|
-
const bundleOpts = haveIds ? { ...(opts || {}), schemas } : (opts || {});
|
|
1532
|
-
const format = (opts && opts.format) || 'cjs';
|
|
1533
|
-
const R = "Object.freeze({valid:true,errors:Object.freeze([])})";
|
|
1534
|
-
let bundleUsesSafeRe = false;
|
|
1535
|
-
const fns = schemas.map((schema) => {
|
|
1536
|
-
const v = new Validator(schema, bundleOpts);
|
|
1537
|
-
v._ensureCompiled();
|
|
1538
|
-
const jsFn = v._jsFn;
|
|
1539
|
-
if (!jsFn || !jsFn._hybridSource) return "null";
|
|
1540
|
-
const jsErrFn = compileToJSCodegenWithErrors(
|
|
1541
|
-
typeof schema === "string" ? JSON.parse(schema) : schema,
|
|
1542
|
-
v._schemaMap,
|
|
1543
|
-
v._userFormats,
|
|
1544
|
-
);
|
|
1545
|
-
if (jsFn._usesSafeRe || (jsErrFn && jsErrFn._usesSafeRe)) bundleUsesSafeRe = true;
|
|
1546
|
-
const errBody =
|
|
1547
|
-
jsErrFn && jsErrFn._errSource
|
|
1548
|
-
? jsErrFn._errSource
|
|
1549
|
-
: "return{valid:false,errors:[{code:'error',path:'',message:'validation failed'}]}";
|
|
1550
|
-
// Serialize custom format closures so the bundle has no runtime dep on ata.
|
|
1551
|
-
let preamble = '';
|
|
1552
|
-
if (jsFn._formatClosures) {
|
|
1553
|
-
preamble = jsFn._formatClosures
|
|
1554
|
-
.map(({ name, fn }) => `var ${name}=${fn.toString()};`)
|
|
1555
|
-
.join('\n');
|
|
1556
|
-
}
|
|
1557
|
-
// Include hoisted anyOf/oneOf branch helpers (e.g. `_af1_b0`) so the
|
|
1558
|
-
// bundle output is self-contained. `toStandalone` emits this same source
|
|
1559
|
-
// for single-schema standalone output.
|
|
1560
|
-
if (jsFn._preambleSource) {
|
|
1561
|
-
preamble = preamble ? `${preamble}\n${jsFn._preambleSource}` : jsFn._preambleSource;
|
|
1562
|
-
}
|
|
1563
|
-
if (opts && opts.verbose) {
|
|
1564
|
-
// Embed the schema and a small resolver so errors carry parentSchema.
|
|
1565
|
-
const schemaLit = JSON.stringify(typeof schema === 'string' ? JSON.parse(schema) : schema);
|
|
1566
|
-
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)`;
|
|
1567
|
-
}
|
|
1568
|
-
return `(function(R){${preamble}var E=function(d){var _all=true;${errBody}};return function(d){${jsFn._hybridSource}}})(R)`;
|
|
1569
|
-
});
|
|
1570
|
-
const arr = `[${fns.join(",")}]`;
|
|
1571
|
-
const safeEmbed = bundleUsesSafeRe ? getSafeRegexEmbed() + "\n" : "";
|
|
1572
|
-
if (format === 'esm') {
|
|
1573
|
-
return `// Auto-generated by ata-validator — do not edit\n${safeEmbed}const R=${R};\nconst validators=${arr};\nexport default validators;\nexport { validators };\n`;
|
|
1574
|
-
}
|
|
1575
|
-
return `'use strict';\n${safeEmbed}var R=${R};\nmodule.exports=[${fns.join(",")}];\n`;
|
|
1413
|
+
return require('./lib/aot').bundleStandalone(Validator, schemas, opts);
|
|
1576
1414
|
};
|
|
1577
1415
|
|
|
1578
|
-
// Compact bundle: deduplicated code. Shared template functions + per-schema params.
|
|
1579
|
-
// Much smaller file → faster V8 parse → faster startup.
|
|
1580
|
-
// opts.format: 'cjs' (default) or 'esm'.
|
|
1581
1416
|
Validator.bundleCompact = function (schemas, opts) {
|
|
1582
|
-
|
|
1583
|
-
const bundleOpts = haveIds ? { ...(opts || {}), schemas } : (opts || {});
|
|
1584
|
-
const format = (opts && opts.format) || 'cjs';
|
|
1585
|
-
let bundleUsesSafeRe = false;
|
|
1586
|
-
// Analyze schemas and group by structure
|
|
1587
|
-
const entries = schemas.map((schema) => {
|
|
1588
|
-
const v = new Validator(schema, bundleOpts);
|
|
1589
|
-
v._ensureCompiled();
|
|
1590
|
-
const jsFn = v._jsFn;
|
|
1591
|
-
if (!jsFn || !jsFn._hybridSource) return null;
|
|
1592
|
-
const jsErrFn = compileToJSCodegenWithErrors(
|
|
1593
|
-
typeof schema === "string" ? JSON.parse(schema) : schema,
|
|
1594
|
-
v._schemaMap,
|
|
1595
|
-
v._userFormats,
|
|
1596
|
-
);
|
|
1597
|
-
if (jsFn._usesSafeRe || (jsErrFn && jsErrFn._usesSafeRe)) bundleUsesSafeRe = true;
|
|
1598
|
-
// Hoisted anyOf/oneOf branch helpers (e.g. `_af1_b0`) must travel with the
|
|
1599
|
-
// hybrid body or it references undefined names. Prepending keeps dedup honest:
|
|
1600
|
-
// schemas with different branch sets no longer collide on body alone.
|
|
1601
|
-
const hybrid = jsFn._preambleSource
|
|
1602
|
-
? `${jsFn._preambleSource}\n${jsFn._hybridSource}`
|
|
1603
|
-
: jsFn._hybridSource;
|
|
1604
|
-
return {
|
|
1605
|
-
hybrid,
|
|
1606
|
-
err: jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : null,
|
|
1607
|
-
fmt: jsFn._formatClosures || null,
|
|
1608
|
-
};
|
|
1609
|
-
});
|
|
1610
|
-
|
|
1611
|
-
// Deduplicate function bodies — many schemas produce identical or near-identical code
|
|
1612
|
-
const bodyMap = new Map(); // body → index
|
|
1613
|
-
const bodies = [];
|
|
1614
|
-
const errMap = new Map();
|
|
1615
|
-
const errBodies = [];
|
|
1616
|
-
|
|
1617
|
-
const indices = entries.map((e) => {
|
|
1618
|
-
if (!e) return [-1, -1];
|
|
1619
|
-
let hi = bodyMap.get(e.hybrid);
|
|
1620
|
-
if (hi === undefined) {
|
|
1621
|
-
hi = bodies.length;
|
|
1622
|
-
bodies.push(e.hybrid);
|
|
1623
|
-
bodyMap.set(e.hybrid, hi);
|
|
1624
|
-
}
|
|
1625
|
-
let ei = -1;
|
|
1626
|
-
if (e.err) {
|
|
1627
|
-
ei = errMap.get(e.err);
|
|
1628
|
-
if (ei === undefined) {
|
|
1629
|
-
ei = errBodies.length;
|
|
1630
|
-
errBodies.push(e.err);
|
|
1631
|
-
errMap.set(e.err, ei);
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
return [hi, ei];
|
|
1635
|
-
});
|
|
1636
|
-
|
|
1637
|
-
// Generate compact bundle
|
|
1638
|
-
const isEsm = format === 'esm';
|
|
1639
|
-
let out = isEsm
|
|
1640
|
-
? "// Auto-generated by ata-validator — do not edit\n"
|
|
1641
|
-
: "'use strict';\n";
|
|
1642
|
-
if (bundleUsesSafeRe) out += getSafeRegexEmbed() + "\n";
|
|
1643
|
-
const declKW = isEsm ? "const" : "var";
|
|
1644
|
-
out += `${declKW} R=Object.freeze({valid:true,errors:Object.freeze([])});\n`;
|
|
1645
|
-
|
|
1646
|
-
// User format functions are referenced as _uf_<name> by the hybrid and error
|
|
1647
|
-
// bodies. Collect them across all schemas (deduped by name) and embed via
|
|
1648
|
-
// Function#toString so the bundle stays self-contained.
|
|
1649
|
-
const fmtSeen = new Set();
|
|
1650
|
-
for (const e of entries) {
|
|
1651
|
-
if (!e || !e.fmt) continue;
|
|
1652
|
-
for (const { name, fn } of e.fmt) {
|
|
1653
|
-
if (fmtSeen.has(name)) continue;
|
|
1654
|
-
fmtSeen.add(name);
|
|
1655
|
-
out += `${declKW} ${name}=${fn.toString()};\n`;
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
// Shared hybrid factories
|
|
1660
|
-
out += `${declKW} H=[\n`;
|
|
1661
|
-
out += bodies
|
|
1662
|
-
.map((b) => `function(R,E){return function(d){${b}}}`)
|
|
1663
|
-
.join(",\n");
|
|
1664
|
-
out += "\n];\n";
|
|
1665
|
-
|
|
1666
|
-
// Shared error functions
|
|
1667
|
-
out += `${declKW} EF=[\n`;
|
|
1668
|
-
out += errBodies.map((b) => `function(d){var _all=true;${b}}`).join(",\n");
|
|
1669
|
-
out += "\n];\n";
|
|
1670
|
-
|
|
1671
|
-
// Build validators from shared templates
|
|
1672
|
-
const arrBody = indices
|
|
1673
|
-
.map(([hi, ei]) => {
|
|
1674
|
-
if (hi < 0) return "null";
|
|
1675
|
-
if (ei >= 0) return `H[${hi}](R,EF[${ei}])`;
|
|
1676
|
-
return `H[${hi}](R,function(){return{valid:false,errors:[]}})`;
|
|
1677
|
-
})
|
|
1678
|
-
.join(",");
|
|
1679
|
-
if (isEsm) {
|
|
1680
|
-
out += `const validators=[${arrBody}];\nexport default validators;\nexport { validators };\n`;
|
|
1681
|
-
} else {
|
|
1682
|
-
out += `module.exports=[${arrBody}];\n`;
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
return out;
|
|
1417
|
+
return require('./lib/aot').bundleCompact(Validator, schemas, opts);
|
|
1686
1418
|
};
|
|
1687
1419
|
|
|
1688
1420
|
Validator.loadBundle = function (mods, schemas, opts) {
|
|
1689
|
-
return
|
|
1690
|
-
if (mods[i]) return Validator.fromStandalone(mods[i], schema, opts);
|
|
1691
|
-
return new Validator(schema, opts);
|
|
1692
|
-
});
|
|
1421
|
+
return require('./lib/aot').loadBundle(Validator, mods, schemas, opts);
|
|
1693
1422
|
};
|
|
1694
1423
|
|
|
1695
1424
|
const parseJSON = native ? native.parseJSON : JSON.parse;
|
|
@@ -1764,6 +1493,8 @@ module.exports = {
|
|
|
1764
1493
|
Validator,
|
|
1765
1494
|
compile,
|
|
1766
1495
|
validate,
|
|
1496
|
+
validateAsync,
|
|
1497
|
+
parseAsync,
|
|
1767
1498
|
version,
|
|
1768
1499
|
createPaddedBuffer,
|
|
1769
1500
|
SIMDJSON_PADDING,
|
package/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import mod from './index.js';
|
|
2
|
-
export const { Validator, validate, version, createPaddedBuffer, SIMDJSON_PADDING, defineSchema, renderPretty, renderCompact, renderJSON } = mod;
|
|
2
|
+
export const { Validator, validate, validateAsync, parseAsync, version, createPaddedBuffer, SIMDJSON_PADDING, defineSchema, renderPretty, renderCompact, renderJSON } = mod;
|
|
3
3
|
export default mod;
|
package/lib/aot-build.js
CHANGED
|
@@ -231,4 +231,33 @@ async function watch(opts, onReport) {
|
|
|
231
231
|
};
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
|
|
234
|
+
// AOT primitives also surface here so consumers can do
|
|
235
|
+
// import { bundleStandalone, toStandaloneModule, bundleCompact } from 'ata-validator/build'
|
|
236
|
+
// without poking at the Validator class. The Validator-bound forms (Validator.bundleStandalone,
|
|
237
|
+
// validator.toStandaloneModule) remain available from the default entry; this is the
|
|
238
|
+
// programmatic alias for code that wants the build surface in one place.
|
|
239
|
+
const aot = require('./aot');
|
|
240
|
+
|
|
241
|
+
function bundleStandalone(schemas, opts) {
|
|
242
|
+
return aot.bundleStandalone(Validator, schemas, opts);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function bundleCompact(schemas, opts) {
|
|
246
|
+
return aot.bundleCompact(Validator, schemas, opts);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function toStandaloneModule(schema, opts) {
|
|
250
|
+
const v = schema instanceof Validator ? schema : new Validator(schema, opts);
|
|
251
|
+
return aot.toStandaloneModule(v, opts);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = {
|
|
255
|
+
build,
|
|
256
|
+
expandGlobs,
|
|
257
|
+
parseSchemaFile,
|
|
258
|
+
outputPathFor,
|
|
259
|
+
watch,
|
|
260
|
+
bundleStandalone,
|
|
261
|
+
bundleCompact,
|
|
262
|
+
toStandaloneModule,
|
|
263
|
+
};
|