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.js CHANGED
@@ -8,6 +8,11 @@ const OPTIONAL = Symbol.for('@@optional');
8
8
  const TUPLE = Symbol.for('@@tuple');
9
9
  const DISCRIMINATED = Symbol.for('@@discriminated');
10
10
  const CHECK = Symbol.for('@@check');
11
+ const ERR = Symbol.for('@@err');
12
+ function ErrCtor(message) {
13
+ this.message = message;
14
+ }
15
+ ErrCtor.prototype[ERR] = true;
11
16
  const OrCtor = function(schemas) {
12
17
  this.schemas = schemas;
13
18
  };
@@ -109,7 +114,39 @@ export const oneOf = (values, message)=>{
109
114
  ].map(toLiteral).join(', ')}], got \${${v}}\``
110
115
  }));
111
116
  };
112
- export const fromBase64 = typeof Buffer === 'undefined' ? (value)=>atob(value) : (value)=>Buffer.from(value, 'base64').toString('utf-8');
117
+ const utf8Encoder = new TextEncoder();
118
+ const utf8Decoder = new TextDecoder('utf-8');
119
+ const fromBase64Bytes = (value)=>{
120
+ const bin = atob(value);
121
+ const out = new Uint8Array(bin.length);
122
+ for(let i = 0; i < bin.length; i++)out[i] = bin.charCodeAt(i);
123
+ return out;
124
+ };
125
+ export const fromBase64 = (value)=>utf8Decoder.decode(fromBase64Bytes(value));
126
+ const HEX_LUT = (()=>{
127
+ const t = new Int8Array(256).fill(-1);
128
+ for(let i = 0; i < 10; i++)t[48 + i] = i;
129
+ for(let i = 0; i < 6; i++){
130
+ t[97 + i] = 10 + i;
131
+ t[65 + i] = 10 + i;
132
+ }
133
+ return t;
134
+ })();
135
+ const fromHex = (value)=>{
136
+ let start = 0;
137
+ if (value.length >= 2 && value.charCodeAt(0) === 48 && (value.charCodeAt(1) | 32) === 120) {
138
+ start = 2;
139
+ }
140
+ const digits = value.length - start;
141
+ if (digits === 0 || digits % 2 !== 0) throw new TypeError('invalid hex length');
142
+ const out = new Uint8Array(digits / 2);
143
+ for(let i = 0; i < out.length; i++){
144
+ const byte = HEX_LUT[value.charCodeAt(start + i * 2)] << 4 | HEX_LUT[value.charCodeAt(start + i * 2 + 1)];
145
+ if (byte < 0) throw new TypeError('invalid hex digit');
146
+ out[i] = byte;
147
+ }
148
+ return out;
149
+ };
113
150
  const MULTIPLIERS = {
114
151
  ms: 1,
115
152
  s: 1000,
@@ -119,7 +156,7 @@ const MULTIPLIERS = {
119
156
  w: 604800000
120
157
  };
121
158
  const TIME_REGEX = /^(\d*\.?\d*)(ms|s|m|h|d|w)?$/;
122
- export const asError = (message)=>new TypeError(message);
159
+ export const asError = (message)=>new ErrCtor(message);
123
160
  export const as = {
124
161
  string: (value)=>{
125
162
  return typeof value === 'string' ? value : asError(`Invalid value "${value}", expected a string`);
@@ -171,6 +208,16 @@ export const as = {
171
208
  } catch {
172
209
  return asError(`Invalid value ${value}, expected a valid base64 string`);
173
210
  }
211
+ },
212
+ data: (value, type = 'utf-8')=>{
213
+ if (typeof value !== 'string') return asError(`Invalid value ${value}, expected a string`);
214
+ try {
215
+ if (type === 'hex') return fromHex(value);
216
+ if (type === 'base64') return fromBase64Bytes(value);
217
+ return utf8Encoder.encode(value);
218
+ } catch {
219
+ return asError(`Invalid value ${value}, expected a valid ${type} string`);
220
+ }
174
221
  }
175
222
  };
176
223
  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;
@@ -257,6 +304,7 @@ class Context {
257
304
  registry = [];
258
305
  lookupMap = new Map();
259
306
  varIndex = 0;
307
+ pure = false;
260
308
  register(value) {
261
309
  const index = this.lookupMap.get(value);
262
310
  if (index !== undefined) {
@@ -273,32 +321,128 @@ class Context {
273
321
  }
274
322
  }
275
323
  const isTagged = (schema)=>schema?.[$op] !== undefined;
276
- const childMode = (mode, key)=>{
324
+ const buildDynamicPathExpr = (staticPath, dynamicParts)=>`[${[
325
+ ...staticPath.map((k)=>JSON.stringify(k)),
326
+ ...dynamicParts
327
+ ].join(',')}]`;
328
+ const childMode = (mode, key, ctx)=>{
329
+ const carryReady = !mode.firstError && mode.issuesReady;
277
330
  if (typeof key === 'object' && 'dynamic' in key) {
278
- return {
331
+ const dynamicParts = [
332
+ ...mode.dynamicParts ?? [],
333
+ key.dynamic
334
+ ];
335
+ const m = {
336
+ fast: false,
337
+ firstError: mode.firstError,
338
+ issues: mode.issues,
339
+ path: mode.path,
340
+ pathExpr: buildDynamicPathExpr(mode.path, dynamicParts),
341
+ dynamicParts
342
+ };
343
+ if (carryReady) m.issuesReady = true;
344
+ return m;
345
+ }
346
+ if (mode.dynamicParts) {
347
+ const dynamicParts = [
348
+ ...mode.dynamicParts,
349
+ JSON.stringify(key)
350
+ ];
351
+ const m = {
279
352
  fast: false,
280
353
  firstError: mode.firstError,
281
354
  issues: mode.issues,
282
355
  path: mode.path,
283
- pathExpr: `[${mode.path.map((k)=>JSON.stringify(k)).join(',')}${mode.path.length ? ',' : ''}${key.dynamic}]`
356
+ pathExpr: buildDynamicPathExpr(mode.path, dynamicParts),
357
+ dynamicParts
284
358
  };
359
+ if (carryReady) m.issuesReady = true;
360
+ return m;
285
361
  }
286
362
  const newPath = [
287
363
  ...mode.path,
288
364
  key
289
365
  ];
290
- return {
366
+ const pathExpr = `reg[${ctx.register(Object.freeze(newPath))}]`;
367
+ const m = {
291
368
  fast: false,
292
369
  firstError: mode.firstError,
293
370
  issues: mode.issues,
294
371
  path: newPath,
295
- pathExpr: JSON.stringify(newPath)
372
+ pathExpr
296
373
  };
374
+ if (carryReady) m.issuesReady = true;
375
+ return m;
297
376
  };
298
377
  const toLiteral = (value)=>typeof value === 'bigint' ? `${value}n` : JSON.stringify(value);
378
+ 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;
379
+ const buildCollapsedOr = (schemas, v)=>{
380
+ let hasNull = false;
381
+ let hasUndefined = false;
382
+ const groups = new Map();
383
+ for (const s of schemas){
384
+ if (s === null) {
385
+ hasNull = true;
386
+ continue;
387
+ }
388
+ if (s === undefined) {
389
+ hasUndefined = true;
390
+ continue;
391
+ }
392
+ const ct = collapsibleTypeOf(s);
393
+ if (ct) {
394
+ let g = groups.get(ct);
395
+ if (!g) {
396
+ g = {
397
+ ctor: false,
398
+ literals: []
399
+ };
400
+ groups.set(ct, g);
401
+ }
402
+ g.ctor = true;
403
+ continue;
404
+ }
405
+ const t = typeof s;
406
+ let g = groups.get(t);
407
+ if (!g) {
408
+ g = {
409
+ ctor: false,
410
+ literals: []
411
+ };
412
+ groups.set(t, g);
413
+ }
414
+ g.literals.push(s);
415
+ }
416
+ const clauses = [];
417
+ if (hasNull) clauses.push(`${v} === null`);
418
+ if (hasUndefined) clauses.push(`${v} === undefined`);
419
+ for (const [type, { ctor, literals }] of groups){
420
+ if (ctor) {
421
+ clauses.push(type === 'number' ? `(typeof ${v} === 'number' && ${v} === ${v})` : `typeof ${v} === '${type}'`);
422
+ } else if (literals.length === 1) {
423
+ clauses.push(`${v} === ${toLiteral(literals[0])}`);
424
+ } else {
425
+ clauses.push(`(typeof ${v} === '${type}' && (${literals.map((l)=>`${v} === ${toLiteral(l)}`).join(' || ')}))`);
426
+ }
427
+ }
428
+ return clauses.join(' || ');
429
+ };
430
+ const buildExpectedDesc = (schemas)=>{
431
+ const parts = [];
432
+ for (const s of schemas){
433
+ if (s === null) parts.push('null');
434
+ else if (s === undefined) parts.push('undefined');
435
+ else if (collapsibleTypeOf(s) !== null) parts.push(s.name);
436
+ else parts.push(toLiteral(s));
437
+ }
438
+ return parts.join(', ');
439
+ };
440
+ const isCollapsible = (s)=>s === null || s === undefined || typeof s !== 'object' && typeof s !== 'function' && typeof s !== 'symbol' || collapsibleTypeOf(s) !== null;
299
441
  const codeGen = (schema, context, valuePath, mode)=>{
300
- 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} });`;
442
+ 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} });`;
301
443
  const fail = mode.fast ? mode.onFail ?? 'return false;' : '';
444
+ const errChk = (v)=>context.pure ? '' : ` || (typeof ${v} === 'object' && ${v} !== null && ${v}[err] === true)`;
445
+ const errBranch = (v)=>context.pure ? '' : `else if (typeof ${v} === 'object' && ${v} !== null && ${v}[err] === true) { ${emit(`\`\${${v}.message}\``)} }`;
302
446
  if (isTagged(schema)) {
303
447
  const tag = schema[$op];
304
448
  if (tag === AND) {
@@ -309,6 +453,27 @@ const codeGen = (schema, context, valuePath, mode)=>{
309
453
  const valueAlias = context.unique('v');
310
454
  const foundValid = context.unique('valid');
311
455
  if (mode.fast) {
456
+ const collapsible = [];
457
+ const complex = [];
458
+ for (const s of schema.schemas){
459
+ if (isCollapsible(s)) collapsible.push(s);
460
+ else complex.push(s);
461
+ }
462
+ if (collapsible.length > 0 && complex.length === 0) {
463
+ return `const ${valueAlias} = ${valuePath};\nif (!(${buildCollapsedOr(collapsible, valueAlias)})) { ${fail} }`;
464
+ }
465
+ if (collapsible.length > 0) {
466
+ const condition = buildCollapsedOr(collapsible, valueAlias);
467
+ const branches = complex.map((s)=>{
468
+ const branchValid = context.unique('valid');
469
+ const branchCode = codeGen(s, context, valueAlias, {
470
+ ...mode,
471
+ onFail: `${branchValid} = false;`
472
+ });
473
+ return `if (!${foundValid}) { let ${branchValid} = true; ${branchCode} if (${branchValid}) { ${foundValid} = true; } }`;
474
+ });
475
+ return `const ${valueAlias} = ${valuePath};\nlet ${foundValid} = ${condition};\n${branches.join('\n')}\nif (!${foundValid}) { ${fail} }`;
476
+ }
312
477
  const branches = schema.schemas.map((s)=>{
313
478
  const branchValid = context.unique('valid');
314
479
  const branchCode = codeGen(s, context, valueAlias, {
@@ -319,6 +484,11 @@ const codeGen = (schema, context, valuePath, mode)=>{
319
484
  });
320
485
  return `const ${valueAlias} = ${valuePath};\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { ${fail} }`;
321
486
  } else if (mode.firstError) {
487
+ if (schema.schemas.every(isCollapsible)) {
488
+ const condition = buildCollapsedOr(schema.schemas, valueAlias);
489
+ const expected = buildExpectedDesc(schema.schemas);
490
+ return `const ${valueAlias} = ${valuePath};\nif (!(${condition})) { ${mode.issues} = [{ message: \`Invalid value \${${valueAlias}}, expected one of: ${expected}\`, path: ${mode.pathExpr} }]; return ${mode.issues}; }`;
491
+ }
322
492
  const firstBranchIssues = context.unique('iss');
323
493
  const branches = schema.schemas.map((s, idx)=>{
324
494
  const branchIssues = context.unique('iss');
@@ -336,6 +506,12 @@ const codeGen = (schema, context, valuePath, mode)=>{
336
506
  });
337
507
  return `const ${valueAlias} = ${valuePath};\nlet ${firstBranchIssues};\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { return ${firstBranchIssues}; }`;
338
508
  } else {
509
+ if (schema.schemas.every(isCollapsible)) {
510
+ const condition = buildCollapsedOr(schema.schemas, valueAlias);
511
+ const expected = buildExpectedDesc(schema.schemas);
512
+ 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} });`;
513
+ return `const ${valueAlias} = ${valuePath};\nif (!(${condition})) { ${push} }`;
514
+ }
339
515
  const localIssues = context.unique('iss');
340
516
  const branches = schema.schemas.map((s)=>{
341
517
  const branchIssues = context.unique('iss');
@@ -348,11 +524,42 @@ const codeGen = (schema, context, valuePath, mode)=>{
348
524
  });
349
525
  return `if (!${foundValid}) { let ${branchIssues}; ${branchCode} if (!${branchIssues}) { ${foundValid} = true; } else { ${localIssues}.push(...${branchIssues}); } }`;
350
526
  });
351
- return `const ${valueAlias} = ${valuePath};\nconst ${localIssues} = [];\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { (${mode.issues} || (${mode.issues} = [])).push(...${localIssues}); }`;
527
+ const pushExpr = !mode.fast && !mode.firstError && mode.issuesReady ? `${mode.issues}.push(...${localIssues})` : `(${mode.issues} || (${mode.issues} = [])).push(...${localIssues})`;
528
+ return `const ${valueAlias} = ${valuePath};\nconst ${localIssues} = [];\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { ${pushExpr}; }`;
352
529
  }
353
530
  } else if (tag === OPTIONAL) {
354
531
  const valueAlias = context.unique('v');
355
- return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(schema.schemas[0], context, valueAlias, mode)} }`;
532
+ const inner = schema.schemas[0];
533
+ if (!mode.fast && typeof inner === 'function') {
534
+ const iname = inner?.name;
535
+ const is = inner;
536
+ const pt = is === String ? 'string' : is === Number ? 'number' : is === Boolean ? 'boolean' : is === BigInt ? 'bigint' : is === Symbol ? 'symbol' : null;
537
+ if (pt) {
538
+ const typeMsgs = Object.fromEntries([
539
+ 'string',
540
+ 'number',
541
+ 'boolean',
542
+ 'bigint',
543
+ 'symbol',
544
+ 'undefined',
545
+ 'object',
546
+ 'function'
547
+ ].map((t)=>[
548
+ t,
549
+ `Invalid type ${t}, expected type ${iname}`
550
+ ]));
551
+ const typeMsgIdx = context.register(typeMsgs);
552
+ const lines = [
553
+ `const ${valueAlias} = ${valuePath};`,
554
+ `if (${valueAlias} !== undefined && ${valueAlias} !== null) {`
555
+ ];
556
+ lines.push(`if (typeof ${valueAlias} !== '${pt}') { ${emit(`reg[${typeMsgIdx}][typeof ${valueAlias}]`)} }`);
557
+ if (pt === 'number') lines.push(`else if (${valueAlias} !== ${valueAlias}) { ${emit(`"Invalid value NaN, expected a valid ${iname}"`)} }`);
558
+ lines.push(`}`);
559
+ return lines.join('\n');
560
+ }
561
+ }
562
+ return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(inner, context, valueAlias, mode)} }`;
356
563
  } else if (tag === TUPLE) {
357
564
  const valueAlias = context.unique('v');
358
565
  if (mode.fast) {
@@ -364,12 +571,12 @@ const codeGen = (schema, context, valuePath, mode)=>{
364
571
  `else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Array\``)} }`,
365
572
  `else if (!Array.isArray(${valueAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}.constructor?.name}, expected an instance of Array\``)} }`,
366
573
  `else if (${valueAlias}.length !== ${schema.schemas.length}) { ${emit(`\`Invalid tuple length \${${valueAlias}.length}, expected ${schema.schemas.length}\``)} }`,
367
- `else { ${schema.schemas.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx))).join('\n')} }`
574
+ `else { ${schema.schemas.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx, context))).join('\n')} }`
368
575
  ].join('\n');
369
576
  }
370
577
  } else if (tag === CHECK) {
371
578
  const valueAlias = context.unique('v');
372
- const ref = (v)=>`ctx.registry[${context.register(v)}]`;
579
+ const ref = (v)=>`reg[${context.register(v)}]`;
373
580
  const { check: cond, message } = schema.compile(valueAlias, {
374
581
  ref
375
582
  });
@@ -396,31 +603,33 @@ const codeGen = (schema, context, valuePath, mode)=>{
396
603
  schema: s
397
604
  });
398
605
  }
606
+ const genVariantProps = (s, variantMode)=>{
607
+ const obj = s;
608
+ 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');
609
+ };
399
610
  if (mode.fast) {
400
611
  const branches = variants.map(({ value, schema: s })=>{
401
- const branchCode = codeGen(s, context, valueAlias, mode);
402
- return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${branchCode} }`;
612
+ return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${genVariantProps(s, mode)} }`;
403
613
  });
404
614
  return [
405
615
  `const ${valueAlias} = ${valuePath};`,
406
- `if (${valueAlias} === null || ${valueAlias} === undefined || typeof ${valueAlias} !== 'object' || ${valueAlias} instanceof Error) { ${fail} }`,
616
+ `if (${valueAlias} === null || ${valueAlias} === undefined || typeof ${valueAlias} !== 'object'${errChk(valueAlias)}) { ${fail} }`,
407
617
  `const ${discriminantAlias} = ${valueAlias}[${keyStr}];`,
408
618
  branches.join(' else ') + ` else { ${fail} }`
409
619
  ].join('\n');
410
620
  } else {
411
621
  const validValues = variants.map((v)=>JSON.stringify(v.value)).join(', ');
412
622
  const branches = variants.map(({ value, schema: s })=>{
413
- const branchCode = codeGen(s, context, valueAlias, mode);
414
- return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${branchCode} }`;
623
+ return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${genVariantProps(s, mode)} }`;
415
624
  });
416
625
  return [
417
626
  `const ${valueAlias} = ${valuePath};`,
418
627
  `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
