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 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 `optional` keys, `array`, `tuple`, `record`, `union`, `intersect`, `literal`, `const`, `enum`), and refs (`ref`). 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.
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 install` / `npm run build`:
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 `pkg-prebuilds`/`__dirname`/
4
- // `path` (the browser entry must not pull those in via the bundler).
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`, `__dirname`, and `pkg-prebuilds`.
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
  //
@@ -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 never resolve `pkg-prebuilds`.
4
+ // package.json `browser` field so bundlers skip native platform probing.
5
5
 
6
6
  module.exports = function loadNative() {
7
7
  return null;
@@ -1,21 +1,93 @@
1
1
  'use strict';
2
2
 
3
- // Locate and load the optional native addon (pkg-prebuilds). Lives in its own
4
- // module so the default and browser entries can stay free of `pkg-prebuilds`,
5
- // `__dirname`, and `path` references. In browser bundles this file is replaced
6
- // with `native-load.browser.js` via the package.json `browser` field, so the
7
- // `pkg-prebuilds` import never enters the bundle in the first place.
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
- // Returns the loaded native binding object, or `null` if no prebuild matches
10
- // (musl/Alpine, unsupported arch, devs running before `npm run build`, etc.).
11
- // The JS codegen path covers all core validation when this returns null.
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 pkg = require('pkg-prebuilds');
17
- return pkg(path.join(__dirname, '..'), require('../binding-options'));
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
- return null;
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 (e.g. `pkg-prebuilds`) into browser builds
6
- // even though the runtime never touches them.
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.2';
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.2",
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
- "install": "node scripts/install.js",
45
- "prepublishOnly": "node scripts/regen-safe-regex-source.js && node scripts/check-prebuilds.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",
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
- "prebuild": "pkg-prebuilds-copy --baseDir build/Release --source ata.node --name=ata --strip --napi_version=10",
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
- "binding-options.js",
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
- "dependencies": {
126
- "node-addon-api": "^8.7.0",
127
- "node-api-headers": "^1.8.0",
128
- "pkg-prebuilds": "^1.0.0"
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;