UNPKG

@lyxa.ai/types

Version:

Lyxa type definitions and validation schemas for both frontend and backend

329 lines 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemaBuilder = exports.ZodValidation = void 0; exports.createSchemaBuilder = createSchemaBuilder; const zod_1 = require("zod"); const typegoose_1 = require("@typegoose/typegoose"); const libphonenumber_js_1 = require("libphonenumber-js"); class ZodValidation { static objectIdOrStringSchema = zod_1.z.custom(val => { if (typeof val === 'string' && /^[0-9a-fA-F]{24}$/.test(val)) return true; if (typeof val === 'object' && val instanceof typegoose_1.mongoose.Types.ObjectId) return true; return false; }, { message: 'Invalid ObjectId' }); static objectId(field = 'Field') { return zod_1.z.union([ zod_1.z .string({ required_error: `${field} is required` }) .refine(val => typegoose_1.mongoose.Types.ObjectId.isValid(val), { message: `${field} must be a valid ObjectID`, }) .transform(val => new typegoose_1.mongoose.Types.ObjectId(val)), zod_1.z.instanceof(typegoose_1.mongoose.Types.ObjectId, { message: `${field} must be a valid ObjectID`, }), ], { message: `${field} is required` }); } static enumType(enumObj, field = 'Field') { return zod_1.z.nativeEnum(enumObj, { message: `${field} must be one of: ${Object.values(enumObj).join(', ')}`, }); } static number(field = 'Field', options) { let schema = zod_1.z.number({ required_error: `${field} is required`, invalid_type_error: `${field} must be a number`, }); if (options?.min !== undefined) { schema = schema.gte(options?.min, { message: `${field} must be at least ${options?.min}`, }); } if (options?.max !== undefined) { schema = schema.lte(options?.max, { message: `${field} must be at most ${options?.max}`, }); } if (options?.isInt) { schema = schema.int({ message: `${field} must be an integer` }); } if (options?.isPositive) { schema = schema.positive({ message: `${field} must be positive` }); } if (options?.isNonpositive) { schema = schema.nonpositive({ message: `${field} must be non-positive` }); } if (options?.isNegative) { schema = schema.negative({ message: `${field} must be negative` }); } if (options?.isNonnegative) { schema = schema.nonnegative({ message: `${field} must be non-negative` }); } return schema; } static string(field = 'Field', options) { let schema = zod_1.z.string({ required_error: options?.requiredMessage || `${field} is required`, }); if (options?.minLength) { schema = schema.min(options.minLength, options.minMessage || `${field} must be at least ${options.minLength} characters`); } if (options?.maxLength) { schema = schema.max(options.maxLength, options.maxMessage || `${field} shouldn’t exceed ${options.maxLength} characters`); } if (options?.regex) { schema = schema.regex(options.regex.pattern, options.regex.message); } if (options?.isTrimmed) { return schema .transform(val => val?.trim()) .refine(val => val?.length && val?.length > 0, { message: options?.requiredMessage || `${field} is required`, }); } return schema; } static email(field = 'Email', options) { const schema = zod_1.z .string({ required_error: options?.requiredMessage || `${field} is required`, }) .trim() .email(options?.invalidMessage || `${field} must be a valid email address`) .transform(value => value.toLowerCase()); return schema; } static url(field = 'Field') { return zod_1.z.string({ required_error: `${field} is required` }).url(`${field} must be a valid URL`); } static phoneNumber(field = 'Field') { return zod_1.z.string({ required_error: `${field} is required` }).refine(value => { try { const phone = (0, libphonenumber_js_1.parsePhoneNumberFromString)(value); return phone?.isValid(); } catch { return false; } }, { message: `Please enter a valid phone number`, }); } static validPhoneNumber(field = 'Field') { return zod_1.z.string({ required_error: `${field} is required` }).superRefine((phoneWithCountryCode, ctx) => { const phoneWithoutCountryCode = phoneWithCountryCode.replace(/^\+\d{1,3}/, ''); if (phoneWithoutCountryCode.length < 4) { ctx.addIssue({ code: zod_1.z.ZodIssueCode.custom, message: `${field} must have 4 or more digits`, }); } if (/^\d{4}$|^\d{5}$|^\d{7}$|^\d{8}$/.test(phoneWithoutCountryCode)) { return; } const phone = (0, libphonenumber_js_1.parsePhoneNumberFromString)(phoneWithCountryCode); if (!phone) { ctx.addIssue({ code: zod_1.z.ZodIssueCode.custom, message: `Invalid phone number format in ${field}`, }); return; } if (!phone.isValid()) { ctx.addIssue({ code: zod_1.z.ZodIssueCode.custom, message: `Please enter a valid phone number in ${field}`, }); } }); } static date(field = 'Field', minDate, maxDate) { return zod_1.z .union([ zod_1.z .string({ required_error: `${field} is required` }) .refine(val => { const date = new Date(val); return !isNaN(date.getTime()); }, `${field} must be a valid date`) .transform(val => new Date(val)), zod_1.z.date({ required_error: `${field} is required`, invalid_type_error: `${field} must be a valid date`, }), ], { message: `${field} is required` }) .refine(val => minDate === undefined || val >= minDate, `${field} must be on or after ${minDate?.toISOString()}`) .refine(val => maxDate === undefined || val <= maxDate, `${field} must be on or before ${maxDate?.toISOString()}`); } static boolean(field = 'Field') { return zod_1.z.boolean({ required_error: `${field} is required`, invalid_type_error: `${field} must be a boolean`, }); } static array(schema, field = 'Array', minItems, maxItems) { let arraySchema = zod_1.z.array(schema, { required_error: `${field} is required`, }); if (minItems === 1) { arraySchema = arraySchema.min(1, `${field} cannot be empty`); } if (minItems !== undefined && minItems !== 1) { arraySchema = arraySchema.min(minItems, `${field} must have at least ${minItems} items`); } if (maxItems !== undefined) { arraySchema = arraySchema.max(maxItems, `${field} must have at most ${maxItems} items`); } return arraySchema; } static timestamps() { return { createdAt: zod_1.z.date().nullable().optional(), updatedAt: zod_1.z.date().nullable().optional(), deletedAt: zod_1.z.date().nullable().optional(), }; } static trackingFields() { return { createdBy: ZodValidation.objectId().optional(), updatedBy: ZodValidation.objectId().optional(), deletedBy: ZodValidation.objectId().optional(), ...ZodValidation.timestamps(), }; } static coerce = { number: (field = 'Field', options) => { let schema = zod_1.z.coerce.number({ required_error: `${field} is required`, invalid_type_error: `${field} must be a number`, }); if (options?.min !== undefined) { schema = schema.gte(options.min, { message: `${field} must be at least ${options.min}` }); } if (options?.max !== undefined) { schema = schema.lte(options.max, { message: `${field} must be at most ${options.max}` }); } if (options?.isInt) schema = schema.int({ message: `${field} must be an integer` }); if (options?.isPositive) schema = schema.positive({ message: `${field} must be positive` }); if (options?.isNonpositive) schema = schema.nonpositive({ message: `${field} must be non-positive` }); if (options?.isNegative) schema = schema.negative({ message: `${field} must be negative` }); if (options?.isNonnegative) schema = schema.nonnegative({ message: `${field} must be non-negative` }); return schema; }, date: (field = 'Field', minDate, maxDate) => { return zod_1.z.coerce .date({ required_error: `${field} is required`, invalid_type_error: `${field} must be a valid date`, }) .refine(val => minDate === undefined || val >= minDate, { message: `${field} must be on or after ${minDate?.toISOString()}`, }) .refine(val => maxDate === undefined || val <= maxDate, { message: `${field} must be on or before ${maxDate?.toISOString()}`, }); }, boolean: (field = 'Field') => { return zod_1.z.coerce.boolean({ required_error: `${field} is required`, invalid_type_error: `${field} must be true or false`, }); }, objectId: (field = 'Field') => zod_1.z .custom(val => { if (typeof val === 'string' && typegoose_1.mongoose.Types.ObjectId.isValid(val)) return true; if (val instanceof typegoose_1.mongoose.Types.ObjectId) return true; return false; }, { message: `${field} must be a valid ObjectId` }) .transform(val => (typeof val === 'string' ? new typegoose_1.mongoose.Types.ObjectId(val) : val)), }; } exports.ZodValidation = ZodValidation; class SchemaBuilder { includeTimestamps; includeTracking; baseSchema; constructor(baseFields, includeTimestamps = true, includeTracking = false) { this.includeTimestamps = includeTimestamps; this.includeTracking = includeTracking; this.baseSchema = this.createBaseSchema(baseFields); } createBaseSchema(baseFields) { const additionalFields = this.includeTracking ? ZodValidation.trackingFields() : this.includeTimestamps ? ZodValidation.timestamps() : {}; return zod_1.z.object({ ...baseFields, ...additionalFields, }); } getBaseSchema() { return this.baseSchema.strict(); } getEntitySchema() { return this.baseSchema .extend({ _id: ZodValidation.objectId(), }) .partial() .passthrough(); } getIdSchema() { return ZodValidation.objectId(); } getUpdateSchema(excludeFields = { createdAt: true, updatedAt: true }) { const entitySchema = this.getEntitySchema(); if (Object.keys(excludeFields).length === 0) { return entitySchema; } const shape = entitySchema._def.shape(); const filteredShape = {}; for (const key in shape) { if (!excludeFields[key]) { filteredShape[key] = shape[key]; } } return zod_1.z.object(filteredShape).partial().strict(); } getDeleteSchema() { return zod_1.z.object({ _id: ZodValidation.objectId('_id'), softDelete: ZodValidation.boolean('Soft Delete').default(true), }); } updateBaseSchema(extendFields, omitFields) { let schema = this.baseSchema; if (omitFields && omitFields.length > 0) { schema = schema.omit(omitFields.reduce((acc, key) => ({ ...acc, [key]: true }), {})); } if (extendFields) { schema = schema.extend({ _id: ZodValidation.objectId().optional(), ...extendFields }); } return schema; } getAllSchemas(excludeFromUpdate) { return { BaseSchema: this.getBaseSchema(), EntitySchema: this.getEntitySchema(), IdSchema: this.getIdSchema(), UpdateSchema: this.getUpdateSchema(excludeFromUpdate), DeleteSchema: this.getDeleteSchema(), }; } } exports.SchemaBuilder = SchemaBuilder; function createSchemaBuilder(baseFields, includeTimestamps = true, includeTracking = false) { return new SchemaBuilder(baseFields, includeTimestamps, includeTracking); } //# sourceMappingURL=global-validation.js.map