ata-validator 1.32.2 → 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/README.md +18 -0
- package/index.js +29 -21
- package/lib/clone-emit.js +26 -4
- package/lib/js-compiler.js +1533 -1338
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -7,6 +7,24 @@ JSON Schema validation that compiles for speed and still runs where code generat
|
|
|
7
7
|
|
|
8
8
|
1.0 is a stability commitment: see [docs/STABILITY.md](docs/STABILITY.md) for the semver, deprecation, and error-code guarantees.
|
|
9
9
|
|
|
10
|
+
## Who uses ata
|
|
11
|
+
|
|
12
|
+
Projects whose code depends on ata, from GitHub's dependency graph, and the framework that lists
|
|
13
|
+
its plugin, with what each does with it:
|
|
14
|
+
|
|
15
|
+
- [Socket](https://github.com/SocketDev) uses ata, compiled ahead of time, in the shared build
|
|
16
|
+
tooling of its repositories.
|
|
17
|
+
- [Fastify](https://fastify.dev/ecosystem/) lists the
|
|
18
|
+
[`fastify-ata`](https://github.com/ata-core/fastify-ata) plugin in its ecosystem.
|
|
19
|
+
- [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form) ships an ata
|
|
20
|
+
validator in its main repository, in a runtime and a precompiled form.
|
|
21
|
+
- [JollyPixel](https://github.com/JollyPixel/editor) parses and validates JSON on its back end
|
|
22
|
+
with ata, compiled ahead of time.
|
|
23
|
+
- [better-drizzle](https://github.com/almeidazs/better-drizzle) ships an ata plugin for queries
|
|
24
|
+
and rows.
|
|
25
|
+
- [svelte-jsonschema-form](https://github.com/x0k/svelte-jsonschema-form) publishes an ata
|
|
26
|
+
validator package, runtime and precompiled.
|
|
27
|
+
|
|
10
28
|
## Quick start
|
|
11
29
|
|
|
12
30
|
```bash
|
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,11 +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;
|
|
295
|
+
if (isPrimitiveOnly(prop)) return read;
|
|
275
296
|
const isRecordable = (s) => s.properties ||
|
|
276
297
|
(s.additionalProperties && typeof s.additionalProperties === 'object');
|
|
277
298
|
if (prop.type === 'object' && isRecordable(prop)) {
|
|
@@ -280,8 +301,9 @@ function emitValueExpr(prop, read, depth) {
|
|
|
280
301
|
if (prop.type === 'array' && prop.items && typeof prop.items === 'object' &&
|
|
281
302
|
!Array.isArray(prop.items)) {
|
|
282
303
|
const it = prop.items;
|
|
283
|
-
|
|
284
|
-
|
|
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()`;
|
|
285
307
|
if (it.type === 'object' && isRecordable(it)) {
|
|
286
308
|
const el = '_e' + depth;
|
|
287
309
|
const inner = emitClone(it, el, depth + 1);
|