419
628
  `else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an object\``)} }`,
420
- `else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`,
629
+ `${errBranch(valueAlias)}`,
421
630
  `else {`,
422
631
  ` const ${discriminantAlias} = ${valueAlias}[${keyStr}];`,
423
- ` ${branches.join(' else ')} else { ${emit(`\`Invalid discriminant value \${JSON.stringify(${discriminantAlias})}, expected one of: ${validValues}\``)} }`,
632
+ ` ${branches.join(' else ')} else { ${emit(`"Invalid discriminant value " + String(${discriminantAlias}) + ", expected one of: ${validValues.replace(/"/g, "'")}"`)} }`,
424
633
  `}`
425
634
  ].join('\n');
426
635
  }
@@ -436,7 +645,7 @@ const codeGen = (schema, context, valuePath, mode)=>{
436
645
  const checks = [
437
646
  `typeof ${valueAlias} !== '${primitiveType}'`
438
647
  ];
439
- if (primitiveType === 'number') checks.push(`Number.isNaN(${valueAlias})`);
648
+ if (primitiveType === 'number') checks.push(`${valueAlias} !== ${valueAlias}`);
440
649
  return `const ${valueAlias} = ${valuePath};\nif (${checks.join(' || ')}) { ${fail} }`;
441
650
  } else if (name === 'Function') {
442
651
  return `const ${valueAlias} = ${valuePath};\nif (typeof ${valueAlias} !== 'function') { ${fail} }`;
@@ -444,28 +653,42 @@ const codeGen = (schema, context, valuePath, mode)=>{
444
653
  const isError = schema === Error || schema?.prototype instanceof Error;
445
654
  const index = context.register(schema);
446
655
  const registryAlias = context.unique('r');
447
- 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} }`;
656
+ 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} }`;
448
657
  }
449
658
  } else {
450
659
  const code = [
451
660
  `const ${valueAlias} = ${valuePath};`
452
661
  ];
453
662
  if (primitiveType) {
454
- code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
455
- code.push(`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`);
456
- code.push(`else if (typeof ${valueAlias} !== '${primitiveType}') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected type ${name}\``)} }`);
457
- if (primitiveType === 'number') code.push(`else if (Number.isNaN(${valueAlias})) { ${emit(`\`Invalid value \${${valueAlias}}, expected a valid ${name}\``)} }`);
663
+ const typeMsgs = Object.fromEntries([
664
+ 'string',
665
+ 'number',
666
+ 'boolean',
667
+ 'bigint',
668
+ 'symbol',
669
+ 'undefined',
670
+ 'object',
671
+ 'function'
672
+ ].map((t)=>[
673
+ t,
674
+ `Invalid type ${t}, expected type ${name}`
675
+ ]));
676
+ const typeMsgIdx = context.register(typeMsgs);
677
+ code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`${valueAlias} === null ? "Invalid value null, expected non-nullable" : "Invalid value undefined, expected non-nullable"`)} }`);
678
+ code.push(`${errBranch(valueAlias)}`);
679
+ code.push(`else if (typeof ${valueAlias} !== '${primitiveType}') { ${emit(`reg[${typeMsgIdx}][typeof ${valueAlias}]`)} }`);
680
+ if (primitiveType === 'number') code.push(`else if (${valueAlias} !== ${valueAlias}) { ${emit(`"Invalid value NaN, expected a valid ${name}"`)} }`);
458
681
  } else if (name === 'Function') {
459
682
  code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
460
- code.push(`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`);
683
+ code.push(`${errBranch(valueAlias)}`);
461
684
  code.push(`else if (typeof ${valueAlias} !== 'function') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected type Function\``)} }`);
462
685
  } else {
463
686
  const isError = schema === Error || schema?.prototype instanceof Error;
464
687
  const index = context.register(schema);
465
688
  const registryAlias = context.unique('r');
466
- code.push(`const ${registryAlias} = ctx.registry[${index}];`);
689
+ code.push(`const ${registryAlias} = reg[${index}];`);
467
690
  code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
468
- if (!isError) code.push(`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`);
691
+ if (!isError) code.push(`${errBranch(valueAlias)}`);
469
692
  code.push(`else if (typeof ${valueAlias} === 'object' && !(${valueAlias} instanceof ${registryAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}?.constructor?.name}, expected an instance of ${name}\``)} }`);
470
693
  code.push(`else if (typeof ${valueAlias} !== 'object' && ${valueAlias}?.constructor !== ${registryAlias}) { ${emit(`\`Invalid type \${${valueAlias}?.constructor?.name}, expected type ${name}\``)} }`);
471
694
  code.push(`else if (Number.isNaN(${valueAlias}?.valueOf?.())) { ${emit(`\`Invalid value \${${valueAlias}}, expected a valid ${name}\``)} }`);
@@ -487,11 +710,24 @@ const codeGen = (schema, context, valuePath, mode)=>{
487
710
  }
488
711
  return code;
489
712
  } else {
713
+ const arrTypeMsgs = Object.fromEntries([
714
+ 'string',
715
+ 'number',
716
+ 'boolean',
717
+ 'bigint',
718
+ 'symbol',
719
+ 'undefined',
720
+ 'function'
721
+ ].map((t)=>[
722
+ t,
723
+ `Invalid type ${t}, expected an instance of Array`
724
+ ]));
725
+ const arrTypeMsgIdx = context.register(arrTypeMsgs);
490
726
  const code = [
491
727
  `const ${valueAlias} = ${valuePath};`,
492
- `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
493
- `else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`,
494
- `else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Array\``)} }`,
728
+ `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`${valueAlias} === null ? "Invalid value null, expected non-nullable" : "Invalid value undefined, expected non-nullable"`)} }`,
729
+ `${errBranch(valueAlias)}`,
730
+ `else if (typeof ${valueAlias} !== 'object') { ${emit(`reg[${arrTypeMsgIdx}][typeof ${valueAlias}]`)} }`,
495
731
  `else if (!Array.isArray(${valueAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}.constructor?.name}, expected an instance of Array\``)} }`
496
732
  ];
497
733
  if (schema.length > 0) {
@@ -500,10 +736,10 @@ const codeGen = (schema, context, valuePath, mode)=>{
500
736
  if (schema.length === 1) {
501
737
  code.push(`else { for (let ${key} = 0; ${key} < ${valueAlias}.length; ${key}++) { const ${value} = ${valueAlias}[${key}]; ${codeGen(schema[0], context, value, childMode(mode, {
502
738
  dynamic: key
503
- }))} } }`);
739
+ }, context))} } }`);
504
740
  } else {
505
741
  code.push(`else if (${valueAlias}.length > ${schema.length}) { ${emit(`\`Invalid tuple length \${${valueAlias}.length}, expected ${schema.length}\``)} }`);
506
- code.push(`else { ${schema.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx))).join('\n')} }`);
742
+ code.push(`else { ${schema.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx, context))).join('\n')} }`);
507
743
  }
508
744
  }
509
745
  return code.join('\n');
@@ -513,68 +749,127 @@ const codeGen = (schema, context, valuePath, mode)=>{
513
749
  if (schema instanceof RegExp) {
514
750
  const valueAlias = context.unique('v');
515
751
  if (mode.fast) {
516
- return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined || ${valueAlias} instanceof Error || !${schema.toString()}.test(String(${valueAlias}))) { ${fail} }`;
752
+ return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined${errChk(valueAlias)} || !${schema.toString()}.test(String(${valueAlias}))) { ${fail} }`;
517
753
  } else {
518
- 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()}\``)} }`;
754
+ 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()}\``)} }`;
519
755
  }
520
756
  } else {
521
757
  const valueAlias = context.unique('v');
522
758
  if (mode.fast) {
523
- let code = `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || typeof ${valueAlias} !== 'object' || ${valueAlias} instanceof Error) { ${fail} }`;
759
+ const indexed = mode.indexed && !mode.onFail;
760
+ const rootFail = indexed ? 'return 0;' : fail;
761
+ let code = `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || typeof ${valueAlias} !== 'object'${errChk(valueAlias)}) { ${rootFail} }`;
524
762
  if ($keys in schema) {
525
- const keysAlias = context.unique('k');
526
763
  const kAlias = context.unique('k');
527
- code += `\nconst ${keysAlias} = Object.keys(${valueAlias});\nfor (const ${kAlias} of ${keysAlias}) { ${codeGen(schema[$keys], context, kAlias, mode)} }`;
764
+ code += `\nfor (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, indexed ? {
765
+ fast: true,
766
+ onFail: 'return 0;'
767
+ } : mode)} }`;
528
768
  }
529
769
  if ($values in schema) {
530
- const vAlias = context.unique('val');
531
770
  const kAlias = context.unique('k');
532
- const entriesAlias = context.unique('en');
533
- code += `\nconst ${entriesAlias} = Object.entries(${valueAlias});\nfor (const [${kAlias}, ${vAlias}] of ${entriesAlias}) { ${codeGen(schema[$values], context, vAlias, mode)} }`;
771
+ code += `\nfor (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, indexed ? {
772
+ fast: true,
773
+ onFail: 'return 0;'
774
+ } : mode)} }`;
534
775
  }
535
776
  if ($strict in schema && schema[$strict]) {
536
- const keysAlias = context.unique('k');
777
+ const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k)=>[
778
+ k,
779
+ 1
780
+ ])))}]`;
537
781
  const kAlias = context.unique('k');
