UNPKG

@palmares/schemas

Version:

This defines a default schema definition for validation of data, it abstract popular schema validation libraries like zod, yup, valibot and others"

478 lines (471 loc) 21.7 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/utils.ts var utils_exports = {}; __export(utils_exports, { WithFallback: () => WithFallback, defaultTransform: () => defaultTransform, defaultTransformToAdapter: () => defaultTransformToAdapter, formatErrorFromParseMethod: () => formatErrorFromParseMethod, parseErrorsFactory: () => parseErrorsFactory, shouldRunDataOnComplexSchemas: () => shouldRunDataOnComplexSchemas, transformSchemaAndCheckIfShouldBeHandledByFallbackOnComplexSchemas: () => transformSchemaAndCheckIfShouldBeHandledByFallbackOnComplexSchemas, withFallbackFactory: () => withFallbackFactory }); module.exports = __toCommonJS(utils_exports); // src/exceptions.ts var NoAdapterFoundError = class extends Error { static { __name(this, "NoAdapterFoundError"); } constructor() { super("No adapter found, please define an adapter using setDefaultAdapter() before using any schema."); } }; // src/conf.ts function getDefaultAdapter() { if (!globalThis.$PSchemasAdapter) throw new NoAdapterFoundError(); return globalThis.$PSchemasAdapter; } __name(getDefaultAdapter, "getDefaultAdapter"); // src/validators/schema.ts function optional(args) { return { name: "optional", type: "high", // eslint-disable-next-line ts/require-await callback: /* @__PURE__ */ __name(async (value, path) => { if (value === void 0) { if (args.allow === true) return { parsed: value, errors: [], preventChildValidation: true }; return { parsed: value, errors: [ { isValid: false, message: args.message, code: "required", // eslint-disable-next-line ts/no-unnecessary-condition path: path || [] } ], preventChildValidation: true }; } return { parsed: value, errors: [], preventChildValidation: false }; }, "callback") }; } __name(optional, "optional"); function nullable(args) { return { name: "nullable", type: "high", // eslint-disable-next-line ts/require-await callback: /* @__PURE__ */ __name(async (value, path) => { if (value === null) { if (args.allow === true) return { parsed: value, errors: [], preventChildValidation: true }; return { parsed: value, errors: [ { isValid: false, message: args.message, code: "null", // eslint-disable-next-line ts/no-unnecessary-condition path: path || [] } ], preventChildValidation: true }; } return { parsed: value, errors: [], preventChildValidation: false }; }, "callback") }; } __name(nullable, "nullable"); function checkType(args) { return { name: "checkType", type: "medium", // eslint-disable-next-line ts/require-await callback: /* @__PURE__ */ __name(async (value, path) => { if (args.check(value)) return { parsed: value, errors: [], preventChildValidation: false }; return { parsed: value, errors: [ { isValid: false, message: args.message, code: "invalid_type", // eslint-disable-next-line ts/no-unnecessary-condition path: path || [] } ], preventChildValidation: true }; }, "callback") }; } __name(checkType, "checkType"); // src/validators/utils.ts var priorityByType = { low: 0, medium: 1, high: 2 }; var typeByPriority = Object.entries(priorityByType).reduce((acc, [key, value]) => { acc[value] = key; return acc; }, {}); var Validator = class _Validator { static { __name(this, "Validator"); } $$type = "$PValidator"; child; parent; fallbackNamesAdded = /* @__PURE__ */ new Set(); priority; fallbacks = []; constructor(type) { this.fallbackNamesAdded = /* @__PURE__ */ new Set(); this.fallbacks = []; this.priority = priorityByType[type]; } /** * We create all of the validators on the schema in order, i actually didn't want to go on that route but i * found it easier to do so. * * The logic here is simple, if it's not the same priority we will walk on the linked list until we find * a validator that matches the priority we are expecting. If we can't walk anymore, we create the next * priority validator and append it to the linked list. Be aware that it's a double linked list, so we * can walk both ways, from the end to the start and from the start to the end. * So you don't really need to start from the root, the linked list can start from anywhere and it will * find it's way through. * * I know there are better ways to do this instead of walking through the linked list, but like i explained * before, this is enough for now. * * If the priority is higher than the current priority saved on the schema, we should substitute the * rootValidator on the schema with the new one. * * @param schema - The schema that we are working on right now, all fallbacks are tied to that specific schema. * @param type - The type of the fallback that we are adding. * @param fallback - The fallback function that we are adding. * @param childOrParent - If we are adding a fallback to the child or to the parent. * @param options - The options that we are passing to the fallback. */ checkAppendOrCreate(schema, type, fallbackName, fallback, childOrParent, options) { const schemaWithProtected = schema; if (this[childOrParent]) this[childOrParent].addFallback(schemaWithProtected, type, fallbackName, fallback, options); else { const nextPriority = childOrParent === "child" ? this.priority - 1 : this.priority + 1; if (Object.keys(typeByPriority).includes(String(nextPriority))) { const nextType = typeByPriority[nextPriority]; const validatorInstance = new _Validator(nextType); this[childOrParent] = validatorInstance; this[childOrParent][childOrParent === "parent" ? "child" : "parent"] = this; this[childOrParent].addFallback(schemaWithProtected, type, fallbackName, fallback, options); if (nextPriority > schemaWithProtected.__rootFallbacksValidator.priority) schemaWithProtected.__rootFallbacksValidator = validatorInstance; } } } addFallback(schema, type, fallbackName, fallback, options) { if (this.fallbackNamesAdded.has(fallbackName) && options?.removeCurrent !== true) return; this.fallbackNamesAdded.add(fallbackName); const priority = priorityByType[type]; if (this.priority === priority) { if (typeof options?.at === "number") this.fallbacks.splice(options.at, options.removeCurrent === true ? 1 : 0, fallback); else this.fallbacks.push(fallback); } else if (priority > this.priority) this.checkAppendOrCreate(schema, type, fallbackName, fallback, "parent", options); else if (priority < this.priority) this.checkAppendOrCreate(schema, type, fallbackName, fallback, "child", options); } /** * Validates the value against all of the fallbacks, the fallbacks are executed in order, from the highest * priority to the lowest priority. A validator can stop the execution of the other validators if it feels * like so. Like on the example of a value being null or undefined. * * @param errorsAsHashedSet - This is a set that contains all of the errors that we already found, this is * used to avoid duplicated errors. * @param path - The path that we are validating right now. * @param parseResult - The result of the parsing, it contains the parsed value and the errors that we found. * @param options - The options that we are passing to the fallback. */ async validate(errorsAsHashedSet, path, parseResult, options) { let doesItShouldPreventChildValidation = false; for (const fallback of this.fallbacks) { const { parsed, errors, preventChildValidation } = await fallback(parseResult.parsed, path, options); parseResult.parsed = parsed; for (const error of errors) { if (error.isValid === false) { const sortedError = Object.fromEntries(Object.entries(error).sort(([a], [b]) => a.localeCompare(b))); const hashedError = JSON.stringify(sortedError); if (errorsAsHashedSet.has(hashedError)) continue; errorsAsHashedSet.add(hashedError); if (!Array.isArray(parseResult.errors)) parseResult.errors = []; parseResult.errors.push({ ...error, received: parseResult.parsed }); } } doesItShouldPreventChildValidation = doesItShouldPreventChildValidation || preventChildValidation || false; } if (this.child && doesItShouldPreventChildValidation === false) return await this.child.validate(errorsAsHashedSet, path, parseResult, options); return parseResult; } /** * This static method takes care of everything for you. This means that you should only call this method * for appending new fallbacks, it takes care of creating the root validator and making sure that the * rootValidator on the schema is the highest priority one. * * @param schema - The schema that we are working on right now, all fallbacks are tied to that specific * schema. We automatically define the rootValidator on the schema so you don't need to worry about that. * @param fallback - The fallback that we are adding. This is an object that contains the type of the * fallback and the callback that we are adding. * @param options - The options that we are passing to the fallback. Options like `at` and `removeCurrent` * are passed to the `addFallback` method. */ static createAndAppendFallback(schema, fallback, options) { const schemaWithProtected = schema; let validatorInstance = schemaWithProtected.__rootFallbacksValidator; if (schemaWithProtected.__rootFallbacksValidator === void 0) { validatorInstance = new _Validator(fallback.type); schemaWithProtected.__rootFallbacksValidator = validatorInstance; } validatorInstance.addFallback(schema, fallback.type, fallback.name, fallback.callback, options); return validatorInstance; } toString(ident = 0) { return `Priority: ${this.priority} Fallbacks: ${this.fallbacks.length} ${this.child ? `Children: ${this.child.toString(ident + 2)}` : ""}`; } }; // src/utils.ts var WithFallback = class { static { __name(this, "WithFallback"); } $$type = "$PWithFallback"; fallbackFor; transformedSchema; adapterType; constructor(adapterType, fallbackFor, transformedSchema) { this.adapterType = adapterType; this.fallbackFor = new Set(fallbackFor); this.transformedSchema = transformedSchema; } }; function withFallbackFactory(adapterType) { return (fallbackFor, transformedSchema) => new WithFallback(adapterType, fallbackFor, transformedSchema); } __name(withFallbackFactory, "withFallbackFactory"); function parseErrorsFactory(schemaAdapter) { return async (errorOrErrors, metadata) => { const errorsIsAnArray = Array.isArray(errorOrErrors); if (errorsIsAnArray) return Promise.all(errorOrErrors.map((error) => schemaAdapter.formatError.bind(schemaAdapter)(error, metadata))); return [ await schemaAdapter.formatError.bind(schemaAdapter)(errorOrErrors, metadata) ]; }; } __name(parseErrorsFactory, "parseErrorsFactory"); async function defaultTransform(type, schema, adapter, fieldAdapter, getValidationData, fallbackFunctions, options) { const validationData = await Promise.resolve(getValidationData(false)); const validationDataForStringVersion = options.shouldAddStringVersion ? await Promise.resolve(getValidationData(true)) : void 0; const schemaWithPrivateFields = schema; const checkIfShouldUseParserAndAppend = /* @__PURE__ */ __name((parser) => { const isValidationDataAParser = validationData.parsers?.[parser] !== void 0; if (isValidationDataAParser) schema.__parsers._fallbacks.add(parser); }, "checkIfShouldUseParserAndAppend"); const getExtendedOrNotSchemaAndString = /* @__PURE__ */ __name((schema2, toStringVersion) => { const extendedOrNotSchema2 = typeof schemaWithPrivateFields.__extends?.callback === "function" ? schemaWithPrivateFields.__extends.callback(schema2) : schema2; const extendedOrNotSchemaString2 = typeof schemaWithPrivateFields.__extends?.toStringCallback === "function" ? schemaWithPrivateFields.__extends.toStringCallback(toStringVersion) : toStringVersion; return [ extendedOrNotSchema2, extendedOrNotSchemaString2 ]; }, "getExtendedOrNotSchemaAndString"); const checkIfShouldAppendFallbackAndAppend = /* @__PURE__ */ __name((fallback) => { const wereArgumentsForThatFallbackDefinedAndFallbackFunctionDefined = validationData[fallback] !== void 0 && fallbackFunctions[fallback] !== void 0; if (wereArgumentsForThatFallbackDefinedAndFallbackFunctionDefined) { const fallbackReturnType = fallbackFunctions[fallback](validationData[fallback]); Validator.createAndAppendFallback(schema, fallbackReturnType); } }, "checkIfShouldAppendFallbackAndAppend"); const appendRootFallback = /* @__PURE__ */ __name(() => { if (options.validatorsIfFallbackOrNotSupported) { const validatorsIfFallbackOrNotSupported = Array.isArray(options.validatorsIfFallbackOrNotSupported) ? options.validatorsIfFallbackOrNotSupported : [ options.validatorsIfFallbackOrNotSupported ]; for (const fallback of validatorsIfFallbackOrNotSupported) Validator.createAndAppendFallback(schema, fallback); } }, "appendRootFallback"); const appendRequiredFallbacks = /* @__PURE__ */ __name(() => { const hasFallbacks = schemaWithPrivateFields.__rootFallbacksValidator?.["$$type"] === "$PValidator"; if (hasFallbacks) { Validator.createAndAppendFallback(schema, optional(schemaWithPrivateFields.__optional)); Validator.createAndAppendFallback(schema, nullable(schemaWithPrivateFields.__nullable)); Validator.createAndAppendFallback(schema, checkType(schemaWithPrivateFields.__type)); } }, "appendRequiredFallbacks"); const isFieldAdapterNotSupportedForThatFieldType = fieldAdapter === void 0; if (options.fallbackIfNotSupported !== void 0 && isFieldAdapterNotSupportedForThatFieldType) { const existingFallbacks = Object.keys(fallbackFunctions); const allParsers = Object.keys(validationData["parsers"]); appendRootFallback(); for (const fallback of existingFallbacks) checkIfShouldAppendFallbackAndAppend(fallback); for (const parser of allParsers) checkIfShouldUseParserAndAppend(parser); appendRequiredFallbacks(); return options.fallbackIfNotSupported(); } if (!fieldAdapter) throw new Error("The field adapter is not supported and no fallback was provided."); const translatedSchemaOrWithFallback = await Promise.resolve(fieldAdapter.translate(adapter.field, { withFallback: withFallbackFactory(type), ...validationData })); let stringVersion = ""; if (options.shouldAddStringVersion) stringVersion = await fieldAdapter.toString(adapter, adapter.field, validationDataForStringVersion); if (translatedSchemaOrWithFallback?.["$$type"] === "$PWithFallback") { appendRootFallback(); for (const fallback of translatedSchemaOrWithFallback.fallbackFor) { checkIfShouldAppendFallbackAndAppend(fallback); checkIfShouldUseParserAndAppend(fallback); } const [extendedOrNotSchema2, extendedOrNotSchemaString2] = getExtendedOrNotSchemaAndString(translatedSchemaOrWithFallback.transformedSchema, stringVersion); appendRequiredFallbacks(); return [ { transformed: extendedOrNotSchema2, asString: extendedOrNotSchemaString2 } ]; } const [extendedOrNotSchema, extendedOrNotSchemaString] = getExtendedOrNotSchemaAndString(translatedSchemaOrWithFallback, stringVersion); return [ { transformed: extendedOrNotSchema, asString: extendedOrNotSchemaString } ]; } __name(defaultTransform, "defaultTransform"); async function defaultTransformToAdapter(callback, schema, transformedSchemas, options, type) { const isTransformedSchemasEmpty = Object.keys(transformedSchemas).length <= 0; if (isTransformedSchemasEmpty) { const adapterInstanceToUse = ( // eslint-disable-next-line ts/no-unnecessary-condition options.schemaAdapter?.["$$type"] === "$PSchemaAdapter" ? options.schemaAdapter : getDefaultAdapter() ); schema["__transformedSchemas"][adapterInstanceToUse.name] = { transformed: false, adapter: adapterInstanceToUse, schemas: [] }; } const schemaAdapterNameToUse = options.schemaAdapter?.name || Object.keys(transformedSchemas)[0]; const isACustomSchemaAdapterAndNotYetDefined = ( // eslint-disable-next-line ts/no-unnecessary-condition transformedSchemas[schemaAdapterNameToUse] === void 0 && options.schemaAdapter !== void 0 ); if (isACustomSchemaAdapterAndNotYetDefined) transformedSchemas[schemaAdapterNameToUse] = { transformed: false, adapter: options.schemaAdapter, schemas: [] }; const shouldTranslate = transformedSchemas[schemaAdapterNameToUse].transformed === false || options.force === true; if (shouldTranslate) { const translatedSchemas = await callback(transformedSchemas[schemaAdapterNameToUse].adapter); transformedSchemas[schemaAdapterNameToUse].schemas = translatedSchemas; transformedSchemas[schemaAdapterNameToUse].transformed = true; } transformedSchemas[schemaAdapterNameToUse].transformed = true; return transformedSchemas[schemaAdapterNameToUse].schemas; } __name(defaultTransformToAdapter, "defaultTransformToAdapter"); async function formatErrorFromParseMethod(adapter, fieldAdapter, error, received, schema, path, errorsAsHashedSet) { const formattedError = await fieldAdapter.formatError(adapter, adapter.field, schema, error); formattedError.path = Array.isArray(formattedError.path) ? [ ...path, ...formattedError.path ] : path; const formattedErrorAsParseResultError = formattedError; formattedErrorAsParseResultError.isValid = false; const sortedError = Object.fromEntries(Object.entries(formattedErrorAsParseResultError).sort(([a], [b]) => a.localeCompare(b))); const hashedError = JSON.stringify(sortedError); errorsAsHashedSet.add(JSON.stringify(sortedError)); formattedErrorAsParseResultError.received = received; return formattedErrorAsParseResultError; } __name(formatErrorFromParseMethod, "formatErrorFromParseMethod"); async function transformSchemaAndCheckIfShouldBeHandledByFallbackOnComplexSchemas(schema, options) { const schemaWithProtected = schema; const transformedData = await schemaWithProtected.__transformToAdapter(options); const doesKeyHaveFallback = schemaWithProtected.__rootFallbacksValidator !== void 0; const doesKeyHaveToInternal = typeof schemaWithProtected.__toInternal === "function"; const doesKeyHaveToValidate = typeof schemaWithProtected.__toValidate === "function"; const doesKeyHaveToDefault = typeof schemaWithProtected.__defaultFunction === "function"; const doesKeyHaveRunBeforeParseAndData = typeof schemaWithProtected.__runBeforeParseAndData === "function"; const doesKeyHaveParserFallback = schemaWithProtected.__parsers._fallbacks.size > 0; const shouldAddFallbackValidation = doesKeyHaveFallback || doesKeyHaveToInternal || doesKeyHaveToValidate || doesKeyHaveToDefault || doesKeyHaveParserFallback || doesKeyHaveRunBeforeParseAndData || // eslint-disable-next-line ts/no-unnecessary-condition transformedData === void 0; return [ transformedData, shouldAddFallbackValidation ]; } __name(transformSchemaAndCheckIfShouldBeHandledByFallbackOnComplexSchemas, "transformSchemaAndCheckIfShouldBeHandledByFallbackOnComplexSchemas"); function shouldRunDataOnComplexSchemas(schema) { return schema["__parsers"].high.size > 0 || schema["__parsers"].medium.size > 0 || schema["__parsers"].low.size > 0 || schema["__parsers"]._fallbacks.size > 0 || typeof schema["__runBeforeParseAndData"] === "function" || typeof schema["__toRepresentation"] === "function" || typeof schema["__defaultFunction"] === "function"; } __name(shouldRunDataOnComplexSchemas, "shouldRunDataOnComplexSchemas"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { WithFallback, defaultTransform, defaultTransformToAdapter, formatErrorFromParseMethod, parseErrorsFactory, shouldRunDataOnComplexSchemas, transformSchemaAndCheckIfShouldBeHandledByFallbackOnComplexSchemas, withFallbackFactory });