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/src/index.ts CHANGED
@@ -19,6 +19,16 @@ const OPTIONAL = Symbol.for('@@optional');
19
19
  const TUPLE = Symbol.for('@@tuple');
20
20
  const DISCRIMINATED = Symbol.for('@@discriminated');
21
21
  const CHECK = Symbol.for('@@check');
22
+ const ERR = Symbol.for('@@err');
23
+
24
+ interface ErrShape {
25
+ readonly message: string;
26
+ readonly [ERR]: true;
27
+ }
28
+ function ErrCtor(this: ErrShape, message: string): void {
29
+ (this as { message: string }).message = message;
30
+ }
31
+ (ErrCtor.prototype as { [ERR]: true })[ERR] = true;
22
32
 
23
33
  type Mutable<T> = { -readonly [K in keyof T]: T[K] };
24
34
 
@@ -304,9 +314,43 @@ export const oneOf = <T extends EnumLike>(values: (string | number)[] | T, messa
304
314
  * @param value - The base64-encoded string to decode.
305
315
  * @returns The decoded UTF-8 string.
306
316
  */
307
- export const fromBase64 =
308
- /* c8 ignore next */
309
- typeof Buffer === 'undefined' ? (value: string) => atob(value) : (value: string) => Buffer.from(value, 'base64').toString('utf-8');
317
+ const utf8Encoder = new TextEncoder();
318
+ const utf8Decoder = new TextDecoder('utf-8');
319
+
320
+ const fromBase64Bytes = (value: string): Uint8Array => {
321
+ const bin = atob(value);
322
+ const out = new Uint8Array(bin.length);
323
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
324
+ return out;
325
+ };
326
+
327
+ export const fromBase64 = (value: string): string => utf8Decoder.decode(fromBase64Bytes(value));
328
+
329
+ const HEX_LUT = (() => {
330
+ const t = new Int8Array(256).fill(-1);
331
+ for (let i = 0; i < 10; i++) t[48 + i] = i;
332
+ for (let i = 0; i < 6; i++) {
333
+ t[97 + i] = 10 + i;
334
+ t[65 + i] = 10 + i;
335
+ }
336
+ return t;
337
+ })();
338
+
339
+ const fromHex = (value: string): Uint8Array => {
340
+ let start = 0;
341
+ if (value.length >= 2 && value.charCodeAt(0) === 48 && (value.charCodeAt(1) | 32) === 120) {
342
+ start = 2;
343
+ }
344
+ const digits = value.length - start;
345
+ if (digits === 0 || digits % 2 !== 0) throw new TypeError('invalid hex length');
346
+ const out = new Uint8Array(digits / 2);
347
+ for (let i = 0; i < out.length; i++) {
348
+ const byte = (HEX_LUT[value.charCodeAt(start + i * 2)] << 4) | HEX_LUT[value.charCodeAt(start + i * 2 + 1)];
349
+ if (byte < 0) throw new TypeError('invalid hex digit');
350
+ out[i] = byte;
351
+ }
352
+ return out;
353
+ };
310
354
 
311
355
  const MULTIPLIERS = {
312
356
  ms: 1,
@@ -329,7 +373,7 @@ const TIME_REGEX = /^(\d*\.?\d*)(ms|s|m|h|d|w)?$/;
329
373
  * @param message - The error message.
330
374
  * @returns A TypeError instance typed as T.
331
375
  */
332
- export const asError = <T>(message: string) => new TypeError(message) as unknown as T;
376
+ export const asError = <T>(message: string) => new (ErrCtor as unknown as new (m: string) => { message: string; [ERR]: true })(message) as unknown as T;
333
377
 
334
378
  /**
335
379
  * Type casting utilities for parsing strings into typed values.
@@ -456,6 +500,17 @@ export const as = {
456
500
  return asError(`Invalid value ${value}, expected a valid base64 string`);
457
501
  }
458
502
  },
503
+
504
+ data: (value: string | undefined, type: 'utf-8' | 'hex' | 'base64' = 'utf-8'): Uint8Array => {
505
+ if (typeof value !== 'string') return asError(`Invalid value ${value}, expected a string`);
506
+ try {
507
+ if (type === 'hex') return fromHex(value);
508
+ if (type === 'base64') return fromBase64Bytes(value);
509
+ return utf8Encoder.encode(value);
510
+ } catch {
511
+ return asError(`Invalid value ${value}, expected a valid ${type} string`);
512
+ }
513
+ },
459
514
  };
460
515
 
461
516
  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;
@@ -553,6 +608,7 @@ class Context {
553
608
  public readonly registry: unknown[] = [];
554
609
  private readonly lookupMap: Map<unknown, number> = new Map();
555
610
  private varIndex = 0;
611
+ public pure = false;
556
612
 
557
613
  register(value: unknown): number {
558
614
  const index = this.lookupMap.get(value);
@@ -572,35 +628,148 @@ class Context {
572
628
  }
573
629
 
574
630
  type Mode =
575
- | { fast: true; onFail?: string }
576
- | { fast: false; firstError: true; issues: string; path: PropertyKey[]; pathExpr: string }
577
- | { fast: false; firstError: false; issues: string; path: PropertyKey[]; pathExpr: string };
631
+ | { fast: true; onFail?: string; indexed?: boolean }
632
+ | { fast: false; firstError: true; issues: string; path: PropertyKey[]; pathExpr: string; startVar?: string; dynamicParts?: string[] }
633
+ | {
634
+ fast: false;
635
+ firstError: false;
636
+ issues: string;
637
+ path: PropertyKey[];
638
+ pathExpr: string;
639
+ startVar?: string;
640
+ issuesReady?: boolean;
641
+ dynamicParts?: string[];
642
+ };
578
643
 
579
644
  const isTagged = (schema: unknown): schema is Tagged<unknown> => (schema as Tagged<unknown>)?.[$op] !== undefined;
580
645
 
581
- const childMode = (mode: Exclude<Mode, { fast: true }>, key: PropertyKey | { dynamic: string }): Mode => {
646
+ const buildDynamicPathExpr = (staticPath: PropertyKey[], dynamicParts: string[]): string =>
647
+ `[${[...staticPath.map((k) => JSON.stringify(k)), ...dynamicParts].join(',')}]`;
648
+
649
+ const childMode = (mode: Exclude<Mode, { fast: true }>, key: PropertyKey | { dynamic: string }, ctx: Context): Mode => {
650
+ const carryReady = !mode.firstError && mode.issuesReady;
582
651
  if (typeof key === 'object' && 'dynamic' in key) {
583
- return {
652
+ const dynamicParts = [...(mode.dynamicParts ?? []), key.dynamic];
653
+ const m: Mode = {
654
+ fast: false,
655
+ firstError: mode.firstError,
656
+ issues: mode.issues,
657
+ path: mode.path,
658
+ pathExpr: buildDynamicPathExpr(mode.path, dynamicParts),
659
+ dynamicParts,
660
+ };
661
+ if (carryReady) (m as { issuesReady: boolean }).issuesReady = true;
662
+ return m;
663
+ }
664
+ if (mode.dynamicParts) {
665
+ const dynamicParts = [...mode.dynamicParts, JSON.stringify(key)];
666
+ const m: Mode = {
584
667
  fast: false,
585
668
  firstError: mode.firstError,
586
669
  issues: mode.issues,
587
670
  path: mode.path,
588
- pathExpr: `[${mode.path.map((k) => JSON.stringify(k)).join(',')}${mode.path.length ? ',' : ''}${key.dynamic}]`,
671
+ pathExpr: buildDynamicPathExpr(mode.path, dynamicParts),
672
+ dynamicParts,
589
673
  };
674
+ if (carryReady) (m as { issuesReady: boolean }).issuesReady = true;
675
+ return m;
590
676
  }
591
677
  const newPath = [...mode.path, key];
592
- return { fast: false, firstError: mode.firstError, issues: mode.issues, path: newPath, pathExpr: JSON.stringify(newPath) };
678
+ const pathExpr = `reg[${ctx.register(Object.freeze(newPath))}]`;
679
+ const m: Mode = { fast: false, firstError: mode.firstError, issues: mode.issues, path: newPath, pathExpr };
680
+ if (carryReady) (m as { issuesReady: boolean }).issuesReady = true;
681
+ return m;
593
682
  };
594
683
 
595
684
  const toLiteral = (value: unknown): string => (typeof value === 'bigint' ? `${value}n` : JSON.stringify(value));
596
685
 
686
+ const collapsibleTypeOf = (s: unknown): string | null =>
687
+ s === String
688
+ ? 'string'
689
+ : s === Number
690
+ ? 'number'
691
+ : s === Boolean
692
+ ? 'boolean'
693
+ : s === BigInt
694
+ ? 'bigint'
695
+ : s === Symbol
696
+ ? 'symbol'
697
+ : typeof s === 'function' && (s as { name?: string })?.name === 'Function'
698
+ ? 'function'
699
+ : null;
700
+
701
+ const buildCollapsedOr = (schemas: unknown[], v: string): string => {
702
+ let hasNull = false;
703
+ let hasUndefined = false;
704
+ const groups = new Map<string, { ctor: boolean; literals: unknown[] }>();
705
+ for (const s of schemas) {
706
+ if (s === null) {
707
+ hasNull = true;
708
+ continue;
709
+ }
710
+ if (s === undefined) {
711
+ hasUndefined = true;
712
+ continue;
713
+ }
714
+ const ct = collapsibleTypeOf(s);
715
+ if (ct) {
716
+ let g = groups.get(ct);
717
+ if (!g) {
718
+ g = { ctor: false, literals: [] };
719
+ groups.set(ct, g);
720
+ }
721
+ g.ctor = true;
722
+ continue;
723
+ }
724
+ const t = typeof s;
725
+ let g = groups.get(t);
726
+ if (!g) {
727
+ g = { ctor: false, literals: [] };
728
+ groups.set(t, g);
729
+ }
730
+ g.literals.push(s);
731
+ }
732
+ const clauses: string[] = [];
733
+ if (hasNull) clauses.push(`${v} === null`);
734
+ if (hasUndefined) clauses.push(`${v} === undefined`);
735
+ for (const [type, { ctor, literals }] of groups) {
736
+ if (ctor) {
737
+ clauses.push(type === 'number' ? `(typeof ${v} === 'number' && ${v} === ${v})` : `typeof ${v} === '${type}'`);
738
+ } else if (literals.length === 1) {
739
+ clauses.push(`${v} === ${toLiteral(literals[0])}`);
740
+ } else {
741
+ clauses.push(`(typeof ${v} === '${type}' && (${literals.map((l) => `${v} === ${toLiteral(l)}`).join(' || ')}))`);
742
+ }
743
+ }
744
+ return clauses.join(' || ');
745
+ };
746
+
747
+ const buildExpectedDesc = (schemas: unknown[]): string => {
748
+ const parts: string[] = [];
749
+ for (const s of schemas) {
750
+ if (s === null) parts.push('null');
751
+ else if (s === undefined) parts.push('undefined');
752
+ else if (collapsibleTypeOf(s) !== null) parts.push((s as { name: string }).name);
753
+ else parts.push(toLiteral(s) as string);
754
+ }
755
+ return parts.join(', ');
756
+ };
757
+
758
+ const isCollapsible = (s: unknown): boolean =>
759
+ s === null || s === undefined || (typeof s !== 'object' && typeof s !== 'function' && typeof s !== 'symbol') || collapsibleTypeOf(s) !== null;
760
+
597
761
  const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode: Mode): string => {
598
762
  const emit = mode.fast
599
763
  ? null
600
764
  : mode.firstError
601
765
  ? (msg: string) => `${mode.issues} = [{ message: ${msg}, path: ${mode.pathExpr} }]; return ${mode.issues};`
602
- : (msg: string) => `(${mode.issues} || (${mode.issues} = [])).push({ message: ${msg}, path: ${mode.pathExpr} });`;
766
+ : !mode.firstError && mode.issuesReady
767
+ ? (msg: string) => `${mode.issues}.push({ message: ${msg}, path: ${mode.pathExpr} });`
768
+ : (msg: string) => `(${mode.issues} || (${mode.issues} = [])).push({ message: ${msg}, path: ${mode.pathExpr} });`;
603
769
  const fail = mode.fast ? (mode.onFail ?? 'return false;') : '';
770
+ const errChk = (v: string) => (context.pure ? '' : ` || (typeof ${v} === 'object' && ${v} !== null && ${v}[err] === true)`);
771
+ const errBranch = (v: string) =>
772
+ context.pure ? '' : `else if (typeof ${v} === 'object' && ${v} !== null && ${v}[err] === true) { ${emit!(`\`\${${v}.message}\``)} }`;
604
773
 
605
774
  if (isTagged(schema)) {
606
775
  const tag = schema[$op];
@@ -612,6 +781,24 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
612
781
  const valueAlias = context.unique('v');
613
782
  const foundValid = context.unique('valid');
614
783
  if (mode.fast) {
784
+ const collapsible: unknown[] = [];
785
+ const complex: Schema<T>[] = [];
786
+ for (const s of schema.schemas) {
787
+ if (isCollapsible(s)) collapsible.push(s);
788
+ else complex.push(s as Schema<T>);
789
+ }
790
+ if (collapsible.length > 0 && complex.length === 0) {
791
+ return `const ${valueAlias} = ${valuePath};\nif (!(${buildCollapsedOr(collapsible, valueAlias)})) { ${fail} }`;
792
+ }
793
+ if (collapsible.length > 0) {
794
+ const condition = buildCollapsedOr(collapsible, valueAlias);
795
+ const branches = complex.map((s) => {
796
+ const branchValid = context.unique('valid');
797
+ const branchCode = codeGen(s, context, valueAlias, { ...mode, onFail: `${branchValid} = false;` });
798
+ return `if (!${foundValid}) { let ${branchValid} = true; ${branchCode} if (${branchValid}) { ${foundValid} = true; } }`;
799
+ });
800
+ return `const ${valueAlias} = ${valuePath};\nlet ${foundValid} = ${condition};\n${branches.join('\n')}\nif (!${foundValid}) { ${fail} }`;
801
+ }
615
802
  const branches = schema.schemas.map((s) => {
616
803
  const branchValid = context.unique('valid');
617
804
  const branchCode = codeGen(s, context, valueAlias, { ...mode, onFail: `${branchValid} = false;` });
@@ -619,6 +806,11 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
619
806
  });
620
807
  return `const ${valueAlias} = ${valuePath};\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { ${fail} }`;
621
808
  } else if (mode.firstError) {
809
+ if (schema.schemas.every(isCollapsible)) {
810
+ const condition = buildCollapsedOr(schema.schemas, valueAlias);
811
+ const expected = buildExpectedDesc(schema.schemas);
812
+ return `const ${valueAlias} = ${valuePath};\nif (!(${condition})) { ${mode.issues} = [{ message: \`Invalid value \${${valueAlias}}, expected one of: ${expected}\`, path: ${mode.pathExpr} }]; return ${mode.issues}; }`;
813
+ }
622
814
  const firstBranchIssues = context.unique('iss');
623
815
  const branches = schema.schemas.map((s, idx) => {
624
816
  const branchIssues = context.unique('iss');
@@ -636,6 +828,14 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
636
828
  });
637
829
  return `const ${valueAlias} = ${valuePath};\nlet ${firstBranchIssues};\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { return ${firstBranchIssues}; }`;
638
830
  } else {
831
+ if (schema.schemas.every(isCollapsible)) {
832
+ const condition = buildCollapsedOr(schema.schemas, valueAlias);
833
+ const expected = buildExpectedDesc(schema.schemas);
834
+ const push = mode.issuesReady
835
+ ? `${mode.issues}.push({ message: \`Invalid value \${${valueAlias}}, expected one of: ${expected}\`, path: ${mode.pathExpr} });`
836
+ : `(${mode.issues} || (${mode.issues} = [])).push({ message: \`Invalid value \${${valueAlias}}, expected one of: ${expected}\`, path: ${mode.pathExpr} });`;
837
+ return `const ${valueAlias} = ${valuePath};\nif (!(${condition})) { ${push} }`;
838
+ }
639
839
  const localIssues = context.unique('iss');
640
840
  const branches = schema.schemas.map((s) => {
641
841
  const branchIssues = context.unique('iss');
@@ -648,11 +848,33 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
648
848
  });
649
849
  return `if (!${foundValid}) { let ${branchIssues}; ${branchCode} if (!${branchIssues}) { ${foundValid} = true; } else { ${localIssues}.push(...${branchIssues}); } }`;
650
850
  });
651
- return `const ${valueAlias} = ${valuePath};\nconst ${localIssues} = [];\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { (${mode.issues} || (${mode.issues} = [])).push(...${localIssues}); }`;
851
+ const pushExpr =
852
+ !mode.fast && !mode.firstError && mode.issuesReady
853
+ ? `${mode.issues}.push(...${localIssues})`
854
+ : `(${mode.issues} || (${mode.issues} = [])).push(...${localIssues})`;
855
+ return `const ${valueAlias} = ${valuePath};\nconst ${localIssues} = [];\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { ${pushExpr}; }`;
652
856
  }
653
857
  } else if (tag === OPTIONAL) {
654
858
  const valueAlias = context.unique('v');
655
- return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen((schema as OptionalShape<T>).schemas[0], context, valueAlias, mode)} }`;
859
+ const inner = (schema as OptionalShape<T>).schemas[0];
860
+ if (!mode.fast && typeof inner === 'function') {
861
+ const iname = (inner as { name?: string })?.name;
862
+ const is = inner as unknown;
863
+ const pt =
864
+ is === String ? 'string' : is === Number ? 'number' : is === Boolean ? 'boolean' : is === BigInt ? 'bigint' : is === Symbol ? 'symbol' : null;
865
+ if (pt) {
866
+ const typeMsgs = Object.fromEntries(
867
+ ['string', 'number', 'boolean', 'bigint', 'symbol', 'undefined', 'object', 'function'].map((t) => [t, `Invalid type ${t}, expected type ${iname}`]),
868
+ );
869
+ const typeMsgIdx = context.register(typeMsgs);
870
+ const lines = [`const ${valueAlias} = ${valuePath};`, `if (${valueAlias} !== undefined && ${valueAlias} !== null) {`];
871
+ lines.push(`if (typeof ${valueAlias} !== '${pt}') { ${emit!(`reg[${typeMsgIdx}][typeof ${valueAlias}]`)} }`);
872
+ if (pt === 'number') lines.push(`else if (${valueAlias} !== ${valueAlias}) { ${emit!(`"Invalid value NaN, expected a valid ${iname}"`)} }`);
873
+ lines.push(`}`);
874
+ return lines.join('\n');
875
+ }
876
+ }
877
+ return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(inner, context, valueAlias, mode)} }`;
656
878
  } else if (tag === TUPLE) {
657
879
  const valueAlias = context.unique('v');
658
880
  if (mode.fast) {
@@ -664,12 +886,12 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
664
886
  `else if (typeof ${valueAlias} !== 'object') { ${emit!(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Array\``)} }`,
665
887
  `else if (!Array.isArray(${valueAlias})) { ${emit!(`\`Invalid instance of \${${valueAlias}.constructor?.name}, expected an instance of Array\``)} }`,
666
888
  `else if (${valueAlias}.length !== ${schema.schemas.length}) { ${emit!(`\`Invalid tuple length \${${valueAlias}.length}, expected ${schema.schemas.length}\``)} }`,
667
- `else { ${schema.schemas.map((s, idx) => codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx))).join('\n')} }`,
889
+ `else { ${schema.schemas.map((s, idx) => codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx, context))).join('\n')} }`,
668
890
  ].join('\n');
669
891
  }
670
892
  } else if (tag === CHECK) {
671
893
  const valueAlias = context.unique('v');
672
- const ref = (v: unknown) => `ctx.registry[${context.register(v)}]`;
894
+ const ref = (v: unknown) => `reg[${context.register(v)}]`;
673
895
  const { check: cond, message } = (schema as CheckShape).compile(valueAlias, { ref });
674
896
  if (mode.fast) {
675
897
  return `const ${valueAlias} = ${valuePath};\nif (${cond}) { ${fail} }`;
@@ -693,31 +915,39 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
693
915
  variants.push({ value: discriminantValue, schema: s });
694
916
  }
695
917
 
918
+ const genVariantProps = (s: Schema<T>, variantMode: Mode) => {
919
+ const obj = s as Record<string, unknown>;
920
+ return Object.entries(obj)
921
+ .filter(([k]) => k !== key)
922
+ .map(([k, ps]) =>
923
+ codeGen(ps as Schema<T>, context, `${valueAlias}[${JSON.stringify(k)}]`, variantMode.fast ? variantMode : childMode(variantMode, k, context)),
924
+ )
925
+ .join('\n');
926
+ };
927
+
696
928
  if (mode.fast) {
697
929
  const branches = variants.map(({ value, schema: s }) => {
698
- const branchCode = codeGen(s, context, valueAlias, mode);
699
- return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${branchCode} }`;
930
+ return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${genVariantProps(s, mode)} }`;
700
931
  });
701
932
  return [
702
933
  `const ${valueAlias} = ${valuePath};`,
703
- `if (${valueAlias} === null || ${valueAlias} === undefined || typeof ${valueAlias} !== 'object' || ${valueAlias} instanceof Error) { ${fail} }`,
934
+ `if (${valueAlias} === null || ${valueAlias} === undefined || typeof ${valueAlias} !== 'object'${errChk(valueAlias)}) { ${fail} }`,
704
935
  `const ${discriminantAlias} = ${valueAlias}[${keyStr}];`,
705
936
  branches.join(' else ') + ` else { ${fail} }`,
706
937
  ].join('\n');
707
938
  } else {
708
939
  const validValues = variants.map((v) => JSON.stringify(v.value)).join(', ');
709
940
  const branches = variants.map(({ value, schema: s }) => {
710
- const branchCode = codeGen(s, context, valueAlias, mode);
711
- return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${branchCode} }`;
941
+ return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${genVariantProps(s, mode)} }`;
712
942
  });
713
943
  return [
714
944
  `const ${valueAlias} = ${valuePath};`,
715
945
  `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit!(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
716
946
  `else if (typeof ${valueAlias} !== 'object') { ${emit!(`\`Invalid type \${typeof ${valueAlias}}, expected an object\``)} }`,
717
- `else if (${valueAlias} instanceof Error) { ${emit!(`\`\${${valueAlias}.message}\``)} }`,
947
+ `${errBranch(valueAlias)}`,
718
948
  `else {`,
719
949
  ` const ${discriminantAlias} = ${valueAlias}[${keyStr}];`,
720
- ` ${branches.join(' else ')} else { ${emit!(`\`Invalid discriminant value \${JSON.stringify(${discriminantAlias})}, expected one of: ${validValues}\``)} }`,
950
+ ` ${branches.join(' else ')} else { ${emit!(`"Invalid discriminant value " + String(${discriminantAlias}) + ", expected one of: ${validValues.replace(/"/g, "'")}"`)} }`,
721
951
  `}`,
722
952
  ].join('\n');
723
953
  }
@@ -734,7 +964,7 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
734
964
  if (mode.fast) {
735
965
  if (primitiveType) {
736
966
  const checks = [`typeof ${valueAlias} !== '${primitiveType}'`];
737
- if (primitiveType === 'number') checks.push(`Number.isNaN(${valueAlias})`);
967
+ if (primitiveType === 'number') checks.push(`${valueAlias} !== ${valueAlias}`);
738
968
  return `const ${valueAlias} = ${valuePath};\nif (${checks.join(' || ')}) { ${fail} }`;
739
969
  } else if (name === 'Function') {
740
970
  return `const ${valueAlias} = ${valuePath};\nif (typeof ${valueAlias} !== 'function') { ${fail} }`;
@@ -742,27 +972,32 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
742
972
  const isError = (schema as unknown) === Error || schema?.prototype instanceof Error;
743
973
  const index = context.register(schema);
744
974
  const registryAlias = context.unique('r');
745
- 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} }`;
975
+ 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} }`;
746
976
  }
747
977
  } else {
748
978
  const code: string[] = [`const ${valueAlias} = ${valuePath};`];
749
979
  if (primitiveType) {
750
- code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit!(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
751
- code.push(`else if (${valueAlias} instanceof Error) { ${emit!(`\`\${${valueAlias}.message}\``)} }`);
752
- code.push(`else if (typeof ${valueAlias} !== '${primitiveType}') { ${emit!(`\`Invalid type \${typeof ${valueAlias}}, expected type ${name}\``)} }`);
753
- if (primitiveType === 'number')
754
- code.push(`else if (Number.isNaN(${valueAlias})) { ${emit!(`\`Invalid value \${${valueAlias}}, expected a valid ${name}\``)} }`);
980
+ const typeMsgs = Object.fromEntries(
981
+ ['string', 'number', 'boolean', 'bigint', 'symbol', 'undefined', 'object', 'function'].map((t) => [t, `Invalid type ${t}, expected type ${name}`]),
982
+ );
983
+ const typeMsgIdx = context.register(typeMsgs);
984
+ code.push(
985
+ `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit!(`${valueAlias} === null ? "Invalid value null, expected non-nullable" : "Invalid value undefined, expected non-nullable"`)} }`,
986
+ );
987
+ code.push(`${errBranch(valueAlias)}`);
988
+ code.push(`else if (typeof ${valueAlias} !== '${primitiveType}') { ${emit!(`reg[${typeMsgIdx}][typeof ${valueAlias}]`)} }`);
989
+ if (primitiveType === 'number') code.push(`else if (${valueAlias} !== ${valueAlias}) { ${emit!(`"Invalid value NaN, expected a valid ${name}"`)} }`);
755
990
  } else if (name === 'Function') {
756
991
  code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit!(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
757
- code.push(`else if (${valueAlias} instanceof Error) { ${emit!(`\`\${${valueAlias}.message}\``)} }`);
992
+ code.push(`${errBranch(valueAlias)}`);
758
993
  code.push(`else if (typeof ${valueAlias} !== 'function') { ${emit!(`\`Invalid type \${typeof ${valueAlias}}, expected type Function\``)} }`);
759
994
  } else {
760
995
  const isError = (schema as unknown) === Error || schema?.prototype instanceof Error;
761
996
  const index = context.register(schema);
762
997
  const registryAlias = context.unique('r');
763
- code.push(`const ${registryAlias} = ctx.registry[${index}];`);
998
+ code.push(`const ${registryAlias} = reg[${index}];`);
764
999
  code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit!(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
765
- if (!isError) code.push(`else if (${valueAlias} instanceof Error) { ${emit!(`\`\${${valueAlias}.message}\``)} }`);
1000
+ if (!isError) code.push(`${errBranch(valueAlias)}`);
766
1001
  code.push(
767
1002
  `else if (typeof ${valueAlias} === 'object' && !(${valueAlias} instanceof ${registryAlias})) { ${emit!(`\`Invalid instance of \${${valueAlias}?.constructor?.name}, expected an instance of ${name}\``)} }`,
768
1003
  );
@@ -789,11 +1024,15 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
789
1024
  }
790
1025
  return code;
791
1026
  } else {
1027
+ const arrTypeMsgs = Object.fromEntries(
1028
+ ['string', 'number', 'boolean', 'bigint', 'symbol', 'undefined', 'function'].map((t) => [t, `Invalid type ${t}, expected an instance of Array`]),
1029
+ );
1030
+ const arrTypeMsgIdx = context.register(arrTypeMsgs);
792
1031
  const code: string[] = [
793
1032
  `const ${valueAlias} = ${valuePath};`,
794
- `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit!(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
795
- `else if (${valueAlias} instanceof Error) { ${emit!(`\`\${${valueAlias}.message}\``)} }`,
796
- `else if (typeof ${valueAlias} !== 'object') { ${emit!(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Array\``)} }`,
1033
+ `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit!(`${valueAlias} === null ? "Invalid value null, expected non-nullable" : "Invalid value undefined, expected non-nullable"`)} }`,
1034
+ `${errBranch(valueAlias)}`,
1035
+ `else if (typeof ${valueAlias} !== 'object') { ${emit!(`reg[${arrTypeMsgIdx}][typeof ${valueAlias}]`)} }`,
797
1036
  `else if (!Array.isArray(${valueAlias})) { ${emit!(`\`Invalid instance of \${${valueAlias}.constructor?.name}, expected an instance of Array\``)} }`,
798
1037
  ];
799
1038
  if (schema.length > 0) {
@@ -802,13 +1041,13 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
802
1041
  if (schema.length === 1) {
803
1042
  // Dynamic key - use runtime concat
804
1043
  code.push(
805
- `else { for (let ${key} = 0; ${key} < ${valueAlias}.length; ${key}++) { const ${value} = ${valueAlias}[${key}]; ${codeGen(schema[0], context, value, childMode(mode, { dynamic: key }))} } }`,
1044
+ `else { for (let ${key} = 0; ${key} < ${valueAlias}.length; ${key}++) { const ${value} = ${valueAlias}[${key}]; ${codeGen(schema[0], context, value, childMode(mode, { dynamic: key }, context))} } }`,
806
1045
  );
807
1046
  } else {
808
1047
  code.push(
809
1048
  `else if (${valueAlias}.length > ${schema.length}) { ${emit!(`\`Invalid tuple length \${${valueAlias}.length}, expected ${schema.length}\``)} }`,
810
1049
  );
811
- code.push(`else { ${schema.map((s, idx) => codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx))).join('\n')} }`);
1050
+ code.push(`else { ${schema.map((s, idx) => codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx, context))).join('\n')} }`);
812
1051
  }
813
1052
  }
814
1053
  return code.join('\n');
@@ -819,73 +1058,122 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
819
1058
  if (schema instanceof RegExp) {
820
1059
  const valueAlias = context.unique('v');
821
1060
  if (mode.fast) {
822
- return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined || ${valueAlias} instanceof Error || !${schema.toString()}.test(String(${valueAlias}))) { ${fail} }`;
1061
+ return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined${errChk(valueAlias)} || !${schema.toString()}.test(String(${valueAlias}))) { ${fail} }`;
823
1062
  } else {
824
- 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()}\``)} }`;
1063
+ 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()}\``)} }`;
825
1064
  }
826
1065
  } else {
827
1066
  const valueAlias = context.unique('v');
828
1067
  if (mode.fast) {
829
- let code = `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || typeof ${valueAlias} !== 'object' || ${valueAlias} instanceof Error) { ${fail} }`;
1068
+ const indexed = mode.indexed && !mode.onFail;
1069
+ const rootFail = indexed ? 'return 0;' : fail;
1070
+ let code = `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || typeof ${valueAlias} !== 'object'${errChk(valueAlias)}) { ${rootFail} }`;
830
1071
  if ($keys in schema) {
831
- const keysAlias = context.unique('k');
832
1072
  const kAlias = context.unique('k');
833
- code += `\nconst ${keysAlias} = Object.keys(${valueAlias});\nfor (const ${kAlias} of ${keysAlias}) { ${codeGen(schema[$keys], context, kAlias, mode)} }`;
1073
+ code += `\nfor (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, indexed ? { fast: true, onFail: 'return 0;' } : mode)} }`;
834
1074
  }
835
1075
  if ($values in schema) {
836
- const vAlias = context.unique('val');
837
1076
  const kAlias = context.unique('k');
838
- const entriesAlias = context.unique('en');
839
- code += `\nconst ${entriesAlias} = Object.entries(${valueAlias});\nfor (const [${kAlias}, ${vAlias}] of ${entriesAlias}) { ${codeGen(schema[$values], context, vAlias, mode)} }`;
1077
+ code += `\nfor (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, indexed ? { fast: true, onFail: 'return 0;' } : mode)} }`;
840
1078
  }
841
1079
  if ($strict in schema && schema[$strict]) {
842
- const keysAlias = context.unique('k');
1080
+ const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k) => [k, 1])))}]`;
843
1081
  const kAlias = context.unique('k');
844
- const extraAlias = context.unique('ex');
845
- 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} }`;
1082
+ code += `\nfor (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) { ${rootFail} } }`;
846
1083
  }
1084
+ const entries = Object.entries(schema);
847
1085
  code +=
848
1086
  '\n' +
849
- Object.entries(schema)
850
- .map(([key, s]) => codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, mode))
1087
+ entries
1088
+ .map(([key, s], idx) => {
1089
+ const propMode: Mode = indexed ? { fast: true, onFail: `return ${idx + 1};` } : mode;
1090
+ return codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, propMode);
1091
+ })
851
1092
  .join('\n');
1093
+ if (indexed) code += `\nreturn -1;`;
852
1094
  return code;
853
1095
  } else {
854
- const code: string[] = [
855
- `const ${valueAlias} = ${valuePath};`,
856
- `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit!(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
857
- `else if (typeof ${valueAlias} !== 'object') { ${emit!(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`,
858
- `else if (${valueAlias} instanceof Error) { ${emit!(`\`\${${valueAlias}.message}\``)} }`,
859
- 'else {',
860
- ];
1096
+ const sv = !mode.fast && mode.startVar;
1097
+ const code: string[] = [`const ${valueAlias} = ${valuePath};`];
1098
+
1099
+ if (sv) {
1100
+ const rootCase: string[] = [];
1101
+ if (mode.firstError) {
1102
+ rootCase.push(
1103
+ `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit!(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
1104
+ );
1105
+ rootCase.push(
1106
+ `else if (typeof ${valueAlias} !== 'object') { ${emit!(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`,
1107
+ );
1108
+ rootCase.push(`${errBranch(valueAlias)}`);
1109
+ } else {
1110
+ rootCase.push(
1111
+ `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit!(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} break; }`,
1112
+ );
1113
+ rootCase.push(
1114
+ `else if (typeof ${valueAlias} !== 'object') { ${emit!(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} break; }`,
1115
+ );
1116
+ if (!context.pure)
1117
+ rootCase.push(`else if (typeof ${valueAlias} === 'object' && ${valueAlias}[err] === true) { ${emit!(`\`\${${valueAlias}.message}\``)} break; }`);
1118
+ }
1119
+ if ($keys in schema) {
1120
+ const kAlias = context.unique('k');
1121
+ rootCase.push(
1122
+ `for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, { dynamic: kAlias }, context))} }`,
1123
+ );
1124
+ }
1125
+ if ($values in schema) {
1126
+ const kAlias = context.unique('k');
1127
+ rootCase.push(
1128
+ `for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, childMode(mode, { dynamic: kAlias }, context))} }`,
1129
+ );
1130
+ }
1131
+ if ($strict in schema && schema[$strict]) {
1132
+ const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k) => [k, 1])))}]`;
1133
+ const kAlias = context.unique('k');
1134
+ const extraAlias = context.unique('ex');
1135
+ rootCase.push(`const ${extraAlias} = [];`);
1136
+ rootCase.push(`for (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) ${extraAlias}.push(${kAlias}); }`);
1137
+ rootCase.push(`if (${extraAlias}.length !== 0) { ${emit!(`\`Extra properties: \${${extraAlias}}, are not allowed\``)} }`);
1138
+ }
1139
+ code.push(`switch (${sv}) {`);
1140
+ code.push(`case 0: { ${rootCase.join('\n')} }`);
1141
+ const entries = Object.entries(schema);
1142
+ entries.forEach(([key, s], idx) => {
1143
+ const propCode = codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, childMode(mode, key, context));
1144
+ code.push(`case ${idx + 1}: { ${propCode} }`);
1145
+ });
1146
+ code.push(`}`);
1147
+ return code.join('\n');
1148
+ }
1149
+
1150
+ code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit!(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
1151
+ code.push(`else if (typeof ${valueAlias} !== 'object') { ${emit!(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`);
1152
+ code.push(`${errBranch(valueAlias)}`);
1153
+ code.push('else {');
861
1154
  const innerCode: string[] = [];
862
1155
  if ($keys in schema) {
863
- const keysAlias = context.unique('k');
864
1156
  const kAlias = context.unique('k');
865
- innerCode.push(`const ${keysAlias} = Object.keys(${valueAlias});`);
866
- // Dynamic key - use runtime concat
867
- innerCode.push(`for (const ${kAlias} of ${keysAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, { dynamic: kAlias }))} }`);
1157
+ innerCode.push(
1158
+ `for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, { dynamic: kAlias }, context))} }`,
1159
+ );
868
1160
  }
869
1161
  if ($values in schema) {
870
- const vAlias = context.unique('val');
871
1162
  const kAlias = context.unique('k');
872
- const entriesAlias = context.unique('en');
873
- innerCode.push(`const ${entriesAlias} = Object.entries(${valueAlias});`);
874
- // Dynamic key - use runtime concat
875
1163
  innerCode.push(
876
- `for (const [${kAlias}, ${vAlias}] of ${entriesAlias}) { ${codeGen(schema[$values], context, vAlias, childMode(mode, { dynamic: kAlias }))} }`,
1164
+ `for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, childMode(mode, { dynamic: kAlias }, context))} }`,
877
1165
  );
878
1166
  }
879
1167
  if ($strict in schema && schema[$strict]) {
880
- const keysAlias = context.unique('k');
1168
+ const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k) => [k, 1])))}]`;
881
1169
  const kAlias = context.unique('k');
882
1170
  const extraAlias = context.unique('ex');
883
- innerCode.push(`const ${keysAlias} = new Set(${JSON.stringify(Object.keys(schema))});`);
884
- innerCode.push(`const ${extraAlias} = Object.keys(${valueAlias}).filter(${kAlias} => !${keysAlias}.has(${kAlias}));`);
1171
+ innerCode.push(`const ${extraAlias} = [];`);
1172
+ innerCode.push(`for (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) ${extraAlias}.push(${kAlias}); }`);
885
1173
  innerCode.push(`if (${extraAlias}.length !== 0) { ${emit!(`\`Extra properties: \${${extraAlias}}, are not allowed\``)} }`);
886
1174
  }
887
- // Static keys - pre-register paths
888
- innerCode.push(...Object.entries(schema).map(([key, s]) => codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, childMode(mode, key))));
1175
+ const entries = Object.entries(schema);
1176
+ innerCode.push(...entries.map(([key, s]) => codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, childMode(mode, key, context))));
889
1177
  code.push(innerCode.join('\n'), '}');
890
1178
  return code.join('\n');
891
1179
  }
@@ -897,9 +1185,9 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
897
1185
  const valueAlias = context.unique('v');
898
1186
  const registryAlias = context.unique('r');
899
1187
  if (mode.fast) {
900
- return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = ctx.registry[${index}];\nif (typeof ${valueAlias} !== 'symbol' || ${valueAlias} !== ${registryAlias}) { ${fail} }`;
1188
+ return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = reg[${index}];\nif (typeof ${valueAlias} !== 'symbol' || ${valueAlias} !== ${registryAlias}) { ${fail} }`;
901
1189
  } else {
902
- 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()}\``)} }`;
1190
+ 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()}\``)} }`;
903
1191
  }
904
1192
  }
905
1193
 
@@ -917,7 +1205,9 @@ const codeGen = <T>(schema: Schema<T>, context: Context, valuePath: string, mode
917
1205
  return `const ${valueAlias} = ${valuePath};\nif (typeof ${valueAlias} !== '${typeof schema}' || ${valueAlias} !== ${toLiteral(schema)}) { ${fail} }`;
918
1206
  } else {
919
1207
  const value = context.unique('val');
920
- 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)}\``)} }`;
1208
+ return context.pure
1209
+ ? `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)}\``)} }`
1210
+ : `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)}\``)} }`;
921
1211
  }
922
1212
  };
923
1213
 
@@ -942,6 +1232,12 @@ export interface CompileOptions {
942
1232
  * Default is false (first-error mode) for optimal performance.
943
1233
  */
944
1234
  allErrors?: boolean;
1235
+ /**
1236
+ * When true, skips `instanceof Error` checks in the fast validator.
1237
+ * Enable this when the data does not come from `as.*` converters for maximum performance.
1238
+ * Error messages in the issue validator are unaffected.
1239
+ */
1240
+ pure?: boolean;
945
1241
  }
946
1242
 
947
1243
  /**
@@ -987,15 +1283,47 @@ export interface CompileOptions {
987
1283
  */
988
1284
  export const compile = <T>(schema: Schema<T>, options?: CompileOptions): Validator<T> => {
989
1285
  const allErrors = options?.allErrors ?? false;
1286
+ const pure = options?.pure ?? false;
1287
+ const canIndex = typeof schema === 'object' && schema !== null && !isTagged(schema) && !Array.isArray(schema) && !(schema instanceof RegExp);
990
1288
 
991
1289
  if (allErrors) {
1290
+ if (canIndex) {
1291
+ const fastContext = new Context();
1292
+ fastContext.pure = pure;
1293
+ const fastCode = codeGen(schema, fastContext, 'data', { fast: true, indexed: true });
1294
+ const indexedFast = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR) as (data: T) => number;
1295
+
1296
+ const issueContext = new Context();
1297
+ issueContext.pure = pure;
1298
+ const rootPath = `reg[${issueContext.register(Object.freeze([]))}]`;
1299
+ const issueCode = `let issues = [];\n${codeGen(schema, issueContext, 'data', { fast: false, firstError: false, issues: 'issues', path: [], pathExpr: rootPath, startVar: 'start', issuesReady: true })}\nreturn issues;`;
1300
+ const issueValidator = new Function('reg', 'err', `return (data, start) => {\n${issueCode}\n};`)(issueContext.registry, ERR) as (
1301
+ data: T,
1302
+ start: number,
1303
+ ) => StandardSchemaV1.Issue[];
1304
+
1305
+ const validator = ((data: T): boolean => {
1306
+ const idx = indexedFast(data);
1307
+ if (idx === -1) return true;
1308
+ validator.issues = issueValidator(data, idx);
1309
+ return false;
1310
+ }) as Validator<T>;
1311
+ validator.issues = emptyIssues;
1312
+ return validator;
1313
+ }
1314
+
992
1315
  const fastContext = new Context();
1316
+ fastContext.pure = pure;
993
1317
  const fastCode = `${codeGen(schema, fastContext, 'data', { fast: true })}\nreturn true;`;
994
- const fastValidator = new Function('ctx', `return (data) => {\n${fastCode}\n};`)(fastContext) as (data: T) => boolean;
1318
+ const fastValidator = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR) as (data: T) => boolean;
995
1319
 
996
1320
  const issueContext = new Context();
997
- const issueCode = `let issues;\n${codeGen(schema, issueContext, 'data', { fast: false, firstError: false, issues: 'issues', path: [], pathExpr: '[]' })}\nreturn issues || [];`;
998
- const issueValidator = new Function('ctx', `return (data) => {\n${issueCode}\n};`)(issueContext) as (data: T) => StandardSchemaV1.Issue[];
1321
+ issueContext.pure = pure;
1322
+ const rootPath2 = `reg[${issueContext.register(Object.freeze([]))}]`;
1323
+ const issueCode = `let issues = [];\n${codeGen(schema, issueContext, 'data', { fast: false, firstError: false, issues: 'issues', path: [], pathExpr: rootPath2, issuesReady: true })}\nreturn issues;`;
1324
+ const issueValidator = new Function('reg', 'err', `return (data) => {\n${issueCode}\n};`)(issueContext.registry, ERR) as (
1325
+ data: T,
1326
+ ) => StandardSchemaV1.Issue[];
999
1327
 
1000
1328
  const validator = ((data: T): boolean => {
1001
1329
  if (fastValidator(data)) {
@@ -1008,13 +1336,43 @@ export const compile = <T>(schema: Schema<T>, options?: CompileOptions): Validat
1008
1336
  return validator;
1009
1337
  }
1010
1338
 
1339
+ if (canIndex) {
1340
+ const fastContext = new Context();
1341
+ fastContext.pure = pure;
1342
+ const fastCode = codeGen(schema, fastContext, 'data', { fast: true, indexed: true });
1343
+ const indexedFast = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR) as (data: T) => number;
1344
+
1345
+ const context = new Context();
1346
+ context.pure = pure;
1347
+ const rootPath = `reg[${context.register(Object.freeze([]))}]`;
1348
+ const code = codeGen(schema, context, 'data', { fast: false, firstError: true, issues: 'issues', path: [], pathExpr: rootPath, startVar: 'start' });
1349
+ const firstErrorValidator = new Function('reg', 'err', `return (data, start) => {\nlet issues;\n${code}\nreturn issues;\n};`)(context.registry, ERR) as (
1350
+ data: T,
1351
+ start: number,
1352
+ ) => StandardSchemaV1.Issue[] | undefined;
1353
+
1354
+ const validator = ((data: T): boolean => {
1355
+ const idx = indexedFast(data);
1356
+ if (idx === -1) {
1357
+ return true;
1358
+ }
1359
+ validator.issues = firstErrorValidator(data, idx)!;
1360
+ return false;
1361
+ }) as Validator<T>;
1362
+ validator.issues = emptyIssues;
1363
+ return validator;
1364
+ }
1365
+
1011
1366
  const fastContext = new Context();
1367
+ fastContext.pure = pure;
1012
1368
  const fastCode = `${codeGen(schema, fastContext, 'data', { fast: true })}\nreturn true;`;
1013
- const fastValidator = new Function('ctx', `return (data) => {\n${fastCode}\n};`)(fastContext) as (data: T) => boolean;
1369
+ const fastValidator = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR) as (data: T) => boolean;
1014
1370
 
1015
1371
  const context = new Context();
1016
- const code = codeGen(schema, context, 'data', { fast: false, firstError: true, issues: 'issues', path: [], pathExpr: '[]' });
1017
- const firstErrorValidator = new Function('ctx', `return (data) => {\nlet issues;\n${code}\nreturn issues;\n};`)(context) as (
1372
+ context.pure = pure;
1373
+ const rootPath3 = `reg[${context.register(Object.freeze([]))}]`;
1374
+ const code = codeGen(schema, context, 'data', { fast: false, firstError: true, issues: 'issues', path: [], pathExpr: rootPath3 });
1375
+ const firstErrorValidator = new Function('reg', 'err', `return (data) => {\nlet issues;\n${code}\nreturn issues;\n};`)(context.registry, ERR) as (
1018
1376
  data: T,
1019
1377
  ) => StandardSchemaV1.Issue[] | undefined;
1020
1378