ata-validator 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,12 @@
1
+ <p align="center">
2
+ <img src="./assets/ata-validator.svg" alt="ata-validator" width="640" />
3
+ </p>
4
+
1
5
  # ata-validator
2
6
 
3
7
  Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjson/simdjson). Multi-core parallel validation, RE2 regex, codegen bytecode engine. Standard Schema V1 compatible.
4
8
 
5
- **[ata-validator.com](https://ata-validator.com)** | **[API Docs](docs/API.md)** | **[Migrate from ajv](docs/migration-from-ajv.md)** | **[Contributing](CONTRIBUTING.md)**
9
+ **[ata-validator.com](https://ata-validator.com)** | **[API Docs](docs/API.md)** | **[Migrate from ajv](docs/migration-from-ajv.md)** | **[Framework integrations](docs/integrations/)** | **[Contributing](CONTRIBUTING.md)**
6
10
 
7
11
  ## Performance
8
12
 
@@ -54,8 +58,8 @@ Three-tier hybrid codegen: static schemas compile to zero-overhead key checks, d
54
58
  |---|---|---|---|---|---|
55
59
  | **validate (valid)** | **9ns** | 38ns | 50ns | 334ns | 326ns |
56
60
  | **validate (invalid)** | **37ns** | 103ns | 4ns | 11.8μs | 842ns |
57
- | **compilation** | **453ns** | 1.24ms | 52μs | | |
58
- | **first validation** | **2.1μs** | 1.11ms | 54μs | | |
61
+ | **compilation** | **453ns** | 1.24ms | 52μs | n/a | n/a |
62
+ | **first validation** | **2.1μs** | 1.11ms | 54μs | n/a | n/a |
59
63
 
60
64
  > Different categories: ata/ajv/typebox are JSON Schema validators, zod/valibot are schema-builder DSLs. [Benchmark code](benchmark/bench_all_mitata.mjs)
61
65
 
@@ -219,7 +223,7 @@ const v = new Validator(schema, {
219
223
 
220
224
  ### Build-time compile (`ata compile`)
221
225
 
222
- The `ata` CLI turns a JSON Schema file into a self-contained JavaScript module. No runtime dependency on `ata-validator`, so only the generated validator ships to the browser typical output is ~1 KB gzipped compared to ~27 KB for the full runtime.
226
+ The `ata` CLI turns a JSON Schema file into a self-contained JavaScript module. No runtime dependency on `ata-validator`, so only the generated validator ships to the browser. Typical output is ~1 KB gzipped compared to ~27 KB for the full runtime.
223
227
 
224
228
  ```bash
225
229
  npx ata compile schemas/user.json -o src/generated/user.validator.mjs
@@ -314,6 +318,24 @@ auto result = ata::validate(schema, R"({"name": "Mert"})");
314
318
  // result.valid == true
315
319
  ```
316
320
 
321
+ ## Framework integrations
322
+
323
+ Copy-paste recipes for the common frameworks. Most need 10-20 lines of glue. See [docs/integrations](docs/integrations/) for the full set.
324
+
325
+ | Framework | Pattern | Recipe |
326
+ |---|---|---|
327
+ | Fastify | dedicated plugin | [`fastify-ata`](https://github.com/ata-core/fastify-ata) |
328
+ | Vite (build-time compile) | dedicated plugin | [`ata-vite`](https://github.com/ata-core/ata-vite) |
329
+ | Hono | async middleware | [docs/integrations/hono.md](docs/integrations/hono.md) |
330
+ | Elysia | direct handler check | [docs/integrations/elysia.md](docs/integrations/elysia.md) |
331
+ | tRPC | Standard Schema V1 input | [docs/integrations/trpc.md](docs/integrations/trpc.md) |
332
+ | TanStack Form | Standard Schema V1 validator | [docs/integrations/tanstack-form.md](docs/integrations/tanstack-form.md) |
333
+ | Express | sync middleware | [docs/integrations/express.md](docs/integrations/express.md) |
334
+ | Koa | async ctx middleware | [docs/integrations/koa.md](docs/integrations/koa.md) |
335
+ | NestJS | validation pipe | [docs/integrations/nestjs.md](docs/integrations/nestjs.md) |
336
+ | SvelteKit | form action, API route | [docs/integrations/sveltekit.md](docs/integrations/sveltekit.md) |
337
+ | Astro | API route, server action | [docs/integrations/astro.md](docs/integrations/astro.md) |
338
+
317
339
  ## Supported Keywords
318
340
 
319
341
  | Category | Keywords |
package/lib/ts-gen.js CHANGED
@@ -56,6 +56,28 @@ function renderValueType(schema, defs, depth = 0) {
56
56
 
57
57
  if (t === 'array') {
58
58
  const items = schema.items;
59
+ const prefix = Array.isArray(schema.prefixItems) ? schema.prefixItems : null;
60
+
61
+ if (prefix) {
62
+ const prefixTypes = prefix.map((s) => renderValueType(s, defs, depth + 1));
63
+ const minItems = typeof schema.minItems === 'number' ? schema.minItems : 0;
64
+ // Elements before minItems are required; the remainder are optional
65
+ // because JSON Schema does not require prefixItems to be present.
66
+ const elements = prefixTypes.map((t, i) => (i < minItems ? t : `${t}?`));
67
+ if (items === false) {
68
+ return `[${elements.join(', ')}]`;
69
+ }
70
+ if (items === undefined || items === true) {
71
+ return `[${elements.join(', ')}, ...unknown[]]`;
72
+ }
73
+ if (typeof items === 'object' && items !== null) {
74
+ const rest = renderValueType(items, defs, depth + 1);
75
+ const restType = rest.includes(' | ') ? `(${rest})` : rest;
76
+ return `[${elements.join(', ')}, ...${restType}[]]`;
77
+ }
78
+ }
79
+
80
+ if (items === false) return 'never[]';
59
81
  if (items === undefined || items === true) return 'unknown[]';
60
82
  const inner = renderValueType(items, defs, depth + 1);
61
83
  return inner.includes(' | ') ? `Array<${inner}>` : `${inner}[]`;
@@ -84,15 +106,30 @@ function renderObject(schema, defs, depth) {
84
106
  const t = renderValueType(props[k], defs, depth + 1);
85
107
  const opt = required.has(k) ? '' : '?';
86
108
  const safeKey = /^[A-Za-z_$][\w$]*$/.test(k) ? k : JSON.stringify(k);
87
- const desc = typeof props[k] === 'object' && props[k] && typeof props[k].description === 'string'
88
- ? ` /** ${props[k].description.replace(/\*\//g, '* /')} */\n`
89
- : '';
90
- return `${desc} ${safeKey}${opt}: ${t};`;
109
+ const doc = renderJsDoc(props[k], ' ');
110
+ return `${doc} ${safeKey}${opt}: ${t};`;
91
111
  });
92
112
  // extra keys when additionalProperties is present as a schema or true
93
113
  const extra = schema.additionalProperties;
94
114
  if (extra && typeof extra === 'object') {
95
- lines.push(` [key: string]: ${renderValueType(extra, defs, depth + 1)};`);
115
+ // TypeScript requires the index signature to be a supertype of every
116
+ // named property's emitted type. Widen to a union covering each property
117
+ // type, plus undefined when any property is optional.
118
+ const widen = new Set();
119
+ widen.add(renderValueType(extra, defs, depth + 1));
120
+ let hasOptional = false;
121
+ for (const k of keys) {
122
+ widen.add(renderValueType(props[k], defs, depth + 1));
123
+ if (!required.has(k)) hasOptional = true;
124
+ }
125
+ if (hasOptional) widen.add('undefined');
126
+ const indexType = widen.has('unknown') ? 'unknown' : Array.from(widen).join(' | ');
127
+ lines.push(` [key: string]: ${indexType};`);
128
+ } else if (extra !== false) {
129
+ // JSON Schema accepts extra keys by default. Emit a permissive index
130
+ // signature so tsc does not reject excess properties that the runtime
131
+ // would consider valid.
132
+ lines.push(` [key: string]: unknown;`);
96
133
  }
97
134
  return `{\n${lines.join('\n')}\n}`;
98
135
  }
@@ -106,10 +143,54 @@ function renderLiteral(v) {
106
143
 
107
144
  function toTypeName(name) {
108
145
  const cleaned = String(name).replace(/[^A-Za-z0-9_]/g, '_');
146
+ if (cleaned === '') return '_Anon';
109
147
  if (/^[0-9]/.test(cleaned)) return `_${cleaned}`;
110
148
  return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
111
149
  }
112
150
 
151
+ // Build a JSDoc block that captures the description plus any runtime-only
152
+ // constraints the TypeScript type cannot express (minLength, format, range,
153
+ // etc.). Editors and TypeDoc surface these on hover, so authors can see what
154
+ // the schema requires even though tsc does not enforce it.
155
+ function renderJsDoc(schema, indent) {
156
+ if (!schema || typeof schema !== 'object') return '';
157
+
158
+ let description = '';
159
+ if (typeof schema.description === 'string' && schema.description.length > 0) {
160
+ description = schema.description.replace(/\*\//g, '* /');
161
+ }
162
+
163
+ const tags = [];
164
+ const numKeys = ['minLength', 'maxLength', 'minItems', 'maxItems', 'minProperties', 'maxProperties',
165
+ 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf'];
166
+ for (const k of numKeys) {
167
+ if (typeof schema[k] === 'number') tags.push(`@${k} ${schema[k]}`);
168
+ }
169
+ if (typeof schema.pattern === 'string') tags.push(`@pattern ${schema.pattern}`);
170
+ if (typeof schema.format === 'string') tags.push(`@format ${schema.format}`);
171
+ if (schema.uniqueItems === true) tags.push('@uniqueItems');
172
+ if (schema.deprecated === true) tags.push('@deprecated');
173
+ if (schema.default !== undefined) {
174
+ try { tags.push(`@default ${JSON.stringify(schema.default)}`); } catch (_) {}
175
+ }
176
+ if (Array.isArray(schema.examples) && schema.examples.length > 0) {
177
+ try { tags.push(`@example ${JSON.stringify(schema.examples[0])}`); } catch (_) {}
178
+ }
179
+
180
+ if (description === '' && tags.length === 0) return '';
181
+
182
+ if (description !== '' && tags.length === 0) {
183
+ return `${indent}/** ${description} */\n`;
184
+ }
185
+
186
+ const lines = [`${indent}/**`];
187
+ if (description !== '') lines.push(`${indent} * ${description}`);
188
+ if (description !== '' && tags.length > 0) lines.push(`${indent} *`);
189
+ for (const t of tags) lines.push(`${indent} * ${t}`);
190
+ lines.push(`${indent} */`);
191
+ return lines.join('\n') + '\n';
192
+ }
193
+
113
194
  // Public: given a schema and optional type name, return a .d.ts source.
114
195
  function toTypeScript(schema, opts) {
115
196
  const options = opts || {};
@@ -125,13 +206,14 @@ function toTypeScript(schema, opts) {
125
206
  }
126
207
 
127
208
  const rootType = renderValueType(schema, defs, 0);
209
+ const rootDoc = renderJsDoc(schema, '');
128
210
  // Use `interface` only for a pure object literal; otherwise fall back to
129
211
  // `type`. Catches cases like `{...}[]` (array of object) and `Record<...>`
130
212
  // which are valid TS but cannot be expressed as an interface body.
131
213
  const isPureObjectLiteral = rootType.startsWith('{') && rootType.endsWith('}') && !rootType.includes(' | ');
132
214
  const rootDecl = isPureObjectLiteral
133
- ? `export interface ${rootName} ${rootType}`
134
- : `export type ${rootName} = ${rootType};`;
215
+ ? `${rootDoc}export interface ${rootName} ${rootType}`
216
+ : `${rootDoc}export type ${rootName} = ${rootType};`;
135
217
 
136
218
  return `// Auto-generated by ata-validator — do not edit.
137
219
  ${defLines.length ? defLines.join('\n\n') + '\n\n' : ''}${rootDecl}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "Ultra-fast JSON Schema validator. 4.7x faster validation, 1,800x faster compilation. Works without native addon. Cross-schema $ref, Draft 2020-12 + Draft 7, V8-optimized JS codegen, simdjson, RE2, multi-core. Standard Schema V1 compatible.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -41,6 +41,8 @@
41
41
  "test:standard-schema": "node tests/test_standard_schema.js",
42
42
  "test:browser": "node tests/test_browser.js",
43
43
  "test:ts": "node tests/test_ts_gen.js",
44
+ "test:ts-corpus": "node tests/test_ts_corpus.js",
45
+ "test:ts-differential": "node tests/test_ts_differential.js",
44
46
  "bench": "node benchmark/bench_large.js",
45
47
  "fuzz": "node tests/fuzz_differential.js",
46
48
  "fuzz:long": "FUZZ_ITERATIONS=100000 node tests/fuzz_differential.js",