UNPKG

telefunc

Version:

Remote functions. Instead of API.

292 lines (291 loc) 10.8 kB
export { shield }; export { shieldIsMissing }; export { shieldApply }; export { shieldToHumandReadable }; import { isPlainObject, unique, isCallable, assert, assertUsage } from '../utils.js'; const shieldKey = '__telefunc_shield'; const isVerifierKey = '__telefunc_isVerifier'; const isVerifierTupleKey = '__telefunc_isVerifierTuple'; const shield = function (arg1, arg2, options) { let telefunction; let telefunctionShield; if (isTelefunction(arg1)) { assertShield(arg2, 'shield(telefunction, telefunctionShield)'); telefunction = arg1; telefunctionShield = arg2; } else if (isTelefunction(arg2)) { assertShield(arg1, 'shield(telefunctionShield, telefunction)'); telefunction = arg2; telefunctionShield = arg1; } else { assertUsage(false, '[shield(arg1, arg2)] Neither `arg1` nor `arg2` is a function, but one should be.'); } installShield(telefunction, telefunctionShield, !!(options === null || options === void 0 ? void 0 : options.__autoGenerated)); return telefunction; }; function assertShield(telefunctionShield, shieldInvocation) { assertUsage(Array.isArray(telefunctionShield) || isVerifierTuple(telefunctionShield), [ `[${shieldInvocation.replace('telefunctionShield', 'args')}]`, '`args` should be an array.', `Example of correct usage: \`${shieldInvocation.replace('telefunctionShield', '[shield.type.string]')}\`.`, `Example of wrong usage: \`${shieldInvocation.replace('telefunctionShield', 'shield.type.string')}\`.`, ].join(' ')); } function isTelefunction(thing) { return isCallable(thing) && !isVerifier(thing); } function installShield(telefunction, telefunctionShield, generated) { const installedShield = telefunction[shieldKey]; if (installedShield && generated) { // another shield is already installed, so not installing generated shield return; } ; telefunction[shieldKey] = telefunctionShield; } function shieldIsMissing(telefunction) { const telefunctionShield = getTelefunctionShield(telefunction); return telefunctionShield === null; } function shieldApply(telefunction, args) { const telefunctionShield = getTelefunctionShield(telefunction); assert(telefunctionShield !== null); return verifyOuter(telefunctionShield, args); } function getTelefunctionShield(telefunction) { return telefunction[shieldKey] || null; } function shieldToHumandReadable(telefunctionShield) { return StringBetter(telefunctionShield); } // Like `String()` but with support for objects and arrays function StringBetter(thing) { if (isPlainObject(thing)) { let str = ''; const entries = Object.entries(thing); entries.forEach(([key, val], i) => { str += `${String(key)}:${StringBetter(val)}`; const isLast = i === entries.length - 1; if (!isLast) { str += ','; } }); str = `{${str}}`; return str; } if (Array.isArray(thing)) { return `[${thing.map((el) => StringBetter(el)).join(',')}]`; } return String(thing); } function verifyOuter(verifier, args) { assert(Array.isArray(args)); if (Array.isArray(verifier)) { verifier = shield.type.tuple(...verifier); assert(isVerifierTuple(verifier)); } if (isVerifierTuple(verifier)) { return verifyRecursive(verifier, args, '[root]'); } assert(false); } function verifyRecursive(verifier, arg, breadcrumbs) { assert(breadcrumbs.startsWith('[root]')); if (isVerifier(verifier)) { return verifier(arg, breadcrumbs); } if (isPlainObject(verifier)) { const obj = verifier; return verifyObject(obj, arg, breadcrumbs); } if (verifier === undefined) { return shield.type.const(undefined)(arg, breadcrumbs); } const errorPrefix = `[shield()] Bad shield definition: ${breadcrumbs}`; const errorSuffix = `See https://telefunc.com/shield`; assertUsage(!Array.isArray(verifier), errorPrefix + ' is a plain JavaScript array which is forbidden: use `shield.type.tuple()` instead of `[]`. ' + errorSuffix); assertUsage(false, `${errorPrefix} is \`${getTypeName(verifier)}\` which is forbidden. Always use \`shield.type[x]\` or a plain JavaScript Object. ${errorSuffix}`); } function verifyObject(obj, arg, breadcrumbs) { if (!isPlainObject(arg)) { return errorMessage(breadcrumbs, getTypeName(arg), 'object'); } for (const key of unique([...Object.keys(obj), ...Object.keys(arg)])) { const res = verifyRecursive(obj[key], arg[key], `${breadcrumbs} > [object: value of key \`${key}\`]`); if (res !== true) { return res; } } return true; } const type = (() => { const or = (...elements) => { const verifier = (input, breadcrumbs) => { const typeTargets = elements.map((el) => verifyRecursive(el, input, `${breadcrumbs}`)); if (typeTargets.includes(true)) { return true; } return `${breadcrumbs} is of wrong type`; }; markVerifier(verifier); verifier.toString = () => elements.map((el) => StringBetter(el)).join('|'); return verifier; }; const tuple = (...elements) => { const verifier = (input, breadcrumbs) => { if (!Array.isArray(input)) { return errorMessage(breadcrumbs, getTypeName(input), 'tuple'); } const errorMessages = [...Array(Math.max(input.length, elements.length)).keys()] .map((i) => verifyRecursive(i > elements.length - 1 ? type.const(undefined) : elements[i], input[i], `${breadcrumbs} > [tuple: element ${i}]`)) .filter((res) => res !== true); if (errorMessages.length === 0) { return true; } return errorMessages[0]; }; markVerifier(verifier); markVerifierTuple(verifier); verifier.toString = () => StringBetter(elements); return verifier; }; const array = (arrayType) => { const verifier = (input, breadcrumbs) => { if (!Array.isArray(input)) { return errorMessage(breadcrumbs, getTypeName(input), 'array'); } const errorMessages = input .map((_, i) => verifyRecursive(arrayType, input[i], `${breadcrumbs} > [array element ${i}]`)) .filter((res) => res !== true); if (errorMessages.length === 0) { return true; } return errorMessages[0]; }; markVerifier(verifier); verifier.toString = () => { let s = StringBetter(arrayType); if (s.includes(',')) { s = `(${s})`; } s = `${s}[]`; return s; }; return verifier; }; const object = (objectType) => { const verifier = (input, breadcrumbs) => { if (typeof input !== 'object' || input === null || input.constructor !== Object) { return errorMessage(breadcrumbs, getTypeName(input), 'object'); } const errorMessages = Object.entries(input) .map(([key, val]) => verifyRecursive(objectType, val, `${breadcrumbs} > [object: value of key \`${key}\`]`)) .filter((res) => res !== true); if (errorMessages.length === 0) { return true; } return errorMessages[0]; }; markVerifier(verifier); verifier.toString = () => { let s = StringBetter(objectType); if (s.includes(',')) { s = `(${s})`; } s = `Record<string, ${s}}`; return s; }; return verifier; }; const const_ = (val) => { const verifier = (input, breadcrumbs) => input === val ? true : errorMessage(breadcrumbs, String(input), String(val)); markVerifier(verifier); verifier.toString = () => StringBetter(val); return verifier; }; const string = (() => { const verifier = (input, breadcrumbs) => typeof input === 'string' ? true : errorMessage(breadcrumbs, getTypeName(input), 'string'); markVerifier(verifier); verifier.toString = () => 'string'; return verifier; })(); const number = (() => { const verifier = (input, breadcrumbs) => typeof input === 'number' ? true : errorMessage(breadcrumbs, getTypeName(input), 'number'); markVerifier(verifier); verifier.toString = () => 'number'; return verifier; })(); const boolean = (() => { const verifier = (input, breadcrumbs) => input === true || input === false ? true : errorMessage(breadcrumbs, getTypeName(input), 'boolean'); markVerifier(verifier); verifier.toString = () => 'boolean'; return verifier; })(); const date = (() => { const verifier = (input, breadcrumbs) => typeof input === 'object' && input !== null && input.constructor === Date ? true : errorMessage(breadcrumbs, getTypeName(input), 'date'); markVerifier(verifier); verifier.toString = () => 'date'; return verifier; })(); const any = (() => { const verifier = () => true; markVerifier(verifier); verifier.toString = () => 'date'; return verifier; })(); return { string, number, boolean, date, array, object, or, tuple, const: const_, optional: (param) => or(param, const_(undefined)), nullable: (param) => or(param, const_(null)), any, }; })(); shield.type = type; function errorMessage(breadcrumbs, is, should) { return `${breadcrumbs} is \`${is}\` but should be \`${should}\`.`; } function isVerifier(thing) { return thing && thing[isVerifierKey] === true; } function markVerifier(verifier) { assert(isCallable(verifier)); verifier[isVerifierKey] = true; } function isVerifierTuple(thing) { return isVerifier(thing) && thing[isVerifierTupleKey] === true; } function markVerifierTuple(verifier) { verifier[isVerifierTupleKey] = true; } function getTypeName(thing) { if (thing === null) { return 'null'; } if (thing === undefined) { return 'undefined'; } if (typeof thing === 'object') { assert(thing !== null); if (thing.constructor === Date) { return 'date'; } if (Array.isArray(thing)) { return 'array'; } } return typeof thing; }