UNPKG

ascertain

Version:

0-Deps, simple, fast, for browser and node js object schema validator

930 lines 61.1 kB
"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