ascertain 3.2.21 → 3.2.22

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/build/index.cjs CHANGED
@@ -101,6 +101,11 @@ const OPTIONAL = Symbol.for('@@optional');
101
101
  const TUPLE = Symbol.for('@@tuple');
102
102
  const DISCRIMINATED = Symbol.for('@@discriminated');
103
103
  const CHECK = Symbol.for('@@check');
104
+ const ERR = Symbol.for('@@err');
105
+ function ErrCtor(message) {
106
+ this.message = message;
107
+ }
108
+ ErrCtor.prototype[ERR] = true;
104
109
  const OrCtor = function(schemas) {
105
110
  this.schemas = schemas;
106
111
  };
@@ -202,7 +207,39 @@ const oneOf = (values, message)=>{
202
207
  ].map(toLiteral).join(', ')}], got \${${v}}\``
203
208
  }));
204
209
  };
205
- const fromBase64 = typeof Buffer === 'undefined' ? (value)=>atob(value) : (value)=>Buffer.from(value, 'base64').toString('utf-8');
210
+ const utf8Encoder = new TextEncoder();
211
+ const utf8Decoder = new TextDecoder('utf-8');
212
+ const fromBase64Bytes = (value)=>{
213
+ const bin = atob(value);
214
+ const out = new Uint8Array(bin.length);
215
+ for(let i = 0; i < bin.length; i++)out[i] = bin.charCodeAt(i);
216
+ return out;
217
+ };
218
+ const fromBase64 = (value)=>utf8Decoder.decode(fromBase64Bytes(value));
219
+ const HEX_LUT = (()=>{
220
+ const t = new Int8Array(256).fill(-1);
221
+ for(let i = 0; i < 10; i++)t[48 + i] = i;
222
+ for(let i = 0; i < 6; i++){
223
+ t[97 + i] = 10 + i;
224
+ t[65 + i] = 10 + i;
225
+ }
226
+ return t;
227
+ })();
228
+ const fromHex = (value)=>{
229
+ let start = 0;
230
+ if (value.length >= 2 && value.charCodeAt(0) === 48 && (value.charCodeAt(1) | 32) === 120) {
231
+ start = 2;
232
+ }
233
+ const digits = value.length - start;
234
+ if (digits === 0 || digits % 2 !== 0) throw new TypeError('invalid hex length');
235
+ const out = new Uint8Array(digits / 2);
236
+ for(let i = 0; i < out.length; i++){
237
+ const byte = HEX_LUT[value.charCodeAt(start + i * 2)] << 4 | HEX_LUT[value.charCodeAt(start + i * 2 + 1)];
238
+ if (byte < 0) throw new TypeError('invalid hex digit');
239
+ out[i] = byte;
240
+ }
241
+ return out;
242
+ };
206
243
  const MULTIPLIERS = {
207
244
  ms: 1,
208
245
  s: 1000,
@@ -212,7 +249,7 @@ const MULTIPLIERS = {
212
249
  w: 604800000
213
250
  };
214
251
  const TIME_REGEX = /^(\d*\.?\d*)(ms|s|m|h|d|w)?$/;
215
- const asError = (message)=>new TypeError(message);
252
+ const asError = (message)=>new ErrCtor(message);
216
253
  const as = {
217
254
  string: (value)=>{
218
255
  return typeof value === 'string' ? value : asError(`Invalid value "${value}", expected a string`);
@@ -264,6 +301,16 @@ const as = {
264
301
  } catch {
265
302
  return asError(`Invalid value ${value}, expected a valid base64 string`);
266
303
  }
304
+ },
305
+ data: (value, type = 'utf-8')=>{
306
+ if (typeof value !== 'string') return asError(`Invalid value ${value}, expected a string`);
307
+ try {
308
+ if (type === 'hex') return fromHex(value);
309
+ if (type === 'base64') return fromBase64Bytes(value);
310
+ return utf8Encoder.encode(value);
311
+ } catch {
312
+ return asError(`Invalid value ${value}, expected a valid ${type} string`);
313
+ }
267
314
  }
268
315
  };
269
316
  const DATETIME_RE = /^\d{4}-[01]\d-[0-3]\d[t\s](?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d{2}(?::?\d{2})?)$/i;
@@ -350,6 +397,7 @@ class Context {
350
397
  registry = [];
351
398
  lookupMap = new Map();
352
399
  varIndex = 0;
400
+ pure = false;
353
401
  register(value) {
354
402
  const index = this.lookupMap.get(value);
355
403
  if (index !== undefined) {
@@ -366,32 +414,128 @@ class Context {
366
414
  }
367
415
  }
368
416
  const isTagged = (schema)=>schema?.[$op] !== undefined;
369
- const childMode = (mode, key)=>{
417
+ const buildDynamicPathExpr = (staticPath, dynamicParts)=>`[${[
418
+ ...staticPath.map((k)=>JSON.stringify(k)),
419
+ ...dynamicParts
420
+ ].join(',')}]`;
421
+ const childMode = (mode, key, ctx)=>{
422
+ const carryReady = !mode.firstError && mode.issuesReady;
370
423
  if (typeof key === 'object' && 'dynamic' in key) {
371
- return {
424
+ const dynamicParts = [
425
+ ...mode.dynamicParts ?? [],
426
+ key.dynamic
427
+ ];
428
+ const m = {
429
+ fast: false,
430
+ firstError: mode.firstError,
431
+ issues: mode.issues,
432
+ path: mode.path,
433
+ pathExpr: buildDynamicPathExpr(mode.path, dynamicParts),
434
+ dynamicParts
435
+ };
436
+ if (carryReady) m.issuesReady = true;
437
+ return m;
438
+ }
439
+ if (mode.dynamicParts) {
440
+ const dynamicParts = [
441
+ ...mode.dynamicParts,
442
+ JSON.stringify(key)
443
+ ];
444
+ const m = {
372
445
  fast: false,
373
446
  firstError: mode.firstError,
374
447
  issues: mode.issues,
375
448
  path: mode.path,
376
- pathExpr: `[${mode.path.map((k)=>JSON.stringify(k)).join(',')}${mode.path.length ? ',' : ''}${key.dynamic}]`
449
+ pathExpr: buildDynamicPathExpr(mode.path, dynamicParts),
450
+ dynamicParts
377
451
  };
452
+ if (carryReady) m.issuesReady = true;
453
+ return m;
378
454
  }
379
455
  const newPath = [
380
456
  ...mode.path,
381
457
  key
382
458
  ];
383
- return {
459
+ const pathExpr = `reg[${ctx.register(Object.freeze(newPath))}]`;
460
+ const m = {
384
461
  fast: false,
385
462
  firstError: mode.firstError,
386
463
  issues: mode.issues,
387
464
  path: newPath,
388
- pathExpr: JSON.stringify(newPath)
465
+ pathExpr
389
466
  };
467
+ if (carryReady) m.issuesReady = true;
468
+ return m;
390
469
  };
391
470
  const toLiteral = (value)=>typeof value === 'bigint' ? `${value}n` : JSON.stringify(value);
471
+ const collapsibleTypeOf = (s)=>s === String ? 'string' : s === Number ? 'number' : s === Boolean ? 'boolean' : s === BigInt ? 'bigint' : s === Symbol ? 'symbol' : typeof s === 'function' && s?.name === 'Function' ? 'function' : null;
472
+ const buildCollapsedOr = (schemas, v)=>{
473
+ let hasNull = false;
474
+ let hasUndefined = false;
475
+ const groups = new Map();
476
+ for (const s of schemas){
477
+ if (s === null) {
478
+ hasNull = true;
479
+ continue;
480
+ }
481
+ if (s === undefined) {
482
+ hasUndefined = true;
483
+ continue;
484
+ }
485
+ const ct = collapsibleTypeOf(s);
486
+ if (ct) {
487
+ let g = groups.get(ct);
488
+ if (!g) {
489
+ g = {
490
+ ctor: false,
491
+ literals: []
492
+ };
493
+ groups.set(ct, g);
494
+ }
495
+ g.ctor = true;
496
+ continue;
497
+ }
498
+ const t = typeof s;
499
+ let g = groups.get(t);
500
+ if (!g) {
501
+ g = {
502
+ ctor: false,
503
+ literals: []
504
+ };
505
+ groups.set(t, g);
506
+ }
507
+ g.literals.push(s);
508
+ }
509
+ const clauses = [];
510
+ if (hasNull) clauses.push(`${v} === null`);
511
+ if (hasUndefined) clauses.push(`${v} === undefined`);
512
+ for (const [type, { ctor, literals }] of groups){
513
+ if (ctor) {
514
+ clauses.push(type === 'number' ? `(typeof ${v} === 'number' && ${v} === ${v})` : `typeof ${v} === '${type}'`);
515
+ } else if (literals.length === 1) {
516
+ clauses.push(`${v} === ${toLiteral(literals[0])}`);
517
+ } else {
518
+ clauses.push(`(typeof ${v} === '${type}' && (${literals.map((l)=>`${v} === ${toLiteral(l)}`).join(' || ')}))`);
519
+ }
520
+ }
521
+ return clauses.join(' || ');
522
+ };
523
+ const buildExpectedDesc = (schemas)=>{
524
+ const parts = [];
525
+ for (const s of schemas){
526
+ if (s === null) parts.push('null');
527
+ else if (s === undefined) parts.push('undefined');
528
+ else if (collapsibleTypeOf(s) !== null) parts.push(s.name);
529
+ else parts.push(toLiteral(s));
530
+ }
531
+ return parts.join(', ');
532
+ };
533
+ const isCollapsible = (s)=>s === null || s === undefined || typeof s !== 'object' && typeof s !== 'function' && typeof s !== 'symbol' || collapsibleTypeOf(s) !== null;
392
534
  const codeGen = (schema, context, valuePath, mode)=>{
393
- const emit = mode.fast ? null : mode.firstError ? (msg)=>`${mode.issues} = [{ message: ${msg}, path: ${mode.pathExpr} }]; return ${mode.issues};` : (msg)=>`(${mode.issues} || (${mode.issues} = [])).push({ message: ${msg}, path: ${mode.pathExpr} });`;
535
+ const emit = mode.fast ? null : mode.firstError ? (msg)=>`${mode.issues} = [{ message: ${msg}, path: ${mode.pathExpr} }]; return ${mode.issues};` : !mode.firstError && mode.issuesReady ? (msg)=>`${mode.issues}.push({ message: ${msg}, path: ${mode.pathExpr} });` : (msg)=>`(${mode.issues} || (${mode.issues} = [])).push({ message: ${msg}, path: ${mode.pathExpr} });`;
394
536
  const fail = mode.fast ? mode.onFail ?? 'return false;' : '';
537
+ const errChk = (v)=>context.pure ? '' : ` || (typeof ${v} === 'object' && ${v} !== null && ${v}[err] === true)`;
538
+ const errBranch = (v)=>context.pure ? '' : `else if (typeof ${v} === 'object' && ${v} !== null && ${v}[err] === true) { ${emit(`\`\${${v}.message}\``)} }`;
395
539
  if (isTagged(schema)) {
396
540
  const tag = schema[$op];
397
541
  if (tag === AND) {
@@ -402,6 +546,27 @@ const codeGen = (schema, context, valuePath, mode)=>{
402
546
  const valueAlias = context.unique('v');
403
547
  const foundValid = context.unique('valid');
404
548
  if (mode.fast) {
549
+ const collapsible = [];
550
+ const complex = [];
551
+ for (const s of schema.schemas){
552
+ if (isCollapsible(s)) collapsible.push(s);
553
+ else complex.push(s);
554
+ }
555
+ if (collapsible.length > 0 && complex.length === 0) {
556
+ return `const ${valueAlias} = ${valuePath};\nif (!(${buildCollapsedOr(collapsible, valueAlias)})) { ${fail} }`;
557
+ }
558
+ if (collapsible.length > 0) {
559
+ const condition = buildCollapsedOr(collapsible, valueAlias);
560
+ const branches = complex.map((s)=>{
561
+ const branchValid = context.unique('valid');
562
+ const branchCode = codeGen(s, context, valueAlias, {
563
+ ...mode,
564
+ onFail: `${branchValid} = false;`
565
+ });
566
+ return `if (!${foundValid}) { let ${branchValid} = true; ${branchCode} if (${branchValid}) { ${foundValid} = true; } }`;
567
+ });
568
+ return `const ${valueAlias} = ${valuePath};\nlet ${foundValid} = ${condition};\n${branches.join('\n')}\nif (!${foundValid}) { ${fail} }`;
569
+ }
405
570
  const branches = schema.schemas.map((s)=>{
406
571
  const branchValid = context.unique('valid');
407
572
  const branchCode = codeGen(s, context, valueAlias, {
@@ -412,6 +577,11 @@ const codeGen = (schema, context, valuePath, mode)=>{
412
577
  });
413
578
  return `const ${valueAlias} = ${valuePath};\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { ${fail} }`;
414
579
  } else if (mode.firstError) {
580
+ if (schema.schemas.every(isCollapsible)) {
581
+ const condition = buildCollapsedOr(schema.schemas, valueAlias);
582
+ const expected = buildExpectedDesc(schema.schemas);
583
+ return `const ${valueAlias} = ${valuePath};\nif (!(${condition})) { ${mode.issues} = [{ message: \`Invalid value \${${valueAlias}}, expected one of: ${expected}\`, path: ${mode.pathExpr} }]; return ${mode.issues}; }`;
584
+ }
415
585
  const firstBranchIssues = context.unique('iss');
416
586
  const branches = schema.schemas.map((s, idx)=>{
417
587
  const branchIssues = context.unique('iss');
@@ -429,6 +599,12 @@ const codeGen = (schema, context, valuePath, mode)=>{
429
599
  });
430
600
  return `const ${valueAlias} = ${valuePath};\nlet ${firstBranchIssues};\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { return ${firstBranchIssues}; }`;
431
601
  } else {
602
+ if (schema.schemas.every(isCollapsible)) {
603
+ const condition = buildCollapsedOr(schema.schemas, valueAlias);
604
+ const expected = buildExpectedDesc(schema.schemas);
605
+ const push = mode.issuesReady ? `${mode.issues}.push({ message: \`Invalid value \${${valueAlias}}, expected one of: ${expected}\`, path: ${mode.pathExpr} });` : `(${mode.issues} || (${mode.issues} = [])).push({ message: \`Invalid value \${${valueAlias}}, expected one of: ${expected}\`, path: ${mode.pathExpr} });`;
606
+ return `const ${valueAlias} = ${valuePath};\nif (!(${condition})) { ${push} }`;
607
+ }
432
608
  const localIssues = context.unique('iss');
433
609
  const branches = schema.schemas.map((s)=>{
434
610
  const branchIssues = context.unique('iss');
@@ -441,11 +617,42 @@ const codeGen = (schema, context, valuePath, mode)=>{
441
617
  });
442
618
  return `if (!${foundValid}) { let ${branchIssues}; ${branchCode} if (!${branchIssues}) { ${foundValid} = true; } else { ${localIssues}.push(...${branchIssues}); } }`;
443
619
  });
444
- return `const ${valueAlias} = ${valuePath};\nconst ${localIssues} = [];\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { (${mode.issues} || (${mode.issues} = [])).push(...${localIssues}); }`;
620
+ const pushExpr = !mode.fast && !mode.firstError && mode.issuesReady ? `${mode.issues}.push(...${localIssues})` : `(${mode.issues} || (${mode.issues} = [])).push(...${localIssues})`;
621
+ return `const ${valueAlias} = ${valuePath};\nconst ${localIssues} = [];\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { ${pushExpr}; }`;
445
622
  }
446
623
  } else if (tag === OPTIONAL) {
447
624
  const valueAlias = context.unique('v');
448
- return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(schema.schemas[0], context, valueAlias, mode)} }`;
625
+ const inner = schema.schemas[0];
626
+ if (!mode.fast && typeof inner === 'function') {
627
+ const iname = inner?.name;
628
+ const is = inner;
629
+ const pt = is === String ? 'string' : is === Number ? 'number' : is === Boolean ? 'boolean' : is === BigInt ? 'bigint' : is === Symbol ? 'symbol' : null;
630
+ if (pt) {
631
+ const typeMsgs = Object.fromEntries([
632
+ 'string',
633
+ 'number',
634
+ 'boolean',
635
+ 'bigint',
636
+ 'symbol',
637
+ 'undefined',
638
+ 'object',
639
+ 'function'
640
+ ].map((t)=>[
641
+ t,
642
+ `Invalid type ${t}, expected type ${iname}`
643
+ ]));
644
+ const typeMsgIdx = context.register(typeMsgs);
645
+ const lines = [
646
+ `const ${valueAlias} = ${valuePath};`,
647
+ `if (${valueAlias} !== undefined && ${valueAlias} !== null) {`
648
+ ];
649
+ lines.push(`if (typeof ${valueAlias} !== '${pt}') { ${emit(`reg[${typeMsgIdx}][typeof ${valueAlias}]`)} }`);
650
+ if (pt === 'number') lines.push(`else if (${valueAlias} !== ${valueAlias}) { ${emit(`"Invalid value NaN, expected a valid ${iname}"`)} }`);
651
+ lines.push(`}`);
652
+ return lines.join('\n');
653
+ }
654
+ }
655
+ return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(inner, context, valueAlias, mode)} }`;
449
656
  } else if (tag === TUPLE) {
450
657
  const valueAlias = context.unique('v');
451
658
  if (mode.fast) {
@@ -457,12 +664,12 @@ const codeGen = (schema, context, valuePath, mode)=>{
457
664
  `else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Array\``)} }`,
458
665
  `else if (!Array.isArray(${valueAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}.constructor?.name}, expected an instance of Array\``)} }`,
459
666
  `else if (${valueAlias}.length !== ${schema.schemas.length}) { ${emit(`\`Invalid tuple length \${${valueAlias}.length}, expected ${schema.schemas.length}\``)} }`,
460
- `else { ${schema.schemas.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx))).join('\n')} }`
667
+ `else { ${schema.schemas.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx, context))).join('\n')} }`
461
668
  ].join('\n');
462
669
  }
463
670
  } else if (tag === CHECK) {
464
671
  const valueAlias = context.unique('v');
465
- const ref = (v)=>`ctx.registry[${context.register(v)}]`;
672
+ const ref = (v)=>`reg[${context.register(v)}]`;
466
673
  const { check: cond, message } = schema.compile(valueAlias, {
467
674
  ref
468
675
  });
@@ -489,31 +696,33 @@ const codeGen = (schema, context, valuePath, mode)=>{
489
696
  schema: s
490
697
  });
491
698
  }
699
+ const genVariantProps = (s, variantMode)=>{
700
+ const obj = s;
701
+ return Object.entries(obj).filter(([k])=>k !== key).map(([k, ps])=>codeGen(ps, context, `${valueAlias}[${JSON.stringify(k)}]`, variantMode.fast ? variantMode : childMode(variantMode, k, context))).join('\n');
702
+ };
492
703
  if (mode.fast) {
493
704
  const branches = variants.map(({ value, schema: s })=>{
494
- const branchCode = codeGen(s, context, valueAlias, mode);
495
- return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${branchCode} }`;
705
+ return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${genVariantProps(s, mode)} }`;
496
706
  });
497
707
  return [
498
708
  `const ${valueAlias} = ${valuePath};`,
499
- `if (${valueAlias} === null || ${valueAlias} === undefined || typeof ${valueAlias} !== 'object' || ${valueAlias} instanceof Error) { ${fail} }`,
709
+ `if (${valueAlias} === null || ${valueAlias} === undefined || typeof ${valueAlias} !== 'object'${errChk(valueAlias)}) { ${fail} }`,
500
710
  `const ${discriminantAlias} = ${valueAlias}[${keyStr}];`,
501
711
  branches.join(' else ') + ` else { ${fail} }`
502
712
  ].join('\n');
503
713
  } else {
504
714
  const validValues = variants.map((v)=>JSON.stringify(v.value)).join(', ');
505
715
  const branches = variants.map(({ value, schema: s })=>{
506
- const branchCode = codeGen(s, context, valueAlias, mode);
507
- return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${branchCode} }`;
716
+ return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${genVariantProps(s, mode)} }`;
508
717
  });
509
718
  return [
510
719
  `const ${valueAlias} = ${valuePath};`,
511
720
  `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
