ata-validator 1.32.3 → 1.33.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/index.js +29 -21
- package/lib/clone-emit.js +26 -15
- package/lib/js-compiler.js +1459 -1335
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/index.js
CHANGED
|
@@ -1254,7 +1254,7 @@ class Validator {
|
|
|
1254
1254
|
} else if (cached && cached.jsFn !== undefined) {
|
|
1255
1255
|
// `full` says the error and combined functions exist too. An entry
|
|
1256
1256
|
// without it still carries a verdict function worth reusing; the pair is
|
|
1257
|
-
// built by
|
|
1257
|
+
// built by _buildErr/_buildCombined below when something asks, and the
|
|
1258
1258
|
// entry is upgraded then. `undefined` in `combined`/`errFn` means not
|
|
1259
1259
|
// built yet; `null` means the compiler declined. Those two must never
|
|
1260
1260
|
// blur: reading the first as the second is the bug this cache had once
|
|
@@ -1272,7 +1272,7 @@ class Validator {
|
|
|
1272
1272
|
// are the other two thirds of a cold first call (8.2, 10.4 and 7.8 ms on
|
|
1273
1273
|
// a 120-property config schema, most of it V8 compiling each generator
|
|
1274
1274
|
// the first time it is entered), and a caller that never reads an error
|
|
1275
|
-
// never needs them.
|
|
1275
|
+
// never needs them. _buildErr/_buildCombined compile them on demand.
|
|
1276
1276
|
jsCombinedFn = undefined;
|
|
1277
1277
|
jsErrFn = undefined;
|
|
1278
1278
|
_isCodegen = !!_cgFn;
|
|
@@ -1348,24 +1348,32 @@ class Validator {
|
|
|
1348
1348
|
)));
|
|
1349
1349
|
const useSimdjsonForLarge = !hasArrayTraversal;
|
|
1350
1350
|
|
|
1351
|
-
//
|
|
1352
|
-
// the shared cache entry.
|
|
1353
|
-
//
|
|
1354
|
-
//
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
if (
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
if (
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
entry.full = true;
|
|
1366
|
-
}
|
|
1351
|
+
// Build the generators the compile step left out, each only when something
|
|
1352
|
+
// asks for it, and upgrade the shared cache entry. A first rejection needs
|
|
1353
|
+
// one of the two, not both; building both was a millisecond of V8 compiling
|
|
1354
|
+
// a generator nobody called. `undefined` means not built yet; `null` means
|
|
1355
|
+
// the compiler declined. Conflating those is what once cost every schema
|
|
1356
|
+
// its generated error function for the life of the process, so they stay
|
|
1357
|
+
// apart, and `full` is set only once both exist.
|
|
1358
|
+
const _upgradeCacheEntry = () => {
|
|
1359
|
+
if (this._userFormats) return;
|
|
1360
|
+
const entry = _compileCache.get(mapKey);
|
|
1361
|
+
if (entry && entry.jsFn === jsFn) {
|
|
1362
|
+
if (jsCombinedFn !== undefined) entry.combined = jsCombinedFn;
|
|
1363
|
+
if (jsErrFn !== undefined) entry.errFn = jsErrFn;
|
|
1364
|
+
if (jsCombinedFn !== undefined && jsErrFn !== undefined) entry.full = true;
|
|
1367
1365
|
}
|
|
1368
1366
|
};
|
|
1367
|
+
const _buildCombined = () => {
|
|
1368
|
+
if (jsCombinedFn !== undefined) return;
|
|
1369
|
+
jsCombinedFn = compileToJSCombined(schemaObj, VALID_RESULT, sm, this._userFormats) || null;
|
|
1370
|
+
_upgradeCacheEntry();
|
|
1371
|
+
};
|
|
1372
|
+
const _buildErr = () => {
|
|
1373
|
+
if (jsErrFn !== undefined) return;
|
|
1374
|
+
jsErrFn = compileToJSCodegenWithErrors(schemaObj, sm, this._userFormats) || null;
|
|
1375
|
+
_upgradeCacheEntry();
|
|
1376
|
+
};
|
|
1369
1377
|
|
|
1370
1378
|
if (jsFn) {
|
|
1371
1379
|
// errFn: use JS codegen if safe, else native fallback (only when native
|
|
@@ -1414,7 +1422,7 @@ class Validator {
|
|
|
1414
1422
|
let _errOnlyImpl = null;
|
|
1415
1423
|
const errOnly = (d) => {
|
|
1416
1424
|
if (_errOnlyImpl === null) {
|
|
1417
|
-
|
|
1425
|
+
_buildErr();
|
|
1418
1426
|
let safe = null;
|
|
1419
1427
|
if (jsErrFn) {
|
|
1420
1428
|
try {
|
|
@@ -1452,7 +1460,7 @@ class Validator {
|
|
|
1452
1460
|
const combinedIfSafe = () => {
|
|
1453
1461
|
if (_combinedProbed) return _safeCombined;
|
|
1454
1462
|
_combinedProbed = true;
|
|
1455
|
-
|
|
1463
|
+
_buildCombined();
|
|
1456
1464
|
if (jsCombinedFn) {
|
|
1457
1465
|
try {
|
|
1458
1466
|
const probe = {};
|
|
@@ -2333,7 +2341,7 @@ class Validator {
|
|
|
2333
2341
|
_rememberInstance(this);
|
|
2334
2342
|
// A partial entry: the verdict function is real, the other two are not
|
|
2335
2343
|
// built yet rather than declined. `undefined` is the not-built marker
|
|
2336
|
-
// the full compile's
|
|
2344
|
+
// the full compile's _buildErr/_buildCombined look for; `null` would read as
|
|
2337
2345
|
// "the compiler declined" and cost the schema its error function, which
|
|
2338
2346
|
// is the bug this cache had once already. `isCodegen` rides along so a
|
|
2339
2347
|
// validator that later reuses this entry reports the same engine it
|
package/lib/clone-emit.js
CHANGED
|
@@ -267,22 +267,32 @@ function stripDefaultsDeep(node) {
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
const _CLONE_PRIMITIVE = new Set(['string', 'number', 'integer', 'boolean', 'null']);
|
|
270
|
+
// Whether every value this schema accepts is a primitive, so a copy has
|
|
271
|
+
// nothing inside it to strip and takes the value as it is. A primitive type
|
|
272
|
+
// says so directly. With no type, an enum or const whose values are all
|
|
273
|
+
// primitives says so too: `{ enum: ['USD', 'EUR'] }` is how such a field is
|
|
274
|
+
// usually written, and it declined parse() for the whole schema. So does an
|
|
275
|
+
// anyOf or oneOf whose every branch says so: a value that passes matched one
|
|
276
|
+
// of them. `number | null` arrives from zod's JSON Schema in that form.
|
|
277
|
+
function isPrimitiveOnly(s) {
|
|
278
|
+
if (!s || typeof s !== 'object' || s.$ref) return false;
|
|
279
|
+
if (s.type !== undefined) {
|
|
280
|
+
const types = Array.isArray(s.type) ? s.type : [s.type];
|
|
281
|
+
return types.every((t) => _CLONE_PRIMITIVE.has(t));
|
|
282
|
+
}
|
|
283
|
+
const isPrim = (v) => v === null || (typeof v !== 'object' && typeof v !== 'function');
|
|
284
|
+
if (Array.isArray(s.enum) && s.enum.length > 0 && s.enum.every(isPrim) && s.const === undefined) return true;
|
|
285
|
+
if (s.const !== undefined && isPrim(s.const) && s.enum === undefined) return true;
|
|
286
|
+
for (const k of ['anyOf', 'oneOf']) {
|
|
287
|
+
if (Array.isArray(s[k]) && s[k].length > 0 && s[k].every(isPrimitiveOnly)) return true;
|
|
288
|
+
}
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
270
292
|
function emitValueExpr(prop, read, depth) {
|
|
271
293
|
if (!prop || typeof prop !== 'object') return null;
|
|
272
294
|
if (prop.$ref) return null;
|
|
273
|
-
|
|
274
|
-
if (types.every((t) => _CLONE_PRIMITIVE.has(t))) return read;
|
|
275
|
-
// No type, but an enum or const whose values are all primitives: whatever
|
|
276
|
-
// passes can only be one of those values, so there is nothing inside it to
|
|
277
|
-
// strip and the value is copied as it is. `{ enum: ['USD', 'EUR'] }` is how
|
|
278
|
-
// such a field is usually written, and it declined parse() for the whole
|
|
279
|
-
// schema.
|
|
280
|
-
if (prop.type === undefined) {
|
|
281
|
-
const isPrim = (v) => v === null || (typeof v !== 'object' && typeof v !== 'function');
|
|
282
|
-
if (Array.isArray(prop.enum) && prop.enum.length > 0 && prop.enum.every(isPrim) &&
|
|
283
|
-
prop.const === undefined) return read;
|
|
284
|
-
if (prop.const !== undefined && isPrim(prop.const) && prop.enum === undefined) return read;
|
|
285
|
-
}
|
|
295
|
+
if (isPrimitiveOnly(prop)) return read;
|
|
286
296
|
const isRecordable = (s) => s.properties ||
|
|
287
297
|
(s.additionalProperties && typeof s.additionalProperties === 'object');
|
|
288
298
|
if (prop.type === 'object' && isRecordable(prop)) {
|
|
@@ -291,8 +301,9 @@ function emitValueExpr(prop, read, depth) {
|
|
|
291
301
|
if (prop.type === 'array' && prop.items && typeof prop.items === 'object' &&
|
|
292
302
|
!Array.isArray(prop.items)) {
|
|
293
303
|
const it = prop.items;
|
|
294
|
-
|
|
295
|
-
|
|
304
|
+
// A fresh array, not the input's: handing the same array back let a
|
|
305
|
+
// caller who changed the copy change the input with it.
|
|
306
|
+
if (isPrimitiveOnly(it)) return `${read}.slice()`;
|
|
296
307
|
if (it.type === 'object' && isRecordable(it)) {
|
|
297
308
|
const el = '_e' + depth;
|
|
298
309
|
const inner = emitClone(it, el, depth + 1);
|