UNPKG

meteor-type-validation

Version:

A lightweight set of TypeScript utilities to add proper type inference and validation for your Meteor publications and methods

754 lines (717 loc) 22.4 kB
// src/globals/Meteor.ts var MeteorGlobal = Meteor; // src/MeteorTypeValidation.ts import { performance } from "node:perf_hooks"; import { parse, ValiError as ValiError2 } from "valibot"; // node_modules/lodash-es/_freeGlobal.js var freeGlobal = typeof global == "object" && global && global.Object === Object && global; var freeGlobal_default = freeGlobal; // node_modules/lodash-es/_root.js var freeSelf = typeof self == "object" && self && self.Object === Object && self; var root = freeGlobal_default || freeSelf || Function("return this")(); var root_default = root; // node_modules/lodash-es/_Symbol.js var Symbol = root_default.Symbol; var Symbol_default = Symbol; // node_modules/lodash-es/_getRawTag.js var objectProto = Object.prototype; var hasOwnProperty = objectProto.hasOwnProperty; var nativeObjectToString = objectProto.toString; var symToStringTag = Symbol_default ? Symbol_default.toStringTag : void 0; function getRawTag(value) { var isOwn = hasOwnProperty.call(value, symToStringTag), tag = value[symToStringTag]; try { value[symToStringTag] = void 0; var unmasked = true; } catch (e) { } var result = nativeObjectToString.call(value); if (unmasked) { if (isOwn) { value[symToStringTag] = tag; } else { delete value[symToStringTag]; } } return result; } var getRawTag_default = getRawTag; // node_modules/lodash-es/_objectToString.js var objectProto2 = Object.prototype; var nativeObjectToString2 = objectProto2.toString; function objectToString(value) { return nativeObjectToString2.call(value); } var objectToString_default = objectToString; // node_modules/lodash-es/_baseGetTag.js var nullTag = "[object Null]"; var undefinedTag = "[object Undefined]"; var symToStringTag2 = Symbol_default ? Symbol_default.toStringTag : void 0; function baseGetTag(value) { if (value == null) { return value === void 0 ? undefinedTag : nullTag; } return symToStringTag2 && symToStringTag2 in Object(value) ? getRawTag_default(value) : objectToString_default(value); } var baseGetTag_default = baseGetTag; // node_modules/lodash-es/isObjectLike.js function isObjectLike(value) { return value != null && typeof value == "object"; } var isObjectLike_default = isObjectLike; // node_modules/lodash-es/isSymbol.js var symbolTag = "[object Symbol]"; function isSymbol(value) { return typeof value == "symbol" || isObjectLike_default(value) && baseGetTag_default(value) == symbolTag; } var isSymbol_default = isSymbol; // node_modules/lodash-es/_arrayMap.js function arrayMap(array, iteratee) { var index = -1, length = array == null ? 0 : array.length, result = Array(length); while (++index < length) { result[index] = iteratee(array[index], index, array); } return result; } var arrayMap_default = arrayMap; // node_modules/lodash-es/isArray.js var isArray = Array.isArray; var isArray_default = isArray; // node_modules/lodash-es/_baseToString.js var INFINITY = 1 / 0; var symbolProto = Symbol_default ? Symbol_default.prototype : void 0; var symbolToString = symbolProto ? symbolProto.toString : void 0; function baseToString(value) { if (typeof value == "string") { return value; } if (isArray_default(value)) { return arrayMap_default(value, baseToString) + ""; } if (isSymbol_default(value)) { return symbolToString ? symbolToString.call(value) : ""; } var result = value + ""; return result == "0" && 1 / value == -INFINITY ? "-0" : result; } var baseToString_default = baseToString; // node_modules/lodash-es/toString.js function toString(value) { return value == null ? "" : baseToString_default(value); } var toString_default = toString; // node_modules/lodash-es/_baseSlice.js function baseSlice(array, start, end) { var index = -1, length = array.length; if (start < 0) { start = -start > length ? 0 : length + start; } end = end > length ? length : end; if (end < 0) { end += length; } length = start > end ? 0 : end - start >>> 0; start >>>= 0; var result = Array(length); while (++index < length) { result[index] = array[index + start]; } return result; } var baseSlice_default = baseSlice; // node_modules/lodash-es/_castSlice.js function castSlice(array, start, end) { var length = array.length; end = end === void 0 ? length : end; return !start && end >= length ? array : baseSlice_default(array, start, end); } var castSlice_default = castSlice; // node_modules/lodash-es/_hasUnicode.js var rsAstralRange = "\\ud800-\\udfff"; var rsComboMarksRange = "\\u0300-\\u036f"; var reComboHalfMarksRange = "\\ufe20-\\ufe2f"; var rsComboSymbolsRange = "\\u20d0-\\u20ff"; var rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange; var rsVarRange = "\\ufe0e\\ufe0f"; var rsZWJ = "\\u200d"; var reHasUnicode = RegExp("[" + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + "]"); function hasUnicode(string) { return reHasUnicode.test(string); } var hasUnicode_default = hasUnicode; // node_modules/lodash-es/_asciiToArray.js function asciiToArray(string) { return string.split(""); } var asciiToArray_default = asciiToArray; // node_modules/lodash-es/_unicodeToArray.js var rsAstralRange2 = "\\ud800-\\udfff"; var rsComboMarksRange2 = "\\u0300-\\u036f"; var reComboHalfMarksRange2 = "\\ufe20-\\ufe2f"; var rsComboSymbolsRange2 = "\\u20d0-\\u20ff"; var rsComboRange2 = rsComboMarksRange2 + reComboHalfMarksRange2 + rsComboSymbolsRange2; var rsVarRange2 = "\\ufe0e\\ufe0f"; var rsAstral = "[" + rsAstralRange2 + "]"; var rsCombo = "[" + rsComboRange2 + "]"; var rsFitz = "\\ud83c[\\udffb-\\udfff]"; var rsModifier = "(?:" + rsCombo + "|" + rsFitz + ")"; var rsNonAstral = "[^" + rsAstralRange2 + "]"; var rsRegional = "(?:\\ud83c[\\udde6-\\uddff]){2}"; var rsSurrPair = "[\\ud800-\\udbff][\\udc00-\\udfff]"; var rsZWJ2 = "\\u200d"; var reOptMod = rsModifier + "?"; var rsOptVar = "[" + rsVarRange2 + "]?"; var rsOptJoin = "(?:" + rsZWJ2 + "(?:" + [rsNonAstral, rsRegional, rsSurrPair].join("|") + ")" + rsOptVar + reOptMod + ")*"; var rsSeq = rsOptVar + reOptMod + rsOptJoin; var rsSymbol = "(?:" + [rsNonAstral + rsCombo + "?", rsCombo, rsRegional, rsSurrPair, rsAstral].join("|") + ")"; var reUnicode = RegExp(rsFitz + "(?=" + rsFitz + ")|" + rsSymbol + rsSeq, "g"); function unicodeToArray(string) { return string.match(reUnicode) || []; } var unicodeToArray_default = unicodeToArray; // node_modules/lodash-es/_stringToArray.js function stringToArray(string) { return hasUnicode_default(string) ? unicodeToArray_default(string) : asciiToArray_default(string); } var stringToArray_default = stringToArray; // node_modules/lodash-es/_createCaseFirst.js function createCaseFirst(methodName) { return function(string) { string = toString_default(string); var strSymbols = hasUnicode_default(string) ? stringToArray_default(string) : void 0; var chr = strSymbols ? strSymbols[0] : string.charAt(0); var trailing = strSymbols ? castSlice_default(strSymbols, 1).join("") : string.slice(1); return chr[methodName]() + trailing; }; } var createCaseFirst_default = createCaseFirst; // node_modules/lodash-es/upperFirst.js var upperFirst = createCaseFirst_default("toUpperCase"); var upperFirst_default = upperFirst; // node_modules/lodash-es/_arrayReduce.js function arrayReduce(array, iteratee, accumulator, initAccum) { var index = -1, length = array == null ? 0 : array.length; if (initAccum && length) { accumulator = array[++index]; } while (++index < length) { accumulator = iteratee(accumulator, array[index], index, array); } return accumulator; } var arrayReduce_default = arrayReduce; // node_modules/lodash-es/_basePropertyOf.js function basePropertyOf(object) { return function(key) { return object == null ? void 0 : object[key]; }; } var basePropertyOf_default = basePropertyOf; // node_modules/lodash-es/_deburrLetter.js var deburredLetters = { // Latin-1 Supplement block. "\xC0": "A", "\xC1": "A", "\xC2": "A", "\xC3": "A", "\xC4": "A", "\xC5": "A", "\xE0": "a", "\xE1": "a", "\xE2": "a", "\xE3": "a", "\xE4": "a", "\xE5": "a", "\xC7": "C", "\xE7": "c", "\xD0": "D", "\xF0": "d", "\xC8": "E", "\xC9": "E", "\xCA": "E", "\xCB": "E", "\xE8": "e", "\xE9": "e", "\xEA": "e", "\xEB": "e", "\xCC": "I", "\xCD": "I", "\xCE": "I", "\xCF": "I", "\xEC": "i", "\xED": "i", "\xEE": "i", "\xEF": "i", "\xD1": "N", "\xF1": "n", "\xD2": "O", "\xD3": "O", "\xD4": "O", "\xD5": "O", "\xD6": "O", "\xD8": "O", "\xF2": "o", "\xF3": "o", "\xF4": "o", "\xF5": "o", "\xF6": "o", "\xF8": "o", "\xD9": "U", "\xDA": "U", "\xDB": "U", "\xDC": "U", "\xF9": "u", "\xFA": "u", "\xFB": "u", "\xFC": "u", "\xDD": "Y", "\xFD": "y", "\xFF": "y", "\xC6": "Ae", "\xE6": "ae", "\xDE": "Th", "\xFE": "th", "\xDF": "ss", // Latin Extended-A block. "\u0100": "A", "\u0102": "A", "\u0104": "A", "\u0101": "a", "\u0103": "a", "\u0105": "a", "\u0106": "C", "\u0108": "C", "\u010A": "C", "\u010C": "C", "\u0107": "c", "\u0109": "c", "\u010B": "c", "\u010D": "c", "\u010E": "D", "\u0110": "D", "\u010F": "d", "\u0111": "d", "\u0112": "E", "\u0114": "E", "\u0116": "E", "\u0118": "E", "\u011A": "E", "\u0113": "e", "\u0115": "e", "\u0117": "e", "\u0119": "e", "\u011B": "e", "\u011C": "G", "\u011E": "G", "\u0120": "G", "\u0122": "G", "\u011D": "g", "\u011F": "g", "\u0121": "g", "\u0123": "g", "\u0124": "H", "\u0126": "H", "\u0125": "h", "\u0127": "h", "\u0128": "I", "\u012A": "I", "\u012C": "I", "\u012E": "I", "\u0130": "I", "\u0129": "i", "\u012B": "i", "\u012D": "i", "\u012F": "i", "\u0131": "i", "\u0134": "J", "\u0135": "j", "\u0136": "K", "\u0137": "k", "\u0138": "k", "\u0139": "L", "\u013B": "L", "\u013D": "L", "\u013F": "L", "\u0141": "L", "\u013A": "l", "\u013C": "l", "\u013E": "l", "\u0140": "l", "\u0142": "l", "\u0143": "N", "\u0145": "N", "\u0147": "N", "\u014A": "N", "\u0144": "n", "\u0146": "n", "\u0148": "n", "\u014B": "n", "\u014C": "O", "\u014E": "O", "\u0150": "O", "\u014D": "o", "\u014F": "o", "\u0151": "o", "\u0154": "R", "\u0156": "R", "\u0158": "R", "\u0155": "r", "\u0157": "r", "\u0159": "r", "\u015A": "S", "\u015C": "S", "\u015E": "S", "\u0160": "S", "\u015B": "s", "\u015D": "s", "\u015F": "s", "\u0161": "s", "\u0162": "T", "\u0164": "T", "\u0166": "T", "\u0163": "t", "\u0165": "t", "\u0167": "t", "\u0168": "U", "\u016A": "U", "\u016C": "U", "\u016E": "U", "\u0170": "U", "\u0172": "U", "\u0169": "u", "\u016B": "u", "\u016D": "u", "\u016F": "u", "\u0171": "u", "\u0173": "u", "\u0174": "W", "\u0175": "w", "\u0176": "Y", "\u0177": "y", "\u0178": "Y", "\u0179": "Z", "\u017B": "Z", "\u017D": "Z", "\u017A": "z", "\u017C": "z", "\u017E": "z", "\u0132": "IJ", "\u0133": "ij", "\u0152": "Oe", "\u0153": "oe", "\u0149": "'n", "\u017F": "s" }; var deburrLetter = basePropertyOf_default(deburredLetters); var deburrLetter_default = deburrLetter; // node_modules/lodash-es/deburr.js var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; var rsComboMarksRange3 = "\\u0300-\\u036f"; var reComboHalfMarksRange3 = "\\ufe20-\\ufe2f"; var rsComboSymbolsRange3 = "\\u20d0-\\u20ff"; var rsComboRange3 = rsComboMarksRange3 + reComboHalfMarksRange3 + rsComboSymbolsRange3; var rsCombo2 = "[" + rsComboRange3 + "]"; var reComboMark = RegExp(rsCombo2, "g"); function deburr(string) { string = toString_default(string); return string && string.replace(reLatin, deburrLetter_default).replace(reComboMark, ""); } var deburr_default = deburr; // node_modules/lodash-es/_asciiWords.js var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g; function asciiWords(string) { return string.match(reAsciiWord) || []; } var asciiWords_default = asciiWords; // node_modules/lodash-es/_hasUnicodeWord.js var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/; function hasUnicodeWord(string) { return reHasUnicodeWord.test(string); } var hasUnicodeWord_default = hasUnicodeWord; // node_modules/lodash-es/_unicodeWords.js var rsAstralRange3 = "\\ud800-\\udfff"; var rsComboMarksRange4 = "\\u0300-\\u036f"; var reComboHalfMarksRange4 = "\\ufe20-\\ufe2f"; var rsComboSymbolsRange4 = "\\u20d0-\\u20ff"; var rsComboRange4 = rsComboMarksRange4 + reComboHalfMarksRange4 + rsComboSymbolsRange4; var rsDingbatRange = "\\u2700-\\u27bf"; var rsLowerRange = "a-z\\xdf-\\xf6\\xf8-\\xff"; var rsMathOpRange = "\\xac\\xb1\\xd7\\xf7"; var rsNonCharRange = "\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf"; var rsPunctuationRange = "\\u2000-\\u206f"; var rsSpaceRange = " \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000"; var rsUpperRange = "A-Z\\xc0-\\xd6\\xd8-\\xde"; var rsVarRange3 = "\\ufe0e\\ufe0f"; var rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange; var rsApos = "['\u2019]"; var rsBreak = "[" + rsBreakRange + "]"; var rsCombo3 = "[" + rsComboRange4 + "]"; var rsDigits = "\\d+"; var rsDingbat = "[" + rsDingbatRange + "]"; var rsLower = "[" + rsLowerRange + "]"; var rsMisc = "[^" + rsAstralRange3 + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + "]"; var rsFitz2 = "\\ud83c[\\udffb-\\udfff]"; var rsModifier2 = "(?:" + rsCombo3 + "|" + rsFitz2 + ")"; var rsNonAstral2 = "[^" + rsAstralRange3 + "]"; var rsRegional2 = "(?:\\ud83c[\\udde6-\\uddff]){2}"; var rsSurrPair2 = "[\\ud800-\\udbff][\\udc00-\\udfff]"; var rsUpper = "[" + rsUpperRange + "]"; var rsZWJ3 = "\\u200d"; var rsMiscLower = "(?:" + rsLower + "|" + rsMisc + ")"; var rsMiscUpper = "(?:" + rsUpper + "|" + rsMisc + ")"; var rsOptContrLower = "(?:" + rsApos + "(?:d|ll|m|re|s|t|ve))?"; var rsOptContrUpper = "(?:" + rsApos + "(?:D|LL|M|RE|S|T|VE))?"; var reOptMod2 = rsModifier2 + "?"; var rsOptVar2 = "[" + rsVarRange3 + "]?"; var rsOptJoin2 = "(?:" + rsZWJ3 + "(?:" + [rsNonAstral2, rsRegional2, rsSurrPair2].join("|") + ")" + rsOptVar2 + reOptMod2 + ")*"; var rsOrdLower = "\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])"; var rsOrdUpper = "\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])"; var rsSeq2 = rsOptVar2 + reOptMod2 + rsOptJoin2; var rsEmoji = "(?:" + [rsDingbat, rsRegional2, rsSurrPair2].join("|") + ")" + rsSeq2; var reUnicodeWord = RegExp([ rsUpper + "?" + rsLower + "+" + rsOptContrLower + "(?=" + [rsBreak, rsUpper, "$"].join("|") + ")", rsMiscUpper + "+" + rsOptContrUpper + "(?=" + [rsBreak, rsUpper + rsMiscLower, "$"].join("|") + ")", rsUpper + "?" + rsMiscLower + "+" + rsOptContrLower, rsUpper + "+" + rsOptContrUpper, rsOrdUpper, rsOrdLower, rsDigits, rsEmoji ].join("|"), "g"); function unicodeWords(string) { return string.match(reUnicodeWord) || []; } var unicodeWords_default = unicodeWords; // node_modules/lodash-es/words.js function words(string, pattern, guard) { string = toString_default(string); pattern = guard ? void 0 : pattern; if (pattern === void 0) { return hasUnicodeWord_default(string) ? unicodeWords_default(string) : asciiWords_default(string); } return string.match(pattern) || []; } var words_default = words; // node_modules/lodash-es/_createCompounder.js var rsApos2 = "['\u2019]"; var reApos = RegExp(rsApos2, "g"); function createCompounder(callback) { return function(string) { return arrayReduce_default(words_default(deburr_default(string).replace(reApos, "")), callback, ""); }; } var createCompounder_default = createCompounder; // node_modules/lodash-es/startCase.js var startCase = createCompounder_default(function(result, word, index) { return result + (index ? " " : "") + upperFirst_default(word); }); var startCase_default = startCase; // src/Errors.ts import { flatten } from "valibot"; function formatValibotError(error) { const errors = []; const { nested, root: root2 } = flatten(error.issues); Object.entries(nested).forEach(([key, messages]) => { messages?.forEach((message) => { errors.push({ message: message.replace("Invalid type: Expected", `Expected ${startCase_default(key)} to be`), reason: message, key }); }); }); root2?.forEach((message) => { errors.push({ message, key: "[root]" }); }); return new MeteorError("ValiError", error.message, { errors, issues: error.issues }); } var MeteorError = class extends MeteorGlobal.Error { constructor(code, message, details) { super( code, message, // @ts-expect-error @types/meteor invalidly sets a 'string' type here. details ); } }; // src/Logger.ts import { pino } from "pino"; var Logger = pino({}); // src/MeteorTypeValidation.ts var MeteorTypeValidation = class { constructor(options = {}) { this.options = options; this.setupDefaultLogger(); } setupDefaultLogger() { if (this.options.createLogger) return; if (this.options.createLogger === false) return; this.options.createLogger = ({ type, name, context }) => { return Logger.child({ [type]: { name }, user: { id: context.userId } }, { msgPrefix: `[${type}] [${name}] ` }); }; } defineMethods(methods) { return methods; } definePublications(publications) { return publications; } exposeMethods(methods) { const methodMap = Object.entries(methods).map(([name, definition]) => { definition.rateLimiters?.forEach((rule) => this.loadRateLimit({ rule, name, type: "method" })); return [name, this.wrapResource({ definition, name })]; }); const wrappedMethods = Object.fromEntries(methodMap); MeteorGlobal.methods(wrappedMethods); return wrappedMethods; } exposePublications(publications) { const publicationMap = Object.entries(publications).map(([name, definition]) => { const wrappedPublication = this.wrapResource({ name, definition }); MeteorGlobal.publish(name, wrappedPublication); definition.rateLimiters?.forEach((rule) => this.loadRateLimit({ rule, name, type: "publication" })); return [name, wrappedPublication]; }); return Object.fromEntries(publicationMap); } loadRateLimit({ rule, type, name }) { DDPRateLimiter.addRule({ ...rule, name, type }, rule.requestCount ?? 10, rule.intervalMs ?? 1e3); } extendContext({ type, context, name }) { const startTime = performance.now(); const logger = this.options.createLogger?.({ type, context, name }); const addedContext = this.options.extendContext?.({ type, context, name }); logger?.debug("Incoming request"); Object.assign(context, { type, name, logger, startTime }); if (!(addedContext instanceof Promise)) { return Object.assign(context, addedContext); } return addedContext.then((addedContext2) => { return Object.assign(context, addedContext2); }); } async validateRequest({ context, definition, params }) { const validatedParams = definition.schema.map((schema, index) => { return parse(schema, params[index]); }); if (params.length > validatedParams.length) { throw new MeteorGlobal.Error( "too_many_parameters", `You're only allowed to supply ${definition.schema.length} parameters` ); } for (const guard of definition.guards) { await new guard(context, validatedParams).validate(); } return { validatedParams }; } withErrorHandler(method) { const customErrorHandler = this.options.errorHandler?.bind(this); return async function(...params) { try { const result = await method.apply(this, params); this.logger?.debug(`Request completed in ${(performance.now() - this.startTime).toLocaleString()}ms`); return result; } catch (error) { if (customErrorHandler) { return customErrorHandler(error); } let formattedError = error instanceof Error ? error : new Error(`Unexpected internal server error: ${error}`); if (error instanceof ValiError2) { formattedError = formatValibotError(error); } this.logger?.error({ error: formattedError }, `Request failed: ${formattedError.message}`); throw formattedError; } }; } wrapResource({ definition, name }) { const api = this; const { run, type } = this.parseDefinition(definition); const handle = async function(...params) { const context = await api.extendContext({ type, name, context: this }); const { validatedParams } = await api.validateRequest({ context, definition, params }); return run.apply(context, validatedParams); }; return this.withErrorHandler(handle); } parseDefinition(definition) { if ("publish" in definition) { return { type: "publication", run: definition.publish }; } return { type: "method", run: definition.method }; } }; // src/Definitions.ts var defaultApi = new MeteorTypeValidation(); var defineMethods = defaultApi.defineMethods.bind(defaultApi); var definePublications = defaultApi.definePublications.bind(defaultApi); var exposeMethods = defaultApi.exposeMethods.bind(defaultApi); var exposePublications = defaultApi.exposePublications.bind(defaultApi); // src/Guard.ts var Guard = class { constructor(context, params) { this.context = context; this.params = params; } }; export { Guard, MeteorTypeValidation, defineMethods, definePublications, exposeMethods, exposePublications }; /*! Bundled license information: lodash-es/lodash.js: (** * @license * Lodash (Custom Build) <https://lodash.com/> * Build: `lodash modularize exports="es" -o ./` * Copyright OpenJS Foundation and other contributors <https://openjsf.org/> * Released under MIT license <https://lodash.com/license> * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE> * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors *) */ //# sourceMappingURL=index.mjs.map