ata-validator 1.13.2 → 1.14.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/aot.d.ts +5 -0
- package/build.d.ts +10 -1
- package/index.js +33 -4
- package/lib/aot-impl.js +102 -4
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/aot.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ export interface StandaloneOptions {
|
|
|
6
6
|
format?: 'esm' | 'cjs';
|
|
7
7
|
/** Use stub error functions for the smallest output. Default: false. */
|
|
8
8
|
abortEarly?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Also export `parse(data)`: validate, then return a copy of the input
|
|
11
|
+
* holding only the properties the schema declares. Default: false.
|
|
12
|
+
*/
|
|
13
|
+
parse?: boolean;
|
|
9
14
|
[key: string]: unknown;
|
|
10
15
|
}
|
|
11
16
|
|
package/build.d.ts
CHANGED
|
@@ -97,6 +97,14 @@ export interface ToStandaloneModuleOptions {
|
|
|
97
97
|
formats?: Record<string, (value: string) => boolean>;
|
|
98
98
|
/** See {@link BundleStandaloneOptions.formatMode}. */
|
|
99
99
|
formatMode?: 'embed' | 'inject';
|
|
100
|
+
/**
|
|
101
|
+
* Also export `parse(data)`: validate, then return a copy of the input
|
|
102
|
+
* holding only the properties the schema declares. Off by default because
|
|
103
|
+
* it adds to the emitted module's size. Emitted only where the copy is
|
|
104
|
+
* provably exact; a schema using `$ref`, a composition, `patternProperties`,
|
|
105
|
+
* an `additionalProperties` schema or an array of objects gets no `parse`.
|
|
106
|
+
*/
|
|
107
|
+
parse?: boolean;
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
/** Bundle multiple schemas into one self-contained module (no ata-validator runtime). */
|
|
@@ -105,5 +113,6 @@ export function bundleStandalone(schemas: unknown[], options?: BundleStandaloneO
|
|
|
105
113
|
/** Like {@link bundleStandalone} but deduplicates shared bodies for smaller output. */
|
|
106
114
|
export function bundleCompact(schemas: unknown[], options?: BundleStandaloneOptions): string;
|
|
107
115
|
|
|
108
|
-
/** Emit a self-contained `validate`/`isValid` module string for a single
|
|
116
|
+
/** Emit a self-contained `validate`/`isValid` module string for a single
|
|
117
|
+
* schema, plus `parse` when {@link ToStandaloneModuleOptions.parse} is set. */
|
|
109
118
|
export function toStandaloneModule(schema: unknown, options?: ToStandaloneModuleOptions): string | null;
|
package/index.js
CHANGED
|
@@ -216,6 +216,31 @@ function collectRemovals(schema, actions, path) {
|
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
// Emit the in-place strip for one schema node and everything under its
|
|
220
|
+
// `properties`. Scope matches collectRemovals(): object properties only, so
|
|
221
|
+
// the two paths keep the same answer.
|
|
222
|
+
function emitRemovals(node, access, lines, depth, seen) {
|
|
223
|
+
if (!node || typeof node !== 'object' || !node.properties) return;
|
|
224
|
+
if (seen.has(node)) return;
|
|
225
|
+
seen.add(node);
|
|
226
|
+
const keys = Object.keys(node.properties);
|
|
227
|
+
if (node.additionalProperties === false && keys.length > 0) {
|
|
228
|
+
const kv = '_k' + depth;
|
|
229
|
+
const checks = keys.map((k) => `${kv}!==${JSON.stringify(k)}`).join('&&');
|
|
230
|
+
const guard = depth === 0
|
|
231
|
+
? ''
|
|
232
|
+
: `if(${access}!==null&&typeof ${access}==='object'&&!Array.isArray(${access}))`;
|
|
233
|
+
lines.push(`${guard}for(var ${kv} in ${access})if(${checks})delete ${access}[${kv}]`);
|
|
234
|
+
}
|
|
235
|
+
for (const key of keys) {
|
|
236
|
+
const prop = node.properties[key];
|
|
237
|
+
if (prop && typeof prop === 'object' && prop.properties) {
|
|
238
|
+
emitRemovals(prop, `${access}[${JSON.stringify(key)}]`, lines, depth + 1, seen);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
seen.delete(node);
|
|
242
|
+
}
|
|
243
|
+
|
|
219
244
|
// Generate a fast preprocess function via codegen instead of closure arrays
|
|
220
245
|
function buildPreprocessCodegen(schema, options) {
|
|
221
246
|
if (typeof schema !== 'object' || schema === null || !schema.properties) return null;
|
|
@@ -223,10 +248,14 @@ function buildPreprocessCodegen(schema, options) {
|
|
|
223
248
|
const props = schema.properties;
|
|
224
249
|
const keys = Object.keys(props);
|
|
225
250
|
|
|
226
|
-
// removeAdditional:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
251
|
+
// removeAdditional: strip unknown keys at every level the schema describes,
|
|
252
|
+
// not just the top one. The closure path below (collectRemovals) always
|
|
253
|
+
// recursed; this one did not, so the same schema and the same document got
|
|
254
|
+
// opposite verdicts depending on whether the runtime allowed code
|
|
255
|
+
// generation, and the codegen answer was the one that disagreed with both
|
|
256
|
+
// the interpreter and the default validator this aims to match.
|
|
257
|
+
if (options.removeAdditional) {
|
|
258
|
+
emitRemovals(schema, 'd', lines, 0, new Set());
|
|
230
259
|
}
|
|
231
260
|
|
|
232
261
|
// coerceTypes: inline per property
|
package/lib/aot-impl.js
CHANGED
|
@@ -140,6 +140,84 @@ function emitFormatDecls(closures, mode, declKW) {
|
|
|
140
140
|
return { decls: out, exportsSetFormats: false };
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
// Emit an expression that rebuilds `access` from the properties the schema
|
|
144
|
+
// declares, dropping everything else. This is the sanitising counterpart to
|
|
145
|
+
// removeAdditional: instead of enumerating the input and deleting the keys
|
|
146
|
+
// that do not belong, it reads only the keys that do. That is O(schema)
|
|
147
|
+
// rather than O(input), it leaves the caller's object untouched, and it keeps
|
|
148
|
+
// one hidden class instead of walking the object through a transition per
|
|
149
|
+
// delete.
|
|
150
|
+
//
|
|
151
|
+
// Returns null when the clone cannot be proven exact, and the module then
|
|
152
|
+
// simply has no parse(). Declining is recoverable; a sanitiser that quietly
|
|
153
|
+
// drops a property the schema allows is not.
|
|
154
|
+
function emitClone(node, access, depth) {
|
|
155
|
+
if (!node || typeof node !== 'object') return null;
|
|
156
|
+
if (depth > 12) return null;
|
|
157
|
+
// Only plain object nodes are cloneable, and only when the schema names
|
|
158
|
+
// every property it keeps. A union type, a $ref, or any composition means
|
|
159
|
+
// the set of allowed keys is not this node's to decide.
|
|
160
|
+
if (node.$ref || node.allOf || node.anyOf || node.oneOf || node.not ||
|
|
161
|
+
node.if || node.patternProperties || node.unevaluatedProperties ||
|
|
162
|
+
node.additionalProperties === true ||
|
|
163
|
+
(node.additionalProperties && typeof node.additionalProperties === 'object')) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
if (node.type !== 'object' || !node.properties) return null;
|
|
167
|
+
const keys = Object.keys(node.properties);
|
|
168
|
+
if (keys.length === 0) return null;
|
|
169
|
+
const required = new Set(Array.isArray(node.required) ? node.required : []);
|
|
170
|
+
|
|
171
|
+
// A property is copyable only when the emitter can name everything that
|
|
172
|
+
// may live under it: a primitive holds nothing, and an object with declared
|
|
173
|
+
// properties is rebuilt the same way. A $ref, a composition, an array of
|
|
174
|
+
// objects or an untyped node could all carry keys this code cannot see, and
|
|
175
|
+
// copying the reference would smuggle them into a value that claims to be
|
|
176
|
+
// sanitised, so the whole clone is declined instead.
|
|
177
|
+
const PRIMITIVE = new Set(['string', 'number', 'integer', 'boolean', 'null']);
|
|
178
|
+
const fixed = [];
|
|
179
|
+
const conditional = [];
|
|
180
|
+
for (const key of keys) {
|
|
181
|
+
const prop = node.properties[key];
|
|
182
|
+
if (!prop || typeof prop !== 'object') return null;
|
|
183
|
+
if (prop.$ref || prop.allOf || prop.anyOf || prop.oneOf || prop.not || prop.if) return null;
|
|
184
|
+
const read = `${access}[${JSON.stringify(key)}]`;
|
|
185
|
+
let value;
|
|
186
|
+
const types = Array.isArray(prop.type) ? prop.type : [prop.type];
|
|
187
|
+
if (types.every((t) => PRIMITIVE.has(t))) {
|
|
188
|
+
value = read;
|
|
189
|
+
} else if (prop.type === 'object' && prop.properties) {
|
|
190
|
+
const nested = emitClone(prop, read, depth + 1);
|
|
191
|
+
if (!nested) return null;
|
|
192
|
+
// The nested schema may also admit null, in which case there is nothing
|
|
193
|
+
// to rebuild and the value passes through.
|
|
194
|
+
value = required.has(key) ? nested : nested;
|
|
195
|
+
} else if (prop.type === 'array' && prop.items && typeof prop.items === 'object' &&
|
|
196
|
+
!Array.isArray(prop.items) &&
|
|
197
|
+
(Array.isArray(prop.items.type) ? prop.items.type : [prop.items.type])
|
|
198
|
+
.every((t) => PRIMITIVE.has(t))) {
|
|
199
|
+
// An array of primitives has nowhere to hide a key. The reference is
|
|
200
|
+
// shared with the input, exactly as a shallow strip would leave it.
|
|
201
|
+
value = read;
|
|
202
|
+
} else {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
if (required.has(key)) fixed.push(`${JSON.stringify(key)}: ${value}`);
|
|
206
|
+
else conditional.push({ key, value });
|
|
207
|
+
}
|
|
208
|
+
if (fixed.length === 0 && conditional.length === 0) return null;
|
|
209
|
+
|
|
210
|
+
const literal = `{ ${fixed.join(', ')} }`;
|
|
211
|
+
if (conditional.length === 0) return literal;
|
|
212
|
+
// Optional properties are added only when present, so the result never
|
|
213
|
+
// gains a key the input did not have.
|
|
214
|
+
const tmp = `_c${depth}`;
|
|
215
|
+
const adds = conditional
|
|
216
|
+
.map(({ key, value }) => `if (${JSON.stringify(key)} in ${access}) ${tmp}[${JSON.stringify(key)}] = ${value};`)
|
|
217
|
+
.join(' ');
|
|
218
|
+
return `(function(){ const ${tmp} = ${literal}; ${adds} return ${tmp}; })()`;
|
|
219
|
+
}
|
|
220
|
+
|
|
143
221
|
function toStandaloneModule(validator, opts) {
|
|
144
222
|
validator._ensureCompiled();
|
|
145
223
|
const jsFn = validator._jsFn;
|
|
@@ -219,10 +297,30 @@ function toStandaloneModule(validator, opts) {
|
|
|
219
297
|
? 'return _fn(data) ? VALID : { valid: false, errors: errFn(data, true).errors }'
|
|
220
298
|
: 'return _fn(data) ? VALID : ABORT';
|
|
221
299
|
|
|
222
|
-
|
|
300
|
+
// parse(): validate, then hand back a copy holding only the properties the
|
|
301
|
+
// schema declares. Off by default: it costs bytes in every emitted module,
|
|
302
|
+
// and the reason to compile a schema ahead of time is usually to ship as
|
|
303
|
+
// little as possible. Ask for it with { parse: true }.
|
|
304
|
+
const cloneExpr = !(opts && opts.parse) ? null : emitClone(
|
|
305
|
+
typeof validator._schemaObj === 'object' ? validator._schemaObj : null,
|
|
306
|
+
'data',
|
|
307
|
+
0,
|
|
308
|
+
);
|
|
309
|
+
// Named _ataParse rather than parse: the emitted module also carries the
|
|
310
|
+
// safe-regex prelude, which has a module-scope parse() of its own for
|
|
311
|
+
// reading patterns. A second declaration of that name shadowed it and the
|
|
312
|
+
// prelude called this one instead, reaching _fn before its initialiser ran.
|
|
313
|
+
const parseCore = cloneExpr
|
|
314
|
+
? `function _ataParse(data) {\n if (!_fn(data)) { const e = new Error('validation failed'); e.name = 'AtaValidationError'; throw e; }\n return ${cloneExpr};\n}\n`
|
|
315
|
+
: '';
|
|
316
|
+
|
|
317
|
+
const baseNames = cloneExpr ? 'validate, isValid' : 'validate, isValid';
|
|
318
|
+
const names = fmt.exportsSetFormats ? baseNames + ', setFormats' : baseNames;
|
|
319
|
+
const parseAlias = cloneExpr ? ', _ataParse as parse' : '';
|
|
320
|
+
const parseProp = cloneExpr ? ', parse: _ataParse' : '';
|
|
223
321
|
const exports = format === 'esm'
|
|
224
|
-
? `export { ${names} };\nexport default { ${names} };\n`
|
|
225
|
-
: `module.exports = { ${names} };\nmodule.exports.default = module.exports;\n`;
|
|
322
|
+
? `export { ${names}${parseAlias} };\nexport default { ${names}${parseProp} };\n`
|
|
323
|
+
: `module.exports = { ${names}${parseProp} };\nmodule.exports.default = module.exports;\n`;
|
|
226
324
|
|
|
227
325
|
return `// Auto-generated by ata-validator — do not edit.
|
|
228
326
|
// Schema is embedded; runtime has zero dependency on ata-validator.
|
|
@@ -243,7 +341,7 @@ ${closureDecls}${preambleDecls}${formatDecls}const _fn = function(d) {
|
|
|
243
341
|
};
|
|
244
342
|
${errCore}function isValid(data) { return _fn(data); }
|
|
245
343
|
function validate(data) { ${validBody}; }
|
|
246
|
-
${exports}`;
|
|
344
|
+
${parseCore}${exports}`;
|
|
247
345
|
}
|
|
248
346
|
|
|
249
347
|
// Bundle multiple validators into a single JS file for fast startup.
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.1",
|
|
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",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"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",
|
|
52
52
|
"build": "cmake-js build --target ata",
|
|
53
53
|
"rebuild": "cmake-js rebuild --target ata",
|
|
54
|
-
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.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_native_loaded.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_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_formats_single_pass.js && node tests/test_format_single_error.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_ajv_errors.js && node tests/test_pattern_properties_errors.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_error_code_lookup.js && node tests/test_error_order.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_plan_compiler.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_enrich_received.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_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.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",
|
|
54
|
+
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.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_native_loaded.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_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_formats_single_pass.js && node tests/test_format_single_error.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_ajv_errors.js && node tests/test_pattern_properties_errors.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_error_code_lookup.js && node tests/test_error_order.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_plan_compiler.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_enrich_received.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_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.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",
|
|
55
55
|
"bench:size": "node benchmark/bench_aot_size.mjs",
|
|
56
56
|
"test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
|
|
57
57
|
"test:compat": "node tests/test_compat.js",
|
|
@@ -122,13 +122,13 @@
|
|
|
122
122
|
"LICENSE"
|
|
123
123
|
],
|
|
124
124
|
"optionalDependencies": {
|
|
125
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
126
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
127
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
128
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
129
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
130
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
131
|
-
"@ata-validator/native-win32-x64": "1.
|
|
125
|
+
"@ata-validator/native-darwin-arm64": "1.14.1",
|
|
126
|
+
"@ata-validator/native-darwin-x64": "1.14.1",
|
|
127
|
+
"@ata-validator/native-linux-x64-gnu": "1.14.1",
|
|
128
|
+
"@ata-validator/native-linux-arm64-gnu": "1.14.1",
|
|
129
|
+
"@ata-validator/native-linux-x64-musl": "1.14.1",
|
|
130
|
+
"@ata-validator/native-linux-arm64-musl": "1.14.1",
|
|
131
|
+
"@ata-validator/native-win32-x64": "1.14.1"
|
|
132
132
|
},
|
|
133
133
|
"peerDependencies": {
|
|
134
134
|
"yaml": "^2.0.0"
|