ata-validator 1.0.2 → 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 +14 -0
- package/README.md +19 -2
- package/index.js +18 -2
- package/lib/aot.js +1 -1
- package/lib/js-compiler.js +32 -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,20 @@
|
|
|
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
|
+
|
|
5
19
|
## 1.0.2 - 2026-07-17
|
|
6
20
|
|
|
7
21
|
### 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,
|
|
@@ -569,6 +569,10 @@ class Validator {
|
|
|
569
569
|
};
|
|
570
570
|
} else {
|
|
571
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();
|
|
572
576
|
}
|
|
573
577
|
return this.isValidObject(data);
|
|
574
578
|
};
|
|
@@ -1004,6 +1008,18 @@ class Validator {
|
|
|
1004
1008
|
return valid;
|
|
1005
1009
|
};
|
|
1006
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;
|
|
1007
1023
|
}
|
|
1008
1024
|
|
|
1009
1025
|
// richErrors enrichment: layered on top of whichever validate path was
|
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
|
@@ -702,6 +702,27 @@ function canResolveDynamicRefs(target, callingSchema, schemaMap) {
|
|
|
702
702
|
return refs.every(r => anchors.has(r))
|
|
703
703
|
}
|
|
704
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
|
+
|
|
705
726
|
// Recursively check if a schema can be safely compiled to JS codegen.
|
|
706
727
|
// Returns false if any sub-schema contains features codegen gets wrong.
|
|
707
728
|
function codegenSafe(schema, schemaMap) {
|
|
@@ -809,6 +830,17 @@ function codegenSafe(schema, schemaMap) {
|
|
|
809
830
|
// Check $defs: targets must be safe, names must be simple, no nested $ref chains
|
|
810
831
|
const defs = schema.$defs || schema.definitions
|
|
811
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
|
|
812
844
|
for (const [name, def] of Object.entries(defs)) {
|
|
813
845
|
if (/[~/"']/.test(name)) return false // special chars in def name
|
|
814
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_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",
|
|
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",
|
package/t.d.ts
CHANGED
|
@@ -167,6 +167,72 @@ export interface TRef extends JSONSchema { $ref: string }
|
|
|
167
167
|
export interface TAny extends JSONSchema {}
|
|
168
168
|
export interface TNever extends JSONSchema { not: {} }
|
|
169
169
|
|
|
170
|
+
/** Any object schema the modifier combinators accept. */
|
|
171
|
+
export interface TObjectLike extends JSONSchema {
|
|
172
|
+
type: 'object';
|
|
173
|
+
properties: Record<string, JSONSchema>;
|
|
174
|
+
required?: ReadonlyArray<string>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
type ReqUnion<S> = S extends { required: ReadonlyArray<infer R> } ? R : never;
|
|
178
|
+
|
|
179
|
+
export interface TPick<S extends TObjectLike, K extends ReadonlyArray<string>> extends JSONSchema {
|
|
180
|
+
type: 'object';
|
|
181
|
+
properties: { [P in Extract<keyof S['properties'], K[number]>]: S['properties'][P] };
|
|
182
|
+
required: ReadonlyArray<Extract<ReqUnion<S>, K[number]>>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface TOmit<S extends TObjectLike, K extends ReadonlyArray<string>> extends JSONSchema {
|
|
186
|
+
type: 'object';
|
|
187
|
+
properties: { [P in Exclude<Extract<keyof S['properties'], string>, K[number]>]: S['properties'][P] };
|
|
188
|
+
required: ReadonlyArray<Exclude<Extract<ReqUnion<S>, string>, K[number]>>;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface TPartial<S extends TObjectLike> extends JSONSchema {
|
|
192
|
+
type: 'object';
|
|
193
|
+
properties: S['properties'];
|
|
194
|
+
required: ReadonlyArray<never>;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface TRequiredAll<S extends TObjectLike> extends JSONSchema {
|
|
198
|
+
type: 'object';
|
|
199
|
+
properties: S['properties'];
|
|
200
|
+
required: ReadonlyArray<Extract<keyof S['properties'], string>>;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface TRequiredWith<S extends TObjectLike, K extends ReadonlyArray<string>> extends JSONSchema {
|
|
204
|
+
type: 'object';
|
|
205
|
+
properties: S['properties'];
|
|
206
|
+
required: ReadonlyArray<Extract<ReqUnion<S> | K[number], Extract<keyof S['properties'], string>>>;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
type MergeProps<A, B> = { [K in Extract<keyof A | keyof B, string>]: K extends keyof B ? B[K] : K extends keyof A ? A[K] : never };
|
|
210
|
+
|
|
211
|
+
type CompositeProps<S extends ReadonlyArray<TObjectLike>> =
|
|
212
|
+
S extends readonly [] ? {} :
|
|
213
|
+
S extends readonly [infer H extends TObjectLike, ...infer R extends ReadonlyArray<TObjectLike>]
|
|
214
|
+
? MergeProps<H['properties'], CompositeProps<R>>
|
|
215
|
+
: Record<string, JSONSchema>;
|
|
216
|
+
|
|
217
|
+
type CompositeReq<S extends ReadonlyArray<TObjectLike>> =
|
|
218
|
+
S extends readonly [] ? never :
|
|
219
|
+
S extends readonly [infer H extends TObjectLike, ...infer R extends ReadonlyArray<TObjectLike>]
|
|
220
|
+
? ReqUnion<H> | CompositeReq<R>
|
|
221
|
+
: never;
|
|
222
|
+
|
|
223
|
+
export interface TComposite<S extends ReadonlyArray<TObjectLike>> extends JSONSchema {
|
|
224
|
+
type: 'object';
|
|
225
|
+
properties: CompositeProps<S>;
|
|
226
|
+
required: ReadonlyArray<Extract<CompositeReq<S>, string>>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface TSelf extends JSONSchema { $ref: '#/$defs/self' }
|
|
230
|
+
|
|
231
|
+
export interface TRecursive<S extends JSONSchema> extends JSONSchema {
|
|
232
|
+
$ref: '#/$defs/self';
|
|
233
|
+
$defs: { self: S };
|
|
234
|
+
}
|
|
235
|
+
|
|
170
236
|
export interface TBuilder {
|
|
171
237
|
string(opts?: StringOptions): TString;
|
|
172
238
|
number(opts?: NumericOptions): TNumber;
|
|
@@ -189,6 +255,24 @@ export interface TBuilder {
|
|
|
189
255
|
intersect<const S extends ReadonlyArray<JSONSchema>>(schemas: S): TIntersect<S>;
|
|
190
256
|
ref(pointer: string): TRef;
|
|
191
257
|
|
|
258
|
+
pick<const S extends TObjectLike, const K extends ReadonlyArray<Extract<keyof S['properties'], string>>>(schema: S, keys: K): TPick<S, K>;
|
|
259
|
+
omit<const S extends TObjectLike, const K extends ReadonlyArray<Extract<keyof S['properties'], string>>>(schema: S, keys: K): TOmit<S, K>;
|
|
260
|
+
|
|
261
|
+
partial<const S extends TObjectLike>(schema: S): TPartial<S>;
|
|
262
|
+
required<const S extends TObjectLike>(schema: S): TRequiredAll<S>;
|
|
263
|
+
required<const S extends TObjectLike, const K extends ReadonlyArray<Extract<keyof S['properties'], string>>>(schema: S, keys: K): TRequiredWith<S, K>;
|
|
264
|
+
|
|
265
|
+
composite<const S extends ReadonlyArray<TObjectLike>>(schemas: S, opts?: ObjectOptions): TComposite<S>;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Build a recursive (self-referential) schema. The `self` argument passed to
|
|
269
|
+
* `build` is a `$ref` pointing to `#/$defs/self`, and the emitted schema
|
|
270
|
+
* routes through the interpreted engine at runtime. Nesting `t.recursive`
|
|
271
|
+
* inside itself is unsupported: both layers would claim `#/$defs/self` and
|
|
272
|
+
* the inner definition would silently overwrite the outer one.
|
|
273
|
+
*/
|
|
274
|
+
recursive<S extends JSONSchema>(build: (self: TSelf) => S, opts?: { title?: string; description?: string }): TRecursive<S>;
|
|
275
|
+
|
|
192
276
|
any(): TAny;
|
|
193
277
|
unknown(): TAny;
|
|
194
278
|
never(): TNever;
|