ata-validator 0.14.0 → 0.15.1
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 +34 -0
- package/README.md +36 -1
- package/bin/ata.js +189 -13
- package/build.d.ts +6 -0
- package/index.browser.mjs +1 -1
- package/index.d.ts +71 -0
- package/index.js +272 -16
- package/index.mjs +1 -1
- package/lib/aot-build.js +25 -1
- package/lib/branch-collapse.js +75 -0
- package/lib/data-position-cache.js +43 -0
- package/lib/data-positions.js +104 -0
- package/lib/enrich-error.js +125 -0
- package/lib/error-codes.js +96 -0
- package/lib/js-compiler.js +108 -25
- package/lib/levenshtein.js +28 -0
- package/lib/render-compact.js +35 -0
- package/lib/render-json.js +12 -0
- package/lib/render-pretty.js +102 -0
- package/lib/render-shared.js +58 -0
- package/lib/source-positions.js +148 -0
- package/lib/suggestions.js +132 -0
- package/package.json +7 -4
- 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 +34 -0
- package/scripts/regen-error-codes-doc.js +40 -0
- package/scripts/regen-lock.js +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
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
|
+
## 0.15.1 - 2026-05-23
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Coercion, defaults, and `removeAdditional` now follow a cross-schema `$ref` to the referenced shape. A whole-schema reference like `{ $ref: 'shared#' }` (used for shared route schemas) or a property reference like `{ id: { $ref: 'shared#/properties/id' } }` is preprocessed instead of skipped.
|
|
10
|
+
- The compile cache now keys on referenced schema content, not just the `$id`. Two validators that share a root schema string and an `$id` pointing at different schemas no longer reuse the wrong compiled function.
|
|
11
|
+
|
|
12
|
+
## 0.15.0 - 2026-05-18
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Compiler-grade error output.** Every validation error now carries a stable `code` (`ATA####`), an `expected`/`received` pair, a `docUrl`, and, when the input came in as a JSON string or Buffer, a `dataFrame` pointing at the offending bytes. The full registry of 46 codes lives at [`docs/error-codes.md`](docs/error-codes.md) with permalinks at `https://ata-validator.com/e/<CODE>`.
|
|
17
|
+
- **Renderer API.** `renderPretty`, `renderCompact`, and `renderJSON` are exported from `ata-validator`. Pretty output mirrors rustc-style code frames with carets, help, and note lines; compact collapses to one line per error; JSON is structured for tooling.
|
|
18
|
+
- **`ata validate` subcommand.** `ata validate <schema> <data>` runs a schema against a JSON data file and prints renderer output. TTY auto-renders pretty; pipes default to compact; `--format=json` returns structured output. `--pretty`, `--compact`, `--max-errors`, `--color`, `--no-color` cover the rest of the surface.
|
|
19
|
+
- **Runtime source maps.** `new Validator(schema, { source: { path, content } })` attaches per-error `schemaSource` (file, line, col, text) by re-parsing the schema with a position-aware scanner.
|
|
20
|
+
- **AOT source maps.** AOT-compiled validators carry the structured error fields (`code`, `docUrl`) and embed per-error `schemaSource` when built with the source map enabled. On by default in development, off when `NODE_ENV=production` or `--no-source` is passed.
|
|
21
|
+
- **`ata compile` / `ata build` flags.** New `--source` / `--no-source` flags. `ata build --dual` emits both a source-mapped artifact (`*.compiled.mjs`) and a stripped one (`*.compiled.min.mjs`) in a single run.
|
|
22
|
+
- **Size budget gate.** `npm run bench:size` enforces a gzipped-byte budget over the AOT codegen output to catch silent bundle bloat. Baseline at `benchmark/baselines/aot-size.json`, gates derive from the baseline with 1.5x headroom.
|
|
23
|
+
- **`oneOf` / `anyOf` collapse.** Branching failures collapse to a single best-branch error (`ATA4001` / `ATA4002` / `ATA4003`) instead of the full branch-tree. The closest matching variant's errors are still available under `branchErrors`. `allOf` errors continue to surface every failing branch.
|
|
24
|
+
- **Suggestions.** A new `suggestion` field nudges users when ata is confident: typo against enum, missing-required typo, format-violation hint, type-coercion nudge. Runtime validators populate automatically; AOT validators expose `attachSuggestions(errors, data)` to keep AOT bundles small.
|
|
25
|
+
- **`richErrors: false` opt-out.** `new Validator(schema, { richErrors: false })` preserves the v0.14 error shape byte-for-byte. `abortEarly: true` continues to short-circuit; the returned error carries `code: 'ATA9000'` and no enrichment.
|
|
26
|
+
- **`release:check` npm script.** Runs the prebuilds, doc-coverage, and error-code lockfile checks in strict mode before a publish.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- `prepublishOnly` now chains `check-prebuilds`, `check-doc-coverage` (lenient until per-code prose lands), and the error-code lockfile test.
|
|
31
|
+
- `ata compile` and `ata build` failures route through the renderer with code `ATA9002`, so command-line schema errors look the same as runtime ones.
|
|
32
|
+
|
|
33
|
+
### Notes
|
|
34
|
+
|
|
35
|
+
- **Log scrapers**: errors now carry `code`, `dataFrame`, `suggestion`, and `docUrl` fields. If you serialize `result.errors` directly into logs, line size will grow. Pass `richErrors: false` for the v0.14 shape, or pipe through `renderCompact` for a stable one-line format.
|
|
36
|
+
- **AOT bundle size**: source-mapped variants (`.compiled.mjs`) add up to 200 bytes gzipped for a 10-field schema. Production builds (`NODE_ENV=production` or `--no-source`) emit the no-source variant. Use `ata build --dual` to emit both.
|
|
37
|
+
- **Fastify**: a companion `fastify-ata` release wires the new format into route error responses.
|
|
38
|
+
|
|
5
39
|
## 0.14.0 - 2026-05-16
|
|
6
40
|
|
|
7
41
|
### Added
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ata-validator
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
JSON Schema validation with first-class TypeScript and zero runtime cost. AOT compile your schemas to per-schema ESM modules with no validator dependency. `Validator<T>` composes with TypeBox, Zod-from-JSON-Schema, Valibot, or hand-written types. Runtime API available for dynamic schemas.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/ata-validator)
|
|
6
6
|
[](LICENSE)
|
|
@@ -39,6 +39,41 @@ Reproduce on your machine with `npm run bench:aot-vs-ajv`. Numbers measured on A
|
|
|
39
39
|
|
|
40
40
|
The wins are largest on bundle size and compile time because AOT moves work from runtime to build time. Throughput and cold start are also faster because the compiled validator is a tight straight-line function with no schema-walk overhead.
|
|
41
41
|
|
|
42
|
+
## Error messages
|
|
43
|
+
|
|
44
|
+
ata's error output is compiler-grade: each error carries a stable code, an inline source frame pointing at the schema file, and another pointing at the offending bytes in the request payload. Renderers ship in three styles:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { Validator, renderPretty, renderCompact, renderJSON } from 'ata-validator'
|
|
48
|
+
|
|
49
|
+
const v = new Validator(schema, { source: { path: 'schemas/user.json', content: schemaText } })
|
|
50
|
+
const r = v.validateJSON(input)
|
|
51
|
+
if (!r.valid) {
|
|
52
|
+
console.error(renderPretty(r.errors))
|
|
53
|
+
// error[ATA3001]: value does not match format "email"
|
|
54
|
+
// --> schemas/user.json:5:7
|
|
55
|
+
// |
|
|
56
|
+
// 5 | "email": { "type": "string", "format": "email" }
|
|
57
|
+
// | ^^^^^^^ expected format 'email'
|
|
58
|
+
// |
|
|
59
|
+
// --> input, byte 23
|
|
60
|
+
// |
|
|
61
|
+
// 1 | {"name":"M","email":"not-an-email","age":-3}
|
|
62
|
+
// | ^^^^^^^^^^^^^^ got "not-an-email"
|
|
63
|
+
// |
|
|
64
|
+
// = help: missing '@' and domain part
|
|
65
|
+
// = note: see https://ata-validator.com/e/ATA3001
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The `ata` CLI ships `ata validate <schema> <data>` for one-off checks. TTY auto-renders pretty; pipes default to compact; `--format=json` produces structured output for tooling.
|
|
70
|
+
|
|
71
|
+
Errors carry a stable `code` field (`ATA####`), see the [error code registry](docs/error-codes.md). Each code has a permalink at `https://ata-validator.com/e/<CODE>`.
|
|
72
|
+
|
|
73
|
+
### Opting out
|
|
74
|
+
|
|
75
|
+
For consumers who built log dashboards on the v0.14 error shape, `new Validator(schema, { richErrors: false })` returns the legacy shape exactly. For high-throughput paths, `abortEarly: true` continues to short-circuit; the returned error carries `code: 'ATA9000'` and no enrichment.
|
|
76
|
+
|
|
42
77
|
## When to use the runtime API instead
|
|
43
78
|
|
|
44
79
|
`ata build` is for schemas you know at build time. If your schemas are user-supplied at runtime (form builders, no-code platforms, dynamic API ingestion), use the runtime API:
|
package/bin/ata.js
CHANGED
|
@@ -8,8 +8,17 @@ function usage() {
|
|
|
8
8
|
process.stdout.write(`ata-validator CLI
|
|
9
9
|
|
|
10
10
|
Usage:
|
|
11
|
-
ata compile
|
|
12
|
-
ata build
|
|
11
|
+
ata compile <schema-file> [options] Compile one schema to a standalone module.
|
|
12
|
+
ata build <glob>... [options] Compile a project's schemas (glob pattern) per file.
|
|
13
|
+
ata validate <schema> <data> [options] Validate a JSON data file against a schema.
|
|
14
|
+
|
|
15
|
+
Validate options:
|
|
16
|
+
--pretty Render errors with source frames (default on TTY)
|
|
17
|
+
--compact Render errors as one line each (default when piped)
|
|
18
|
+
--format <fmt> Output format: pretty | compact | json
|
|
19
|
+
--max-errors <n> Limit pretty output to N errors (0 = no limit). Default: 20
|
|
20
|
+
--color <when> Color output: auto | always | never. Default: auto
|
|
21
|
+
--no-color Disable color output (alias for --color=never)
|
|
13
22
|
|
|
14
23
|
Compile options:
|
|
15
24
|
-o, --output <file> Output path. Default: <schema-file>.validator.mjs
|
|
@@ -17,6 +26,8 @@ Compile options:
|
|
|
17
26
|
--name <TypeName> Name of the top-level type in .d.ts. Default: inferred from filename
|
|
18
27
|
--no-types Skip .d.ts generation
|
|
19
28
|
--abort-early Use stub errors (smallest bundle)
|
|
29
|
+
--source Embed schema source map (default in development)
|
|
30
|
+
--no-source Omit source map (default in production, NODE_ENV=production)
|
|
20
31
|
|
|
21
32
|
Build options:
|
|
22
33
|
--out-dir <dir> Write outputs into this directory instead of alongside sources
|
|
@@ -29,6 +40,9 @@ Build options:
|
|
|
29
40
|
--strict Treat any AOT-incompatible schema as a build error (default: skip + warn)
|
|
30
41
|
--watch Re-emit on schema change (Ctrl-C to exit)
|
|
31
42
|
--no-types Skip .d.mts/.d.cts emission alongside compiled modules
|
|
43
|
+
--source Embed schema source map (default in development)
|
|
44
|
+
--no-source Omit source map (default in production, NODE_ENV=production)
|
|
45
|
+
--dual Emit both .compiled.mjs (with source) and .compiled.min.mjs (without)
|
|
32
46
|
|
|
33
47
|
-h, --help Show this message
|
|
34
48
|
|
|
@@ -36,11 +50,25 @@ Examples:
|
|
|
36
50
|
ata compile schemas/user.json -o src/generated/user.validator.mjs
|
|
37
51
|
ata build 'schemas/*.json'
|
|
38
52
|
ata build 'src/**/*.schema.json' --out-dir build/validators
|
|
53
|
+
ata validate schemas/user.json payload.json --pretty
|
|
39
54
|
`);
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
function parseArgs(argv) {
|
|
43
58
|
const out = { _: [], opts: {} };
|
|
59
|
+
// Normalize --key=value into --key value pairs so equals-form is accepted
|
|
60
|
+
// for every option without per-option branches below.
|
|
61
|
+
const normalized = [];
|
|
62
|
+
for (let i = 0; i < argv.length; i++) {
|
|
63
|
+
const a = argv[i];
|
|
64
|
+
if (a.startsWith('--') && a.includes('=')) {
|
|
65
|
+
const eq = a.indexOf('=');
|
|
66
|
+
normalized.push(a.slice(0, eq), a.slice(eq + 1));
|
|
67
|
+
} else {
|
|
68
|
+
normalized.push(a);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
argv = normalized;
|
|
44
72
|
for (let i = 0; i < argv.length; i++) {
|
|
45
73
|
const a = argv[i];
|
|
46
74
|
if (a === '-h' || a === '--help') { out.opts.help = true; continue; }
|
|
@@ -64,12 +92,34 @@ function parseArgs(argv) {
|
|
|
64
92
|
continue;
|
|
65
93
|
}
|
|
66
94
|
if (a === '--watch') { out.opts.watch = true; continue; }
|
|
95
|
+
if (a === '--source') { out.opts.source = true; continue; }
|
|
96
|
+
if (a === '--no-source') { out.opts.source = false; continue; }
|
|
97
|
+
if (a === '--dual') { out.opts.dual = true; continue; }
|
|
98
|
+
if (a === '--pretty') { out.opts.pretty = true; continue; }
|
|
99
|
+
if (a === '--compact') { out.opts.compact = true; continue; }
|
|
100
|
+
if (a === '--no-color') { out.opts.noColor = true; continue; }
|
|
101
|
+
if (a === '--color') { out.opts.color = argv[++i]; continue; }
|
|
102
|
+
if (a === '--max-errors') {
|
|
103
|
+
const v = argv[++i];
|
|
104
|
+
const n = Number(v);
|
|
105
|
+
if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) {
|
|
106
|
+
throw new Error(`--max-errors requires a non-negative integer (got "${v}")`);
|
|
107
|
+
}
|
|
108
|
+
out.opts.maxErrors = n;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
67
111
|
if (a.startsWith('-')) { throw new Error(`Unknown option: ${a}`); }
|
|
68
112
|
out._.push(a);
|
|
69
113
|
}
|
|
70
114
|
return out;
|
|
71
115
|
}
|
|
72
116
|
|
|
117
|
+
function resolveSourceDefault (opts) {
|
|
118
|
+
if (opts.source === true) return true;
|
|
119
|
+
if (opts.source === false) return false;
|
|
120
|
+
return process.env.NODE_ENV !== 'production';
|
|
121
|
+
}
|
|
122
|
+
|
|
73
123
|
function inferOutput(inputPath, format) {
|
|
74
124
|
const dir = path.dirname(inputPath);
|
|
75
125
|
const base = path.basename(inputPath, path.extname(inputPath));
|
|
@@ -77,6 +127,27 @@ function inferOutput(inputPath, format) {
|
|
|
77
127
|
return path.join(dir, base + ext);
|
|
78
128
|
}
|
|
79
129
|
|
|
130
|
+
// Renders a schema-compile failure through the structured error pipeline so
|
|
131
|
+
// `ata compile` / `ata build` surface the same look-and-feel as runtime
|
|
132
|
+
// validation errors. The actual position of the schema flaw isn't known here
|
|
133
|
+
// (parsing or codegen already bailed), so schemaSource is left undefined.
|
|
134
|
+
function reportCompileError (schemaFile, message) {
|
|
135
|
+
const { renderPretty, renderCompact } = require('..');
|
|
136
|
+
const fmt = process.stdout.isTTY ? 'pretty' : 'compact';
|
|
137
|
+
const err = {
|
|
138
|
+
code: 'ATA9002',
|
|
139
|
+
keyword: '__compile__',
|
|
140
|
+
path: '',
|
|
141
|
+
message,
|
|
142
|
+
schemaSource: undefined,
|
|
143
|
+
docUrl: 'https://ata-validator.com/e/ATA9002',
|
|
144
|
+
};
|
|
145
|
+
const out = fmt === 'pretty'
|
|
146
|
+
? renderPretty([err], { color: 'auto', context: schemaFile })
|
|
147
|
+
: renderCompact([err], { color: 'auto', context: schemaFile });
|
|
148
|
+
process.stderr.write(out + '\n');
|
|
149
|
+
}
|
|
150
|
+
|
|
80
151
|
function cmdCompile(args) {
|
|
81
152
|
if (args._.length === 0) {
|
|
82
153
|
process.stderr.write('error: missing <schema-file>\n\n');
|
|
@@ -96,7 +167,7 @@ function cmdCompile(args) {
|
|
|
96
167
|
try {
|
|
97
168
|
schemaStr = fs.readFileSync(input, 'utf8');
|
|
98
169
|
} catch (e) {
|
|
99
|
-
|
|
170
|
+
reportCompileError(input, `cannot read ${input}: ${e.message}`);
|
|
100
171
|
process.exit(1);
|
|
101
172
|
}
|
|
102
173
|
|
|
@@ -104,15 +175,32 @@ function cmdCompile(args) {
|
|
|
104
175
|
try {
|
|
105
176
|
schema = JSON.parse(schemaStr);
|
|
106
177
|
} catch (e) {
|
|
107
|
-
|
|
178
|
+
reportCompileError(input, `${input} is not valid JSON: ${e.message}`);
|
|
108
179
|
process.exit(1);
|
|
109
180
|
}
|
|
110
181
|
|
|
111
182
|
const { Validator } = require('..');
|
|
112
|
-
|
|
113
|
-
|
|
183
|
+
let v;
|
|
184
|
+
try {
|
|
185
|
+
v = new Validator(schema);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
reportCompileError(input, e.message);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
const source = resolveSourceDefault(args.opts);
|
|
191
|
+
let sourceMap = null;
|
|
192
|
+
if (source) {
|
|
193
|
+
try {
|
|
194
|
+
const { buildPositionMap } = require('../lib/source-positions');
|
|
195
|
+
sourceMap = buildPositionMap(schemaStr);
|
|
196
|
+
} catch {
|
|
197
|
+
sourceMap = null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const schemaFile = path.relative(process.cwd(), input) || input;
|
|
201
|
+
const src = v.toStandaloneModule({ format, abortEarly, source, sourceMap, schemaFile });
|
|
114
202
|
if (!src) {
|
|
115
|
-
|
|
203
|
+
reportCompileError(input, 'schema is too complex for standalone compilation');
|
|
116
204
|
process.exit(1);
|
|
117
205
|
}
|
|
118
206
|
|
|
@@ -165,6 +253,9 @@ function cmdBuild(args) {
|
|
|
165
253
|
strict: !!args.opts.strict,
|
|
166
254
|
types: args.opts.types,
|
|
167
255
|
cacheFile: args.opts.cacheFile,
|
|
256
|
+
// Forward the explicit user choice (true / false / undefined). The
|
|
257
|
+
// aot-build module applies the NODE_ENV-aware default when undefined.
|
|
258
|
+
source: args.opts.source,
|
|
168
259
|
};
|
|
169
260
|
|
|
170
261
|
const printReport = (report) => {
|
|
@@ -182,7 +273,7 @@ function cmdBuild(args) {
|
|
|
182
273
|
process.stdout.write(`ata: skipped ${s.input}: ${s.reason}\n`);
|
|
183
274
|
}
|
|
184
275
|
for (const f of report.failed) {
|
|
185
|
-
|
|
276
|
+
reportCompileError(f.input, f.error);
|
|
186
277
|
}
|
|
187
278
|
};
|
|
188
279
|
|
|
@@ -199,16 +290,96 @@ function cmdBuild(args) {
|
|
|
199
290
|
return;
|
|
200
291
|
}
|
|
201
292
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
293
|
+
// --dual emits two artifacts per schema: one source-mapped (default suffix)
|
|
294
|
+
// and one stripped (suffix .compiled.min). Useful for shipping both a
|
|
295
|
+
// developer-friendly bundle and a production-lean one from a single build.
|
|
296
|
+
const dual = !!args.opts.dual;
|
|
297
|
+
const runs = dual
|
|
298
|
+
? [
|
|
299
|
+
{ ...buildOpts, source: true, suffix: args.opts.suffix || '.compiled' },
|
|
300
|
+
{ ...buildOpts, source: false, suffix: (args.opts.suffix || '.compiled') + '.min' },
|
|
301
|
+
]
|
|
302
|
+
: [buildOpts];
|
|
303
|
+
|
|
304
|
+
(async () => {
|
|
305
|
+
let anyFailed = false;
|
|
306
|
+
let anyStale = false;
|
|
307
|
+
for (const run of runs) {
|
|
308
|
+
const report = await buildLib.build(run);
|
|
309
|
+
printReport(report);
|
|
310
|
+
if (args.opts.check && report.staleCount > 0) anyStale = true;
|
|
311
|
+
if (report.failed.length > 0) anyFailed = true;
|
|
312
|
+
}
|
|
313
|
+
if (anyFailed || anyStale) process.exit(1);
|
|
314
|
+
})().catch((e) => {
|
|
207
315
|
process.stderr.write(`error: ${e.message}\n`);
|
|
208
316
|
process.exit(1);
|
|
209
317
|
});
|
|
210
318
|
}
|
|
211
319
|
|
|
320
|
+
function cmdValidate (args) {
|
|
321
|
+
if (args._.length < 2) {
|
|
322
|
+
process.stderr.write('error: ata validate <schema> <data-file>\n');
|
|
323
|
+
process.exit(2);
|
|
324
|
+
}
|
|
325
|
+
const [schemaPath, dataPath] = args._;
|
|
326
|
+
|
|
327
|
+
let schemaContent;
|
|
328
|
+
try {
|
|
329
|
+
schemaContent = fs.readFileSync(schemaPath, 'utf8');
|
|
330
|
+
} catch (e) {
|
|
331
|
+
process.stderr.write(`error: cannot read ${schemaPath}: ${e.message}\n`);
|
|
332
|
+
process.exit(2);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let schema;
|
|
336
|
+
try {
|
|
337
|
+
schema = JSON.parse(schemaContent);
|
|
338
|
+
} catch (e) {
|
|
339
|
+
process.stderr.write(`error: ${schemaPath} is not valid JSON: ${e.message}\n`);
|
|
340
|
+
process.exit(2);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let data;
|
|
344
|
+
try {
|
|
345
|
+
data = fs.readFileSync(dataPath, 'utf8');
|
|
346
|
+
} catch (e) {
|
|
347
|
+
process.stderr.write(`error: cannot read ${dataPath}: ${e.message}\n`);
|
|
348
|
+
process.exit(2);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const { Validator, renderPretty, renderCompact, renderJSON } = require('..');
|
|
352
|
+
let v;
|
|
353
|
+
try {
|
|
354
|
+
v = new Validator(schema, { source: { path: schemaPath, content: schemaContent } });
|
|
355
|
+
} catch (e) {
|
|
356
|
+
process.stderr.write(`error: schema compile failed: ${e.message}\n`);
|
|
357
|
+
process.exit(2);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const r = v.validateJSON(data);
|
|
361
|
+
if (r.valid) { process.exit(0); }
|
|
362
|
+
|
|
363
|
+
const fmt = args.opts.format
|
|
364
|
+
|| (args.opts.pretty ? 'pretty' : args.opts.compact ? 'compact' : null)
|
|
365
|
+
|| (process.stdout.isTTY ? 'pretty' : 'compact');
|
|
366
|
+
const colorOpt = args.opts.color || (args.opts.noColor ? 'never' : 'auto');
|
|
367
|
+
const ctx = `${dataPath} against ${schemaPath}`;
|
|
368
|
+
let out;
|
|
369
|
+
if (fmt === 'json') {
|
|
370
|
+
out = renderJSON(r.errors, { pretty: true, context: ctx });
|
|
371
|
+
} else if (fmt === 'pretty') {
|
|
372
|
+
out = renderPretty(r.errors, { color: colorOpt, context: ctx, maxErrors: args.opts.maxErrors });
|
|
373
|
+
} else if (fmt === 'compact') {
|
|
374
|
+
out = renderCompact(r.errors, { color: colorOpt, context: ctx });
|
|
375
|
+
} else {
|
|
376
|
+
process.stderr.write(`error: --format must be pretty, compact, or json (got "${fmt}")\n`);
|
|
377
|
+
process.exit(2);
|
|
378
|
+
}
|
|
379
|
+
process.stderr.write(out + '\n');
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
|
|
212
383
|
function main() {
|
|
213
384
|
const argv = process.argv.slice(2);
|
|
214
385
|
if (argv.length === 0) { usage(); process.exit(0); }
|
|
@@ -237,6 +408,11 @@ function main() {
|
|
|
237
408
|
return;
|
|
238
409
|
}
|
|
239
410
|
|
|
411
|
+
if (cmd === 'validate') {
|
|
412
|
+
cmdValidate(args);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
240
416
|
process.stderr.write(`error: unknown command "${cmd}"\n\n`);
|
|
241
417
|
usage();
|
|
242
418
|
process.exit(1);
|
package/build.d.ts
CHANGED
|
@@ -19,6 +19,12 @@ export interface BuildOptions {
|
|
|
19
19
|
strict?: boolean;
|
|
20
20
|
/** Emit a .d.mts/.d.cts/.d.ts sibling for each compiled module. Default: true. */
|
|
21
21
|
types?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Embed a schema source map and bake per-error `schemaSource` frames.
|
|
24
|
+
* Defaults to true when `NODE_ENV !== 'production'`, false otherwise.
|
|
25
|
+
* Set explicitly to override the environment default.
|
|
26
|
+
*/
|
|
27
|
+
source?: boolean;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
export interface CompiledEntry {
|
package/index.browser.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Browser ESM entry — same code, native addon stubbed out by bundler via "browser" field.
|
|
2
2
|
import mod from './index.js';
|
|
3
|
-
export const { Validator, validate, version, createPaddedBuffer, SIMDJSON_PADDING } = mod;
|
|
3
|
+
export const { Validator, validate, version, createPaddedBuffer, SIMDJSON_PADDING, renderPretty, renderCompact, renderJSON } = mod;
|
|
4
4
|
export default mod;
|
package/index.d.ts
CHANGED
|
@@ -12,6 +12,66 @@ export interface ValidationError {
|
|
|
12
12
|
parentSchema?: object;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export interface SchemaSource {
|
|
16
|
+
file: string;
|
|
17
|
+
line: number;
|
|
18
|
+
col: number;
|
|
19
|
+
text: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DataFrame {
|
|
23
|
+
byteOffset: number;
|
|
24
|
+
length: number;
|
|
25
|
+
line: number;
|
|
26
|
+
col: number;
|
|
27
|
+
text: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface Suggestion {
|
|
31
|
+
text: string;
|
|
32
|
+
kind: 'typo' | 'format' | 'coercion' | 'similar-key';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type ErrorCode = `ATA${number}`;
|
|
36
|
+
|
|
37
|
+
export interface RichValidationError {
|
|
38
|
+
code: ErrorCode;
|
|
39
|
+
message: string;
|
|
40
|
+
keyword: string;
|
|
41
|
+
path: string;
|
|
42
|
+
expected?: string;
|
|
43
|
+
received?: string;
|
|
44
|
+
schemaPath?: string;
|
|
45
|
+
schemaSource?: SchemaSource;
|
|
46
|
+
dataFrame?: DataFrame;
|
|
47
|
+
suggestion?: Suggestion;
|
|
48
|
+
docUrl?: string;
|
|
49
|
+
// Back-compat aliases retained indefinitely
|
|
50
|
+
instancePath?: string;
|
|
51
|
+
dataPath?: string;
|
|
52
|
+
params?: Record<string, unknown>;
|
|
53
|
+
parentSchema?: unknown;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface RenderOptions {
|
|
57
|
+
color?: 'auto' | 'always' | 'never';
|
|
58
|
+
cwd?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface PrettyOptions extends RenderOptions {
|
|
62
|
+
maxErrors?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface CompactOptions extends RenderOptions {}
|
|
66
|
+
|
|
67
|
+
export interface JSONRenderOptions {
|
|
68
|
+
pretty?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function renderPretty(errors: RichValidationError[], opts?: PrettyOptions): string;
|
|
72
|
+
export function renderCompact(errors: RichValidationError[], opts?: CompactOptions): string;
|
|
73
|
+
export function renderJSON(errors: RichValidationError[], opts?: JSONRenderOptions): string;
|
|
74
|
+
|
|
15
75
|
/** A user-supplied format checker. Receives the candidate value, returns true if valid. */
|
|
16
76
|
export type FormatChecker = (value: string) => boolean;
|
|
17
77
|
|
|
@@ -42,6 +102,17 @@ export interface ValidatorOptions {
|
|
|
42
102
|
* instead of collecting full error details. Smaller hot-path allocation.
|
|
43
103
|
*/
|
|
44
104
|
abortEarly?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Optional source descriptor for the schema. When supplied, validation errors
|
|
107
|
+
* carry a `schemaSource` frame pointing to the originating file/line/col.
|
|
108
|
+
*/
|
|
109
|
+
source?: { path: string; content: string };
|
|
110
|
+
/**
|
|
111
|
+
* When true (default), validate() returns enriched errors with stable codes,
|
|
112
|
+
* docUrl, expected/received hints. Set to false to opt back into the
|
|
113
|
+
* v0.14 error shape.
|
|
114
|
+
*/
|
|
115
|
+
richErrors?: boolean;
|
|
45
116
|
}
|
|
46
117
|
|
|
47
118
|
export interface BundleStandaloneOptions extends ValidatorOptions {
|