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 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 _buildDeferred below if something asks for an error, and the
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. _buildDeferred compiles them on the first rejection.
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
- // Builds the two generators the compile step left out, once, and upgrades
1352
- // the shared cache entry. `undefined` means not built yet; `null` means the
1353
- // compiler declined. Conflating those is what once cost every schema its
1354
- // generated error function for the life of the process, so they stay apart.
1355
- const _buildDeferred = () => {
1356
- if (jsCombinedFn !== undefined && jsErrFn !== undefined) return;
1357
- const uf2 = this._userFormats;
1358
- if (jsCombinedFn === undefined) jsCombinedFn = compileToJSCombined(schemaObj, VALID_RESULT, sm, uf2) || null;
1359
- if (jsErrFn === undefined) jsErrFn = compileToJSCodegenWithErrors(schemaObj, sm, uf2) || null;
1360
- if (!uf2) {
1361
- const entry = _compileCache.get(mapKey);
1362
- if (entry && entry.jsFn === jsFn) {
1363
- entry.combined = jsCombinedFn;
1364
- entry.errFn = jsErrFn;
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
- _buildDeferred();
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
- _buildDeferred();
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 _buildDeferred looks for; `null` would read as
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
- const types = Array.isArray(prop.type) ? prop.type : [prop.type];
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
- const itemTypes = Array.isArray(it.type) ? it.type : [it.type];
295
- if (itemTypes.every((t) => _CLONE_PRIMITIVE.has(t))) return read;
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);