@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
JavaScript
;
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
});