ascertain
Version:
0-Deps, simple, fast, for browser and node js object schema validator
930 lines • 61.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: Object.getOwnPropertyDescriptor(all, name).get
});
}
_export(exports, {
get $keys () {
return $keys;
},
get $strict () {
return $strict;
},
get $values () {
return $values;
},
get and () {
return and;
},
get as () {
return as;
},
get asError () {
return asError;
},
get ascertain () {
return ascertain;
},
get check () {
return check;
},
get compile () {
return compile;
},
get createValidator () {
return createValidator;
},
get discriminated () {
return discriminated;
},
get format () {
return format;
},
get fromBase64 () {
return fromBase64;
},
get gt () {
return gt;
},
get integer () {
return integer;
},
get lt () {
return lt;
},
get max () {
return max;
},
get maxLength () {
return maxLength;
},
get min () {
return min;
},
get minLength () {
return minLength;
},
get multipleOf () {
return multipleOf;
},
get oneOf () {
return oneOf;
},
get optional () {
return optional;
},
get or () {
return or;
},
get standardSchema () {
return standardSchema;
},
get tuple () {
return tuple;
},
get uniqueItems () {
return uniqueItems;
}
});
const $keys = Symbol.for('@@keys');
const $values = Symbol.for('@@values');
const $strict = Symbol.for('@@strict');
const $op = Symbol.for('@@op');
const OR = Symbol.for('@@or');
const AND = Symbol.for('@@and');
const OPTIONAL = Symbol.for('@@optional');
const TUPLE = Symbol.for('@@tuple');
const DISCRIMINATED = Symbol.for('@@discriminated');
const CHECK = Symbol.for('@@check');
const ERR = Symbol.for('@@err');
function ErrCtor(message) {
this.message = message;
}
ErrCtor.prototype[ERR] = true;
const OrCtor = function(schemas) {
this.schemas = schemas;
};
OrCtor.prototype[$op] = OR;
const AndCtor = function(schemas) {
this.schemas = schemas;
};
AndCtor.prototype[$op] = AND;
const OptionalCtor = function(schema) {
this.schemas = [
schema
];
};
OptionalCtor.prototype[$op] = OPTIONAL;
const TupleCtor = function(schemas) {
this.schemas = schemas;
};
TupleCtor.prototype[$op] = TUPLE;
const DiscriminatedCtor = function(schemas, key) {
this.schemas = schemas;
this.key = key;
};
DiscriminatedCtor.prototype[$op] = DISCRIMINATED;
const CheckCtor = function(compileFn) {
this.compile = compileFn;
};
CheckCtor.prototype[$op] = CHECK;
const or = (...schemas)=>{
if (schemas.length === 0) throw new TypeError('Operator requires at least one schema');
return new OrCtor(schemas);
};
const and = (...schemas)=>{
if (schemas.length === 0) throw new TypeError('Operator requires at least one schema');
return new AndCtor(schemas);
};
const optional = (schema)=>new OptionalCtor(schema);
const tuple = (...schemas)=>{
if (schemas.length === 0) throw new TypeError('Operator requires at least one schema');
return new TupleCtor(schemas);
};
const discriminated = (schemas, key)=>{
if (schemas.length === 0) throw new TypeError('discriminated requires at least one schema');
return new DiscriminatedCtor(schemas, key);
};
const check = (fnOrOpts, message)=>{
if (typeof fnOrOpts === 'function') {
return new CheckCtor((v, ctx)=>{
const fnRef = ctx.ref(fnOrOpts);
return {
check: `!${fnRef}(${v})`,
message: message ? JSON.stringify(message) : `\`check failed for value \${${v}}\``
};
});
}
return new CheckCtor(fnOrOpts.compile);
};
const min = (n, message)=>new CheckCtor((v)=>({
check: `${v} < ${n}`,
message: message ? JSON.stringify(message) : `\`must be >= ${n}, got \${${v}}\``
}));
const max = (n, message)=>new CheckCtor((v)=>({
check: `${v} > ${n}`,
message: message ? JSON.stringify(message) : `\`must be <= ${n}, got \${${v}}\``
}));
const integer = (message)=>new CheckCtor((v)=>({
check: `!Number.isInteger(${v})`,
message: message ? JSON.stringify(message) : `\`must be an integer, got \${${v}}\``
}));
const minLength = (n, message)=>new CheckCtor((v)=>({
check: `${v}.length < ${n}`,
message: message ? JSON.stringify(message) : `\`length must be >= ${n}, got \${${v}.length}\``
}));
const maxLength = (n, message)=>new CheckCtor((v)=>({
check: `${v}.length > ${n}`,
message: message ? JSON.stringify(message) : `\`length must be <= ${n}, got \${${v}.length}\``
}));
const gt = (n, message)=>new CheckCtor((v)=>({
check: `${v} <= ${n}`,
message: message ? JSON.stringify(message) : `\`must be > ${n}, got \${${v}}\``
}));
const lt = (n, message)=>new CheckCtor((v)=>({
check: `${v} >= ${n}`,
message: message ? JSON.stringify(message) : `\`must be < ${n}, got \${${v}}\``
}));
const multipleOf = (n, message)=>new CheckCtor((v)=>({
check: `${v} % ${n} !== 0`,
message: message ? JSON.stringify(message) : `\`must be a multiple of ${n}, got \${${v}}\``
}));
const uniqueItems = (message)=>new CheckCtor((v)=>({
check: `new Set(${v}).size !== ${v}.length`,
message: message ? JSON.stringify(message) : `\`must have unique items\``
}));
const oneOf = (values, message)=>{
const set = new Set(Array.isArray(values) ? values : Object.values(values));
return new CheckCtor((v, ctx)=>({
check: `!${ctx.ref(set)}.has(${v})`,
message: message ? JSON.stringify(message) : `\`must be one of [${[
...set
].map(toLiteral).join(', ')}], got \${${v}}\``
}));
};
const utf8Encoder = new TextEncoder();
const utf8Decoder = new TextDecoder('utf-8');
const fromBase64Bytes = (value)=>{
const bin = atob(value);
const out = new Uint8Array(bin.length);
for(let i = 0; i < bin.length; i++)out[i] = bin.charCodeAt(i);
return out;
};
const fromBase64 = (value)=>utf8Decoder.decode(fromBase64Bytes(value));
const HEX_LUT = (()=>{
const t = new Int8Array(256).fill(-1);
for(let i = 0; i < 10; i++)t[48 + i] = i;
for(let i = 0; i < 6; i++){
t[97 + i] = 10 + i;
t[65 + i] = 10 + i;
}
return t;
})();
const fromHex = (value)=>{
let start = 0;
if (value.length >= 2 && value.charCodeAt(0) === 48 && (value.charCodeAt(1) | 32) === 120) {
start = 2;
}
const digits = value.length - start;
if (digits === 0 || digits % 2 !== 0) throw new TypeError('invalid hex length');
const out = new Uint8Array(digits / 2);
for(let i = 0; i < out.length; i++){
const byte = HEX_LUT[value.charCodeAt(start + i * 2)] << 4 | HEX_LUT[value.charCodeAt(start + i * 2 + 1)];
if (byte < 0) throw new TypeError('invalid hex digit');
out[i] = byte;
}
return out;
};
const MULTIPLIERS = {
ms: 1,
s: 1000,
m: 60000,
h: 3600000,
d: 86400000,
w: 604800000
};
const TIME_REGEX = /^(\d*\.?\d*)(ms|s|m|h|d|w)?$/;
const asError = (message)=>new ErrCtor(message);
const as = {
string: (value)=>{
return typeof value === 'string' ? value : asError(`Invalid value "${value}", expected a string`);
},
number: (value)=>{
if (typeof value !== 'string') {
return asError(`Invalid value ${value}, expected a valid number`);
}
const start = value[0] === '-' || value[0] === '+' ? 1 : 0;
const c0 = value.charCodeAt(start);
const c1 = value.charCodeAt(start + 1) | 32;
if (c0 === 48 && (c1 === 120 || c1 === 111 || c1 === 98)) {
const result = Number(start ? value.slice(1) : value);
if (Number.isNaN(result)) return asError(`Invalid value ${value}, expected a valid number`);
return value[0] === '-' ? -result : result;
}
const result = value.trim() ? Number(value) : NaN;
return Number.isNaN(result) ? asError(`Invalid value ${value}, expected a valid number`) : result;
},
date: (value)=>{
const result = Date.parse(value);
const date = new Date(result);
return Number.isNaN(date.valueOf()) ? asError(`Invalid value "${value}", expected a valid date format`) : date;
},
time: (value, conversionFactor = 1)=>{
if (!value) return asError(`Invalid value ${value}, expected a valid time format`);
const matches = value.match(TIME_REGEX);
if (!matches) return asError(`Invalid value ${value}, expected a valid time format`);
const [, amount, unit = 'ms'] = matches;
const multiplier = MULTIPLIERS[unit];
const parsed = parseFloat(amount);
if (!multiplier || Number.isNaN(parsed)) {
return asError(`Invalid value ${value}, expected a valid time format`);
}
return Math.floor(parsed * multiplier / conversionFactor);
},
boolean: (value)=>/^(0|1|true|false|enabled|disabled)$/i.test(value) ? /^(1|true|enabled)$/i.test(value) : asError(`Invalid value ${value}, expected a boolean like`),
array: (value, delimiter)=>value?.split?.(delimiter) ?? asError(`Invalid value ${value}, expected an array`),
json: (value)=>{
try {
return JSON.parse(value);
} catch {
return asError(`Invalid value ${value}, expected a valid JSON string`);
}
},
base64: (value)=>{
try {
return fromBase64(value);
} catch {
return asError(`Invalid value ${value}, expected a valid base64 string`);
}
},
data: (value, type = 'utf-8')=>{
if (typeof value !== 'string') return asError(`Invalid value ${value}, expected a string`);
try {
if (type === 'hex') return fromHex(value);
if (type === 'base64') return fromBase64Bytes(value);
return utf8Encoder.encode(value);
} catch {
return asError(`Invalid value ${value}, expected a valid ${type} string`);
}
}
};
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;
const TIME_FMT_RE = /^(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d{2}(?::?\d{2})?)$/i;
const DURATION_RE = /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$/;
const EMAIL_RE = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
const IDN_EMAIL_RE = /^[a-z0-9!#$%&'*+/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]+)*@(?:[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF](?:[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]*[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?\.)+[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF](?:[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]*[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?$/i;
const HOSTNAME_RE = /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*\.?$/i;
const IDN_HOSTNAME_RE = /^(?=.{1,253}\.?$)[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF](?:[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]{0,61}[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?(?:\.[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF](?:[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]{0,61}[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?)*\.?$/i;
const IPV4_RE = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
const IPV6_RE = /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?$/i;
const URI_RE = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
const isUriRef = (s)=>URI_RE.test(s) || /^[a-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]*$/i.test(s);
const IRI_RE = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*)?$/i;
const isIriRef = (s)=>IRI_RE.test(s) || /^[a-z0-9\-._~:/?#\[\]@!$&'()*+,;=\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF%]*$/i.test(s);
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const URI_TEMPLATE_RE = /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i;
const JSON_POINTER_RE = /^(?:\/(?:[^~/]|~0|~1)*)*$/;
const REL_JSON_POINTER_RE = /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/;
const DAYS = [
0,
31,
28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
];
const isValidDate = (s)=>{
const m = /^\d{4}-(\d{2})-(\d{2})$/.exec(s);
if (!m) return false;
const month = +m[1], day = +m[2];
if (month < 1 || month > 12 || day < 1) return false;
if (month === 2) {
const y = +s.slice(0, 4);
return day <= (y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0) ? 29 : 28);
}
return day <= DAYS[month];
};
const isValidRegex = (s)=>{
try {
new RegExp(s);
return true;
} catch {
return false;
}
};
const regexFormat = (re, name, message)=>new CheckCtor((v, ctx)=>({
check: `!${ctx.ref(re)}.test(${v})`,
message: message ? JSON.stringify(message) : `\`must be a valid ${name}, got \${${v}}\``
}));
const fnFormat = (fn, name, message)=>new CheckCtor((v, ctx)=>({
check: `!${ctx.ref(fn)}(${v})`,
message: message ? JSON.stringify(message) : `\`must be a valid ${name}, got \${${v}}\``
}));
const format = {
dateTime: (message)=>regexFormat(DATETIME_RE, 'date-time', message),
date: (message)=>fnFormat(isValidDate, 'date', message),
time: (message)=>regexFormat(TIME_FMT_RE, 'time', message),
duration: (message)=>regexFormat(DURATION_RE, 'duration', message),
email: (message)=>regexFormat(EMAIL_RE, 'email', message),
idnEmail: (message)=>regexFormat(IDN_EMAIL_RE, 'idn-email', message),
hostname: (message)=>regexFormat(HOSTNAME_RE, 'hostname', message),
idnHostname: (message)=>regexFormat(IDN_HOSTNAME_RE, 'idn-hostname', message),
ipv4: (message)=>regexFormat(IPV4_RE, 'ipv4', message),
ipv6: (message)=>regexFormat(IPV6_RE, 'ipv6', message),
uri: (message)=>regexFormat(URI_RE, 'uri', message),
uriReference: (message)=>fnFormat(isUriRef, 'uri-reference', message),
iri: (message)=>regexFormat(IRI_RE, 'iri', message),
iriReference: (message)=>fnFormat(isIriRef, 'iri-reference', message),
uuid: (message)=>regexFormat(UUID_RE, 'uuid', message),
uriTemplate: (message)=>regexFormat(URI_TEMPLATE_RE, 'uri-template', message),
jsonPointer: (message)=>regexFormat(JSON_POINTER_RE, 'json-pointer', message),
relativeJsonPointer: (message)=>regexFormat(REL_JSON_POINTER_RE, 'relative-json-pointer', message),
regex: (message)=>fnFormat(isValidRegex, 'regex', message)
};
class Context {
registry = [];
lookupMap = new Map();
varIndex = 0;
pure = false;
register(value) {
const index = this.lookupMap.get(value);
if (index !== undefined) {
return index;
}
{
const index = this.registry.push(value) - 1;
this.lookupMap.set(value, index);
return index;
}
}
unique(prefix) {
return `${prefix}$$${this.varIndex++}`;
}
}
const isTagged = (schema)=>schema?.[$op] !== undefined;
const buildDynamicPathExpr = (staticPath, dynamicParts)=>`[${[
...staticPath.map((k)=>JSON.stringify(k)),
...dynamicParts
].join(',')}]`;
const childMode = (mode, key, ctx)=>{
const carryReady = !mode.firstError && mode.issuesReady;
if (typeof key === 'object' && 'dynamic' in key) {
const dynamicParts = [
...mode.dynamicParts ?? [],
key.dynamic
];
const m = {
fast: false,
firstError: mode.firstError,
issues: mode.issues,
path: mode.path,
pathExpr: buildDynamicPathExpr(mode.path, dynamicParts),
dynamicParts
};
if (carryReady) m.issuesReady = true;
return m;
}
if (mode.dynamicParts) {
const dynamicParts = [
...mode.dynamicParts,
JSON.stringify(key)
];
const m = {
fast: false,
firstError: mode.firstError,
issues: mode.issues,
path: mode.path,
pathExpr: buildDynamicPathExpr(mode.path, dynamicParts),
dynamicParts
};
if (carryReady) m.issuesReady = true;
return m;
}
const newPath = [
...mode.path,
key
];
const pathExpr = `reg[${ctx.register(Object.freeze(newPath))}]`;
const m = {
fast: false,
firstError: mode.firstError,
issues: mode.issues,
path: newPath,
pathExpr
};
if (carryReady) m.issuesReady = true;
return m;
};
const toLiteral = (value)=>typeof value === 'bigint' ? `${value}n` : JSON.stringify(value);
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;
const buildCollapsedOr = (schemas, v)=>{
let hasNull = false;
let hasUndefined = false;
const groups = new Map();
for (const s of schemas){
if (s === null) {
hasNull = true;
continue;
}
if (s === undefined) {
hasUndefined = true;
continue;
}
const ct = collapsibleTypeOf(s);
if (ct) {
let g = groups.get(ct);
if (!g) {
g = {
ctor: false,
literals: []
};
groups.set(ct, g);
}
g.ctor = true;
continue;
}
const t = typeof s;
let g = groups.get(t);
if (!g) {
g = {
ctor: false,
literals: []
};
groups.set(t, g);
}
g.literals.push(s);
}
const clauses = [];
if (hasNull) clauses.push(`${v} === null`);
if (hasUndefined) clauses.push(`${v} === undefined`);
for (const [type, { ctor, literals }] of groups){
if (ctor) {
clauses.push(type === 'number' ? `(typeof ${v} === 'number' && ${v} === ${v})` : `typeof ${v} === '${type}'`);
} else if (literals.length === 1) {
clauses.push(`${v} === ${toLiteral(literals[0])}`);
} else {
clauses.push(`(typeof ${v} === '${type}' && (${literals.map((l)=>`${v} === ${toLiteral(l)}`).join(' || ')}))`);
}
}
return clauses.join(' || ');
};
const buildExpectedDesc = (schemas)=>{
const parts = [];
for (const s of schemas){
if (s === null) parts.push('null');
else if (s === undefined) parts.push('undefined');
else if (collapsibleTypeOf(s) !== null) parts.push(s.name);
else parts.push(toLiteral(s));
}
return parts.join(', ');
};
const isCollapsible = (s)=>s === null || s === undefined || typeof s !== 'object' && typeof s !== 'function' && typeof s !== 'symbol' || collapsibleTypeOf(s) !== null;
const codeGen = (schema, context, valuePath, mode)=>{
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} });`;
const fail = mode.fast ? mode.onFail ?? 'return false;' : '';
const errChk = (v)=>context.pure ? '' : ` || (typeof ${v} === 'object' && ${v} !== null && ${v}[err] === true)`;
const errBranch = (v)=>context.pure ? '' : `else if (typeof ${v} === 'object' && ${v} !== null && ${v}[err] === true) { ${emit(`\`\${${v}.message}\``)} }`;
if (isTagged(schema)) {
const tag = schema[$op];
if (tag === AND) {
const valueAlias = context.unique('v');
const code = schema.schemas.map((s)=>codeGen(s, context, valueAlias, mode)).join('\n');
return `const ${valueAlias} = ${valuePath};\n${code}`;
} else if (tag === OR) {
const valueAlias = context.unique('v');
const foundValid = context.unique('valid');
if (mode.fast) {
const collapsible = [];
const complex = [];
for (const s of schema.schemas){
if (isCollapsible(s)) collapsible.push(s);
else complex.push(s);
}
if (collapsible.length > 0 && complex.length === 0) {
return `const ${valueAlias} = ${valuePath};\nif (!(${buildCollapsedOr(collapsible, valueAlias)})) { ${fail} }`;
}
if (collapsible.length > 0) {
const condition = buildCollapsedOr(collapsible, valueAlias);
const branches = complex.map((s)=>{
const branchValid = context.unique('valid');
const branchCode = codeGen(s, context, valueAlias, {
...mode,
onFail: `${branchValid} = false;`
});
return `if (!${foundValid}) { let ${branchValid} = true; ${branchCode} if (${branchValid}) { ${foundValid} = true; } }`;
});
return `const ${valueAlias} = ${valuePath};\nlet ${foundValid} = ${condition};\n${branches.join('\n')}\nif (!${foundValid}) { ${fail} }`;
}
const branches = schema.schemas.map((s)=>{
const branchValid = context.unique('valid');
const branchCode = codeGen(s, context, valueAlias, {
...mode,
onFail: `${branchValid} = false;`
});
return `if (!${foundValid}) { let ${branchValid} = true; ${branchCode} if (${branchValid}) { ${foundValid} = true; } }`;
});
return `const ${valueAlias} = ${valuePath};\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { ${fail} }`;
} else if (mode.firstError) {
if (schema.schemas.every(isCollapsible)) {
const condition = buildCollapsedOr(schema.schemas, valueAlias);
const expected = buildExpectedDesc(schema.schemas);
return `const ${valueAlias} = ${valuePath};\nif (!(${condition})) { ${mode.issues} = [{ message: \`Invalid value \${${valueAlias}}, expected one of: ${expected}\`, path: ${mode.pathExpr} }]; return ${mode.issues}; }`;
}
const firstBranchIssues = context.unique('iss');
const branches = schema.schemas.map((s, idx)=>{
const branchIssues = context.unique('iss');
const branchCode = codeGen(s, context, valueAlias, {
fast: false,
firstError: true,
issues: branchIssues,
path: mode.path,
pathExpr: mode.pathExpr
}).replace(new RegExp(`; return ${branchIssues};`, 'g'), ';');
if (idx === 0) {
return `if (!${foundValid}) { let ${branchIssues}; ${branchCode} if (!${branchIssues}) { ${foundValid} = true; } else { ${firstBranchIssues} = ${branchIssues}; } }`;
}
return `if (!${foundValid}) { let ${branchIssues}; ${branchCode} if (!${branchIssues}) { ${foundValid} = true; } }`;
});
return `const ${valueAlias} = ${valuePath};\nlet ${firstBranchIssues};\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { return ${firstBranchIssues}; }`;
} else {
if (schema.schemas.every(isCollapsible)) {
const condition = buildCollapsedOr(schema.schemas, valueAlias);
const expected = buildExpectedDesc(schema.schemas);
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} });`;
return `const ${valueAlias} = ${valuePath};\nif (!(${condition})) { ${push} }`;
}
const localIssues = context.unique('iss');
const branches = schema.schemas.map((s)=>{
const branchIssues = context.unique('iss');
const branchCode = codeGen(s, context, valueAlias, {
fast: false,
firstError: false,
issues: branchIssues,
path: mode.path,
pathExpr: mode.pathExpr
});
return `if (!${foundValid}) { let ${branchIssues}; ${branchCode} if (!${branchIssues}) { ${foundValid} = true; } else { ${localIssues}.push(...${branchIssues}); } }`;
});
const pushExpr = !mode.fast && !mode.firstError && mode.issuesReady ? `${mode.issues}.push(...${localIssues})` : `(${mode.issues} || (${mode.issues} = [])).push(...${localIssues})`;
return `const ${valueAlias} = ${valuePath};\nconst ${localIssues} = [];\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { ${pushExpr}; }`;
}
} else if (tag === OPTIONAL) {
const valueAlias = context.unique('v');
const inner = schema.schemas[0];
if (!mode.fast && typeof inner === 'function') {
const iname = inner?.name;
const is = inner;
const pt = is === String ? 'string' : is === Number ? 'number' : is === Boolean ? 'boolean' : is === BigInt ? 'bigint' : is === Symbol ? 'symbol' : null;
if (pt) {
const typeMsgs = Object.fromEntries([
'string',
'number',
'boolean',
'bigint',
'symbol',
'undefined',
'object',
'function'
].map((t)=>[
t,
`Invalid type ${t}, expected type ${iname}`
]));
const typeMsgIdx = context.register(typeMsgs);
const lines = [
`const ${valueAlias} = ${valuePath};`,
`if (${valueAlias} !== undefined && ${valueAlias} !== null) {`
];
lines.push(`if (typeof ${valueAlias} !== '${pt}') { ${emit(`reg[${typeMsgIdx}][typeof ${valueAlias}]`)} }`);
if (pt === 'number') lines.push(`else if (${valueAlias} !== ${valueAlias}) { ${emit(`"Invalid value NaN, expected a valid ${iname}"`)} }`);
lines.push(`}`);
return lines.join('\n');
}
}
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(inner, context, valueAlias, mode)} }`;
} else if (tag === TUPLE) {
const valueAlias = context.unique('v');
if (mode.fast) {
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || typeof ${valueAlias} !== 'object' || !Array.isArray(${valueAlias}) || ${valueAlias}.length !== ${schema.schemas.length}) { ${fail} }\n${schema.schemas.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, mode)).join('\n')}`;
} else {
return [
`const ${valueAlias} = ${valuePath};`,
`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Array\``)} }`,
`else if (!Array.isArray(${valueAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}.constructor?.name}, expected an instance of Array\``)} }`,
`else if (${valueAlias}.length !== ${schema.schemas.length}) { ${emit(`\`Invalid tuple length \${${valueAlias}.length}, expected ${schema.schemas.length}\``)} }`,
`else { ${schema.schemas.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx, context))).join('\n')} }`
].join('\n');
}
} else if (tag === CHECK) {
const valueAlias = context.unique('v');
const ref = (v)=>`reg[${context.register(v)}]`;
const { check: cond, message } = schema.compile(valueAlias, {
ref
});
if (mode.fast) {
return `const ${valueAlias} = ${valuePath};\nif (${cond}) { ${fail} }`;
}
return `const ${valueAlias} = ${valuePath};\nif (${cond}) { ${emit(message)} }`;
} else {
const { key, schemas } = schema;
const valueAlias = context.unique('v');
const discriminantAlias = context.unique('d');
const keyStr = JSON.stringify(key);
const variants = [];
for (const s of schemas){
if (typeof s !== 'object' || s === null || !(key in s)) {
throw new TypeError(`discriminated: each schema must have the discriminant key "${key}"`);
}
const discriminantValue = s[key];
if (typeof discriminantValue !== 'string' && typeof discriminantValue !== 'number' && typeof discriminantValue !== 'boolean') {
throw new TypeError(`discriminated: discriminant value must be a string, number, or boolean literal`);
}
variants.push({
value: discriminantValue,
schema: s
});
}
const genVariantProps = (s, variantMode)=>{
const obj = s;
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');
};
if (mode.fast) {
const branches = variants.map(({ value, schema: s })=>{
return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${genVariantProps(s, mode)} }`;
});
return [
`const ${valueAlias} = ${valuePath};`,
`if (${valueAlias} === null || ${valueAlias} === undefined || typeof ${valueAlias} !== 'object'${errChk(valueAlias)}) { ${fail} }`,
`const ${discriminantAlias} = ${valueAlias}[${keyStr}];`,
branches.join(' else ') + ` else { ${fail} }`
].join('\n');
} else {
const validValues = variants.map((v)=>JSON.stringify(v.value)).join(', ');
const branches = variants.map(({ value, schema: s })=>{
return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${genVariantProps(s, mode)} }`;
});
return [
`const ${valueAlias} = ${valuePath};`,
`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an object\``)} }`,
`${errBranch(valueAlias)}`,
`else {`,
` const ${discriminantAlias} = ${valueAlias}[${keyStr}];`,
` ${branches.join(' else ')} else { ${emit(`"Invalid discriminant value " + String(${discriminantAlias}) + ", expected one of: ${validValues.replace(/"/g, "'")}"`)} }`,
`}`
].join('\n');
}
}
}
if (typeof schema === 'function') {
const valueAlias = context.unique('v');
const name = schema?.name;
const s = schema;
const primitiveType = s === String ? 'string' : s === Number ? 'number' : s === Boolean ? 'boolean' : s === BigInt ? 'bigint' : s === Symbol ? 'symbol' : null;
if (mode.fast) {
if (primitiveType) {
const checks = [
`typeof ${valueAlias} !== '${primitiveType}'`
];
if (primitiveType === 'number') checks.push(`${valueAlias} !== ${valueAlias}`);
return `const ${valueAlias} = ${valuePath};\nif (${checks.join(' || ')}) { ${fail} }`;
} else if (name === 'Function') {
return `const ${valueAlias} = ${valuePath};\nif (typeof ${valueAlias} !== 'function') { ${fail} }`;
} else {
const isError = schema === Error || schema?.prototype instanceof Error;
const index = context.register(schema);
const registryAlias = context.unique('r');
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} }`;
}
} else {
const code = [
`const ${valueAlias} = ${valuePath};`
];
if (primitiveType) {
const typeMsgs = Object.fromEntries([
'string',
'number',
'boolean',
'bigint',
'symbol',
'undefined',
'object',
'function'
].map((t)=>[
t,
`Invalid type ${t}, expected type ${name}`
]));
const typeMsgIdx = context.register(typeMsgs);
code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`${valueAlias} === null ? "Invalid value null, expected non-nullable" : "Invalid value undefined, expected non-nullable"`)} }`);
code.push(`${errBranch(valueAlias)}`);
code.push(`else if (typeof ${valueAlias} !== '${primitiveType}') { ${emit(`reg[${typeMsgIdx}][typeof ${valueAlias}]`)} }`);
if (primitiveType === 'number') code.push(`else if (${valueAlias} !== ${valueAlias}) { ${emit(`"Invalid value NaN, expected a valid ${name}"`)} }`);
} else if (name === 'Function') {
code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
code.push(`${errBranch(valueAlias)}`);
code.push(`else if (typeof ${valueAlias} !== 'function') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected type Function\``)} }`);
} else {
const isError = schema === Error || schema?.prototype instanceof Error;
const index = context.register(schema);
const registryAlias = context.unique('r');
code.push(`const ${registryAlias} = reg[${index}];`);
code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
if (!isError) code.push(`${errBranch(valueAlias)}`);
code.push(`else if (typeof ${valueAlias} === 'object' && !(${valueAlias} instanceof ${registryAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}?.constructor?.name}, expected an instance of ${name}\``)} }`);
code.push(`else if (typeof ${valueAlias} !== 'object' && ${valueAlias}?.constructor !== ${registryAlias}) { ${emit(`\`Invalid type \${${valueAlias}?.constructor?.name}, expected type ${name}\``)} }`);
code.push(`else if (Number.isNaN(${valueAlias}?.valueOf?.())) { ${emit(`\`Invalid value \${${valueAlias}}, expected a valid ${name}\``)} }`);
}
return code.join('\n');
}
}
if (Array.isArray(schema)) {
const valueAlias = context.unique('v');
if (mode.fast) {
let code = `const ${valueAlias} = ${valuePath};\nif (!Array.isArray(${valueAlias})) { ${fail} }`;
if (schema.length === 1) {
const value = context.unique('val');
const key = context.unique('key');
code += `\nfor (let ${key} = 0; ${key} < ${valueAlias}.length; ${key}++) { const ${value} = ${valueAlias}[${key}]; ${codeGen(schema[0], context, value, mode)} }`;
} else if (schema.length > 1) {
code += `\nif (${valueAlias}.length > ${schema.length}) { ${fail} }`;
code += '\n' + schema.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, mode)).join('\n');
}
return code;
} else {
const arrTypeMsgs = Object.fromEntries([
'string',
'number',
'boolean',
'bigint',
'symbol',
'undefined',
'function'
].map((t)=>[
t,
`Invalid type ${t}, expected an instance of Array`
]));
const arrTypeMsgIdx = context.register(arrTypeMsgs);
const code = [
`const ${valueAlias} = ${valuePath};`,
`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`${valueAlias} === null ? "Invalid value null, expected non-nullable" : "Invalid value undefined, expected non-nullable"`)} }`,
`${errBranch(valueAlias)}`,
`else if (typeof ${valueAlias} !== 'object') { ${emit(`reg[${arrTypeMsgIdx}][typeof ${valueAlias}]`)} }`,
`else if (!Array.isArray(${valueAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}.constructor?.name}, expected an instance of Array\``)} }`
];
if (schema.length > 0) {
const value = context.unique('val');
const key = context.unique('key');
if (schema.length === 1) {
code.push(`else { for (let ${key} = 0; ${key} < ${valueAlias}.length; ${key}++) { const ${value} = ${valueAlias}[${key}]; ${codeGen(schema[0], context, value, childMode(mode, {
dynamic: key
}, context))} } }`);
} else {
code.push(`else if (${valueAlias}.length > ${schema.length}) { ${emit(`\`Invalid tuple length \${${valueAlias}.length}, expected ${schema.length}\``)} }`);
code.push(`else { ${schema.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx, context))).join('\n')} }`);
}
}
return code.join('\n');
}
}
if (typeof schema === 'object' && schema !== null) {
if (schema instanceof RegExp) {
const valueAlias = context.unique('v');
if (mode.fast) {
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined${errChk(valueAlias)} || !${schema.toString()}.test(String(${valueAlias}))) { ${fail} }`;
} else {
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()}\``)} }`;
}
} else {
const valueAlias = context.unique('v');
if (mode.fast) {
const indexed = mode.indexed && !mode.onFail;
const rootFail = indexed ? 'return 0;' : fail;
let code = `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || typeof ${valueAlias} !== 'object'${errChk(valueAlias)}) { ${rootFail} }`;
if ($keys in schema) {
const kAlias = context.unique('k');
code += `\nfor (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, indexed ? {
fast: true,
onFail: 'return 0;'
} : mode)} }`;
}
if ($values in schema) {
const kAlias = context.unique('k');
code += `\nfor (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, indexed ? {
fast: true,
onFail: 'return 0;'
} : mode)} }`;
}
if ($strict in schema && schema[$strict]) {
const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k)=>[
k,
1
])))}]`;
const kAlias = context.unique('k');
code += `\nfor (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) { ${rootFail} } }`;
}
const entries = Object.entries(schema);
code += '\n' + entries.map(([key, s], idx)=>{
const propMode = indexed ? {
fast: true,
onFail: `return ${idx + 1};`
} : mode;
return codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, propMode);
}).join('\n');
if (indexed) code += `\nreturn -1;`;
return code;
} else {
const sv = !mode.fast && mode.startVar;
const code = [
`const ${valueAlias} = ${valuePath};`
];
if (sv) {
const rootCase = [];
if (mode.firstError) {
rootCase.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
rootCase.push(`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`);
rootCase.push(`${errBranch(valueAlias)}`);
} else {
rootCase.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} break; }`);
rootCase.push(`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} break; }`);
if (!context.pure) rootCase.push(`else if (typeof ${valueAlias} === 'object' && ${valueAlias}[err] === true) { ${emit(`\`\${${valueAlias}.message}\``)} break; }`);
}
if ($keys in schema) {
const kAlias = context.unique('k');
rootCase.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, {
dynamic: kAlias
}, context))} }`);
}
if ($values in schema) {
const kAlias = context.unique('k');
rootCase.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, childMode(mode, {
dynamic: kAlias
}, context))} }`);
}
if ($strict in schema && schema[$strict]) {
const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k)=>[
k,
1
])))}]`;
const kAlias = context.unique('k');
const extraAlias = context.unique('ex');
rootCase.push(`const ${extraAlias} = [];`);
rootCase.push(`for (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) ${extraAlias}.push(${kAlias}); }`);
rootCase.push(`if (${extraAlias}.length !== 0) { ${emit(`\`Extra properties: \${${extraAlias}}, are not allowed\``)} }`);
}
code.push(`switch (${sv}) {`);
code.push(`case 0: { ${rootCase.join('\n')} }`);
const entries = Object.entries(schema);
entries.forEach(([key, s], idx)=>{
const propCode = codeGe