512
721
  `else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an object\``)} }`,
513
- `else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`,
722
+ `${errBranch(valueAlias)}`,
514
723
  `else {`,
515
724
  ` const ${discriminantAlias} = ${valueAlias}[${keyStr}];`,
516
- ` ${branches.join(' else ')} else { ${emit(`\`Invalid discriminant value \${JSON.stringify(${discriminantAlias})}, expected one of: ${validValues}\``)} }`,
725
+ ` ${branches.join(' else ')} else { ${emit(`"Invalid discriminant value " + String(${discriminantAlias}) + ", expected one of: ${validValues.replace(/"/g, "'")}"`)} }`,
517
726
  `}`
518
727
  ].join('\n');
519
728
  }
@@ -529,7 +738,7 @@ const codeGen = (schema, context, valuePath, mode)=>{
529
738
  const checks = [
530
739
  `typeof ${valueAlias} !== '${primitiveType}'`
531
740
  ];
532
- if (primitiveType === 'number') checks.push(`Number.isNaN(${valueAlias})`);
741
+ if (primitiveType === 'number') checks.push(`${valueAlias} !== ${valueAlias}`);
533
742
  return `const ${valueAlias} = ${valuePath};\nif (${checks.join(' || ')}) { ${fail} }`;
534
743
  } else if (name === 'Function') {
535
744
  return `const ${valueAlias} = ${valuePath};\nif (typeof ${valueAlias} !== 'function') { ${fail} }`;
@@ -537,28 +746,42 @@ const codeGen = (schema, context, valuePath, mode)=>{
537
746
  const isError = schema === Error || schema?.prototype instanceof Error;
538
747
  const index = context.register(schema);
539
748
  const registryAlias = context.unique('r');
540
- return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = ctx.registry[${index}];\nif (${valueAlias} === null || ${valueAlias} === undefined${isError ? '' : ` || ${valueAlias} instanceof Error`} || (typeof ${valueAlias} === 'object' && !(${valueAlias} instanceof ${registryAlias})) || (typeof ${valueAlias} !== 'object' && ${valueAlias}?.constructor !== ${registryAlias}) || Number.isNaN(${valueAlias}?.valueOf?.())) { ${fail} }`;
749
+ return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = reg[${index}];\nif (${valueAlias} === null || ${valueAlias} === undefined${isError || context.pure ? '' : ` || (typeof ${valueAlias} === 'object' && ${valueAlias}[err] === true)`} || (typeof ${valueAlias} === 'object' && !(${valueAlias} instanceof ${registryAlias})) || (typeof ${valueAlias} !== 'object' && ${valueAlias}?.constructor !== ${registryAlias}) || Number.isNaN(${valueAlias}?.valueOf?.())) { ${fail} }`;
541
750
  }
542
751
  } else {
543
752
  const code = [
544
753
  `const ${valueAlias} = ${valuePath};`
545
754
  ];
546
755
  if (primitiveType) {
547
- code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
548
- code.push(`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`);
549
- code.push(`else if (typeof ${valueAlias} !== '${primitiveType}') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected type ${name}\``)} }`);
550
- if (primitiveType === 'number') code.push(`else if (Number.isNaN(${valueAlias})) { ${emit(`\`Invalid value \${${valueAlias}}, expected a valid ${name}\``)} }`);
756
+ const typeMsgs = Object.fromEntries([
757
+ 'string',
758
+ 'number',
759
+ 'boolean',
760
+ 'bigint',
761
+ 'symbol',
762
+ 'undefined',
763
+ 'object',
764
+ 'function'
765
+ ].map((t)=>[
766
+ t,
767
+ `Invalid type ${t}, expected type ${name}`
768
+ ]));
769
+ const typeMsgIdx = context.register(typeMsgs);
770
+ code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`${valueAlias} === null ? "Invalid value null, expected non-nullable" : "Invalid value undefined, expected non-nullable"`)} }`);
771
+ code.push(`${errBranch(valueAlias)}`);
772
+ code.push(`else if (typeof ${valueAlias} !== '${primitiveType}') { ${emit(`reg[${typeMsgIdx}][typeof ${valueAlias}]`)} }`);
773
+ if (primitiveType === 'number') code.push(`else if (${valueAlias} !== ${valueAlias}) { ${emit(`"Invalid value NaN, expected a valid ${name}"`)} }`);
551
774
  } else if (name === 'Function') {
552
775
  code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
553
- code.push(`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`);
776
+ code.push(`${errBranch(valueAlias)}`);
554
777
  code.push(`else if (typeof ${valueAlias} !== 'function') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected type Function\``)} }`);
555
778
  } else {
556
779
  const isError = schema === Error || schema?.prototype instanceof Error;
557
780
  const index = context.register(schema);
558
781
  const registryAlias = context.unique('r');
559
- code.push(`const ${registryAlias} = ctx.registry[${index}];`);
782
+ code.push(`const ${registryAlias} = reg[${index}];`);
560
783
  code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
561
- if (!isError) code.push(`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`);
784
+ if (!isError) code.push(`${errBranch(valueAlias)}`);
562
785
  code.push(`else if (typeof ${valueAlias} === 'object' && !(${valueAlias} instanceof ${registryAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}?.constructor?.name}, expected an instance of ${name}\``)} }`);
563
786
  code.push(`else if (typeof ${valueAlias} !== 'object' && ${valueAlias}?.constructor !== ${registryAlias}) { ${emit(`\`Invalid type \${${valueAlias}?.constructor?.name}, expected type ${name}\``)} }`);
564
787
  code.push(`else if (Number.isNaN(${valueAlias}?.valueOf?.())) { ${emit(`\`Invalid value \${${valueAlias}}, expected a valid ${name}\``)} }`);
@@ -580,11 +803,24 @@ const codeGen = (schema, context, valuePath, mode)=>{
580
803
  }
581
804
  return code;
582
805
  } else {
806
+ const arrTypeMsgs = Object.fromEntries([
807
+ 'string',
808
+ 'number',
809
+ 'boolean',
810
+ 'bigint',
811
+ 'symbol',
812
+ 'undefined',
813
+ 'function'
814
+ ].map((t)=>[
815
+ t,
816
+ `Invalid type ${t}, expected an instance of Array`
817
+ ]));
818
+ const arrTypeMsgIdx = context.register(arrTypeMsgs);
583
819
  const code = [
584
820
  `const ${valueAlias} = ${valuePath};`,
585
- `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
586
- `else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`,
587
- `else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Array\``)} }`,
821
+ `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`${valueAlias} === null ? "Invalid value null, expected non-nullable" : "Invalid value undefined, expected non-nullable"`)} }`,
822
+ `${errBranch(valueAlias)}`,
823
+ `else if (typeof ${valueAlias} !== 'object') { ${emit(`reg[${arrTypeMsgIdx}][typeof ${valueAlias}]`)} }`,
588
824
  `else if (!Array.isArray(${valueAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}.constructor?.name}, expected an instance of Array\``)} }`
589
825
  ];
590
826
  if (schema.length > 0) {
@@ -593,10 +829,10 @@ const codeGen = (schema, context, valuePath, mode)=>{
593
829
  if (schema.length === 1) {
594
830
  code.push(`else { for (let ${key} = 0; ${key} < ${valueAlias}.length; ${key}++) { const ${value} = ${valueAlias}[${key}]; ${codeGen(schema[0], context, value, childMode(mode, {
595
831
  dynamic: key
596
- }))} } }`);
832
+ }, context))} } }`);
597
833
  } else {
598
834
  code.push(`else if (${valueAlias}.length > ${schema.length}) { ${emit(`\`Invalid tuple length \${${valueAlias}.length}, expected ${schema.length}\``)} }`);
599
- code.push(`else { ${schema.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx))).join('\n')} }`);
835
+ code.push(`else { ${schema.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx, context))).join('\n')} }`);
600
836
  }
601
837
  }
602
838
  return code.join('\n');
@@ -606,68 +842,127 @@ const codeGen = (schema, context, valuePath, mode)=>{
606
842
  if (schema instanceof RegExp) {
607
843
  const valueAlias = context.unique('v');
608
844
  if (mode.fast) {
609
- return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined || ${valueAlias} instanceof Error || !${schema.toString()}.test(String(${valueAlias}))) { ${fail} }`;
845
+ return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined${errChk(valueAlias)} || !${schema.toString()}.test(String(${valueAlias}))) { ${fail} }`;
610
846
  } else {
611
- return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }\nelse if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }\nelse if (!${schema.toString()}.test(String(${valueAlias}))) { ${emit(`\`Invalid value \${${valueAlias}}, expected to match ${schema.toString()}\``)} }`;
847
+ return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }\n${errBranch(valueAlias)}\n${context.pure ? '' : 'else '}if (!${schema.toString()}.test(String(${valueAlias}))) { ${emit(`\`Invalid value \${${valueAlias}}, expected to match ${schema.toString()}\``)} }`;
612
848
  }
613
849
  } else {
614
850
  const valueAlias = context.unique('v');
615
851
  if (mode.fast) {
616
- let code = `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || typeof ${valueAlias} !== 'object' || ${valueAlias} instanceof Error) { ${fail} }`;
852
+ const indexed = mode.indexed && !mode.onFail;
853
+ const rootFail = indexed ? 'return 0;' : fail;
854
+ let code = `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || typeof ${valueAlias} !== 'object'${errChk(valueAlias)}) { ${rootFail} }`;
617
855
  if ($keys in schema) {
618
- const keysAlias = context.unique('k');
619
856
  const kAlias = context.unique('k');
620
- code += `\nconst ${keysAlias} = Object.keys(${valueAlias});\nfor (const ${kAlias} of ${keysAlias}) { ${codeGen(schema[$keys], context, kAlias, mode)} }`;
857
+ code += `\nfor (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, indexed ? {
858
+ fast: true,
859
+ onFail: 'return 0;'
860
+ } : mode)} }`;
621
861
  }
622
862
  if ($values in schema) {
623
- const vAlias = context.unique('val');
624
863
  const kAlias = context.unique('k');
625
- const entriesAlias = context.unique('en');
626
- code += `\nconst ${entriesAlias} = Object.entries(${valueAlias});\nfor (const [${kAlias}, ${vAlias}] of ${entriesAlias}) { ${codeGen(schema[$values], context, vAlias, mode)} }`;
864
+ code += `\nfor (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, indexed ? {
865
+ fast: true,
866
+ onFail: 'return 0;'
867
+ } : mode)} }`;
627
868
  }
628
869
  if ($strict in schema && schema[$strict]) {
629
- const keysAlias = context.unique('k');
870
+ const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k)=>[
871
+ k,
872
+ 1
873
+ ])))}]`;
630
874
  const kAlias = context.unique('k');
