ata-validator 1.60.0 → 1.61.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.
Files changed (56) hide show
  1. package/README.md +134 -6
  2. package/bin/ata.js +84 -0
  3. package/build.d.ts +29 -0
  4. package/build.mjs +2 -0
  5. package/compiled-bare-rich.d.ts +6 -0
  6. package/compiled-bare.d.ts +7 -0
  7. package/compiled-bare.js +5 -0
  8. package/compiled-bare.mjs +35 -0
  9. package/compiled-object.d.ts +11 -0
  10. package/compiled-object.js +6 -0
  11. package/compiled-object.mjs +3 -0
  12. package/compiled-plain.d.ts +16 -0
  13. package/compiled-plain.js +5 -0
  14. package/compiled-plain.mjs +3 -0
  15. package/compiled.d.ts +3 -1
  16. package/index.browser.mjs +2 -2
  17. package/index.bundle.mjs +2 -2
  18. package/index.d.ts +9 -0
  19. package/index.edge.mjs +2 -2
  20. package/index.js +48 -0
  21. package/index.mjs +1 -1
  22. package/index.node.mjs +1 -1
  23. package/lib/aot-build.js +81 -6
  24. package/lib/aot-impl.js +24 -21
  25. package/lib/bun-target.js +612 -0
  26. package/lib/codegen-engine.js +9 -3
  27. package/lib/combined-runtime.js +1 -1
  28. package/lib/compiled-bare-rich.js +54 -0
  29. package/lib/compiled-bare.js +44 -0
  30. package/lib/compiled-common.js +17 -2
  31. package/lib/compiled-defaults.browser.js +9 -0
  32. package/lib/compiled-defaults.js +8 -0
  33. package/lib/compiled-object.js +123 -0
  34. package/lib/compiled-plain.js +57 -0
  35. package/lib/compiled.js +2 -1
  36. package/lib/email-literal.js +29 -0
  37. package/lib/enrich-error.js +5 -1
  38. package/lib/error-codes.js +6 -76
  39. package/lib/formats-source.js +20 -3
  40. package/lib/formats.js +1 -1
  41. package/lib/js-compiler-errors.js +49 -10
  42. package/lib/js-compiler.js +92 -13
  43. package/lib/json-rejections.js +159 -0
  44. package/lib/native-codes.js +67 -0
  45. package/lib/native-message.js +8 -0
  46. package/lib/native.browser.js +11 -0
  47. package/lib/public-api.js +1 -0
  48. package/lib/rejections.js +13 -152
  49. package/lib/standard-schema.js +1 -1
  50. package/lib/tier0.js +1 -1
  51. package/lib/validator-core.js +46 -4
  52. package/lib/verify.js +386 -0
  53. package/lib/version.js +1 -1
  54. package/lite.js +1 -0
  55. package/lite.mjs +1 -1
  56. package/package.json +44 -9
package/lib/aot-build.js CHANGED
@@ -202,7 +202,29 @@ function pruneRegistry(roots, byId) {
202
202
  return out;
203
203
  }
204
204
 
