ata-validator 1.61.0 → 1.61.2

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.
Files changed (43) hide show
  1. package/README.md +34 -7
  2. package/compiled-bare-rich.d.ts +6 -0
  3. package/compiled-bare.d.ts +7 -0
  4. package/compiled-bare.js +5 -0
  5. package/compiled-bare.mjs +35 -0
  6. package/compiled-object.d.ts +11 -0
  7. package/compiled-object.js +6 -0
  8. package/compiled-object.mjs +3 -0
  9. package/compiled-plain.d.ts +16 -0
  10. package/compiled-plain.js +5 -0
  11. package/compiled-plain.mjs +3 -0
  12. package/index.aot.js +8 -0
  13. package/index.aot.mjs +5 -0
  14. package/lib/aot-build.js +9 -1
  15. package/lib/aot-impl.js +10 -2
  16. package/lib/aot-mark.js +7 -0
  17. package/lib/compiled-bare-rich.js +54 -0
  18. package/lib/compiled-bare.js +44 -0
  19. package/lib/compiled-common.js +17 -2
  20. package/lib/compiled-defaults.browser.js +9 -0
  21. package/lib/compiled-defaults.js +8 -0
  22. package/lib/compiled-object.js +123 -0
  23. package/lib/compiled-plain.js +57 -0
  24. package/lib/compiled.js +2 -1
  25. package/lib/email-literal.js +29 -0
  26. package/lib/enrich-error.js +5 -1
  27. package/lib/error-codes.js +6 -76
  28. package/lib/formats-source.js +20 -3
  29. package/lib/formats.js +28 -1
  30. package/lib/interpreter-core.js +4 -28
  31. package/lib/js-compiler-errors.js +4 -0
  32. package/lib/json-rejections.js +159 -0
  33. package/lib/native-codes.js +67 -0
  34. package/lib/native-message.js +8 -0
  35. package/lib/native.browser.js +11 -0
  36. package/lib/rejections.js +13 -152
  37. package/lib/shape-classifier.js +6 -1
  38. package/lib/standard-schema.js +1 -1
  39. package/lib/tier0.js +17 -4
  40. package/lib/validator-core.js +50 -19
  41. package/lib/verify.js +68 -3
  42. package/lib/version.js +1 -1
  43. package/package.json +54 -10
package/README.md CHANGED
@@ -54,7 +54,14 @@ resolves to an entry without the code generator through the `workerd` and `edge-
54
54
  conditions: the same names and answers, and a Worker bundled by wrangler goes from 179.1 to
55
55
  70.4 KiB gzipped.
56
56
 
57
- In a browser bundle the code generator is not part of the page. Every schema starts on the
57
+ In a browser, compile with the bundler plugin. [`@ata-project/unplugin`](https://github.com/ata-core/unplugin-ata)
58
+ turns each `new Validator(schema)` whose schema it can read at build time into a compiled
59
+ validator that gives the same answers, and the runtime stays out of the page. A form with two
60
+ validators, one reading errors and one asking only for a verdict, bundles to 10.7 KB gzipped,
61
+ or 3.1 KB with `richErrors: false` on the one that reads errors, against 53.2 KB for the same
62
+ code on the runtime (esbuild, minified, the runtime with code splitting).
63
+
64
+ Without the plugin, the code generator is still not part of the page. Every schema starts on the
58
65
  interpreted engine, which gives the same answers, and a validator called a thousand times within
59
66
  a second loads the generator in the background, through a dynamic import that Vite, webpack and
60
67
  Rollup put in a chunk of their own, and is rebuilt on generated code when the page is idle. A
@@ -649,7 +656,24 @@ checks takes 1.12 ms where the runtime takes 6.63 ms (median of 15 fresh process
649
656
  SchemaStore's 977 schemas, 725 can be compiled away; the rest stay on the runtime. Where the code only calls `isValidObject()` or `isValidJSON()`, the plugin uses
650
657
  `fromCompiledVerdict()` from `ata-validator/compiled-verdict` instead, which carries no error pipeline: a
651
658
  small app that only asks for a boolean bundles to 2.6 KB gzipped, against 12.1 KB with the full wrapper, on
652
- ata 1.40.0 with `@ata-project/unplugin` 0.5.0.
659
+ ata 1.40.0 with `@ata-project/unplugin` 0.5.0. Where it reads errors but never validates JSON text, the plugin
660
+ uses `fromCompiledObject()` from `ata-validator/compiled-object`, the full wrapper without
661
+ `validateJSON()` and `isValidJSON()` and the position map behind them; and the wrappers fill
662
+ defaults with the compiled module's own `applyDefaults`. A form app with one validator that reads
663
+ errors and one that asks for a boolean went from 16.7 to 13.9 KB gzipped (esbuild, minified),
664
+ ata 1.61.0 against master with `@ata-project/unplugin` 0.11.0 and the change. The wrappers on
665
+ their own: full 13.8 to 12.8 KB, without JSON entry points 10.9, verdict only 2.7 to 1.6.
666
+ A validator built with `{ richErrors: false }` is compiled away as well: the module writes its
667
+ errors in that plain shape (`compiledModuleFor(schema, { plainErrors: true })`) and
668
+ `fromCompiledPlain()` from `ata-validator/compiled-plain` hands them out as they are, with no
669
+ error pipeline in the bundle, 2.6 KB gzipped for the wrapper. Where the plugin sees a
670
+ validator only ever called (`v.validate(x)`, `v.isValidObject(x)`), never handed to other code,
671
+ it uses the bare wrappers of `ata-validator/compiled-bare` instead, which leave out Standard
672
+ Schema and the extension hook nothing can reach there: 0.3 KB gzipped for both, and
673
+ `ata-validator/compiled-bare-rich` for one that reads ata's own errors. The same form app
674
+ bundles to 10.7 KB gzipped, or 3.1 KB with `richErrors: false` on the validator that reads errors. The errors
675
+ then carry `keyword`, `instancePath`, `schemaPath`, `params` and `message`, as that option has
676
+ always given them, without `received`, `suggestion` or `docUrl`.
653
677
 
654
678
  Programmatic API if you prefer to script it:
655
679
 
@@ -693,7 +717,8 @@ npx ata verify 'schemas/*.json' --samples test/fixtures
693
717
  ```