631
- const extraAlias = context.unique('ex');
632
- code += `\nconst ${keysAlias} = new Set(${JSON.stringify(Object.keys(schema))});\nconst ${extraAlias} = Object.keys(${valueAlias}).filter(${kAlias} => !${keysAlias}.has(${kAlias}));\nif (${extraAlias}.length !== 0) { ${fail} }`;
875
+ code += `\nfor (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) { ${rootFail} } }`;
633
876
  }
634
- code += '\n' + Object.entries(schema).map(([key, s])=>codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, mode)).join('\n');
877
+ const entries = Object.entries(schema);
878
+ code += '\n' + entries.map(([key, s], idx)=>{
879
+ const propMode = indexed ? {
880
+ fast: true,
881
+ onFail: `return ${idx + 1};`
882
+ } : mode;
883
+ return codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, propMode);
884
+ }).join('\n');
885
+ if (indexed) code += `\nreturn -1;`;
635
886
  return code;
636
887
  } else {
888
+ const sv = !mode.fast && mode.startVar;
637
889
  const code = [
638
- `const ${valueAlias} = ${valuePath};`,
639
- `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
640
- `else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`,
641
- `else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`,
642
- 'else {'
890
+ `const ${valueAlias} = ${valuePath};`
643
891
  ];
892
+ if (sv) {
893
+ const rootCase = [];
894
+ if (mode.firstError) {
895
+ rootCase.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
896
+ rootCase.push(`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`);
897
+ rootCase.push(`${errBranch(valueAlias)}`);
898
+ } else {
899
+ rootCase.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} break; }`);
900
+ rootCase.push(`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} break; }`);
901
+ if (!context.pure) rootCase.push(`else if (typeof ${valueAlias} === 'object' && ${valueAlias}[err] === true) { ${emit(`\`\${${valueAlias}.message}\``)} break; }`);
902
+ }
903
+ if ($keys in schema) {
904
+ const kAlias = context.unique('k');
905
+ rootCase.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, {
906
+ dynamic: kAlias
907
+ }, context))} }`);
908
+ }
909
+ if ($values in schema) {
910
+ const kAlias = context.unique('k');
911
+ rootCase.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, childMode(mode, {
912
+ dynamic: kAlias
913
+ }, context))} }`);
914
+ }
915
+ if ($strict in schema && schema[$strict]) {
916
+ const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k)=>[
917
+ k,
918
+ 1
919
+ ])))}]`;
920
+ const kAlias = context.unique('k');
921
+ const extraAlias = context.unique('ex');
922
+ rootCase.push(`const ${extraAlias} = [];`);
923
+ rootCase.push(`for (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) ${extraAlias}.push(${kAlias}); }`);
924
+ rootCase.push(`if (${extraAlias}.length !== 0) { ${emit(`\`Extra properties: \${${extraAlias}}, are not allowed\``)} }`);
925
+ }
926
+ code.push(`switch (${sv}) {`);
927
+ code.push(`case 0: { ${rootCase.join('\n')} }`);
928
+ const entries = Object.entries(schema);
929
+ entries.forEach(([key, s], idx)=>{
930
+ const propCode = codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, childMode(mode, key, context));
931
+ code.push(`case ${idx + 1}: { ${propCode} }`);
932
+ });
933
+ code.push(`}`);
934
+ return code.join('\n');
935
+ }
936
+ code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
937
+ code.push(`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`);
938
+ code.push(`${errBranch(valueAlias)}`);
939
+ code.push('else {');
644
940
  const innerCode = [];
645
941
  if ($keys in schema) {
646
- const keysAlias = context.unique('k');
647
942
  const kAlias = context.unique('k');
648
- innerCode.push(`const ${keysAlias} = Object.keys(${valueAlias});`);
649
- innerCode.push(`for (const ${kAlias} of ${keysAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, {
943
+ innerCode.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, {
650
944
  dynamic: kAlias
651
- }))} }`);
945
+ }, context))} }`);
652
946
  }
