ata-validator 1.0.1 → 1.1.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 +22 -0
- package/README.md +19 -2
- package/index.js +69 -18
- package/lib/aot.js +1 -1
- package/lib/js-compiler.js +37 -0
- package/lib/native-load.browser.js +1 -1
- package/lib/native-load.js +83 -11
- package/lib/t.js +90 -0
- package/lib/version.js +3 -3
- package/package.json +14 -20
- package/t.d.ts +84 -0
- package/CMakeLists.txt +0 -150
- package/binding/ata_napi.cpp +0 -1574
- package/binding-options.js +0 -4
- package/deps/simdjson/simdjson.cpp +0 -56221
- package/deps/simdjson/simdjson.h +0 -122784
- package/include/ata.h +0 -111
- package/include/ata_c.h +0 -57
- package/prebuilds/ata-darwin-arm64/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-arm64/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-arm64-musl/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-x64/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-x64-musl/node-napi-v10.node +0 -0
- package/prebuilds/ata-win32-x64/node-napi-v10.node +0 -0
- package/scripts/check-doc-coverage.js +0 -34
- package/scripts/check-prebuilds.js +0 -54
- package/scripts/install.js +0 -23
- package/scripts/regen-error-codes-doc.js +0 -40
- package/scripts/regen-lock.js +0 -19
- package/scripts/regen-safe-regex-source.js +0 -33
- package/src/ata.cpp +0 -3146
- package/src/ata_c.cpp +0 -63
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
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.1.0 - 2026-07-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- TypeBox-style modifier combinators on `ata-validator/t`: `t.pick`, `t.omit`, `t.partial`, `t.required`, `t.composite`, and `t.recursive`. All six emit plain JSON Schema, so `Infer`, the runtime validator, and the AOT pipeline consume them with no adapter. This closes the authoring-parity gap for TypeBox migrations. Note: `t.recursive` schemas validate through the interpreted engine and are not AOT-precompilable; the other five combinators AOT-compile like any schema.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- The `ata-validator` package is now pure JavaScript: no bundled binaries, no vendored C++ sources, no install script. The native engine moved to per-platform `@ata-validator/native-*` packages, installed automatically as optional dependencies (the same pattern Vite uses for esbuild). The tarball shrinks from ~5.3 MB to under 300 KB. `npm install --omit=optional` or `ATA_NO_NATIVE=1` gives a guaranteed zero-binary setup; validation behavior is identical for every schema shape the JS engine compiles, and the few shapes that still need the native engine now throw a clear error instead (see Fixed below).
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Schemas the JS engine cannot compile (some `$dynamicRef`, cyclic `$ref`, and unusual keyword interactions) crashed with `Maximum call stack size exceeded` in environments without the native addon: the lazy `validate` stub and the rich-error wrapper dispatched to each other forever. These schemas now throw a clear "not supported by the pure-JS engine" error on first use. The same cycle could hit `isValidObject` even with the native addon present when it was the first method called; it now falls through to the full compile and validates correctly.
|
|
18
|
+
|
|
19
|
+
## 1.0.2 - 2026-07-17
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Cross-schema `$ref` pointers into `#/definitions/...` resolved to nothing after draft-07 normalization renamed the target to `$defs`, and the generated validator silently accepted invalid data. The pointer walk now treats `definitions` and `$defs` as aliases.
|
|
24
|
+
- Schemas passed by the caller (the `schemas` option, `addSchema()`, and the root schema) were normalized in place, mutating objects the caller still owns. Anyone reusing those objects afterwards, such as Fastify handing the same shared schema to its serializer, saw corrupted keys. Normalization now works on a copy; caller objects are never touched.
|
|
25
|
+
- The new copy preserves symbol-keyed markers, so `t.refine` refinements survive normalization and `validateAsync` keeps enforcing them.
|
|
26
|
+
|
|
5
27
|
## 1.0.1 - 2026-07-16
|
|
6
28
|
|
|
7
29
|
### Fixed
|
package/README.md
CHANGED
|
@@ -14,6 +14,14 @@ npm install --save-dev ata-validator
|
|
|
14
14
|
npx ata build 'schemas/*.json' --out-dir src/generated
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
The `ata-validator` package itself is pure JavaScript. The native accelerator (simdjson parsing, parallel NDJSON, buffer APIs) ships as per-platform optional packages that npm installs automatically where they fit, the same pattern Vite uses for esbuild. For a guaranteed zero-binary install:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install ata-validator --omit=optional
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
or set `ATA_NO_NATIVE=1` at runtime. Validation behavior is identical for the schema shapes the JS engine compiles, which covers typical API payload schemas. The buffer and parallel APIs, plus a small set of complex shapes (some `$dynamicRef`, cyclic `$ref`, and unusual keyword interactions), currently need the native engine and throw a clear error without it rather than validating incorrectly.
|
|
24
|
+
|
|
17
25
|
In your code:
|
|
18
26
|
|
|
19
27
|
```ts
|
|
@@ -227,7 +235,14 @@ type User = Infer<typeof User>
|
|
|
227
235
|
const v = new Validator(User)
|
|
228
236
|
```
|
|
229
237
|
|
|
230
|
-
The builder covers primitives (`string`, `number`, `integer`, `boolean`, `null`), composites (`object` with
|
|
238
|
+
The builder covers primitives (`string`, `number`, `integer`, `boolean`, `null`), composites (`object` with optional keys, `array`, `tuple`, `record`, `union`, `intersect`, `literal`, `const`, `enum`), plus the TypeBox-style modifiers `pick`, `omit`, `partial`, `required`, `composite`, and `recursive`. Optionality is carried by a Symbol marker that the emitted JSON Schema and ata's codegen never see, so the output is still a plain JSON Schema literal that you can pass to anything that takes one.
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
const User = t.object({ id: t.integer(), name: t.string(), email: t.optional(t.string()) })
|
|
242
|
+
const Patch = t.partial(t.omit(User, ['id']))
|
|
243
|
+
type Patch = Infer<typeof Patch>
|
|
244
|
+
// { name?: string; email?: string }
|
|
245
|
+
```
|
|
231
246
|
|
|
232
247
|
#### Async refinement
|
|
233
248
|
|
|
@@ -462,6 +477,8 @@ If one of these blocks you, open an issue; scope decisions get revisited with re
|
|
|
462
477
|
|
|
463
478
|
## Building from Source
|
|
464
479
|
|
|
480
|
+
This section applies to contributors building the repository. Regular `npm install` users need not have a C++ toolchain.
|
|
481
|
+
|
|
465
482
|
### Development prerequisites
|
|
466
483
|
|
|
467
484
|
Native builds require C/C++ toolchain support and the following libraries:
|
|
@@ -470,7 +487,7 @@ Native builds require C/C++ toolchain support and the following libraries:
|
|
|
470
487
|
- `abseil`
|
|
471
488
|
- `mimalloc`
|
|
472
489
|
|
|
473
|
-
Install them before running `npm
|
|
490
|
+
Install them before running `npm run build`:
|
|
474
491
|
|
|
475
492
|
```bash
|
|
476
493
|
# macOS (Homebrew)
|
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Native addon: optional. Core validate() uses JS codegen and works without it.
|
|
2
2
|
// Buffer APIs (isValid, countValid, isValidParallel) require native.
|
|
3
|
-
// Loading is delegated so this file stays free of
|
|
4
|
-
// `path` (the browser entry must not pull those in
|
|
3
|
+
// Loading is delegated to lib/native-load.js so this file stays free of
|
|
4
|
+
// platform probing and `path` (the browser entry must not pull those in).
|
|
5
5
|
const native = require("./lib/native-load")();
|
|
6
6
|
const {
|
|
7
7
|
compileToJS,
|
|
@@ -344,22 +344,57 @@ function createPaddedBuffer(jsonStr) {
|
|
|
344
344
|
return { buffer: padded, length: jsonBuf.length };
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
+
// Deep-clone a value, copying own symbol keys by reference at every level.
|
|
348
|
+
// Arrays and plain objects are cloned recursively; primitives, RegExp,
|
|
349
|
+
// functions, and other non-plain values are returned as-is. Symbol values
|
|
350
|
+
// (e.g. refinement lists, OPTIONAL markers) are owned by the caller's builder
|
|
351
|
+
// and sharing them is correct — they are never mutated by normalization.
|
|
352
|
+
function _deepCloneWithSymbols(v) {
|
|
353
|
+
if (v === null || typeof v !== 'object') return v;
|
|
354
|
+
if (Array.isArray(v)) {
|
|
355
|
+
const a = new Array(v.length);
|
|
356
|
+
for (let i = 0; i < v.length; i++) a[i] = _deepCloneWithSymbols(v[i]);
|
|
357
|
+
return a;
|
|
358
|
+
}
|
|
359
|
+
// Only clone plain objects (skip RegExp, Date, etc.).
|
|
360
|
+
if (Object.getPrototypeOf(v) !== Object.prototype && Object.getPrototypeOf(v) !== null) return v;
|
|
361
|
+
const out = Object.create(null);
|
|
362
|
+
for (const k of Object.keys(v)) Object.defineProperty(out, k, { value: _deepCloneWithSymbols(v[k]), writable: true, enumerable: true, configurable: true });
|
|
363
|
+
for (const sym of Object.getOwnPropertySymbols(v)) out[sym] = v[sym];
|
|
364
|
+
return Object.setPrototypeOf(out, Object.prototype);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Normalize a caller-provided schema without mutating the original.
|
|
368
|
+
// Clones only when normalization would change the object (draft-07 keys
|
|
369
|
+
// present or nullable fields present). Internal-only — not exported.
|
|
370
|
+
function _normalizeCallerSchema(s) {
|
|
371
|
+
const needsDraft7 = s && s.$schema && (
|
|
372
|
+
s.$schema === 'http://json-schema.org/draft-07/schema#' ||
|
|
373
|
+
s.$schema === 'http://json-schema.org/draft-07/schema'
|
|
374
|
+
)
|
|
375
|
+
const str = JSON.stringify(s)
|
|
376
|
+
const copy = _deepCloneWithSymbols(s)
|
|
377
|
+
if (needsDraft7) normalizeDraft7(copy)
|
|
378
|
+
normalizeNullable(copy)
|
|
379
|
+
// Return original when normalization produced no change, copy otherwise.
|
|
380
|
+
// Change-detection uses JSON content only; symbols do not affect it.
|
|
381
|
+
return JSON.stringify(copy) === str ? s : copy
|
|
382
|
+
}
|
|
383
|
+
|
|
347
384
|
function buildSchemaMap(schemas) {
|
|
348
385
|
if (!schemas) return null
|
|
349
386
|
const map = new Map()
|
|
350
387
|
if (Array.isArray(schemas)) {
|
|
351
388
|
for (const s of schemas) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const id = s.$id
|
|
389
|
+
const normalized = _normalizeCallerSchema(s)
|
|
390
|
+
const id = normalized.$id
|
|
355
391
|
if (!id) throw new Error('Schema in schemas option must have $id')
|
|
356
|
-
map.set(id,
|
|
392
|
+
map.set(id, normalized)
|
|
357
393
|
}
|
|
358
394
|
} else {
|
|
359
395
|
for (const [key, s] of Object.entries(schemas)) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
map.set(s.$id || key, s)
|
|
396
|
+
const normalized = _normalizeCallerSchema(s)
|
|
397
|
+
map.set(normalized.$id || key, normalized)
|
|
363
398
|
}
|
|
364
399
|
}
|
|
365
400
|
return map
|
|
@@ -449,7 +484,6 @@ function resolveSchemaForPreprocess(schema, schemaMap) {
|
|
|
449
484
|
class Validator {
|
|
450
485
|
constructor(schema, opts) {
|
|
451
486
|
const options = opts || {};
|
|
452
|
-
const schemaObj = typeof schema === "string" ? JSON.parse(schema) : schema;
|
|
453
487
|
|
|
454
488
|
// Ultra-fast path: same schema object reference -> return cached instance
|
|
455
489
|
// JS constructor returning an object makes `new` return that object
|
|
@@ -459,10 +493,12 @@ class Validator {
|
|
|
459
493
|
if (hit) return hit;
|
|
460
494
|
}
|
|
461
495
|
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
//
|
|
465
|
-
|
|
496
|
+
// When schema is a string, JSON.parse already produces a fresh object.
|
|
497
|
+
// When schema is an object, normalization runs on a clone so the caller's
|
|
498
|
+
// object is never touched.
|
|
499
|
+
const schemaObj = typeof schema === "string"
|
|
500
|
+
? JSON.parse(schema)
|
|
501
|
+
: _normalizeCallerSchema(schema);
|
|
466
502
|
|
|
467
503
|
this._schemaStr = null; // lazy: computed on first use
|
|
468
504
|
this._schemaObj = schemaObj;
|
|
@@ -533,6 +569,10 @@ class Validator {
|
|
|
533
569
|
};
|
|
534
570
|
} else {
|
|
535
571
|
this._ensureCodegen();
|
|
572
|
+
// Codegen can bail on shapes it cannot represent; the full compile
|
|
573
|
+
// binds the native path or the unsupported thrower instead of
|
|
574
|
+
// leaving this stub to re-dispatch to itself.
|
|
575
|
+
if (!this._jsFn) this._ensureCompiled();
|
|
536
576
|
}
|
|
537
577
|
return this.isValidObject(data);
|
|
538
578
|
};
|
|
@@ -968,6 +1008,18 @@ class Validator {
|
|
|
968
1008
|
return valid;
|
|
969
1009
|
};
|
|
970
1010
|
}
|
|
1011
|
+
} else {
|
|
1012
|
+
// No JS codegen and no native engine. Leaving the lazy stubs in place
|
|
1013
|
+
// makes them re-dispatch to themselves forever, so bind a clear error.
|
|
1014
|
+
const unsupported = () => {
|
|
1015
|
+
throw new Error(
|
|
1016
|
+
'This schema is not supported by the pure-JS engine and the native addon is unavailable in this environment'
|
|
1017
|
+
);
|
|
1018
|
+
};
|
|
1019
|
+
this.validate = unsupported;
|
|
1020
|
+
this.isValidObject = unsupported;
|
|
1021
|
+
this.validateJSON = unsupported;
|
|
1022
|
+
this.isValidJSON = unsupported;
|
|
971
1023
|
}
|
|
972
1024
|
|
|
973
1025
|
// richErrors enrichment: layered on top of whichever validate path was
|
|
@@ -1132,10 +1184,9 @@ class Validator {
|
|
|
1132
1184
|
if (!schema || !schema.$id) {
|
|
1133
1185
|
throw new Error('Schema must have $id')
|
|
1134
1186
|
}
|
|
1135
|
-
//
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
this._schemaMap.set(schema.$id, schema)
|
|
1187
|
+
// Normalize a copy so the caller's object is never mutated.
|
|
1188
|
+
const normalized = _normalizeCallerSchema(schema)
|
|
1189
|
+
this._schemaMap.set(normalized.$id, normalized)
|
|
1139
1190
|
}
|
|
1140
1191
|
|
|
1141
1192
|
_ensureCodegen() {
|
package/lib/aot.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// All ahead-of-time (AOT) code generation lives here so the default and browser
|
|
4
|
-
// entries can stay free of `fs`, `path`,
|
|
4
|
+
// entries can stay free of `fs`, `path`, and `__dirname`.
|
|
5
5
|
// Browser bundles get `lib/aot.browser.js` instead (see package.json `browser`
|
|
6
6
|
// field), which throws if anyone tries to call an AOT function from the browser.
|
|
7
7
|
//
|
package/lib/js-compiler.js
CHANGED
|
@@ -549,6 +549,11 @@ function walkJsonPointer(root, fragment) {
|
|
|
549
549
|
let target = root
|
|
550
550
|
for (const p of parts) {
|
|
551
551
|
if (target == null || typeof target !== 'object') return null
|
|
552
|
+
if (!(p in target)) {
|
|
553
|
+
const alt = p === 'definitions' ? '$defs' : p === '$defs' ? 'definitions' : null
|
|
554
|
+
if (alt !== null && alt in target) { target = target[alt]; continue }
|
|
555
|
+
return null
|
|
556
|
+
}
|
|
552
557
|
target = target[p]
|
|
553
558
|
}
|
|
554
559
|
return target == null ? null : target
|
|
@@ -697,6 +702,27 @@ function canResolveDynamicRefs(target, callingSchema, schemaMap) {
|
|
|
697
702
|
return refs.every(r => anchors.has(r))
|
|
698
703
|
}
|
|
699
704
|
|
|
705
|
+
// Returns true if the node's subtree contains a local $ref pointing to one of
|
|
706
|
+
// the given def names (i.e., '#/$defs/Name' or '#/definitions/Name' where Name
|
|
707
|
+
// is in defNames). Used to detect whether a $defs body cycles back to a sibling.
|
|
708
|
+
function subtreeRefersToLocalDef(node, defNames) {
|
|
709
|
+
if (typeof node !== 'object' || node === null) return false
|
|
710
|
+
if (node.$ref) {
|
|
711
|
+
const m = /^#\/(?:\$defs|definitions)\/([^/]+)$/.exec(node.$ref)
|
|
712
|
+
if (m && defNames.has(m[1])) return true
|
|
713
|
+
}
|
|
714
|
+
for (const val of Object.values(node)) {
|
|
715
|
+
if (typeof val === 'object' && val !== null) {
|
|
716
|
+
if (Array.isArray(val)) {
|
|
717
|
+
for (const item of val) { if (subtreeRefersToLocalDef(item, defNames)) return true }
|
|
718
|
+
} else {
|
|
719
|
+
if (subtreeRefersToLocalDef(val, defNames)) return true
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return false
|
|
724
|
+
}
|
|
725
|
+
|
|
700
726
|
// Recursively check if a schema can be safely compiled to JS codegen.
|
|
701
727
|
// Returns false if any sub-schema contains features codegen gets wrong.
|
|
702
728
|
function codegenSafe(schema, schemaMap) {
|
|
@@ -804,6 +830,17 @@ function codegenSafe(schema, schemaMap) {
|
|
|
804
830
|
// Check $defs: targets must be safe, names must be simple, no nested $ref chains
|
|
805
831
|
const defs = schema.$defs || schema.definitions
|
|
806
832
|
if (defs) {
|
|
833
|
+
const defNames = new Set(Object.keys(defs))
|
|
834
|
+
// Detect cyclic local refs: if any def's own body references back to a sibling
|
|
835
|
+
// def, the schema contains a cycle. The JS codegen returns (() => true) on cycle
|
|
836
|
+
// (permissive), so cyclic schemas must route to the interpreted engine instead.
|
|
837
|
+
// We check only the def bodies, not the top-level schema, so that a plain
|
|
838
|
+
// { $ref: '#/$defs/Name', $defs: { Name: { type: 'string' } } } (no cycle)
|
|
839
|
+
// still compiles.
|
|
840
|
+
const hasCycle = Object.values(defs).some(
|
|
841
|
+
(def) => typeof def === 'object' && def !== null && subtreeRefersToLocalDef(def, defNames)
|
|
842
|
+
)
|
|
843
|
+
if (hasCycle) return false
|
|
807
844
|
for (const [name, def] of Object.entries(defs)) {
|
|
808
845
|
if (/[~/"']/.test(name)) return false // special chars in def name
|
|
809
846
|
if (typeof def === 'boolean') return false
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// Browser stub: there is no native addon in the browser. Swapped in via the
|
|
4
|
-
// package.json `browser` field so bundlers
|
|
4
|
+
// package.json `browser` field so bundlers skip native platform probing.
|
|
5
5
|
|
|
6
6
|
module.exports = function loadNative() {
|
|
7
7
|
return null;
|
package/lib/native-load.js
CHANGED
|
@@ -1,21 +1,93 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Locate
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// Locate the optional native engine. Resolution order:
|
|
4
|
+
// 1. ATA_NO_NATIVE set -> null (explicit pure-JS mode)
|
|
5
|
+
// 2. @ata-validator/native-* -> the per-platform optional package
|
|
6
|
+
// 3. other libc variant -> linux only, in case detection misfired
|
|
7
|
+
// 4. repo-local dev build -> contributors working from source
|
|
8
|
+
// 5. null -> JS codegen covers all core validation
|
|
8
9
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
10
|
+
// Lives in its own module so the default and browser entries stay free of
|
|
11
|
+
// platform probing; browser bundles swap this file for
|
|
12
|
+
// `native-load.browser.js` via the package.json `browser` field.
|
|
13
|
+
|
|
14
|
+
const VERSION = require('./version');
|
|
15
|
+
|
|
16
|
+
function nativePackageName(platform, arch, isMusl) {
|
|
17
|
+
if (platform === 'darwin') return arch === 'arm64' ? '@ata-validator/native-darwin-arm64' : null;
|
|
18
|
+
if (platform === 'win32') return arch === 'x64' ? '@ata-validator/native-win32-x64' : null;
|
|
19
|
+
if (platform === 'linux') {
|
|
20
|
+
if (arch !== 'x64' && arch !== 'arm64') return null;
|
|
21
|
+
return `@ata-validator/native-linux-${arch}-${isMusl ? 'musl' : 'gnu'}`;
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function detectMusl() {
|
|
27
|
+
// glibc exposes its version in the process report header; musl does not.
|
|
28
|
+
try {
|
|
29
|
+
const report = process.report && process.report.getReport && process.report.getReport();
|
|
30
|
+
return !!report && !!report.header && !report.header.glibcVersionRuntime;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let warned = false;
|
|
37
|
+
function checkVersion(binding, source) {
|
|
38
|
+
if (!binding) return null;
|
|
39
|
+
try {
|
|
40
|
+
const v = typeof binding.version === 'function' ? binding.version() : null;
|
|
41
|
+
if (v && v !== VERSION) {
|
|
42
|
+
if (!warned) {
|
|
43
|
+
warned = true;
|
|
44
|
+
process.emitWarning(
|
|
45
|
+
`ata-validator ${VERSION} found a native engine reporting ${v} (${source}); ` +
|
|
46
|
+
'ignoring it and using the pure-JS engine. Reinstall to realign versions.',
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return binding;
|
|
55
|
+
}
|
|
12
56
|
|
|
13
57
|
module.exports = function loadNative() {
|
|
58
|
+
if (process.env.ATA_NO_NATIVE) return null;
|
|
59
|
+
|
|
60
|
+
const isMusl = process.platform === 'linux' ? detectMusl() : false;
|
|
61
|
+
const candidates = [];
|
|
62
|
+
const primary = nativePackageName(process.platform, process.arch, isMusl);
|
|
63
|
+
if (primary) candidates.push(primary);
|
|
64
|
+
if (process.platform === 'linux') {
|
|
65
|
+
const alt = nativePackageName(process.platform, process.arch, !isMusl);
|
|
66
|
+
if (alt) candidates.push(alt);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const name of candidates) {
|
|
70
|
+
try {
|
|
71
|
+
const binding = checkVersion(require(name), name);
|
|
72
|
+
if (binding) return binding;
|
|
73
|
+
} catch {
|
|
74
|
+
// not installed on this platform; keep going
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Contributors running from a source checkout.
|
|
14
79
|
try {
|
|
15
80
|
const path = require('path');
|
|
16
|
-
const
|
|
17
|
-
|
|
81
|
+
const binding = checkVersion(
|
|
82
|
+
require(path.join(__dirname, '..', 'build', 'Release', 'ata.node')),
|
|
83
|
+
'local build',
|
|
84
|
+
);
|
|
85
|
+
if (binding) return binding;
|
|
18
86
|
} catch {
|
|
19
|
-
|
|
87
|
+
// no dev build; fall through
|
|
20
88
|
}
|
|
89
|
+
|
|
90
|
+
return null;
|
|
21
91
|
};
|
|
92
|
+
|
|
93
|
+
module.exports.nativePackageName = nativePackageName;
|
package/lib/t.js
CHANGED
|
@@ -124,6 +124,90 @@ function refine(schema, check, opts) {
|
|
|
124
124
|
return attachRefine(schema, check, opts);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
function assertObjectSchema(schema, fn) {
|
|
128
|
+
if (!schema || typeof schema !== 'object' || schema.type !== 'object' ||
|
|
129
|
+
!schema.properties || typeof schema.properties !== 'object') {
|
|
130
|
+
throw new Error(`t.${fn}: expected an object schema with properties`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Copy every top-level keyword except the two the combinator recomputes.
|
|
135
|
+
function cloneMeta(schema) {
|
|
136
|
+
const out = {};
|
|
137
|
+
for (const key of Object.keys(schema)) {
|
|
138
|
+
if (key !== 'properties' && key !== 'required') out[key] = schema[key];
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function pick(schema, keys) {
|
|
144
|
+
assertObjectSchema(schema, 'pick');
|
|
145
|
+
const keep = new Set(keys);
|
|
146
|
+
const props = {};
|
|
147
|
+
for (const key of Object.keys(schema.properties)) {
|
|
148
|
+
if (keep.has(key)) props[key] = schema.properties[key];
|
|
149
|
+
}
|
|
150
|
+
const required = (schema.required || []).filter((key) => keep.has(key));
|
|
151
|
+
const out = Object.assign(cloneMeta(schema), { properties: props });
|
|
152
|
+
if (required.length) out.required = required;
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function omit(schema, keys) {
|
|
157
|
+
assertObjectSchema(schema, 'omit');
|
|
158
|
+
const drop = new Set(keys);
|
|
159
|
+
const props = {};
|
|
160
|
+
for (const key of Object.keys(schema.properties)) {
|
|
161
|
+
if (!drop.has(key)) props[key] = schema.properties[key];
|
|
162
|
+
}
|
|
163
|
+
const required = (schema.required || []).filter((key) => !drop.has(key));
|
|
164
|
+
const out = Object.assign(cloneMeta(schema), { properties: props });
|
|
165
|
+
if (required.length) out.required = required;
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function partial(schema) {
|
|
170
|
+
assertObjectSchema(schema, 'partial');
|
|
171
|
+
return Object.assign(cloneMeta(schema), { properties: schema.properties });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function requiredOf(schema, keys) {
|
|
175
|
+
assertObjectSchema(schema, 'required');
|
|
176
|
+
const propKeys = Object.keys(schema.properties);
|
|
177
|
+
let required;
|
|
178
|
+
if (keys === undefined) {
|
|
179
|
+
required = propKeys.slice();
|
|
180
|
+
} else {
|
|
181
|
+
const known = new Set(propKeys);
|
|
182
|
+
required = Array.from(new Set([...(schema.required || []), ...keys])).filter((k) => known.has(k));
|
|
183
|
+
}
|
|
184
|
+
const out = Object.assign(cloneMeta(schema), { properties: schema.properties });
|
|
185
|
+
if (required.length) out.required = required;
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function recursive(build, opts) {
|
|
190
|
+
// Single fixed def name; nesting t.recursive inside itself is unsupported
|
|
191
|
+
// (both layers would claim '#/$defs/self') and documented as such.
|
|
192
|
+
const self = { $ref: '#/$defs/self' };
|
|
193
|
+
const body = build(self);
|
|
194
|
+
return Object.assign({ $ref: '#/$defs/self', $defs: { self: body } }, opts);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function composite(schemas, opts) {
|
|
198
|
+
const props = {};
|
|
199
|
+
const requiredSet = new Set();
|
|
200
|
+
for (const schema of schemas) {
|
|
201
|
+
assertObjectSchema(schema, 'composite');
|
|
202
|
+
for (const key of Object.keys(schema.properties)) props[key] = schema.properties[key];
|
|
203
|
+
for (const key of schema.required || []) requiredSet.add(key);
|
|
204
|
+
}
|
|
205
|
+
const required = Array.from(requiredSet).filter((key) => Object.hasOwn(props, key));
|
|
206
|
+
const out = { type: 'object', properties: props };
|
|
207
|
+
if (required.length) out.required = required;
|
|
208
|
+
return Object.assign(out, opts);
|
|
209
|
+
}
|
|
210
|
+
|
|
127
211
|
module.exports = {
|
|
128
212
|
string,
|
|
129
213
|
number,
|
|
@@ -146,4 +230,10 @@ module.exports = {
|
|
|
146
230
|
never,
|
|
147
231
|
refine,
|
|
148
232
|
OPTIONAL,
|
|
233
|
+
pick,
|
|
234
|
+
omit,
|
|
235
|
+
partial,
|
|
236
|
+
required: requiredOf,
|
|
237
|
+
composite,
|
|
238
|
+
recursive,
|
|
149
239
|
};
|
package/lib/version.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// The package version, exported as a plain string so neither the default nor
|
|
4
4
|
// the browser entry has to `require('./package.json')`. Bundling package.json
|
|
5
|
-
// would drag dependency strings
|
|
6
|
-
//
|
|
5
|
+
// would drag dependency strings into browser builds even though the runtime
|
|
6
|
+
// never touches them.
|
|
7
7
|
//
|
|
8
8
|
// Kept in lockstep with package.json by `tests/test_version_sync.js`.
|
|
9
9
|
|
|
10
|
-
module.exports = '1.0
|
|
10
|
+
module.exports = '1.1.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.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",
|
|
@@ -37,18 +37,14 @@
|
|
|
37
37
|
"ata": "bin/ata.js"
|
|
38
38
|
},
|
|
39
39
|
"browser": {
|
|
40
|
-
"pkg-prebuilds": false,
|
|
41
40
|
"./lib/native-load.js": "./lib/native-load.browser.js"
|
|
42
41
|
},
|
|
43
42
|
"scripts": {
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"release:check": "node scripts/regen-safe-regex-source.js && node scripts/check-prebuilds.js && 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",
|
|
43
|
+
"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",
|
|
44
|
+
"release:check": "node scripts/regen-safe-regex-source.js && node tests/test_pack_purity.js && 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",
|
|
47
45
|
"build": "cmake-js build --target ata",
|
|
48
46
|
"rebuild": "cmake-js rebuild --target ata",
|
|
49
|
-
"
|
|
50
|
-
"prebuild-all": "npm run prebuild -- --arch x64 && npm run prebuild -- --arch arm64",
|
|
51
|
-
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_data_positions.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
|
|
47
|
+
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_defs_pointer_alias.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_data_positions.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
|
|
52
48
|
"bench:size": "node benchmark/bench_aot_size.mjs",
|
|
53
49
|
"test:suite": "node tests/run_suite.js",
|
|
54
50
|
"test:compat": "node tests/test_compat.js",
|
|
@@ -110,22 +106,18 @@
|
|
|
110
106
|
"t.js",
|
|
111
107
|
"t.mjs",
|
|
112
108
|
"t.d.ts",
|
|
113
|
-
"
|
|
114
|
-
"binding/",
|
|
115
|
-
"include/",
|
|
116
|
-
"src/",
|
|
117
|
-
"deps/",
|
|
118
|
-
"prebuilds/",
|
|
119
|
-
"scripts/",
|
|
120
|
-
"CMakeLists.txt",
|
|
109
|
+
"bin/",
|
|
121
110
|
"README.md",
|
|
122
111
|
"CHANGELOG.md",
|
|
123
112
|
"LICENSE"
|
|
124
113
|
],
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"
|
|
114
|
+
"optionalDependencies": {
|
|
115
|
+
"@ata-validator/native-darwin-arm64": "1.1.0",
|
|
116
|
+
"@ata-validator/native-linux-x64-gnu": "1.1.0",
|
|
117
|
+
"@ata-validator/native-linux-arm64-gnu": "1.1.0",
|
|
118
|
+
"@ata-validator/native-linux-x64-musl": "1.1.0",
|
|
119
|
+
"@ata-validator/native-linux-arm64-musl": "1.1.0",
|
|
120
|
+
"@ata-validator/native-win32-x64": "1.1.0"
|
|
129
121
|
},
|
|
130
122
|
"peerDependencies": {
|
|
131
123
|
"yaml": "^2.0.0"
|
|
@@ -142,6 +134,8 @@
|
|
|
142
134
|
"esbuild": "^0.28.0",
|
|
143
135
|
"jsonc-parser": "^3.3.1",
|
|
144
136
|
"mitata": "^1.0.34",
|
|
137
|
+
"node-addon-api": "^8.7.0",
|
|
138
|
+
"node-api-headers": "^1.8.0",
|
|
145
139
|
"typebox": "^1.1.7",
|
|
146
140
|
"typescript": "^6.0.3",
|
|
147
141
|
"valibot": "^1.3.1",
|