694
718
 
695
719
  For each schema it loads the module `ata build` wrote beside it, makes
696
- documents from the schema (200 by default, half of them with one rule broken),
720
+ documents from the schema (200 by default, half of them with one rule broken,
721
+ then a document on either side of each bound the schema states),
697
722
  reads the project's sample documents (JSON files, JSON Lines, or a directory
698
723
  of them), and has both the module and a `Validator` built from the schema now
699
724
  answer every one. Verdicts and error lists have to agree; a module whose
@@ -723,10 +748,12 @@ So, in order of preference:
723
748
  module by hand. The module is plain JavaScript and compiles with the
724
749
  program: 24 ns, the same errors as the runtime.
725
750
  2. Where the runtime `Validator` stays, let it take the interpreted engine.
726
- It does so by itself when Bun reports `Bun.isAOT`, when the executable is
727
- built with `--disallow-code-generation-from-strings` (the program then
728
- counts as closed for the compiler, too), or when `ATA_ENGINE=interpreter`
729
- is set: 80 ns against 492 for the generated code there.
751
+ It does so by itself under `bun build --compile --aot` and `bun run --aot`,
752
+ where Bun resolves the package's `"aot"` export condition to an entry that
753
+ says so; also when the executable is built with
754
+ `--disallow-code-generation-from-strings` (the program then counts as
755
+ closed for the compiler, too), or when `ATA_ENGINE=interpreter` is set:
756
+ 78 ns against 487 for the generated code there.
730
757
  3. Write the module for the compiler. `ata build schema.json --target bun`
731
758
  writes it with a closed object's keys read once and compared with the