538
- const extraAlias = context.unique('ex');
539
- 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} }`;
782
+ code += `\nfor (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) { ${rootFail} } }`;
540
783
  }
541
- code += '\n' + Object.entries(schema).map(([key, s])=>codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, mode)).join('\n');
784
+ const entries = Object.entries(schema);
785
+ code += '\n' + entries.map(([key, s], idx)=>{
786
+ const propMode = indexed ? {
787
+ fast: true,
788
+ onFail: `return ${idx + 1};`
789
+ } : mode;
790
+ return codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, propMode);
791
+ }).join('\n');
792
+ if (indexed) code += `\nreturn -1;`;
542
793
  return code;
543
794
  } else {
795
+ const sv = !mode.fast && mode.startVar;
544
796
  const code = [
545
- `const ${valueAlias} = ${valuePath};`,
546
- `if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
547
- `else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`,
548
- `else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`,
549
- 'else {'
797
+ `const ${valueAlias} = ${valuePath};`
550
798
  ];
799
+ if (sv) {
800
+ const rootCase = [];
801
+ if (mode.firstError) {
802
+ rootCase.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
803
+ rootCase.push(`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`);
804
+ rootCase.push(`${errBranch(valueAlias)}`);
805
+ } else {
806
+ rootCase.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} break; }`);
807
+ rootCase.push(`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} break; }`);
808
+ if (!context.pure) rootCase.push(`else if (typeof ${valueAlias} === 'object' && ${valueAlias}[err] === true) { ${emit(`\`\${${valueAlias}.message}\``)} break; }`);
809
+ }
810
+ if ($keys in schema) {
811
+ const kAlias = context.unique('k');
812
+ rootCase.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, {
813
+ dynamic: kAlias
814
+ }, context))} }`);
815
+ }
816
+ if ($values in schema) {
817
+ const kAlias = context.unique('k');
818
+ rootCase.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, childMode(mode, {
819
+ dynamic: kAlias
820
+ }, context))} }`);
821
+ }
822
+ if ($strict in schema && schema[$strict]) {
823
+ const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k)=>[
824
+ k,
825
+ 1
826
+ ])))}]`;
827
+ const kAlias = context.unique('k');
828
+ const extraAlias = context.unique('ex');
829
+ rootCase.push(`const ${extraAlias} = [];`);
830
+ rootCase.push(`for (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) ${extraAlias}.push(${kAlias}); }`);
831
+ rootCase.push(`if (${extraAlias}.length !== 0) { ${emit(`\`Extra properties: \${${extraAlias}}, are not allowed\``)} }`);
832
+ }
833
+ code.push(`switch (${sv}) {`);
834
+ code.push(`case 0: { ${rootCase.join('\n')} }`);
835
+ const entries = Object.entries(schema);
836
+ entries.forEach(([key, s], idx)=>{
837
+ const propCode = codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, childMode(mode, key, context));
838
+ code.push(`case ${idx + 1}: { ${propCode} }`);
839
+ });
840
+ code.push(`}`);
841
+ return code.join('\n');
842
+ }
843
+ code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
844
+ code.push(`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`);
845
+ code.push(`${errBranch(valueAlias)}`);
846
+ code.push('else {');
551
847
  const innerCode = [];
552
848
  if ($keys in schema) {
553
- const keysAlias = context.unique('k');
554
849
  const kAlias = context.unique('k');
555
- innerCode.push(`const ${keysAlias} = Object.keys(${valueAlias});`);
556
- innerCode.push(`for (const ${kAlias} of ${keysAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, {
850
+ innerCode.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, {
557
851
  dynamic: kAlias
558
- }))} }`);
852
+ }, context))} }`);
559
853
  }
