ata-validator 1.61.1 → 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.
package/README.md CHANGED
@@ -748,10 +748,12 @@ So, in order of preference:
748
748
  module by hand. The module is plain JavaScript and compiles with the
749
749
  program: 24 ns, the same errors as the runtime.
750
750
  2. Where the runtime `Validator` stays, let it take the interpreted engine.
751
- It does so by itself when Bun reports `Bun.isAOT`, when the executable is
752
- built with `--disallow-code-generation-from-strings` (the program then
753
- counts as closed for the compiler, too), or when `ATA_ENGINE=interpreter`
754
- 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.
755
757
  3. Write the module for the compiler. `ata build schema.json --target bun`
756
758
  writes it with a closed object's keys read once and compared with the
757
759
  declared list in order (on a ten-item order with 1024 distinct documents,
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';
@@ -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();
package/lib/formats.js CHANGED
@@ -538,4 +538,31 @@ function email(s) {
538
538
  const DURATION_RE = /^P(?:\d+W|(?:\d+Y(?:\d+M(?:\d+D)?)?|\d+M(?:\d+D)?|\d+D)(?:T(?:\d+H(?:\d+M(?:\d+S)?)?|\d+M(?:\d+S)?|\d+S))?|T(?:\d+H(?:\d+M(?:\d+S)?)?|\d+M(?:\d+S)?|\d+S))$/;
539
539
  function duration (s) { return DURATION_RE.test(s); }
540
540
 
541
- module.exports = { IPV4, DURATION_RE, IPV6_FULL, ipv4Range, URI_FAST, date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uriWalk: _uriWalk, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, iri, iriReference, idnEmail, DURATION_RE, URI_TEMPLATE_EXPR };
541
+ // The built-in format checks by name, built on first use. The interpreted
542
+ // engine and the tier-0 verdict (lib/tier0.js) read the same table, so the two
543
+ // cannot answer differently for a format.
544
+ let _checks = null;
545
+ function formatChecks () {
546
+ return _checks || (_checks = {
547
+ email,
548
+ date,
549
+ 'date-time': dateTime,
550
+ time,
551
+ duration,
552
+ uuid,
553
+ uri,
554
+ 'uri-reference': uriReference,
555
+ ipv4,
556
+ ipv6,
557
+ hostname,
558
+ regex: (s) => { try { new RegExp(s, 'u'); return true; } catch { return false; } },
559
+ 'json-pointer': jsonPointer,
560
+ 'relative-json-pointer': relativeJsonPointer,
561
+ 'uri-template': uriTemplate,
562
+ iri,
563
+ 'iri-reference': iriReference,
564
+ 'idn-email': idnEmail,
565
+ });
566
+ }
567
+
568
+ module.exports = { formatChecks, IPV4, DURATION_RE, IPV6_FULL, ipv4Range, URI_FAST, date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uriWalk: _uriWalk, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, iri, iriReference, idnEmail, DURATION_RE, URI_TEMPLATE_EXPR };
@@ -288,33 +288,9 @@ function multipleOfOk(d, m) {
288
288
  return Math.abs(q - Math.round(q)) < 1e-9;
289
289
  }
290
290
 
291
- // The built-in format checks, built on the first schema that names a format:
292
- // the engine loads without lib/formats.js, which schemas without `format`
293
- // never need.
294
- let _formatChecks = null;
295
- function buildFormatChecks() {
296
- const _formats = require('./formats');
297
- return {
298
- email: _formats.email,
299
- date: _formats.date,
300
- 'date-time': _formats.dateTime,
301
- time: _formats.time,
302
- duration: _formats.duration,
303
- uuid: _formats.uuid,
304
- uri: _formats.uri,
305
- 'uri-reference': _formats.uriReference,
306
- ipv4: _formats.ipv4,
307
- ipv6: _formats.ipv6,
308
- hostname: _formats.hostname,
309
- regex: (s) => { try { new RegExp(s, 'u'); return true; } catch { return false; } },
310
- 'json-pointer': _formats.jsonPointer,
311
- 'relative-json-pointer': _formats.relativeJsonPointer,
312
- 'uri-template': _formats.uriTemplate,
313
- iri: _formats.iri,
314
- 'iri-reference': _formats.iriReference,
315
- 'idn-email': _formats.idnEmail,
316
- };
317
- }
291
+ // The built-in format checks come from lib/formats.js (formatChecks), loaded
292
+ // on the first schema that names a format: the engine loads without it, which
293
+ // schemas without `format` never need.
318
294
 
319
295
  // ---------- evaluation ----------
320
296
 
@@ -454,7 +430,7 @@ class Plan {
454
430
  this.formatFn = null;
455
431
  if (schema.format !== undefined) {
456
432
  const uf = interp.userFormats;
457
- const fc = (uf && typeof uf[schema.format] === 'function') ? uf[schema.format] : (_formatChecks || (_formatChecks = buildFormatChecks()))[schema.format];
433
+ const fc = (uf && typeof uf[schema.format] === 'function') ? uf[schema.format] : require('./formats').formatChecks()[schema.format];
458
434
  this.formatFn = fc || null;
459
435
  }
460
436
  this.hasString = this.minLength !== undefined || this.maxLength !== undefined || this.pattern !== null || this.formatFn !== null;
@@ -24,7 +24,7 @@ const TIER0_PRIMITIVE_ALLOWED = new Set([
24
24
  'type', 'enum', 'const',
25
25
  'minLength', 'maxLength',
26
26
  'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum',
27
- 'multipleOf',
27
+ 'multipleOf', 'format',
28
28
  ...META_KEYS,
29
29
  ]);
30
30
 
@@ -54,6 +54,11 @@ function isTier0Primitive(schema) {
54
54
  }
55
55
  }
56
56
  if (schema.const !== undefined && !isPrimitiveEnumValue(schema.const)) return false;
57
+ // `format` on a string, by name: tier 0 checks it with the table the
58
+ // interpreted engine reads (formats.formatChecks) or the caller's own
59
+ // function. A format on another type is a compat check for numbers, which
60
+ // the full engines handle.
61
+ if (schema.format !== undefined && (schema.type !== 'string' || typeof schema.format !== 'string')) return false;
57
62
  return true;
58
63
  }
59
64
 
package/lib/tier0.js CHANGED
@@ -49,7 +49,17 @@ const _hop = Object.prototype.hasOwnProperty;
49
49
  // Deno removes the __proto__ accessor; read the prototype another way there.
50
50
  const _PN = typeof Bun === 'undefined' && ({}).__proto__ === Object.prototype; // JavaScriptCore goes through Object.getPrototypeOf, see js-compiler's _PN
51
51
 
52
- function primConstraint(key, propSchema) {
52
+ // The check for a string's `format`: the caller's own function by that name,
53
+ // else the built-in one, else none (an unknown format is an annotation), as
54
+ // the interpreted engine picks it.
55
+ function formatFnOf(propSchema, userFormats) {
56
+ const f = propSchema.format;
57
+ if (typeof f !== 'string') return null;
58
+ if (userFormats && typeof userFormats[f] === 'function') return userFormats[f];
59
+ return require('./formats').formatChecks()[f] || null;
60
+ }
61
+
62
+ function primConstraint(key, propSchema, userFormats) {
53
63
  const t = propSchema.type;
54
64
  const hasEnum = Array.isArray(propSchema.enum);
55
65
  const hasConst = propSchema.const !== undefined;
@@ -75,14 +85,15 @@ function primConstraint(key, propSchema) {
75
85
  exclMin: typeof propSchema.exclusiveMinimum === 'number' ? propSchema.exclusiveMinimum : 0,
76
86
  exclMax: typeof propSchema.exclusiveMaximum === 'number' ? propSchema.exclusiveMaximum : 0,
77
87
  multipleOf: typeof propSchema.multipleOf === 'number' ? propSchema.multipleOf : 0,
88
+ formatFn: propSchema.type === 'string' ? formatFnOf(propSchema, userFormats) : null,
78
89
  };
79
90
  }
80
91
 
81
- function buildTier0Plan(schema) {
92
+ function buildTier0Plan(schema, userFormats) {
82
93
  if (schema.type !== 'object') {
83
94
  return {
84
95
  isPrimitive: true,
85
- constraints: [primConstraint('__root__', schema)],
96
+ constraints: [primConstraint('__root__', schema, userFormats)],
86
97
  requiredMask: 0,
87
98
  additionalAllowed: true,
88
99
  knownKeys: null,
@@ -96,7 +107,7 @@ function buildTier0Plan(schema) {
96
107
  let requiredMask = 0;
97
108
  for (let i = 0; i < keys.length; i++) {
98
109
  const k = keys[i];
99
- constraints[i] = primConstraint(k, props[k]);
110
+ constraints[i] = primConstraint(k, props[k], userFormats);
100
111
  knownKeys.add(k);
101
112
  if (required && required.has(k)) requiredMask |= (1 << i);
102
113
  }
@@ -128,6 +139,7 @@ function checkPrimitive(c, v) {
128
139
  const maxLen = c.maxLen;
129
140
  if (minLen >= 0) { const l = v.length; if (l < minLen) return false; if (l < minLen * 2 && codePointLength(v) < minLen) return false; }
130
141
  if (maxLen >= 0) { const l = v.length; if (l > maxLen && codePointLength(v) > maxLen) return false; }
142
+ if (c.formatFn !== null && !c.formatFn(v)) return false;
131
143
  } else if (m === T_INTEGER) {
132
144
  if (typeof v !== 'number' || !Number.isInteger(v)) return false;
133
145
  const f = c.numFlags;
@@ -186,6 +198,7 @@ function tier0ValidateObject(plan, data) {
186
198
  const maxLen = c.maxLen;
187
199
  if (minLen >= 0) { const l = v.length; if (l < minLen) return false; if (l < minLen * 2 && codePointLength(v) < minLen) return false; }
188
200
  if (maxLen >= 0) { const l = v.length; if (l > maxLen && codePointLength(v) > maxLen) return false; }
201
+ if (c.formatFn !== null && !c.formatFn(v)) return false;
189
202
  } else if (m === T_INTEGER) {
190
203
  if (typeof v !== 'number' || !Number.isInteger(v)) return false;
191
204
  const f = c.numFlags;
@@ -19,10 +19,17 @@ const { normalizeKeywords, schemaUsesKeywords } = require('./keywords');
19
19
  // bundle built from lite.js never contains it. Until something registers, the
20
20
  // core behaves exactly as it does where `new Function` is blocked: every
21
21
  // schema runs on the interpreted engine, which passes the same test suite.
22
- // Whether this process runs code compiled ahead of time, where generated
23
- // code falls to an interpreter: Bun's flag for its compiler, or the
24
- // environment saying so (an executable built before the flag existed).
22
+ // Whether this process runs code compiled ahead of time, where code made at
23
+ // run time with `new Function` falls to an interpreter and ata's interpreted
24
+ // engine, compiled with the program, is six times faster (481 against 78 ns
25
+ // a document under bun build --compile --aot). Bun resolves the package's
26
+ // `"aot"` export condition there, which loads index.aot.js and marks the
27
+ // process (_markAotHost); `Bun.isAOT` was the earlier signal, and
28
+ // ATA_ENGINE=interpreter says so from outside.
29
+ let _aotEntry = false;
30
+ function _markAotHost() { _aotEntry = true; }
25
31
  function aotHost() {
32
+ if (_aotEntry) return true;
26
33
  if (env('ATA_ENGINE') === 'interpreter') return true;
27
34
  const b = typeof Bun !== 'undefined' ? Bun : undefined;
28
35
  return !!(b && b.isAOT === true);
@@ -1213,7 +1220,13 @@ class Validator {
1213
1220
  // once per call: deciding first and walking again for the errors would
1214
1221
  // run the keyword twice, the second time on what the first left.
1215
1222
  const once = interp.dataContext === true;
1216
- if (!preprocess && !once) this._fastVerdict = (d) => interp.isValid(d);
1223
+ // A schema tier 0 takes answers its verdict there, with or without a
1224
+ // call to validate() first: the shared walk over a flat plan beats the
1225
+ // closure tree, 76 to 60 ns on Node and 85 to 49 under Bun's compiler,
1226
+ // where the closures are not typed. Not for custom keywords, which
1227
+ // tier 0 does not know.
1228
+ const t0 = !preprocess && !once && !this._usesKeywords ? _tier0Verdict(this) : null;
1229
+ if (!preprocess && !once) this._fastVerdict = t0 !== null ? t0 : (d) => interp.isValid(d);
1217
1230
  if (preprocess && !once) _preVerdict = (d) => { preprocess(d); return interp.isValid(d); };
1218
1231
  // abortEarly is a documented contract, not a property of whichever engine
1219
1232
  // answered: it promises the frozen ATA9000 stub instead of a detailed
@@ -2332,6 +2345,22 @@ for (const [name, pick] of [
2332
2345
 
2333
2346
  // Cold start: an interpreted twin answers a large schema's first calls where
2334
2347
  // the code generator is registered (see coldTwin in index.js). Lite has none.
2348
+ // Whether this validator, in the browser build before the generator has
2349
+ // come, counts its calls so a busy one moves to generated code (lib/tiering.js).
2350
+ function _ticks(self) {
2351
+ return _codegen === null && _tiering !== null && !self._interpretOnly && !env('ATA_FORCE_NAPI');
2352
+ }
2353
+ // The tier-0 verdict for a schema it takes, or null: counting calls where
2354
+ // _ticks says so.
2355
+ function _tier0Verdict(self) {
2356
+ if (require('./shape-classifier').classify(self._schemaObj).tier !== 0) return null;
2357
+ const { buildTier0Plan, tier0Validate } = require('./tier0');
2358
+ const plan = buildTier0Plan(self._schemaObj, self._userFormats);
2359
+ if (!_ticks(self)) return (d) => tier0Validate(plan, d);
2360
+ const tick = _tiering.counter(self);
2361
+ return (d) => { tick(); return tier0Validate(plan, d); };
2362
+ }
2363
+
2335
2364
  Validator.prototype._coldTwin = function () {
2336
2365
  const cg = _loadCodegen();
2337
2366
  return cg === null || cg.coldTwin === undefined ? null : cg.coldTwin(this, Validator);
@@ -2363,21 +2392,13 @@ _defineLazyMethod('isValidObject', (self) => {
2363
2392
  return self.isValidObject(data);
2364
2393
  }
2365
2394
  // Lazy: classify + build tier 0 plan on first call, not in constructor.
2366
- const _tier = require("./shape-classifier").classify(self._schemaObj);
2367
- if (_tier.tier === 0) {
2368
- // Bound once here, so the verdict below reads a local, not the module.
2369
- const { buildTier0Plan, tier0Validate } = require("./tier0");
2370
- const _plan = buildTier0Plan(self._schemaObj);
2395
+ const _t0 = _tier0Verdict(self);
2396
+ if (_t0 !== null) {
2397
+ // In the browser build the verdict counts its calls (see _tier0Verdict);
2398
+ // elsewhere the second call builds the generated code behind it.
2371
2399
  let _n = 0;
2372
- // In the browser build the generator is not there yet: count the calls
2373
- // like the interpreted engine does, or a schema checked only through
2374
- // this method stays on the plan walk, four times slower, for good.
2375
- const _tick = _codegen === null && _tiering !== null && !self._interpretOnly && !env('ATA_FORCE_NAPI') ? _tiering.counter(self) : null;
2376
- _bindVerdict(self, _tick !== null ? (d) => {
2377
- _tick();
2378
- return tier0Validate(_plan, d);
2379
- } : (d) => {
2380
- const r = tier0Validate(_plan, d);
2400
+ _bindVerdict(self, _ticks(self) ? _t0 : (d) => {
2401
+ const r = _t0(d);
2381
2402
  if (++_n === 2) {
2382
2403
  try { self._ensureCodegen(); } catch {}
2383
2404
  }
@@ -2450,6 +2471,7 @@ Object.defineProperty(module.exports, '_registerCodegen', { value: _registerCode
2450
2471
  Object.defineProperty(module.exports, '_setCodegenLoader', { value: (load) => { _loadCodegen = load; }, enumerable: false });
2451
2472
  Object.defineProperty(module.exports, '_registerTiering', { value: _registerTiering, enumerable: false });
2452
2473
  Object.defineProperty(module.exports, '_registerBuiltHook', { value: _registerBuiltHook, enumerable: false });
2474
+ Object.defineProperty(module.exports, '_markAotHost', { value: _markAotHost, enumerable: false });
2453
2475
  Object.defineProperty(module.exports, '_codegenRegistered', { value: () => _codegen !== null, enumerable: false });
2454
2476
  Object.defineProperty(module.exports, '_tiering', { get: () => _tiering, enumerable: false });
2455
2477
  Object.defineProperty(module.exports, '_registerAot', { value: _registerAot, enumerable: false });
package/lib/version.js CHANGED
@@ -7,4 +7,4 @@
7
7
  //
8
8
  // Kept in lockstep with package.json by `tests/test_version_sync.js`.
9
9
 
10
- module.exports = '1.61.1';
10
+ module.exports = '1.61.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.61.1",
3
+ "version": "1.61.2",
4
4
  "description": "JSON Schema validator that compiles to plain JavaScript, still runs where code generation is blocked, and can be compiled away at build time. Passes the full official test suite.",
5
5
  "type": "commonjs",
6
6
  "main": "index.js",
@@ -8,6 +8,11 @@
8
8
  "types": "index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
+ "aot": {
12
+ "types": "./index.d.ts",
13
+ "import": "./index.aot.mjs",
14
+ "require": "./index.aot.js"
15
+ },
11
16
  "workerd": {
12
17
  "types": "./index.d.ts",
13
18
  "import": "./index.edge.mjs",
@@ -98,7 +103,10 @@
98
103
  },
99
104
  "sideEffects": [
100
105
  "./codegen.js",
101
- "./codegen.mjs"
106
+ "./codegen.mjs",
107
+ "./lib/aot-mark.js",
108
+ "./index.aot.js",
109
+ "./index.aot.mjs"
102
110
  ],
103
111
  "bin": {
104
112
  "ata": "bin/ata.js"
@@ -170,6 +178,8 @@
170
178
  },
171
179
  "files": [
172
180
  "index.js",
181
+ "index.aot.js",
182
+ "index.aot.mjs",
173
183
  "index.mjs",
174
184
  "index.node.mjs",
175
185
  "index.bundle.mjs",
@@ -221,13 +231,13 @@
221
231
  "codegen.d.ts"
222
232
  ],
223
233
  "optionalDependencies": {
224
- "@ata-validator/native-darwin-arm64": "1.61.1",
225
- "@ata-validator/native-darwin-x64": "1.61.1",
226
- "@ata-validator/native-linux-arm64-gnu": "1.61.1",
227
- "@ata-validator/native-linux-arm64-musl": "1.61.1",
228
- "@ata-validator/native-linux-x64-gnu": "1.61.1",
229
- "@ata-validator/native-linux-x64-musl": "1.61.1",
230
- "@ata-validator/native-win32-x64": "1.61.1"
234
+ "@ata-validator/native-darwin-arm64": "1.61.2",
235
+ "@ata-validator/native-darwin-x64": "1.61.2",
236
+ "@ata-validator/native-linux-arm64-gnu": "1.61.2",
237
+ "@ata-validator/native-linux-arm64-musl": "1.61.2",
238
+ "@ata-validator/native-linux-x64-gnu": "1.61.2",
239
+ "@ata-validator/native-linux-x64-musl": "1.61.2",
240
+ "@ata-validator/native-win32-x64": "1.61.2"
231
241
  },
232
242
  "peerDependencies": {
233
243
  "yaml": "^2.0.0"