732
759
  declared list in order (on a ten-item order with 1024 distinct documents,
@@ -0,0 +1,6 @@
1
+ // ata-validator/compiled-bare-rich: the compiled-bare wrapper for a validator
2
+ // a bundler plugin sees only called, that reads ata's own errors.
3
+ import type { ValidationResult } from './index.js';
4
+ import type { CompiledModule } from './compiled.js';
5
+
6
+ export function bareRich<T = unknown>(mod: CompiledModule, schema: object | boolean, useDefaults?: boolean): { validate(data: unknown): ValidationResult<T>; isValidObject(data: unknown): data is T };
@@ -0,0 +1,7 @@
1
+ // ata-validator/compiled-bare: the smallest compiled-module wrappers, for a
2
+ // validator a bundler plugin sees used only through direct method calls.
3
+ import type { ValidationResult } from './index.js';
4
+ import type { CompiledModule } from './compiled.js';
5
+
6
+ export function bareVerdict(mod: CompiledModule, useDefaults?: boolean): { isValidObject(data: unknown): boolean };
7
+ export function barePlain<T = unknown>(mod: CompiledModule, useDefaults?: boolean): { validate(data: unknown): ValidationResult<T>; isValidObject(data: unknown): data is T };
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ // ata-validator/compiled-bare: the smallest compiled-module wrappers, for a
4
+ // bundler plugin. See lib/compiled-bare.js.
5
+ module.exports = require('./lib/compiled-bare.js');
@@ -0,0 +1,35 @@
1
+ // ata-validator/compiled-bare as an ES module of its own rather than a
2
+ // re-export of the CommonJS file: a bundler then has no CommonJS interop to
3
+ // add, which was more than half of the bundled size (662 to 315 bytes
4
+ // gzipped). The same functions as lib/compiled-bare.js;
5
+ // tests/test_compiled_bare.js holds the two to the same answers.
6
+
7
+ const EMPTY_ERRORS = Object.freeze([]);
8
+ const VERDICT_DISAGREES = Object.freeze({
9
+ valid: false,
10
+ errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
11
+ });
12
+
13
+ function verdictOf(mod, useDefaults) {
14
+ const fill = useDefaults !== false && typeof mod.applyDefaults === 'function' ? mod.applyDefaults : null;
15
+ return fill ? (d) => { fill(d); return mod.isValid(d); } : mod.isValid;
16
+ }
17
+
18
+ export function bareVerdict(mod, useDefaults) {
19
+ return { isValidObject: verdictOf(mod, useDefaults) };
20
+ }
21
+
22
+ export function barePlain(mod, useDefaults) {
23
+ const isValid = verdictOf(mod, useDefaults);
24
+ const errFn = mod.validate;
25
+ return {
26
+ validate(data) {
27
+ if (isValid(data)) return { valid: true, data, errors: EMPTY_ERRORS };
28
+ const r = errFn(data);
29
+ return r && r.valid === false ? { valid: false, errors: r.errors } : VERDICT_DISAGREES;
30
+ },
31
+ isValidObject: isValid,
32
+ };
33
+ }
34
+
35
+ export default { bareVerdict, barePlain };
@@ -0,0 +1,11 @@
1
+ // ata-validator/compiled-object: ata-validator/compiled without the JSON text
2
+ // entry points, for code that reads errors but never calls validateJSON().
3
+ import type { ValidationResult } from './index.js';
4
+ import type { CompiledModule, CompiledOptions } from './compiled.js';
5
+
6
+ export interface CompiledObjectValidator<T = unknown> {
7
+ validate(data: unknown): ValidationResult<T>;
8
+ isValidObject(data: unknown): data is T;
9
+ }
10
+
11
+ export function fromCompiledObject<T = unknown>(mod: CompiledModule, schema: object, options?: CompiledOptions): CompiledObjectValidator<T>;
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ // ata-validator/compiled-object: ata-validator/compiled without validateJSON()
4
+ // and isValidJSON(), for code that reads errors but never validates JSON text.
5
+ // See lib/compiled-object.js.
6
+ module.exports = require('./lib/compiled-object.js');
@@ -0,0 +1,3 @@
1
+ import mod from './compiled-object.js';
2
+ export const { fromCompiledObject } = mod;
3
+ export default mod;
@@ -0,0 +1,16 @@
1
+ // ata-validator/compiled-plain: the compiled-module wrapper for a Validator
2
+ // built with `richErrors: false`, for a module written with `plainErrors: true`.
3
+ import type { ValidationResult } from './index.js';
4
+ import type { CompiledModule } from './compiled.js';
5
+
6
+ export interface CompiledPlainValidator<T = unknown> {
7
+ validate(data: unknown): ValidationResult<T>;
8
+ isValidObject(data: unknown): data is T;
9
+ }
10
+
11
+ export interface CompiledPlainOptions {
12
+ richErrors?: false;
13
+ useDefaults?: boolean;
14
+ }
15
+
16
+ export function fromCompiledPlain<T = unknown>(mod: CompiledModule, schema: object, options?: CompiledPlainOptions): CompiledPlainValidator<T>;
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ // ata-validator/compiled-plain: the compiled-module wrapper for a Validator
4
+ // built with `richErrors: false`. See lib/compiled-plain.js.
5
+ module.exports = require('./lib/compiled-plain.js');
@@ -0,0 +1,3 @@
1
+ import mod from './compiled-plain.js';
2
+ export const { fromCompiledPlain } = mod;
3
+ export default mod;
package/index.aot.js ADDED
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ // ata-validator under Bun's ahead-of-time compiler, where Bun resolves the
4
+ // `"aot"` export condition: the same package, with validators on the
5
+ // interpreted engine by default, which is compiled with the program, rather
6
+ // than on code made at run time, which is not (lib/aot-mark.js).
7
+ require('./lib/aot-mark.js');
8
+ module.exports = require('./index.js');
package/index.aot.mjs ADDED
@@ -0,0 +1,5 @@
1
+ // ata-validator under Bun's ahead-of-time compiler; see index.aot.js. The mark
2
+ // is imported first, so it runs before the package and before any caller.
3
+ import './lib/aot-mark.js';
4
+ export * from './index.mjs';
5
+ export { default } from './index.mjs';
package/lib/aot-build.js CHANGED
@@ -692,7 +692,15 @@ function compiledModuleFor(schema, opts) {
692
692
  // The one-pass error function where the caller asks (a plugin writing
693
693
  // for a Bun executable): 3.6x cheaper on a rejection, 20% larger gzipped.
694
694
  onePass: opts && opts.onePass === true ? true : undefined,
695
- source: opts && opts.onePass === true ? false : undefined,
695
+ source: opts && (opts.onePass === true || opts.plainErrors === true) ? false : undefined,
696
+ // Errors in the runtime's `richErrors: false` shape, for
697
+ // ata-validator/compiled-plain, which hands them out as they are.
698
+ plainErrors: !!(opts && opts.plainErrors === true),
699
+ // `verdictOnly`: the module for a reader that only ever calls isValid
700
+ // (a bundler plugin's verdict wrappers). Its validate() hands out the
701
+ // abort-early stub, so the error collector, never called there, is not
702
+ // written: 954 to 539 bytes gzipped on a one-field object.
703
+ abortEarly: !!(opts && opts.verdictOnly === true),
696
704
  // Errors in the runtime's order, for a reader that does not sort them
697
705
  // itself (a form resolver); fromCompiled sorts, so a plugin putting
698
706
  // that wrapper around the module leaves it off.
package/lib/aot-impl.js CHANGED
@@ -430,7 +430,12 @@ function toStandaloneModule(validator, opts) {
430
430
  // documents against large schemas asks for it.
431
431
  let onePass = null;
432
432
  let ordered = false;
433
- const wantOnePass = opts && opts.onePass === true ? 'always' : (opts && (opts.onePass === false || opts.noOnePass)) ? 'never' : 'auto';
433
+ // `plainErrors`: the errors in the runtime's `richErrors: false` shape, as
434
+ // the one-pass program writes them before codes and links are stamped on.
435
+ // A wrapper that hands them out as they are (ata-validator/compiled-plain)
436
+ // then carries no error pipeline of its own.
437
+ const plainErrors = !!(opts && opts.plainErrors === true);
438
+ const wantOnePass = plainErrors || (opts && opts.onePass === true) ? 'always' : (opts && (opts.onePass === false || opts.noOnePass)) ? 'never' : 'auto';
434
439
  // Asked for explicitly, the one-pass function wins over schema-source
435
440
  // frames, which `ata compile` writes by default outside production; the
436
441
  // caller is told, since it asked for both.
@@ -445,7 +450,10 @@ function toStandaloneModule(validator, opts) {
445
450
  }
446
451
  if (wantOnePass === 'always' || errLen === 0) onePass = onePassCore(validator, opts);
447
452
  }
448
- if (onePass !== null) {
453
+ if (onePass !== null && plainErrors) {
454
+ errCore = `const _ER = function(e) { this.valid = false; this.errors = e; };\n` +
455
+ `const _vC = (function() {\n${onePass.core}\n})();\n`;
456
+ } else if (onePass !== null) {
449
457
  errCore = `function _ataStamp(e) { for (let i = 0; i < e.length; i++) { const x = e[i]; if (x.code === undefined) { const c = x.keyword === 'required' && typeof x.schemaPath === 'string' && x.schemaPath.endsWith('/dependentRequired') ? 'ATA7005' : _ataCode(x.keyword, x.params && x.params.format); if (c !== null) { x.code = c; x.docUrl = 'https://ata-validator.com/e/' + c; } } else if (x.docUrl === undefined) x.docUrl = 'https://ata-validator.com/e/' + x.code; if (x.branchErrors !== undefined) _ataStamp(x.branchErrors); } }\n` +
450
458
  `const _ER = function(e) { _ataStamp(e); this.valid = false; this.errors = e; };\n` +
451
459
  `${codeTableSource(onePass.keywords, onePass.formats)}const _vC = (function() {\n${onePass.core}\n})();\n`;
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ // Loaded first by index.aot.js and index.aot.mjs, the entries Bun resolves
4
+ // through the `"aot"` export condition when it compiles a program ahead of
5
+ // time: every Validator made after this takes the interpreted engine by
6
+ // default (see aotHost in lib/validator-core.js).
7
+ require('./validator-core')._markAotHost();
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ // bareRich: the compiled-bare wrapper for a validator that reads ata's own
4
+ // errors (`richErrors` left on) and is only ever called, never handed to other
5
+ // code: validate() and isValidObject() as lib/compiled-object.js answers them,
6
+ // down to the error objects, without the Standard Schema member and the
7
+ // extension hook nothing can reach there, and without the option checks the
8
+ // bundler plugin already made. A separate entry from lib/compiled-bare.js so
9
+ // a bundle with only plain or verdict validators carries no error pipeline.
10
+ // tests/test_compiled_bare.js holds it to the runtime.
11
+
12
+ const { LazyRejection, RichRejection, _enrichLazy } = require('./rejections');
13
+
14
+ const EMPTY_ERRORS = Object.freeze([]);
15
+ const VERDICT_DISAGREES = Object.freeze({
16
+ valid: false,
17
+ errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
18
+ });
19
+
20
+ // The fields of a Validator the rejection classes read.
21
+ class CompiledState {
22
+ constructor(schema, fill) {
23
+ this._schemaObj = schema;
24
+ this._schemaPositions = undefined;
25
+ this._source = undefined;
26
+ this._mutatesInput = fill !== null;
27
+ this._preprocess = fill;
28
+ }
29
+ }
30
+
31
+ function bareRich(mod, schema, useDefaults) {
32
+ const fill = useDefaults !== false && typeof mod.applyDefaults === 'function' ? mod.applyDefaults : null;
33
+ const verdict = mod.isValid;
34
+ const isValid = fill ? (d) => { fill(d); return verdict(d); } : verdict;
35
+ const self = new CompiledState(schema, fill);
36
+ const errFn = mod.validate;
37
+ const rich = (data) => {
38
+ const r = errFn(data);
39
+ return new RichRejection(r && r.valid === false ? r : VERDICT_DISAGREES, data, null, self, schema, _enrichLazy);
40
+ };
41
+ const buildErrors = (data) => rich(data).errors;
42
+ const buildRawErrors = (data) => rich(data)._ataRaw();
43
+ return {
44
+ validate(data) {
45
+ if (isValid(data)) return { valid: true, data, errors: EMPTY_ERRORS };
46
+ // The runtime returns the rich rejection directly when it rewrites input.
47
+ if (fill) return rich(data);
48
+ return new LazyRejection(buildErrors, data, buildRawErrors);
49
+ },
50
+ isValidObject: isValid,
51
+ };
52
+ }
53
+
54
+ module.exports = { bareRich };
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ // The smallest wrappers around a compiled module, for a validator the bundler
4
+ // plugin sees used only through direct calls of its methods: never handed to
5
+ // other code, so nothing reads `~standard` or registers a check on it with
6
+ // `_extendChecks`, and its options were read at build time. What is left is
7
+ // what those calls answer: defaults filled with the module's own pass, the
8
+ // verdict, and for `richErrors: false` the module's plain errors as they are.
9
+ // The same answers as fromCompiledVerdict and fromCompiledPlain
10
+ // (tests/test_compiled_bare.js), without their API surface: 0.3 KB gzipped
11
+ // against 1.6 and 2.6.
12
+
13
+ const EMPTY_ERRORS = Object.freeze([]);
14
+ const VERDICT_DISAGREES = Object.freeze({
15
+ valid: false,
16
+ errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
17
+ });
18
+
19
+ function verdictOf(mod, useDefaults) {
20
+ const fill = useDefaults !== false && typeof mod.applyDefaults === 'function' ? mod.applyDefaults : null;
21
+ return fill ? (d) => { fill(d); return mod.isValid(d); } : mod.isValid;
22
+ }
23
+
24
+ // isValidObject() only.
25
+ function bareVerdict(mod, useDefaults) {
26
+ return { isValidObject: verdictOf(mod, useDefaults) };
27
+ }
28
+
29
+ // validate() and isValidObject() for `richErrors: false`, around a module
30
+ // written with `plainErrors: true`.
31
+ function barePlain(mod, useDefaults) {
32
+ const isValid = verdictOf(mod, useDefaults);
33
+ const errFn = mod.validate;
34
+ return {
35
+ validate(data) {
36
+ if (isValid(data)) return { valid: true, data, errors: EMPTY_ERRORS };
37
+ const r = errFn(data);
38
+ return r && r.valid === false ? { valid: false, errors: r.errors } : VERDICT_DISAGREES;
39
+ },
40
+ isValidObject: isValid,
41
+ };
42
+ }
43
+
44
+ module.exports = { bareVerdict, barePlain };
@@ -5,7 +5,22 @@
5
5
  // points; lib/compiled-verdict.js answers only the two that return a boolean,
6
6
  // so a bundle that never reads errors does not carry the error pipeline.
7
7
 
8
- const { buildDefaultsApplier } = require('./defaults');
8
+ // A module from 1.58.0 on fills its own defaults (`applyDefaults`, exported
9
+ // when the schema has one, the same pass as the runtime's: tests hold the two
10
+ // equal), so the wrapper takes that and the defaults engine stays out of a
11
+ // bundle that wraps current modules. An older module, or one without the
12
+ // version stamp, takes the runtime's pass as before (lib/compiled-defaults.js).
13
+ const MODULE_DEFAULTS_SINCE = [1, 58, 0];
14
+ function carriesDefaults (mod) {
15
+ const v = typeof mod.ataVersion === 'string' ? mod.ataVersion.split(/[.-]/).slice(0, 3).map(Number) : null;
16
+ if (!v || v.some((n) => !Number.isInteger(n))) return false;
17
+ for (let i = 0; i < 3; i++) if (v[i] !== MODULE_DEFAULTS_SINCE[i]) return v[i] > MODULE_DEFAULTS_SINCE[i];
18
+ return true;
19
+ }
20
+ function defaultsFor (mod, schema) {
21
+ if (carriesDefaults(mod)) return typeof mod.applyDefaults === 'function' ? mod.applyDefaults : null;
22
+ return require('./compiled-defaults')(schema);
23
+ }
9
24
 
10
25
  // The Validator options the wrapper answers the same way as the runtime.
11
26
  // Anything else changes what the runtime does in a way the wrapper does not
@@ -23,7 +38,7 @@ function prepare(mod, schema, options, name) {
23
38
  throw new TypeError(`${name} does not support the ${key} option; a Validator with it has to stay on the runtime`);
24
39
  }
25
40
  }
26
- const fill = options && options.useDefaults === false ? null : buildDefaultsApplier(schema);
41
+ const fill = options && options.useDefaults === false ? null : defaultsFor(mod, schema);
27
42
  const isValid = fill ? (d) => { fill(d); return mod.isValid(d); } : mod.isValid;
28
43
  return { fill, isValid };
29
44
  }
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ // Browser build of lib/compiled-defaults.js. A module from ata-validator 1.58.0
4
+ // on carries `applyDefaults` when its schema has a default, and the wrapper
5
+ // uses it; only an older module needs the runtime's pass, which this build
6
+ // leaves out. Such a module is refused rather than run without its defaults.
7
+ module.exports = function () {
8
+ throw new TypeError('This compiled module was built before ata-validator 1.58.0 and its schema has defaults; rebuild it to use it in a browser bundle.');
9
+ };
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ // The runtime's defaults pass, for a module built before modules carried their
4
+ // own (ata-validator 1.58.0, when the pass reached behind `$ref`, `allOf` and
5
+ // array elements). The browser build maps this file to
6
+ // lib/compiled-defaults.browser.js, so a page that wraps a current module does
7
+ // not carry the defaults engine twice.
8
+ module.exports = require('./defaults').buildDefaultsApplier;
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ // lib/compiled.js without the JSON entry points: validate(), isValidObject()
4
+ // and Standard Schema, answered as a Validator with default options answers
5
+ // them, down to the error objects. A bundler plugin puts this in place of
6
+ // `new Validator(schema)` when the code reads errors but never calls
7
+ // validateJSON() or isValidJSON(), so the position map those use to frame an
8
+ // error in the JSON text (lib/data-position-cache.js and the walk behind it)
9
+ // stays out of the bundle: 14.1 to 12.3 KB gzipped for the wrapper.
10
+ // tests/test_compiled_parity.js holds it to the full wrapper.
11
+ //
12
+ // Only for a schema compiledEligible() accepts, and with the schema
13
+ // compiledSchemaFor() returns: the one the runtime reads, after normalization.
14
+ // Custom error messages stay on the runtime, and so does every option except
15
+ // the ones COMPILED_OPTIONS lists. Defaults are applied as a default Validator
16
+ // applies them, before any check, with the same code; `useDefaults: false`
17
+ // leaves the input as it is, as the runtime then does.
18
+
19
+ const { LazyRejection, RichRejection, _enrichLazy } = require('./rejections');
20
+ const { prepare, installExtension, EXT_FALLBACK } = require('./compiled-common');
21
+ const { installStandard } = require('./standard-schema');
22
+
23
+ const EMPTY_ERRORS = Object.freeze([]);
24
+ const VERDICT_DISAGREES = Object.freeze({
25
+ valid: false,
26
+ errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
27
+ });
28
+
29
+ // The fields of a Validator the rejection classes read.
30
+ class CompiledState {
31
+ constructor(schema) {
32
+ this._schemaObj = schema;
33
+ this._schemaPositions = undefined;
34
+ this._source = undefined;
35
+ this._mutatesInput = false;
36
+ this._preprocess = null;
37
+ }
38
+ }
39
+
40
+ function fromCompiledObject(mod, schema, options) {
41
+ const { fill, isValid } = prepare(mod, schema, options, 'fromCompiledObject');
42
+ const self = new CompiledState(schema);
43
+ if (fill) {
44
+ self._mutatesInput = true;
45
+ self._preprocess = fill;
46
+ }
47
+ const errFn = mod.validate;
48
+ // The generated function's own result, rejected: if it says valid where the
49
+ // verdict said no, the disagreement is reported rather than turned into an
50
+ // acceptance, as the core does.
51
+ const raw = (data) => {
52
+ const r = errFn(data);
53
+ return r && r.valid === false ? r : VERDICT_DISAGREES;
54
+ };
55
+ const rich = (data) => new RichRejection(raw(data), data, null, self, schema, _enrichLazy);
56
+ const buildErrors = (data) => rich(data).errors;
57
+ const buildRawErrors = (data) => rich(data)._ataRaw();
58
+ const api = {
59
+ validate(data) {
60
+ if (isValid(data)) return { valid: true, data, errors: EMPTY_ERRORS };
61
+ // The runtime returns the rich rejection directly when it rewrites input.
62
+ if (fill) return rich(data);
63
+ return new LazyRejection(buildErrors, data, buildRawErrors);
64
+ },
65
+ isValidObject(data) {
66
+ return isValid(data);
67
+ },
68
+ };
69
+ installExtension(api, schema, (base, ext) => {
70
+ return {
71
+ // The verdict is the schema's and the check's. A failure lists the
72
+ // schema's errors, then the check's, read when somebody asks.
73
+ validate(data) {
74
+ const r = base.validate(data);
75
+ if (r.valid && ext.check(data)) return r;
76
+ return new ExtendedRejection(r, data, ext.errors);
77
+ },
78
+ isValidObject: (d) => base.isValidObject(d) && ext.check(d),
79
+ };
80
+ });
81
+ // Standard Schema V1, as the runtime Validator hands it out, so a validator
82
+ // the build compiled away still works where the runtime one did (a form
83
+ // library, a router, an RPC layer). The schema it publishes is the one the
84
+ // module was built from, as the runtime reads it.
85
+ installStandard(api, () => schema, (self, value) => self.validate(value));
86
+ return api;
87
+ }
88
+
89
+ // validate() under a check, as the runtime reports it (ExtendedRejection in
90
+ // validator-core.js): the inner result's errors, then the check's.
91
+ class ExtendedRejection {
92
+ constructor(inner, data, collect) {
93
+ this.valid = false;
94
+ this._inner = inner;
95
+ this._data = data;
96
+ this._collect = collect;
97
+ this._errors = null;
98
+ }
99
+ toJSON() {
100
+ return { valid: false, errors: this.errors };
101
+ }
102
+ _ataRaw() {
103
+ const inner = this._inner;
104
+ const more = this._collect(this._data) || [];
105
+ const raw = inner.valid ? more : (typeof inner._ataRaw === 'function' ? inner._ataRaw() : inner.errors).concat(more);
106
+ return raw.length ? raw : [EXT_FALLBACK];
107
+ }
108
+ }
109
+ Object.defineProperty(ExtendedRejection.prototype, 'errors', {
110
+ enumerable: true,
111
+ configurable: true,
112
+ get() {
113
+ if (this._errors === null) {
114
+ const inner = this._inner;
115
+ const more = this._collect(this._data) || [];
116
+ const all = inner.valid ? more : inner.errors.concat(more);
117
+ this._errors = all.length ? all : [EXT_FALLBACK];
118
+ }
119
+ return this._errors;
120
+ },
121
+ });
122
+
123
+ module.exports = { fromCompiledObject };
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ // The compiled-module wrapper for `new Validator(schema, { richErrors: false })`:
4
+ // validate(), isValidObject() and Standard Schema, answered as such a
5
+ // Validator answers them, errors in its plain shape (keyword, instancePath,
6
+ // schemaPath, params, message). The module is written with `plainErrors: true`
7
+ // (compiledModuleFor in ata-validator/build), so its errors are already that
8
+ // shape and in that order, and this wrapper hands them out as they are: no
9
+ // rejection classes, no enrichment, no suggestions. 2.6 KB gzipped against
10
+ // 10.9 for the rich wrapper without JSON entry points.
11
+ // tests/test_compiled_plain.js holds it to the runtime over the suite.
12
+
13
+ const { prepare, installExtension, EXT_FALLBACK } = require('./compiled-common');
14
+ const { installStandard } = require('./standard-schema');
15
+
16
+ const EMPTY_ERRORS = Object.freeze([]);
17
+ // The module's own verdict and its error function disagree (the data changed
18
+ // between them): reported, never turned into an acceptance, as the core does.
19
+ const VERDICT_DISAGREES = Object.freeze({
20
+ valid: false,
21
+ errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
22
+ });
23
+
24
+ function fromCompiledPlain(mod, schema, options) {
25
+ const opts = options ? { ...options } : undefined;
26
+ // `richErrors: false` is what this wrapper is; anything else it is not.
27
+ if (opts) {
28
+ if (opts.richErrors !== false) throw new TypeError('fromCompiledPlain answers `richErrors: false`; a Validator with rich errors takes fromCompiled');
29
+ delete opts.richErrors;
30
+ }
31
+ const { isValid } = prepare(mod, schema, opts, 'fromCompiledPlain');
32
+ const errFn = mod.validate;
33
+ const api = {
34
+ validate(data) {
35
+ if (isValid(data)) return { valid: true, data, errors: EMPTY_ERRORS };
36
+ const r = errFn(data);
37
+ return r && r.valid === false ? { valid: false, errors: r.errors } : VERDICT_DISAGREES;
38
+ },
39
+ isValidObject(data) {
40
+ return isValid(data);
41
+ },
42
+ };
43
+ installExtension(api, schema, (base, ext) => ({
44
+ validate(data) {
45
+ const r = base.validate(data);
46
+ if (r.valid && ext.check(data)) return r;
47
+ const more = ext.errors(data) || [];
48
+ const all = r.valid ? more : r.errors.concat(more);
49
+ return { valid: false, errors: all.length ? all : [EXT_FALLBACK] };
50
+ },
51
+ isValidObject: (d) => base.isValidObject(d) && ext.check(d),
52
+ }));
53
+ installStandard(api, () => schema, (self, value) => self.validate(value));
54
+ return api;
55
+ }
56
+
57
+ module.exports = { fromCompiledPlain };
package/lib/compiled.js CHANGED
@@ -15,7 +15,8 @@
15
15
  // applies them, before any check, with the same code; `useDefaults: false`
16
16
  // leaves the input as it is, as the runtime then does.
17
17
 
18
- const { LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy } = require('./rejections');
18
+ const { LazyRejection, RichRejection, _enrichLazy } = require('./rejections');
19
+ const { LazyJsonRejection } = require('./json-rejections');
19
20
  const { COMPILED_OPTIONS, prepare, parse, installExtension, extendJSON, EXT_FALLBACK } = require('./compiled-common');
20
21
  const { installStandard } = require('./standard-schema');
21
22
 
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ // Every RFC 4291 text form of an IPv6 address as one expression: eight groups,
4
+ // or a "::" standing for one or more groups of zeros with up to seven groups
5
+ // around it, the last 32 bits optionally a dotted quad. ipv6() in lib/formats.js
6
+ // is the fast check; this is the short one, which an email address literal in a
7
+ // compiled module carries instead of the parser (2.2 KB of every module that
8
+ // checks an email). tests/test_ipv6_expression.js holds the two to the same
9
+ // answer.
10
+ const IPV6_RE = (() => {
11
+ // A dotted quad, as lib/formats.js reads one.
12
+ const v4 = require('./formats').IPV4.source.slice(1, -1);
13
+ const h = '[0-9A-Fa-f]{1,4}';
14
+ const alts = [`(?:${h}:){7}${h}`, `(?:${h}:){6}${v4}`];
15
+ for (let k = 0; k <= 7; k++) {
16
+ const left = k === 0 ? '' : `(?:${h}:){${k - 1}}${h}`;
17
+ alts.push(k === 7 ? `${left}::` : `${left}::(?:(?:${h}:){0,${6 - k}}${h})?`);
18
+ if (k <= 5) alts.push(`${left}::(?:(?:${h}:){0,${5 - k}})?${v4}`);
19
+ }
20
+ return new RegExp(`^(?:${alts.join('|')})$`);
21
+ })();
22
+
23
+ // `_in` is the text between the brackets; the check returns false from the
24
+ // function it is written into when the literal is not an address.
25
+ let _src = null;
26
+ module.exports = () => _src || (_src =
27
+ `if((_in.charCodeAt(0)|32)===105&&(_in.charCodeAt(1)|32)===112&&(_in.charCodeAt(2)|32)===118&&_in.charCodeAt(3)===54&&_in.charCodeAt(4)===58){const _i6=_in.slice(5);if(_i6.length>45||!${IPV6_RE}.test(_i6))return false}` +
28
+ `else if(_in.length>15||!${require('./formats').IPV4}.test(_in))return false`);
29
+ module.exports.IPV6_RE = IPV6_RE;