560
854
  if ($values in schema) {
561
- const vAlias = context.unique('val');
562
855
  const kAlias = context.unique('k');
563
- const entriesAlias = context.unique('en');
564
- innerCode.push(`const ${entriesAlias} = Object.entries(${valueAlias});`);
565
- innerCode.push(`for (const [${kAlias}, ${vAlias}] of ${entriesAlias}) { ${codeGen(schema[$values], context, vAlias, childMode(mode, {
856
+ innerCode.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, childMode(mode, {
566
857
  dynamic: kAlias
567
- }))} }`);
858
+ }, context))} }`);
568
859
  }
569
860
  if ($strict in schema && schema[$strict]) {
570
- const keysAlias = context.unique('k');
861
+ const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k)=>[
862
+ k,
863
+ 1
864
+ ])))}]`;
571
865
  const kAlias = context.unique('k');
572
866
  const extraAlias = context.unique('ex');
573
- innerCode.push(`const ${keysAlias} = new Set(${JSON.stringify(Object.keys(schema))});`);
574
- innerCode.push(`const ${extraAlias} = Object.keys(${valueAlias}).filter(${kAlias} => !${keysAlias}.has(${kAlias}));`);
867
+ innerCode.push(`const ${extraAlias} = [];`);
868
+ innerCode.push(`for (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) ${extraAlias}.push(${kAlias}); }`);
575
869
  innerCode.push(`if (${extraAlias}.length !== 0) { ${emit(`\`Extra properties: \${${extraAlias}}, are not allowed\``)} }`);
576
870
  }
577
- innerCode.push(...Object.entries(schema).map(([key, s])=>codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, childMode(mode, key))));
871
+ const entries = Object.entries(schema);
872
+ innerCode.push(...entries.map(([key, s])=>codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, childMode(mode, key, context))));
578
873
  code.push(innerCode.join('\n'), '}');
579
874
  return code.join('\n');
580
875
  }
@@ -585,9 +880,9 @@ const codeGen = (schema, context, valuePath, mode)=>{
585
880
  const valueAlias = context.unique('v');
586
881
  const registryAlias = context.unique('r');
587
882
  if (mode.fast) {
588
- return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = ctx.registry[${index}];\nif (typeof ${valueAlias} !== 'symbol' || ${valueAlias} !== ${registryAlias}) { ${fail} }`;
883
+ return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = reg[${index}];\nif (typeof ${valueAlias} !== 'symbol' || ${valueAlias} !== ${registryAlias}) { ${fail} }`;
589
884
  } else {
590
- 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()}\``)} }`;
885
+ 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()}\``)} }`;
591
886
  }
592
887
  }
593
888
  if (schema === null || schema === undefined) {
@@ -603,27 +898,63 @@ const codeGen = (schema, context, valuePath, mode)=>{
603
898
  return `const ${valueAlias} = ${valuePath};\nif (typeof ${valueAlias} !== '${typeof schema}' || ${valueAlias} !== ${toLiteral(schema)}) { ${fail} }`;
604
899
  } else {
605
900
  const value = context.unique('val');
606
- 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)}\``)} }`;
901
+ 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)}\``)} }`;
607
902
  }
608
903
  };
609
904
  const emptyIssues = [];
610
905
  export const compile = (schema, options)=>{
611
906
  const allErrors = options?.allErrors ?? false;
907
+ const pure = options?.pure ?? false;
908
+ const canIndex = typeof schema === 'object' && schema !== null && !isTagged(schema) && !Array.isArray(schema) && !(schema instanceof RegExp);
612
909
  if (allErrors) {
910
+ if (canIndex) {
911
+ const fastContext = new Context();
912
+ fastContext.pure = pure;
913
+ const fastCode = codeGen(schema, fastContext, 'data', {
914
+ fast: true,
915
+ indexed: true
916
+ });
917
+ const indexedFast = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR);
918
+ const issueContext = new Context();
919
+ issueContext.pure = pure;
920
+ const rootPath = `reg[${issueContext.register(Object.freeze([]))}]`;
921
+ const issueCode = `let issues = [];\n${codeGen(schema, issueContext, 'data', {
922
+ fast: false,
923
+ firstError: false,
924
+ issues: 'issues',
925
+ path: [],
926
+ pathExpr: rootPath,
927
+ startVar: 'start',
928
+ issuesReady: true
929
+ })}\nreturn issues;`;
930
+ const issueValidator = new Function('reg', 'err', `return (data, start) => {\n${issueCode}\n};`)(issueContext.registry, ERR);
931
+ const validator = (data)=>{
932
+ const idx = indexedFast(data);
933
+ if (idx === -1) return true;
934
+ validator.issues = issueValidator(data, idx);
935
+ return false;
936
+ };
937
+ validator.issues = emptyIssues;
938
+ return validator;
939
+ }
613
940
  const fastContext = new Context();
941
+ fastContext.pure = pure;
614
942
  const fastCode = `${codeGen(schema, fastContext, 'data', {
615
943
  fast: true
616
944
  })}\nreturn true;`;
617
- const fastValidator = new Function('ctx', `return (data) => {\n${fastCode}\n};`)(fastContext);
945
+ const fastValidator = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR);
618
946
  const issueContext = new Context();
619
- const issueCode = `let issues;\n${codeGen(schema, issueContext, 'data', {
947
+ issueContext.pure = pure;
948
+ const rootPath2 = `reg[${issueContext.register(Object.freeze([]))}]`;
949
+ const issueCode = `let issues = [];\n${codeGen(schema, issueContext, 'data', {
620
950
  fast: false,
621
951
  firstError: false,
622
952
  issues: 'issues',
623
953
  path: [],
624
- pathExpr: '[]'
625
- })}\nreturn issues || [];`;
626
- const issueValidator = new Function('ctx', `return (data) => {\n${issueCode}\n};`)(issueContext);
954
+ pathExpr: rootPath2,
955
+ issuesReady: true
956
+ })}\nreturn issues;`;
957
+ const issueValidator = new Function('reg', 'err', `return (data) => {\n${issueCode}\n};`)(issueContext.registry, ERR);
627
958
  const validator = (data)=>{
628
959
  if (fastValidator(data)) {
629
960
  return true;
@@ -634,20 +965,54 @@ export const compile = (schema, options)=>{
634
965
  validator.issues = emptyIssues;
635
966
  return validator;
636
967
  }
968
+ if (canIndex) {
969
+ const fastContext = new Context();
970
+ fastContext.pure = pure;
971
+ const fastCode = codeGen(schema, fastContext, 'data', {
972
+ fast: true,
973
+ indexed: true
974
+ });
975
+ const indexedFast = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR);
976
+ const context = new Context();
977
+ context.pure = pure;
978
+ const rootPath = `reg[${context.register(Object.freeze([]))}]`;
979
+ const code = codeGen(schema, context, 'data', {
980
+ fast: false,
981
+ firstError: true,
982
+ issues: 'issues',
983
+ path: [],
984
+ pathExpr: rootPath,
985
+ startVar: 'start'
986
+ });
987
+ const firstErrorValidator = new Function('reg', 'err', `return (data, start) => {\nlet issues;\n${code}\nreturn issues;\n};`)(context.registry, ERR);
988
+ const validator = (data)=>{
989
+ const idx = indexedFast(data);
990
+ if (idx === -1) {
991
+ return true;
992
+ }
993
+ validator.issues = firstErrorValidator(data, idx);
994
+ return false;
995
+ };
996
+ validator.issues = emptyIssues;
997
+ return validator;
998
+ }
637
999
  const fastContext = new Context();
1000
+ fastContext.pure = pure;
638
1001
  const fastCode = `${codeGen(schema, fastContext, 'data', {
639
1002
  fast: true
640
1003
  })}\nreturn true;`;
641
- const fastValidator = new Function('ctx', `return (data) => {\n${fastCode}\n};`)(fastContext);
1004
+ const fastValidator = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR);
642
1005
  const context = new Context();
1006
+ context.pure = pure;
1007
+ const rootPath3 = `reg[${context.register(Object.freeze([]))}]`;
643
1008
  const code = codeGen(schema, context, 'data', {
644
1009
  fast: false,
645
1010
  firstError: true,
646
1011
  issues: 'issues',
647
1012
  path: [],
648
- pathExpr: '[]'
1013
+ pathExpr: rootPath3
649
1014
  });
650
- const firstErrorValidator = new Function('ctx', `return (data) => {\nlet issues;\n${code}\nreturn issues;\n};`)(context);
1015
+ const firstErrorValidator = new Function('reg', 'err', `return (data) => {\nlet issues;\n${code}\nreturn issues;\n};`)(context.registry, ERR);
651
1016
  const validator = (data)=>{
652
1017
  if (fastValidator(data)) {
653
1018
  return true;