@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"
824 lines (818 loc) • 32.1 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// 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/utils.ts
var priorityByType = {
low: 0,
medium: 1,
high: 2
};
var typeByPriority = Object.entries(priorityByType).reduce((acc, [key, value]) => {
acc[value] = key;
return acc;
}, {});
// src/utils.ts
async function formatErrorFromParseMethod(adapter, fieldAdapter, error, received, schema2, path, errorsAsHashedSet) {
const formattedError = await fieldAdapter.formatError(adapter, adapter.field, schema2, 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");
// src/schema/schema.ts
var Schema = class _Schema {
static {
__name(this, "Schema");
}
"~standard";
$$type = "$PSchema";
fieldType = "schema";
// Those functions will assume control of the validation process on adapters, instead of the schema.
// Why this is used? The idea is that the Schema has NO idea
// that one of it's children might be an UnionSchema for example. The adapter might not support unions,
// so then we give control to the union. The parent schema will already have an array of translated
// adapter schemas. This means for a union with Number and String it'll generate two schemas, one for number
// and one for the value as String. Of course this gets multiplied. So if we have a union with Number and String.
// We should take those two schemas from the array and validate them individually. This logic is
// handled by the union schema. If we have an intersection type for example, instead of validating
// One schema OR the other, we validate one schema AND the other. This will be handled
// by the schema that contains that intersection logic.
__beforeValidationCallbacks = /* @__PURE__ */ new Map();
__cachedGetParent;
set __getParent(value) {
this.__cachedGetParent = value;
}
get __getParent() {
return this.__cachedGetParent;
}
__alreadyAppliedModel;
__runBeforeParseAndData;
__rootFallbacksValidator;
__saveCallback;
__modelOmitCallback;
__parsers = {
high: /* @__PURE__ */ new Map(),
medium: /* @__PURE__ */ new Map(),
low: /* @__PURE__ */ new Map(),
_fallbacks: /* @__PURE__ */ new Set()
};
__refinements = [];
__nullable = {
message: "Cannot be null",
allow: false
};
__optional = {
message: "Required",
allow: false
};
__extends = void 0;
__transformedSchemas = {};
__defaultFunction = void 0;
__toRepresentation = void 0;
__toValidate = void 0;
__toInternal = void 0;
__type = {
message: "Invalid type",
check: /* @__PURE__ */ __name(() => true, "check")
};
__getDefaultTransformedSchemas() {
const adapterInstance = getDefaultAdapter();
if (this.__transformedSchemas[adapterInstance.constructor.name] === void 0) this.__transformedSchemas[adapterInstance.constructor.name] = {
transformed: false,
adapter: adapterInstance,
schemas: []
};
}
/**
* This will validate the data with the fallbacks, so internally, without relaying on the schema adapter.
* This is nice because we can support things that the schema adapter is not able to support by default.
*
* @param errorsAsHashedSet - The errors as a hashed set. This is used to prevent duplicate errors.
* @param path - The path of the error.
* @param parseResult - The result of the parse method.
*/
async __validateByFallbacks(path, parseResult, options) {
if (this.__rootFallbacksValidator) return this.__rootFallbacksValidator.validate(options.errorsAsHashedSet, path, parseResult, options);
return parseResult;
}
/**
* This will validate by the adapter. In other words, we send the data to the schema adapter and then we validate
* that data.
* So understand that, first we send the data to the adapter, the adapter validates it, then, after we validate
* from the adapter we validate with the fallbacks so we can do all of the extra validations not handled by
* the adapter.
*
* @param value - The value to be validated.
* @param errorsAsHashedSet - The errors as a hashed set. This is used to prevent duplicate errors on the validator.
* @param path - The path of the error so we can construct an object with the nested paths of the error.
* @param parseResult - The result of the parse method.
*
* @returns The result and the errors of the parse method.
*/
async __validateByAdapter(adapter, fieldAdapter, schema2, value, path, options) {
const parseResult = {
errors: [],
parsed: value
};
parseResult.errors = [];
parseResult.parsed = value;
if (fieldAdapter === void 0 || typeof fieldAdapter.parse !== "function") return parseResult;
const adapterParseResult = await fieldAdapter.parse(adapter, adapter.field, schema2.transformed, value, options.args);
parseResult.parsed = adapterParseResult.parsed;
if (adapterParseResult.errors) {
if (Array.isArray(adapterParseResult.errors)) parseResult.errors = await Promise.all(adapterParseResult.errors.map(async (error) => formatErrorFromParseMethod(adapter, fieldAdapter, error, value, schema2.transformed, path, options.errorsAsHashedSet || /* @__PURE__ */ new Set())));
else parseResult.errors = [
await formatErrorFromParseMethod(adapter, fieldAdapter, parseResult.errors, value, schema2.transformed, path, options.errorsAsHashedSet || /* @__PURE__ */ new Set())
];
}
parseResult.errors = parseResult.errors.filter((error) => typeof error !== "undefined");
return parseResult;
}
// eslint-disable-next-line ts/require-await
async __transformToAdapter(_options) {
throw new Error("Not implemented");
}
/** */
async __parsersToTransformValue(value, parsersToUse) {
let shouldStop = false;
for (const [parserName, parser] of this.__parsers.high.entries()) {
if (parsersToUse instanceof Set === false || parsersToUse.has(parserName)) {
const result = await Promise.resolve(parser(value));
if (result.preventNextParsers) shouldStop = true;
value = result.value;
} else continue;
}
if (shouldStop === false) {
for (const [parserName, parser] of this.__parsers.medium.entries()) {
if (parsersToUse instanceof Set === false || parsersToUse.has(parserName)) {
const result = await Promise.resolve(parser(value));
if (result.preventNextParsers) shouldStop = true;
value = result.value;
} else continue;
}
}
if (shouldStop === false) {
for (const [parserName, parser] of this.__parsers.low.entries()) {
if (parsersToUse instanceof Set === false || parsersToUse.has(parserName)) {
const result = await Promise.resolve(parser(value));
if (result.preventNextParsers) shouldStop = true;
value = result.value;
} else continue;
}
}
return value;
}
async __parse(value, path = [], options) {
this.__getDefaultTransformedSchemas();
if (typeof this.__runBeforeParseAndData === "function") await this.__runBeforeParseAndData(this);
const shouldRunToInternalToBubbleUp = options.toInternalToBubbleUp === void 0;
if (shouldRunToInternalToBubbleUp) options.toInternalToBubbleUp = [];
if (options.errorsAsHashedSet instanceof Set === false) options.errorsAsHashedSet = /* @__PURE__ */ new Set();
const shouldCallDefaultFunction = value === void 0 && typeof this.__defaultFunction === "function";
const shouldCallToValidateCallback = typeof this.__toValidate === "function";
const schemaAdapterFieldType = this.fieldType;
if (shouldCallDefaultFunction) value = await this.__defaultFunction();
if (shouldCallToValidateCallback) value = await Promise.resolve(this.__toValidate(value));
const parseResult = {
errors: [],
parsed: value
};
value = await this.__parsersToTransformValue(value, this.__parsers._fallbacks);
if (options.appendFallbacksBeforeAdapterValidation === void 0) options.appendFallbacksBeforeAdapterValidation = (schema2, name, callback) => {
if (this !== schema2) this.__beforeValidationCallbacks.set(name, callback);
};
if (this.__transformedSchemas[options.schemaAdapter?.constructor.name || getDefaultAdapter().constructor.name].transformed === false) await this.__transformToAdapter(options);
const adapterToUse = options.schemaAdapter ? options.schemaAdapter : Object.values(this.__transformedSchemas)[0].adapter;
const parsedResultsAfterFallbacks = await this.__validateByFallbacks(path, {
errors: parseResult.errors,
parsed: value
}, options);
parseResult.parsed = parsedResultsAfterFallbacks.parsed;
parseResult.errors = (parseResult.errors || []).concat(parsedResultsAfterFallbacks.errors || []);
if (this.__beforeValidationCallbacks.size > 0) {
for (const callback of this.__beforeValidationCallbacks.values()) {
const parsedValuesAfterValidationCallbacks = await callback(adapterToUse, adapterToUse[schemaAdapterFieldType], this, this.__transformedSchemas[adapterToUse.constructor.name].schemas, value, path, options);
parseResult.parsed = parsedValuesAfterValidationCallbacks.parsed;
parseResult.errors = Array.isArray(parseResult.errors) && Array.isArray(parsedValuesAfterValidationCallbacks.errors) ? [
...parseResult.errors,
...parsedValuesAfterValidationCallbacks.errors
] : Array.isArray(parseResult.errors) ? parseResult.errors : parsedValuesAfterValidationCallbacks.errors;
}
} else {
const parsedValuesAfterValidatingByAdapter = await this.__validateByAdapter(adapterToUse, adapterToUse[schemaAdapterFieldType], this.__transformedSchemas[adapterToUse.constructor.name].schemas[0], value, path, options);
parseResult.parsed = parsedValuesAfterValidatingByAdapter.parsed;
parseResult.errors = (parseResult.errors || []).concat(parsedValuesAfterValidatingByAdapter.errors);
}
const hasToInternalCallback = typeof this.__toInternal === "function";
const shouldCallToInternalDuringParse = hasToInternalCallback && (options.toInternalToBubbleUp?.length === 0 || Array.isArray(options.toInternalToBubbleUp) === false);
const hasNoErrors = parseResult.errors === void 0 || (parseResult.errors || []).length === 0;
await Promise.all(this.__refinements.map(async (refinement) => {
const errorOrNothing = await Promise.resolve(refinement({
value: parseResult.parsed,
context: options.context
}));
if (typeof errorOrNothing === "undefined") return;
parseResult.errors.push({
isValid: false,
code: errorOrNothing.code,
message: errorOrNothing.message,
received: parseResult.parsed,
path
});
}));
if (shouldCallToInternalDuringParse && hasNoErrors) parseResult.parsed = await this.__toInternal(value);
if (shouldRunToInternalToBubbleUp && hasNoErrors) for (const functionToModifyResult of options.toInternalToBubbleUp || []) await functionToModifyResult();
return parseResult;
}
/**
* This let's you refine the schema with custom validations. This is useful when you want to validate something
* that is not supported by default by the schema adapter.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
*
* const numberSchema = p.number().refine((value) => {
* if (value < 0) return { code: 'invalid_number', message: 'The number should be greater than 0' };
* });
*
* const { errors, parsed } = await numberSchema.parse(-1);
*
* console.log(errors);
* // [{ isValid: false, code: 'invalid_number', message: 'The number should be greater than 0', path: [] }]
* ```
*
* @param refinementCallback - The callback that will be called to validate the value.
*/
refine(refinementCallback) {
this.__refinements.push(refinementCallback);
return this;
}
/**
* Allows the value to be either undefined or null.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
*
* const numberSchema = p.number().optional();
*
* const { errors, parsed } = await numberSchema.parse(undefined);
*
* console.log(parsed); // undefined
*
* const { errors, parsed } = await numberSchema.parse(null);
*
* console.log(parsed); // null
*
* const { errors, parsed } = await numberSchema.parse(1);
*
* console.log(parsed); // 1
* ```
*
* @returns - The schema we are working with.
*/
optional(options) {
this.__optional = {
message: typeof options?.message === "string" ? options.message : "Required",
allow: typeof options?.allow === "boolean" ? options.allow : true
};
return this;
}
/**
* Allows the value to be null and ONLY null. You can also use this function to set a custom message when
* the value is NULL by setting the { message: 'Your custom message', allow: false } on the options.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
*
* const numberSchema = p.number().nullable();
*
* const { errors, parsed } = await numberSchema.parse(null);
*
* console.log(parsed); // null
*
* const { errors, parsed } = await numberSchema.parse(undefined);
*
* console.log(errors); // [{ isValid: false, code: 'invalid_type', message: 'Invalid type', path: [] }]
* ```
*
* @param options - The options for the nullable function.
* @param options.message - The message to be shown when the value is not null. Defaults to 'Cannot be null'.
* @param options.allow - Whether the value can be null or not. Defaults to true.
*
* @returns The schema.
*/
nullable(options) {
this.__nullable = {
message: typeof options?.message === "string" ? options.message : "Cannot be null",
allow: typeof options?.allow === "boolean" ? options.allow : true
};
return this;
}
/**
* Appends a custom schema to the schema, this way it will bypass the creation of the schema in runtime.
*
* By default when validating, on the first validation we create the schema. Just during the first validation.
* With this function, you bypass that, so you can speed up the validation process.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
* import * as z from 'zod';
*
* const numberSchema = p.number().appendSchema(z.number());
*
* const { errors, parsed } = await numberSchema.parse(1);
* ```
*
* @param schema - The schema to be appended.
* @param args - The arguments for the schema.
* @param args.adapter - The adapter to be used. If not provided, the default adapter will be used.
*
* @returns The same schema again.
*/
appendSchema(schema2, args) {
const adapter = args?.adapter || getDefaultAdapter();
this.__transformedSchemas[adapter.constructor.name] = {
transformed: true,
adapter,
schemas: [
schema2
]
};
return this;
}
/**
* This method will remove the value from the representation of the schema. If the value is undefined it will keep
* that way otherwise it will set the value to undefined after it's validated.
* This is used in conjunction with the {@link data} function, the {@link parse} function or {@link validate}
* function. This will remove the value from the representation of the schema.
*
* By default, the value will be removed just from the representation, in other words, when you call the {@link data}
* function. But if you want to remove the value from the internal representation, you can pass the argument
* `toInternal` as true. Then if you still want to remove the value from the representation, you will need to pass
* the argument `toRepresentation` as true as well.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
*
* const userSchema = p.object({
* id: p.number().optional(),
* name: p.string(),
* password: p.string().omit()
* });
*
* const user = await userSchema.data({
* id: 1,
* name: 'John Doe',
* password: '123456'
* });
*
* console.log(user); // { id: 1, name: 'John Doe' }
* ```
*
*
* @param args - By default, the value will be removed just from the representation, in other words, when you call
* the {@link data} function.
* But if you want to remove the value from the internal representation, you can pass the argument `toInternal`
* as true. Then if you still want to remove the value from the representation, you will need to pass the
* argument `toRepresentation` as true as well.
*
* @returns The schema.
*/
omit(args) {
const toRepresentation = typeof args?.toRepresentation === "boolean" ? args.toRepresentation : typeof args?.toInternal !== "boolean";
const toInternal = typeof args?.toInternal === "boolean" ? args.toInternal : false;
if (toInternal) {
if (this.__toInternal) {
const toInternal2 = this.__toInternal;
this.__toInternal = async (value) => {
await toInternal2(value);
return void 0;
};
} else this.__toInternal = async () => void 0;
} else if (toRepresentation) {
if (this.__toRepresentation) {
const toRepresentation2 = this.__toRepresentation;
this.__toRepresentation = async (value) => {
await toRepresentation2(value);
return void 0;
};
} else this.__toRepresentation = async () => void 0;
}
return this;
}
/**
* This function is used in conjunction with the {@link validate} function. It's used to save a value to an external
* source like a database. You should always return the schema after you save the value, that way we will always have
* the correct type of the schema after the save operation.
*
* You can use the {@link toRepresentation} function to transform and clean the value it returns after the save.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
*
* import { User } from './models';
*
* const userSchema = p.object({
* id: p.number().optional(),
* name: p.string(),
* email: p.string().email(),
* }).onSave(async (value) => {
* // Create or update the user on the database using palmares models or any other library of your choice.
* if (value.id)
* await User.default.set(value, { search: { id: value.id } });
* else
* await User.default.set(value);
*
* return value;
* });
*
*
* // Then, on your controller, do something like this:
* const { isValid, save, errors } = await userSchema.validate(req.body);
* if (isValid) {
* const savedValue = await save();
* return Response.json(savedValue, { status: 201 });
* }
*
* return Response.json({ errors }, { status: 400 });
* ```
*
* @param callback - The callback that will be called to save the value on an external source.
*
* @returns The schema.
*/
onSave(callback) {
this.__saveCallback = callback;
return this;
}
/**
* This function is used to validate the schema and save the value to the database. It is used in
* conjunction with the {@link onSave} function.
*
* Different from other validation libraries, palmares schemas is aware that you want to save. On your
* routes/functions we recommend to ALWAYS use this function instead of {@link parse} directly. This is because
* this function by default will return an object with the property `save` or the `errors`. If the errors are present,
* you can return the errors to the user. If the save property is present, you can use to save the value to an
* external source. e.g. a database.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
*
* import { User } from './models';
*
* const userSchema = p.object({
* id: p.number().optional(),
* name: p.string(),
* email: p.string().email(),
* }).onSave(async (value) => {
* // Create or update the user on the database using palmares models or any other library of your choice.
* if (value.id)
* await User.default.set(value, { search: { id: value.id } });
* else
* await User.default.set(value);
*
* return value;
* });
*
*
* // Then, on your controller, do something like this:
* const { isValid, save, errors } = await userSchema.validate(req.body);
* if (isValid) {
* const savedValue = await save();
* return Response.json(savedValue, { status: 201 });
* }
*
* return Response.json({ errors }, { status: 400 });
* ```
*
* @param value - The value to be validated.
*
* @returns An object with the property isValid, if the value is valid, the function `save` will be present.
* If the value is invalid, the property errors will be present.
*/
async validate(value, context = {}) {
const { errors, parsed } = await this.__parse(value, [], {
context
});
if ((errors || []).length > 0) return {
isValid: false,
errors,
save: void 0
};
return {
isValid: true,
save: /* @__PURE__ */ __name(async () => this._save.bind(this)(parsed, context), "save"),
errors: void 0
};
}
/**
* Internal function, when we call the {@link validate} function it's this function that gets called
* when the user uses the `save` function returned by the {@link validate} function if the value is valid.
*
* @param value - The value to be saved.
*
* @returns The value to representation.
*/
async _save(value, context) {
if (this.__saveCallback) {
let result = this.__saveCallback(value);
if (typeof result === "function") result = result(context);
return this.data(result instanceof Promise ? await result : result);
}
return this.data(value);
}
/**
* This function is used to validate and parse the value to the internal representation of the schema.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
*
* const numberSchema = p.number().allowString();
*
* const { errors, parsed } = await numberSchema.parse('123');
*
* console.log(parsed); // 123
* ```
*
* @param value - The value to be parsed.
*
* @returns The parsed value.
*/
async parse(value) {
return this.__parse(value, [], {});
}
/**
* This function is used to transform the value to the representation without validating it.
* This is useful when you want to return a data from a query directly to the user. But for example
* you are returning the data of a user, you can clean the password or any other sensitive data.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
*
* const userSchema = p.object({
* id: p.number().optional(),
* name: p.string(),
* email: p.string().email(),
* password: p.string().optional()
* }).toRepresentation(async (value) => {
* return {
* id: value.id,
* name: value.name,
* email: value.email
* }
* });
*
* const user = await userSchema.data({
* id: 1,
* name: 'John Doe',
* email: 'john@gmail.com',
* password: '123456'
* });
* ```
*/
async data(value) {
this.__getDefaultTransformedSchemas();
if (typeof this.__runBeforeParseAndData === "function") await this.__runBeforeParseAndData(this);
value = await this.__parsersToTransformValue(value);
if (this.__toRepresentation) value = await Promise.resolve(this.__toRepresentation(value));
if (this.__defaultFunction && value === void 0) value = await Promise.resolve(this.__defaultFunction());
return value;
}
instanceOf(args) {
this.__type.check = typeof args.check === "function" ? args.check : this.__type.check;
this.__type.message = typeof args.message === "string" ? args.message : this.__type.message;
return this;
}
/**
* This function is used to add a default value to the schema. If the value is either undefined or null,
* the default value will be used.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
*
* const numberSchema = p.number().default(0);
*
* const { errors, parsed } = await numberSchema.parse(undefined);
*
* console.log(parsed); // 0
* ```
*/
default(defaultValueOrFunction) {
const isFunction = typeof defaultValueOrFunction === "function";
if (isFunction) this.__defaultFunction = defaultValueOrFunction;
else this.__defaultFunction = async () => defaultValueOrFunction;
return this;
}
/**
* This function let's you customize the schema your own way. After we translate the schema on the adapter we call
* this function to let you customize the custom schema your own way. Our API does not support passthrough?
* No problem, you can use this function to customize the zod schema.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
*
* const numberSchema = p.number().extends((schema) => {
* return schema.nonnegative();
* });
*
* const { errors, parsed } = await numberSchema.parse(-1);
*
* console.log(errors);
* // [{ isValid: false, code: 'nonnegative', message: 'The number should be nonnegative', path: [] }]
* ```
*
* @param callback - The callback that will be called to customize the schema.
* @param toStringCallback - The callback that will be called to transform the schema to a string when you want
* to compile the underlying schema to a string so you can save it for future runs.
*
* @returns The schema.
*/
extends(callback, toStringCallback) {
this.__extends = {
callback,
toStringCallback
};
return this;
}
/**
* This function is used to transform the value to the representation of the schema. When using the {@link data}
* function. With this function you have full control to add data cleaning for example, transforming the data
* and whatever. Another use case is when you want to return deeply nested recursive data.
* The schema maps to itself.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
*
* const recursiveSchema = p.object({
* id: p.number().optional(),
* name: p.string(),
* }).toRepresentation(async (value) => {
* return {
* id: value.id,
* name: value.name,
* children: await Promise.all(value.children.map(async (child) => await recursiveSchema.data(child)))
* }
* });
*
* const data = await recursiveSchema.data({
* id: 1,
* name: 'John Doe',
* });
* ```
*
* @example
* ```
* import * as p from '@palmares/schemas';
*
* const colorToRGBSchema = p.string().toRepresentation(async (value) => {
* switch (value) {
* case 'red': return { r: 255, g: 0, b: 0 };
* case 'green': return { r: 0, g: 255, b: 0 };
* case 'blue': return { r: 0, g: 0, b: 255 };
* default: return { r: 0, g: 0, b: 0 };
* }
* });
* ```
* @param toRepresentationCallback - The callback that will be called to transform the value to the representation.
* @param options - Options for the toRepresentation function.
* @param options.after - Whether the toRepresentationCallback should be called after the existing
* toRepresentationCallback. Defaults to true.
* @param options.before - Whether the toRepresentationCallback should be called before the existing
* toRepresentationCallback. Defaults to true.
*
* @returns The schema with a new return type
*/
toRepresentation(toRepresentationCallback, options) {
if (this.__toRepresentation) {
const before = typeof options?.before === "boolean" ? options.before : typeof options?.after === "boolean" ? !options.after : true;
const existingToRepresentation = this.__toRepresentation;
this.__toRepresentation = async (value) => {
if (before) return toRepresentationCallback(await existingToRepresentation(value));
else return existingToRepresentation(await toRepresentationCallback(value));
};
} else this.__toRepresentation = toRepresentationCallback;
return this;
}
/**
* This function is used to transform the value to the internal representation of the schema. This is useful
* when you want to transform the value to a type that the schema adapter can understand. For example, you
* might want to transform a string to a date. This is the function you use.
*
* @example
* ```typescript
* import * as p from '@palmares/schemas';
*
* const dateSchema = p.string().toInternal((value) => {
* return new Date(value);
* });
*
* const date = await dateSchema.parse('2021-01-01');
*
* console.log(date); // Date object
*
* const rgbToColorSchema = p.object({
* r: p.number().min(0).max(255),
* g: p.number().min(0).max(255),
* b: p.number().min(0).max(255),
* }).toInternal(async (value) => {
* if (value.r === 255 && value.g === 0 && value.b === 0) return 'red';
* if (value.r === 0 && value.g === 255 && value.b === 0) return 'green';
* if (value.r === 0 && value.g === 0 && value.b === 255) return 'blue';
* return `rgb(${value.r}, ${value.g}, ${value.b})`;
* });
* ```
*
* @param toInternalCallback - The callback that will be called to transform the value to the internal representation.
*
* @returns The schema with a new return type.
*/
toInternal(toInternalCallback) {
if (this.__toInternal) {
const toInternal = this.__toInternal;
this.__toInternal = async (value) => {
const newValue = await toInternal(value);
return toInternalCallback(newValue);
};
} else this.__toInternal = toInternalCallback;
return this;
}
/**
* Called before the validation of the schema. Let's say that you want to validate a date that might receive a
* string, you can convert that string to a date here BEFORE the validation. This pretty much transforms the value
* to a type that the schema adapter can understand.
*
* @example
* ```
* import * as p from '@palmares/schemas';
* import * as z from 'zod';
*
* const customRecordToMapSchema = p.schema().appendSchema(z.map()).toValidate(async (value) => {
* return new Map(value); // Before validating we transform the value to a map.
* });
*
* const { errors, parsed } = await customRecordToMapSchema.parse({ key: 'value' });
* ```
*
* @param toValidateCallback - The callback that will be called to validate the value.
*
* @returns The schema with a new return type.
*/
toValidate(toValidateCallback) {
this.__toValidate = toValidateCallback;
return this;
}
/**
* Used to transform the given schema on a stringfied version of the adapter.
*/
async compile(adapter) {
const data = await this.__transformToAdapter({
shouldAddStringVersion: true,
force: true
});
const stringVersions = data.map((value) => value.asString);
return stringVersions;
}
static new(..._args) {
const result = new _Schema();
return result;
}
};
var schema = Schema.new;
export {
Schema,
schema
};