ata-validator 0.12.5 → 0.12.6

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/index.d.ts CHANGED
@@ -140,8 +140,12 @@ export class Validator {
140
140
  */
141
141
  static bundleStandalone(schemas: object[], options?: BundleStandaloneOptions): string;
142
142
 
143
- /** Bundle multiple schemas with deduplicated shared templates. Smaller output than bundle(). */
144
- static bundleCompact(schemas: object[], options?: ValidatorOptions): string;
143
+ /**
144
+ * Bundle multiple schemas with deduplicated shared templates. Smaller output
145
+ * than bundle(). Accepts the same options as bundleStandalone, including
146
+ * `format: 'esm' | 'cjs'` and cross-schema `$ref` resolution.
147
+ */
148
+ static bundleCompact(schemas: object[], options?: BundleStandaloneOptions): string;
145
149
 
146
150
  /** Load a bundle created by Validator.bundle(). Returns array of Validator instances. */
147
151
  static loadBundle(mods: object[], schemas: object[], options?: ValidatorOptions): Validator[];
package/index.js CHANGED
@@ -1187,10 +1187,12 @@ Validator.bundle = function (schemas, opts) {
1187
1187
 
1188
1188
  // Zero-dependency self-contained bundle — no require('ata-validator') needed at runtime.
1189
1189
  // opts.format: 'cjs' (default) or 'esm'.
1190
+ // opts.formats: { name: fn } — embedded in the output via Function#toString.
1190
1191
  Validator.bundleStandalone = function (schemas, opts) {
1191
- // Cross-schema $ref resolution: every Validator in the bundle needs to know
1192
- // about the others so $ref to a sibling $id can resolve at compile time.
1193
- const bundleOpts = { ...(opts || {}), schemas };
1192
+ // Cross-schema $ref resolution: only meaningful when at least one schema has
1193
+ // an $id. Skip the schemas-as-map plumbing when none of them do.
1194
+ const haveIds = schemas.some((s) => s && typeof s === 'object' && s.$id);
1195
+ const bundleOpts = haveIds ? { ...(opts || {}), schemas } : (opts || {});
1194
1196
  const format = (opts && opts.format) || 'cjs';
1195
1197
  const R = "Object.freeze({valid:true,errors:Object.freeze([])})";
1196
1198
  const fns = schemas.map((schema) => {
@@ -1201,12 +1203,25 @@ Validator.bundleStandalone = function (schemas, opts) {
1201
1203
  const jsErrFn = compileToJSCodegenWithErrors(
1202
1204
  typeof schema === "string" ? JSON.parse(schema) : schema,
1203
1205
  v._schemaMap,
1206
+ v._userFormats,
1204
1207
  );
1205
1208
  const errBody =
1206
1209
  jsErrFn && jsErrFn._errSource
1207
1210
  ? jsErrFn._errSource
1208
1211
  : "return{valid:false,errors:[{code:'error',path:'',message:'validation failed'}]}";
1209
- return `(function(R){var E=function(d){var _all=true;${errBody}};return function(d){${jsFn._hybridSource}}})(R)`;
1212
+ // Serialize custom format closures so the bundle has no runtime dep on ata.
1213
+ let preamble = '';
1214
+ if (jsFn._formatClosures) {
1215
+ preamble = jsFn._formatClosures
1216
+ .map(({ name, fn }) => `var ${name}=${fn.toString()};`)
1217
+ .join('\n');
1218
+ }
1219
+ if (opts && opts.verbose) {
1220
+ // Embed the schema and a small resolver so errors carry parentSchema.
1221
+ const schemaLit = JSON.stringify(typeof schema === 'string' ? JSON.parse(schema) : schema);
1222
+ return `(function(R){${preamble}var _S=${schemaLit};function _PS(p){if(!p||p[0]!=='#')return undefined;var s=p.slice(1);if(!s)return _S;var ps=s.split('/').filter(Boolean).map(function(x){return x.replace(/~1/g,'/').replace(/~0/g,'~')});var t=_S;for(var i=0;i<ps.length-1;i++){if(t==null||typeof t!=='object')return undefined;t=t[ps[i]]}return t}var E=function(d){var _all=true;${errBody}};var _v=function(d){${jsFn._hybridSource}};return function(d){var r=_v(d);if(r&&r.valid===false&&r.errors){var es=[];for(var i=0;i<r.errors.length;i++){var e=r.errors[i];es.push(Object.assign({},e,{parentSchema:_PS(e.schemaPath)}))}return{valid:false,errors:es}}return r}})(R)`;
1223
+ }
1224
+ return `(function(R){${preamble}var E=function(d){var _all=true;${errBody}};return function(d){${jsFn._hybridSource}}})(R)`;
1210
1225
  });
1211
1226
  const arr = `[${fns.join(",")}]`;
1212
1227
  if (format === 'esm') {
@@ -1217,15 +1232,20 @@ Validator.bundleStandalone = function (schemas, opts) {
1217
1232
 
1218
1233
  // Compact bundle: deduplicated code. Shared template functions + per-schema params.
1219
1234
  // Much smaller file → faster V8 parse → faster startup.
1235
+ // opts.format: 'cjs' (default) or 'esm'.
1220
1236
  Validator.bundleCompact = function (schemas, opts) {
1237
+ const haveIds = schemas.some((s) => s && typeof s === 'object' && s.$id);
1238
+ const bundleOpts = haveIds ? { ...(opts || {}), schemas } : (opts || {});
1239
+ const format = (opts && opts.format) || 'cjs';
1221
1240
  // Analyze schemas and group by structure
1222
1241
  const entries = schemas.map((schema) => {
1223
- const v = new Validator(schema, opts);
1242
+ const v = new Validator(schema, bundleOpts);
1224
1243
  v._ensureCompiled();
1225
1244
  const jsFn = v._jsFn;
1226
1245
  if (!jsFn || !jsFn._hybridSource) return null;
1227
1246
  const jsErrFn = compileToJSCodegenWithErrors(
1228
1247
  typeof schema === "string" ? JSON.parse(schema) : schema,
1248
+ v._schemaMap,
1229
1249
  );
1230
1250
  return {
1231
1251
  hybrid: jsFn._hybridSource,
@@ -1260,31 +1280,38 @@ Validator.bundleCompact = function (schemas, opts) {
1260
1280
  });
1261
1281
 
1262
1282
  // Generate compact bundle
1263
- let out = "'use strict';\n";
1264
- out += "var R=Object.freeze({valid:true,errors:Object.freeze([])});\n";
1283
+ const isEsm = format === 'esm';
1284
+ let out = isEsm
1285
+ ? "// Auto-generated by ata-validator — do not edit\n"
1286
+ : "'use strict';\n";
1287
+ const declKW = isEsm ? "const" : "var";
1288
+ out += `${declKW} R=Object.freeze({valid:true,errors:Object.freeze([])});\n`;
1265
1289
 
1266
1290
  // Shared hybrid factories
1267
- out += "var H=[\n";
1291
+ out += `${declKW} H=[\n`;
1268
1292
  out += bodies
1269
1293
  .map((b) => `function(R,E){return function(d){${b}}}`)
1270
1294
  .join(",\n");
1271
1295
  out += "\n];\n";
1272
1296
 
1273
1297
  // Shared error functions
1274
- out += "var EF=[\n";
1298
+ out += `${declKW} EF=[\n`;
1275
1299
  out += errBodies.map((b) => `function(d){var _all=true;${b}}`).join(",\n");
1276
1300
  out += "\n];\n";
1277
1301
 
1278
1302
  // Build validators from shared templates
1279
- out += "module.exports=[";
1280
- out += indices
1303
+ const arrBody = indices
1281
1304
  .map(([hi, ei]) => {
1282
1305
  if (hi < 0) return "null";
1283
1306
  if (ei >= 0) return `H[${hi}](R,EF[${ei}])`;
1284
1307
  return `H[${hi}](R,function(){return{valid:false,errors:[]}})`;
1285
1308
  })
1286
1309
  .join(",");
1287
- out += "];\n";
1310
+ if (isEsm) {
1311
+ out += `const validators=[${arrBody}];\nexport default validators;\nexport { validators };\n`;
1312
+ } else {
1313
+ out += `module.exports=[${arrBody}];\n`;
1314
+ }
1288
1315
 
1289
1316
  return out;
1290
1317
  };
@@ -946,6 +946,17 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
946
946
  boolFn._source = helperStr + body
947
947
  boolFn._preambleSource = preambleStr
948
948
  boolFn._hybridSource = helperStr + hybridBody
949
+ // Custom-format closure entries that the bundle output needs to recreate.
950
+ // Stored as { name, fn } so consumers can serialize via Function#toString.
951
+ if (ctx.userFormats) {
952
+ const fmtEntries = []
953
+ for (let i = 0; i < closureNames.length; i++) {
954
+ if (closureNames[i].startsWith('_uf_')) {
955
+ fmtEntries.push({ name: closureNames[i], fn: closureValues[i] })
956
+ }
957
+ }
958
+ if (fmtEntries.length) boolFn._formatClosures = fmtEntries
959
+ }
949
960
 
950
961
  return boolFn
951
962
  } catch {
@@ -2448,7 +2459,7 @@ function genCharCodeSwitch(keys, v) {
2448
2459
  // --- Error-collecting codegen: same checks, but pushes errors instead of returning false ---
2449
2460
  // Returns a function: (data, allErrors) => { valid, errors }
2450
2461
  // Valid path is still fast — only error path does extra work.
2451
- function compileToJSCodegenWithErrors(schema, schemaMap) {
2462
+ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats) {
2452
2463
  // Bail on unevaluated keywords — error codegen doesn't support them yet
2453
2464
  if (typeof schema === 'object' && schema !== null) {
2454
2465
  const s = JSON.stringify(schema)
@@ -2509,7 +2520,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap) {
2509
2520
  }
2510
2521
  }
2511
2522
 
2512
- const ctx = { varCounter: 0, helperCode: [], rootDefs: eRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: eAnchors, rootSchema: schema }
2523
+ const ctx = { varCounter: 0, helperCode: [], rootDefs: eRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: eAnchors, rootSchema: schema, userFormats: userFormats || null }
2513
2524
  ctx.helperCode.push('const _cpLen=s=>{let n=0;for(const _ of s)n++;return n}')
2514
2525
  const lines = []
2515
2526
  genCodeE(schema, 'd', '', lines, ctx, '#')
@@ -2733,15 +2744,23 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
2733
2744
  }
2734
2745
  if (schema.format) {
2735
2746
  const fc = FORMAT_CODEGEN[schema.format]
2747
+ const failPush = `_e.push({keyword:'format',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/format',params:{format:'${esc(schema.format)}'},message:'must match format "${esc(schema.format)}"'});if(!_all)return{valid:false,errors:_e}`
2736
2748
  // Format errors use the boolean codegen — just wrap with error push
2737
2749
  if (fc) {
2738
2750
  const ri = ctx.varCounter++
2739
2751
  const boolLines = []
2740
2752
  boolLines.push(fc(v, isStr))
2741
2753
  // Replace `return false` with error push in the format check
2742
- const fmtCode = boolLines.join(';').replace(/return false/g,
2743
- `{_e.push({keyword:'format',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/format',params:{format:'${esc(schema.format)}'},message:'must match format "${esc(schema.format)}"'});if(!_all)return{valid:false,errors:_e}}`)
2754
+ const fmtCode = boolLines.join(';').replace(/return false/g, `{${failPush}}`)
2744
2755
  lines.push(fmtCode)
2756
+ } else if (ctx.userFormats && typeof ctx.userFormats[schema.format] === 'function') {
2757
+ // User-supplied format checker on the error path. Same closure plumbing
2758
+ // as the boolean codegen — but error codegen has no closure factory, so
2759
+ // bundle output serializes the function via Function#toString separately.
2760
+ const safeName = schema.format.replace(/[^a-zA-Z0-9_]/g, '_')
2761
+ const closureName = `_uf_${safeName}`
2762
+ const guard = isStr ? '' : `typeof ${v}==='string'&&`
2763
+ lines.push(`if(${guard}!${closureName}(${v})){${failPush}}`)
2745
2764
  }
2746
2765
  }
2747
2766
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "0.12.5",
3
+ "version": "0.12.6",
4
4
  "description": "Ultra-fast JSON Schema validator. 5x faster validation, 159,000x faster compilation. Works without native addon. Cross-schema $ref, Draft 2020-12 + Draft 7, V8-optimized JS codegen, simdjson, RE2, multi-core. Standard Schema V1 compatible.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",