653
947
  if ($values in schema) {
654
- const vAlias = context.unique('val');
655
948
  const kAlias = context.unique('k');
656
- const entriesAlias = context.unique('en');
657
- innerCode.push(`const ${entriesAlias} = Object.entries(${valueAlias});`);
658
- innerCode.push(`for (const [${kAlias}, ${vAlias}] of ${entriesAlias}) { ${codeGen(schema[$values], context, vAlias, childMode(mode, {
949
+ innerCode.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, childMode(mode, {
659
950
  dynamic: kAlias
660
- }))} }`);
951
+ }, context))} }`);
661
952
  }
662
953
  if ($strict in schema && schema[$strict]) {
663
- const keysAlias = context.unique('k');
954
+ const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k)=>[
955
+ k,
956
+ 1
957
+ ])))}]`;
664
958
  const kAlias = context.unique('k');
665
959
  const extraAlias = context.unique('ex');
666
- innerCode.push(`const ${keysAlias} = new Set(${JSON.stringify(Object.keys(schema))});`);
667
- innerCode.push(`const ${extraAlias} = Object.keys(${valueAlias}).filter(${kAlias} => !${keysAlias}.has(${kAlias}));`);
960
+ innerCode.push(`const ${extraAlias} = [];`);
961
+ innerCode.push(`for (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) ${extraAlias}.push(${kAlias}); }`);
668
962
  innerCode.push(`if (${extraAlias}.length !== 0) { ${emit(`\`Extra properties: \${${extraAlias}}, are not allowed\``)} }`);
669
963
  }
670
- innerCode.push(...Object.entries(schema).map(([key, s])=>codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, childMode(mode, key))));
964
+ const entries = Object.entries(schema);
965
+ innerCode.push(...entries.map(([key, s])=>codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, childMode(mode, key, context))));
671
966
  code.push(innerCode.join('\n'), '}');
672
967
  return code.join('\n');
673
968
  }
@@ -678,9 +973,9 @@ const codeGen = (schema, context, valuePath, mode)=>{
678
973
  const valueAlias = context.unique('v');
679
974
  const registryAlias = context.unique('r');
680
975
  if (mode.fast) {
681
- return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = ctx.registry[${index}];\nif (typeof ${valueAlias} !== 'symbol' || ${valueAlias} !== ${registryAlias}) { ${fail} }`;
976
+ return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = reg[${index}];\nif (typeof ${valueAlias} !== 'symbol' || ${valueAlias} !== ${registryAlias}) { ${fail} }`;
682
977
  } else {
683
- return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = ctx.registry[${index}];\nif (typeof ${valueAlias} !== 'symbol') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected symbol\``)} }\nelse if (${valueAlias} !== ${registryAlias}) { ${emit(`\`Invalid value \${${valueAlias}.toString()}, expected ${schema.toString()}\``)} }`;
978
+ return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = reg[${index}];\nif (typeof ${valueAlias} !== 'symbol') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected symbol\``)} }\nelse if (${valueAlias} !== ${registryAlias}) { ${emit(`\`Invalid value \${${valueAlias}.toString()}, expected ${schema.toString()}\``)} }`;
684
979
  }
685
980
  }
686
981
  if (schema === null || schema === undefined) {
@@ -696,27 +991,63 @@ const codeGen = (schema, context, valuePath, mode)=>{
696
991
  return `const ${valueAlias} = ${valuePath};\nif (typeof ${valueAlias} !== '${typeof schema}' || ${valueAlias} !== ${toLiteral(schema)}) { ${fail} }`;
697
992
  } else {
698
993
  const value = context.unique('val');
699
- return `const ${valueAlias} = ${valuePath};\nconst ${value} = ${toLiteral(schema)};\nif (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }\nelse if (typeof ${valueAlias} !== '${typeof schema}') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected ${typeof schema}\``)} }\nelse if (${valueAlias} !== ${value}) { ${emit(`\`Invalid value \${String(${valueAlias})}, expected ${toLiteral(schema)}\``)} }`;
994
+ return context.pure ? `const ${valueAlias} = ${valuePath};\nconst ${value} = ${toLiteral(schema)};\nif (typeof ${valueAlias} !== '${typeof schema}') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected ${typeof schema}\``)} }\nelse if (${valueAlias} !== ${value}) { ${emit(`\`Invalid value \${String(${valueAlias})}, expected ${toLiteral(schema)}\``)} }` : `const ${valueAlias} = ${valuePath};\nconst ${value} = ${toLiteral(schema)};\nif (typeof ${valueAlias} === 'object' && ${valueAlias} !== null && ${valueAlias}[err] === true) { ${emit(`\`\${${valueAlias}.message}\``)} }\nelse if (typeof ${valueAlias} !== '${typeof schema}') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected ${typeof schema}\``)} }\nelse if (${valueAlias} !== ${value}) { ${emit(`\`Invalid value \${String(${valueAlias})}, expected ${toLiteral(schema)}\``)} }`;
700
995
  }
701
996
  };
702
997
  const emptyIssues = [];
703
998
  const compile = (schema, options)=>{
704
999
  const allErrors = options?.allErrors ?? false;
1000
+ const pure = options?.pure ?? false;
1001
+ const canIndex = typeof schema === 'object' && schema !== null && !isTagged(schema) && !Array.isArray(schema) && !(schema instanceof RegExp);
705
1002
  if (allErrors) {
1003
+ if (canIndex) {
1004
+ const fastContext = new Context();
1005
+ fastContext.pure = pure;
1006
+ const fastCode = codeGen(schema, fastContext, 'data', {
1007
+ fast: true,
1008
+ indexed: true
1009
+ });
1010
+ const indexedFast = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR);
1011
+ const issueContext = new Context();
1012
+ issueContext.pure = pure;
1013
+ const rootPath = `reg[${issueContext.register(Object.freeze([]))}]`;
1014
+ const issueCode = `let issues = [];\n${codeGen(schema, issueContext, 'data', {
1015
+ fast: false,
1016
+ firstError: false,
1017
+ issues: 'issues',
1018
+ path: [],
1019
+ pathExpr: rootPath,
1020
+ startVar: 'start',
1021
+ issuesReady: true
1022
+ })}\nreturn issues;`;
1023
+ const issueValidator = new Function('reg', 'err', `return (data, start) => {\n${issueCode}\n};`)(issueContext.registry, ERR);
1024
+ const validator = (data)=>{
1025
+ const idx = indexedFast(data);
1026
+ if (idx === -1) return true;
1027
+ validator.issues = issueValidator(data, idx);
1028
+ return false;
1029
+ };
1030
+ validator.issues = emptyIssues;
1031
+ return validator;
1032
+ }
706
1033
  const fastContext = new Context();
1034
+ fastContext.pure = pure;
707
1035
  const fastCode = `${codeGen(schema, fastContext, 'data', {
708
1036
  fast: true
709
1037
  })}\nreturn true;`;
710
- const fastValidator = new Function('ctx', `return (data) => {\n${fastCode}\n};`)(fastContext);
1038
+ const fastValidator = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR);
711
1039
  const issueContext = new Context();
712
- const issueCode = `let issues;\n${codeGen(schema, issueContext, 'data', {
1040
+ issueContext.pure = pure;
1041
+ const rootPath2 = `reg[${issueContext.register(Object.freeze([]))}]`;
1042
+ const issueCode = `let issues = [];\n${codeGen(schema, issueContext, 'data', {
713
1043
  fast: false,
714
1044
  firstError: false,
715
1045
  issues: 'issues',
716
1046
  path: [],
717
- pathExpr: '[]'
718
- })}\nreturn issues || [];`;
719
- const issueValidator = new Function('ctx', `return (data) => {\n${issueCode}\n};`)(issueContext);
1047
+ pathExpr: rootPath2,
1048
+ issuesReady: true
1049
+ })}\nreturn issues;`;
1050
+ const issueValidator = new Function('reg', 'err', `return (data) => {\n${issueCode}\n};`)(issueContext.registry, ERR);
720
1051
  const validator = (data)=>{
721
1052
  if (fastValidator(data)) {
722
1053
  return true;
@@ -727,20 +1058,54 @@ const compile = (schema, options)=>{
727
1058
  validator.issues = emptyIssues;
728
1059
  return validator;
729
1060
  }
1061
+ if (canIndex) {
1062
+ const fastContext = new Context();
1063
+ fastContext.pure = pure;
1064
+ const fastCode = codeGen(schema, fastContext, 'data', {
1065
+ fast: true,
1066
+ indexed: true
1067
+ });
1068
+ const indexedFast = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR);
1069
+ const context = new Context();
1070
+ context.pure = pure;
1071
+ const rootPath = `reg[${context.register(Object.freeze([]))}]`;
1072
+ const code = codeGen(schema, context, 'data', {
1073
+ fast: false,
1074
+ firstError: true,
1075
+ issues: 'issues',
1076
+ path: [],
1077
+ pathExpr: rootPath,
1078
+ startVar: 'start'
1079
+ });
1080
+ const firstErrorValidator = new Function('reg', 'err', `return (data, start) => {\nlet issues;\n${code}\nreturn issues;\n};`)(context.registry, ERR);
1081
+ const validator = (data)=>{
1082
+ const idx = indexedFast(data);
1083
+ if (idx === -1) {
1084
+ return true;
1085
+ }
1086
+ validator.issues = firstErrorValidator(data, idx);
1087
+ return false;
1088
+ };
1089
+ validator.issues = emptyIssues;
1090
+ return validator;
1091
+ }
730
1092
  const fastContext = new Context();
1093
+ fastContext.pure = pure;
731
1094
  const fastCode = `${codeGen(schema, fastContext, 'data', {
732
1095
  fast: true
733
1096
  })}\nreturn true;`;
734
- const fastValidator = new Function('ctx', `return (data) => {\n${fastCode}\n};`)(fastContext);
1097
+ const fastValidator = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR);
735
1098
  const context = new Context();
1099
+ context.pure = pure;
1100
+ const rootPath3 = `reg[${context.register(Object.freeze([]))}]`;
736
1101
  const code = codeGen(schema, context, 'data', {
737
1102
  fast: false,
738
1103
  firstError: true,
739
1104
  issues: 'issues',
740
1105
  path: [],
741
- pathExpr: '[]'
1106
+ pathExpr: rootPath3
742
1107
  });
743
- const firstErrorValidator = new Function('ctx', `return (data) => {\nlet issues;\n${code}\nreturn issues;\n};`)(context);
1108
+ const firstErrorValidator = new Function('reg', 'err', `return (data) => {\nlet issues;\n${code}\nreturn issues;\n};`)(context.registry, ERR);
744
1109
  const validator = (data)=>{
745
1110
  if (fastValidator(data)) {
746
1111
  return true;