205
+ function bunTargetPath (output) {
206
+ const ext = path.extname(output);
207
+ return output.slice(0, output.length - ext.length) + '.bun.ts';
208
+ }
209
+
205
210
  async function build(opts) {
211
+ // `--target bun`: every module written for JavaScriptCore, the plain ones
212
+ // included, since the executable the target is for runs on it.
213
+ const { setTargetEngine } = require('./js-compiler');
214
+ const prevEngine = setTargetEngine(opts.target === 'bun' ? 'jsc-aot' : null);
215
+ // An executable compiled ahead of time pays no bundle size and answers
216
+ // rejections: the one-pass error function (3.6x cheaper on a rejection, 20%
217
+ // larger gzipped) is the default there, and schema-source frames, which it
218
+ // does not carry, are off unless asked for.
219
+ if (opts.target === 'bun') opts = { ...opts, onePass: opts.onePass === undefined ? true : opts.onePass, source: opts.source === true ? true : false };
220
+ try {
221
+ return await buildWith(opts);
222
+ } finally {
223
+ setTargetEngine(prevEngine);
224
+ }
225
+ }
226
+
227
+ async function buildWith(opts) {
206
228
  const globs = opts.globs || [];
207
229
  if (globs.length === 0) throw new Error('build: at least one glob required');
208
230
  const format = opts.format || 'esm';
@@ -226,6 +248,10 @@ async function build(opts) {
226
248
 
227
249
  const compiled = [];
228
250
  const cached = [];
251
+ // --target bun: the .bun.ts written beside a module, and the schemas whose
252
+ // rules a type cannot carry.
253
+ const bunTargets = [];
254
+ const bunDeclined = [];
229
255
  const skipped = [];
230
256
  const failed = [];
231
257
  const warnings = [];
@@ -345,7 +371,10 @@ async function build(opts) {
345
371
  // the registry is put together from such modules: always rebuilt.
346
372
  !cacheEntry.usesSiblings &&
347
373
  fs.existsSync(output) &&
348
- cacheEntry.outputHash === hashContent(fs.readFileSync(output))
374
+ cacheEntry.outputHash === hashContent(fs.readFileSync(output)) &&
375
+ // The Bun target is written beside the module; when it is asked for
376
+ // and missing, the module is rebuilt so that it gets written.
377
+ (opts.target !== 'bun' || fs.existsSync(bunTargetPath(output)))
349
378
  );
350
379
  if (opts.check) {
351
380
  if (fresh) cached.push({ input, output });
@@ -460,12 +489,25 @@ async function build(opts) {
460
489
  continue;
461
490
  }
462
491
  compiled.push({ input, output, bytes: outBytes, gzipBytes: gzBytes, errors: e.mode });
492
+ const stem = path.basename(input, path.extname(input)).replace(/\.schema$/, '');
493
+ const typeName = stem
494
+ .replace(/[^A-Za-z0-9_]/g, '_')
495
+ .replace(/^([a-z])/, (m) => m.toUpperCase()) || 'Data';
496
+ if (opts.target === 'bun') {
497
+ const { writeBunTarget, Declined } = require('./bun-target');
498
+ try {
499
+ const w = writeBunTarget({ schema: e.schema, input, output, typeName });
500
+ bunTargets.push({ input, output: w.bunPath, definitions: w.definitions });
501
+ } catch (err) {
502
+ if (!(err instanceof Declined)) throw err;
503
+ // No type can be written; the plain module stands alone.
504
+ const reason = `--target bun: ${err.message}; the plain module stands alone`;
505
+ if (opts.strict) { failed.push({ input, error: reason }); continue; }
506
+ bunDeclined.push({ input, reason });
507
+ }
508
+ }
463
509
  if (opts.types !== false) {
464
510
  const { toTypeScript } = require('./ts-gen');
465
- const stem = path.basename(input, path.extname(input)).replace(/\.schema$/, '');
466
- const typeName = stem
467
- .replace(/[^A-Za-z0-9_]/g, '_')
468
- .replace(/^([a-z])/, (m) => m.toUpperCase()) || 'Data';
469
511
  const dts = toTypeScript(e.schema, { name: typeName, applyDefaults: /\nfunction applyDefaults\(/.test(src) });
470
512
  const ext = path.extname(output);
471
513
  const dtsExt = ext === '.mjs' ? '.d.mts'
@@ -514,7 +556,7 @@ async function build(opts) {
514
556
  // module, whose errors come from the runtime. Only the written one counts.
515
557
  const fromRuntime = new Set(entries.filter((e) => e.mode !== 'inline').map((e) => e.input));
516
558
  const kept = warnings.filter((w) => !(w.kind === 'error-detail' && fromRuntime.has(w.input)));
517
- return { compiled, cached, skipped, failed, warnings: kept, runtime: runtimeReport };
559
+ return { compiled, cached, skipped, failed, warnings: kept, runtime: runtimeReport, bunTargets, bunDeclined };
518
560
  }
519
561
 
520
562
  async function watch(opts, onReport) {
@@ -647,6 +689,18 @@ function compiledModuleFor(schema, opts) {
647
689
  try {
648
690
  src = aot.toStandaloneModule(validatorFor(schema), {
649
691
  format: (opts && opts.format) || 'esm',
692
+ // The one-pass error function where the caller asks (a plugin writing
693
+ // for a Bun executable): 3.6x cheaper on a rejection, 20% larger gzipped.
694
+ onePass: opts && opts.onePass === true ? true : 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),
650
704
  // Errors in the runtime's order, for a reader that does not sort them
651
705
  // itself (a form resolver); fromCompiled sorts, so a plugin putting
652
706
  // that wrapper around the module leaves it off.
@@ -664,7 +718,28 @@ function compiledModuleFor(schema, opts) {
664
718
  // not export this, and a plugin should then replace only calls without options.
665
719
  const { COMPILED_OPTIONS: compiledOptions } = require('./compiled');
666
720
 
721
+ // For a build plugin: the Bun target (lib/bun-target.js) and the verification
722
+ // (lib/verify.js), loaded on first use.
723
+ const bunTarget = {
724
+ get schemaToBunType () { return require('./bun-target').schemaToBunType },
725
+ get toBunModule () { return require('./bun-target').toBunModule },
726
+ get writeBunTarget () { return require('./bun-target').writeBunTarget },
727
+ get Declined () { return require('./bun-target').Declined },
728
+ // The generated code's target engine (lib/js-compiler): a plugin that
729
+ // writes for a Bun executable compiled ahead of time sets 'jsc-aot'.
730
+ setTargetEngine (engine) { return require('./js-compiler').setTargetEngine(engine) },
731
+ }
732
+ const verify = {
733
+ get verifyOne () { return require('./verify').verifyOne },
734
+ get verifySource () { return require('./verify').verifySource },
735
+ get generate () { return require('./verify').generate },
736
+ get renderReport () { return require('./verify').renderReport },
737
+ get ok () { return require('./verify').ok },
738
+ }
739
+
667
740
  module.exports = {
741
+ bunTarget,
742
+ verify,
668
743
  compiledOptions,
669
744
  compiledEligible,
670
745
  compiledSchemaFor,
package/lib/aot-impl.js CHANGED
@@ -367,7 +367,7 @@ function defaultsDecl(validator) {
367
367
  if (defaults.buildDefaultsApplier(schema) === null) return '';
368
368
  const embedded = require('./schema-strip').stripAnnotationsKeepingDefaults(schema);
369
369
  const text = JSON.stringify(JSON.stringify(embedded)).replace(/</g, '\\u003c');
370
- return `const _ataDefaults = (function () {\nconst MAX_DEPTH = ${defaults.MAX_DEPTH};\n${require(AOT_ORDER_MODULE).take('defaults.js', defaults.EMBED)}\nreturn buildDefaultsApplier;\n})();\n` +
370
+ return `const _ataDefaults = (function () {\nconst MAX_DEPTH = ${defaults.MAX_DEPTH};\n${loadAotOrder().take('defaults.js', defaults.EMBED)}\nreturn buildDefaultsApplier;\n})();\n` +
371
371
  `let _ataFill;\nfunction applyDefaults(d) { (_ataFill || (_ataFill = _ataDefaults(JSON.parse(${text}))))(d); }\n`;
372
372
  }
373
373
 
@@ -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`;
@@ -458,7 +466,7 @@ function toStandaloneModule(validator, opts) {
458
466
  // sorts by each error site's ordinal, the collector writes it (`_o`).
459
467
  const schemaForErrors = typeof validator._schemaObj === 'object' ? validator._schemaObj : {};
460
468
  ordered = !!(opts && opts.order === true);
461
- const noOrdinal = !ordered || !require(AOT_ORDER_MODULE).wantsOrdinals(schemaForErrors);
469
+ const noOrdinal = !ordered || !loadAotOrder().wantsOrdinals(schemaForErrors);
462
470
  jsErrFn = compileToJSCodegenWithErrors(
463
471
  schemaForErrors,
464
472
  null,
@@ -600,7 +608,7 @@ function toStandaloneModule(validator, opts) {
600
608
  compactCore = `let _ataErrs = null;\nfunction _ataErrors(d) { if (_ataErrs === null) _ataErrs = _ataCreateErrors(JSON.parse(${text})${rtOpts ? ', { ' + rtOpts + ' }' : ''}); return _ataErrs(d); }\n`;
601
609
  }
602
610
  const orderDecl = errCore && ordered && errorRuntime === null && onePass === null
603
- ? require(AOT_ORDER_MODULE).orderDecl(validator._schemaObj)
611
+ ? loadAotOrder().orderDecl(validator._schemaObj)
604
612
  : '';
605
613
  // Asked for and not given: stub errors, the one-pass function, or no error
606
614
  // code generated. The caller is told rather than handed the collector's
@@ -736,20 +744,15 @@ function validate(data) { ${pre.call}${validBody}; }
736
744
  ${parseCore}${positionsCore}${fillDecl}${hashDecl}${exports}`;
737
745
  }
738
746
 
739
- // Why a validator cannot be written as a compact module, or null when it can.
740
- // The error runtime walks the embedded schema with the interpreted engine and
741
- // knows nothing the schema does not say, so anything the validator adds from
742
- // outside the schema keeps the module self-contained.
743
- // The error runtime's generator reads the library's own files with fs, and
744
- // runs in Node only (ata build, errorRuntimeSource). It is required through a
745
- // name rather than a literal so that a bundler following this module, in a
746
- // build of the package for a runtime without fs, does not try to bring fs
747
- // along: 1.49.0 bundled for such runtimes, and the generator must not break
748
- // that.
749
- const ERROR_RUNTIME_MODULE = './error-runtime';
750
- // Reads the library's files too, at build time (see lib/aot-order.js).
751
- const AOT_ORDER_MODULE = './aot-order';
752
- function loadErrorRuntime() { return require(ERROR_RUNTIME_MODULE); }
747
+ // The error runtime and the order helpers are required by their literal
748
+ // names: an executable compiled ahead of time (Bun) loads a module from a
749
+ // computed path by throwing, and warns about every such require at build
750
+ // time. Both modules read the library's own files with fs at build time;
751
+ // a bundle for a runtime without fs is kept away from lib/aot-order.js by
752
+ // the package's `browser` field, and lib/error-runtime.js loads fs only in
753
+ // the functions that read files.
754
+ function loadErrorRuntime() { return require('./error-runtime'); }
755
+ function loadAotOrder() { return require('./aot-order'); }
753
756
 
754
757
  // The schema hash a module stamps, once per validator: a plugin writes both
755
758
  // modules of a schema (self-contained and compact) from one validator, and
@@ -973,7 +976,7 @@ function bundleStandalone(Validator, schemas, opts) {
973
976
  v._ensureCompiled();
974
977
  const jsFn = v._jsFn;
975
978
  if (!jsFn || !jsFn._hybridSource) return null;
976
- const noOrdinal = !ordered || !require(AOT_ORDER_MODULE).wantsOrdinals(v._schemaObj);
979
+ const noOrdinal = !ordered || !loadAotOrder().wantsOrdinals(v._schemaObj);
977
980
  // The schema the validator compiled, not the one the caller passed. They
978
981
  // differ whenever anything prepared the schema: draft-07 normalization,
979
982
  // `format` removed under assertFormat: false, keywords outside the
@@ -1040,7 +1043,7 @@ function bundleStandalone(Validator, schemas, opts) {
1040
1043
  // In order: the collector's errors sorted as the runtime sorts them.
1041
1044
  const hasErr = !!(jsErrFn && jsErrFn._errSource);
1042
1045
  const errDecl = ordered && hasErr
1043
- ? `${require(AOT_ORDER_MODULE).orderDecl(v._schemaObj)}var E=function(d){var r=(function(d){var _all=true;${errBody}})(d);return r&&r.errors?{valid:false,errors:_ataOrder(r.errors)}:r};`
1046
+ ? `${loadAotOrder().orderDecl(v._schemaObj)}var E=function(d){var r=(function(d){var _all=true;${errBody}})(d);return r&&r.errors?{valid:false,errors:_ataOrder(r.errors)}:r};`
1044
1047
  : `var E=function(d){var _all=true;${errBody}};`;
1045
1048
  if (opts && opts.verbose) {
1046
1049
  // Embed the schema and a small resolver so errors carry parentSchema.