UNPKG

@decaf-ts/decorator-validation

Version:
401 lines 16.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ModelBuilder = exports.AttributeBuilder = void 0; const Model_1 = require("./Model.cjs"); const typed_object_accumulator_1 = require("typed-object-accumulator"); const decoration_1 = require("@decaf-ts/decoration"); const decorators_1 = require("./decorators.cjs"); const decorators_2 = require("./../validation/decorators.cjs"); const constants_1 = require("./../validation/Validators/constants.cjs"); const Validation_1 = require("./../validation/Validation.cjs"); class AttributeBuilder { constructor(parent, attr, declaredType) { this.parent = parent; this.attr = attr; this.declaredType = declaredType; this.decorators = []; } decorate(...decorators) { for (const decorator of decorators) { if (this.decorators.includes(decorator)) throw new Error(`Decorator "${decorator}" has already been used`); this.decorators.push(decorator); } return this.parent; } undecorate(...decorators) { for (const decorator of decorators) { const index = this.decorators.indexOf(decorator); if (index < 0) throw new Error(`Decorator "${decorator}" is not applied to ${this.attr}`); this.decorators.splice(index, 1); } return this.parent; } required(messageOrMeta) { const meta = AttributeBuilder.asMeta(messageOrMeta); const message = typeof messageOrMeta === "string" ? messageOrMeta : AttributeBuilder.resolveMessage(meta); return this.decorate((0, decorators_2.required)(message)); } min(valueOrMeta, message) { const meta = AttributeBuilder.asMeta(valueOrMeta); const value = meta?.[constants_1.ValidationKeys.MIN] ?? (meta ? undefined : valueOrMeta); if (value === undefined) throw new Error(`Missing ${constants_1.ValidationKeys.MIN} for ${String(this.attr)}`); return this.decorate((0, decorators_2.min)(value, AttributeBuilder.resolveMessage(meta, message))); } max(valueOrMeta, message) { const meta = AttributeBuilder.asMeta(valueOrMeta); const value = meta?.[constants_1.ValidationKeys.MAX] ?? (meta ? undefined : valueOrMeta); if (value === undefined) throw new Error(`Missing ${constants_1.ValidationKeys.MAX} for ${String(this.attr)}`); return this.decorate((0, decorators_2.max)(value, AttributeBuilder.resolveMessage(meta, message))); } step(valueOrMeta, message) { const meta = AttributeBuilder.asMeta(valueOrMeta); const value = meta?.[constants_1.ValidationKeys.STEP] ?? (meta ? undefined : valueOrMeta); if (value === undefined) throw new Error(`Missing ${constants_1.ValidationKeys.STEP} for ${String(this.attr)}`); return this.decorate((0, decorators_2.step)(value, AttributeBuilder.resolveMessage(meta, message))); } minlength(valueOrMeta, message) { const meta = AttributeBuilder.asMeta(valueOrMeta); const value = meta?.[constants_1.ValidationKeys.MIN_LENGTH] ?? (meta ? undefined : valueOrMeta); if (value === undefined) throw new Error(`Missing ${constants_1.ValidationKeys.MIN_LENGTH} for ${String(this.attr)}`); return this.decorate((0, decorators_2.minlength)(value, AttributeBuilder.resolveMessage(meta, message))); } maxlength(valueOrMeta, message) { const meta = AttributeBuilder.asMeta(valueOrMeta); const value = meta?.[constants_1.ValidationKeys.MAX_LENGTH] ?? (meta ? undefined : valueOrMeta); if (value === undefined) throw new Error(`Missing ${constants_1.ValidationKeys.MAX_LENGTH} for ${String(this.attr)}`); return this.decorate((0, decorators_2.maxlength)(value, AttributeBuilder.resolveMessage(meta, message))); } pattern(valueOrMeta, message) { const meta = AttributeBuilder.asMeta(valueOrMeta); const rawPattern = meta?.[constants_1.ValidationKeys.PATTERN] ?? (meta ? undefined : valueOrMeta); const regex = AttributeBuilder.patternFromString(rawPattern) ?? /.*/; return this.decorate((0, decorators_2.pattern)(regex, AttributeBuilder.resolveMessage(meta, message))); } email(messageOrMeta) { const meta = AttributeBuilder.asMeta(messageOrMeta); const message = typeof messageOrMeta === "string" ? messageOrMeta : AttributeBuilder.resolveMessage(meta); return this.decorate((0, decorators_2.email)(message)); } url(messageOrMeta) { const meta = AttributeBuilder.asMeta(messageOrMeta); const message = typeof messageOrMeta === "string" ? messageOrMeta : AttributeBuilder.resolveMessage(meta); return this.decorate((0, decorators_2.url)(message)); } type(valueOrMeta, message) { const meta = AttributeBuilder.asMeta(valueOrMeta); const types = meta?.customTypes ?? meta?.type ?? (meta ? undefined : valueOrMeta); return this.decorate((0, decorators_2.type)(types, AttributeBuilder.resolveMessage(meta, message))); } date(formatOrMeta, message) { const meta = AttributeBuilder.asMeta(formatOrMeta); const format = meta?.[constants_1.ValidationKeys.FORMAT] ?? (meta ? undefined : formatOrMeta); return this.decorate((0, decorators_2.date)(format, AttributeBuilder.resolveMessage(meta, message))); } password(valueOrMeta, message) { const meta = AttributeBuilder.asMeta(valueOrMeta); const rawPattern = meta?.[constants_1.ValidationKeys.PATTERN] ?? (meta ? undefined : valueOrMeta); const regex = AttributeBuilder.patternFromString(rawPattern); return this.decorate((0, decorators_2.password)(regex, AttributeBuilder.resolveMessage(meta, message))); } list(clazzOrMeta, collection, message) { const meta = AttributeBuilder.asMeta(clazzOrMeta); const clazz = meta?.clazz ?? (meta ? undefined : clazzOrMeta); const typeOfCollection = meta?.type ?? collection; return this.decorate((0, decorators_2.list)(clazz, typeOfCollection, AttributeBuilder.resolveMessage(meta, message))); } set(clazzOrMeta, message) { if (AttributeBuilder.isMetadataPayload(clazzOrMeta)) return this.list(clazzOrMeta); return this.list(clazzOrMeta, "Set", message); } enum(valueOrMeta, message) { const meta = AttributeBuilder.asMeta(valueOrMeta); const values = meta?.[constants_1.ValidationKeys.ENUM] ?? (meta ? undefined : valueOrMeta); return this.decorate((0, decorators_2.option)(values, AttributeBuilder.resolveMessage(meta, message))); } option(value, message) { return this.enum(value, message); } static isMetadataPayload(value) { if (!value) return false; if (value instanceof Date) return false; if (value instanceof RegExp) return false; if (Array.isArray(value)) return false; return typeof value === "object"; } static asMeta(value) { return AttributeBuilder.isMetadataPayload(value) ? value : undefined; } static resolveMessage(meta, fallback) { return meta?.message ?? fallback; } static patternFromString(pattern) { if (!pattern) return undefined; if (pattern instanceof RegExp) return pattern; const match = pattern.match(/^\/(.+)\/([gimsuy]*)$/); if (match) return new RegExp(match[1], match[2]); return new RegExp(pattern); } resolveComparison(propertyOrMeta, key, options) { const meta = AttributeBuilder.asMeta(propertyOrMeta); if (meta) { return { target: meta[key], options: { label: meta.label, message: meta.message, }, }; } return { target: propertyOrMeta, options }; } equals(propertyOrMeta, options) { const { target, options: resolvedOptions } = this.resolveComparison(propertyOrMeta, constants_1.ValidationKeys.EQUALS, options); return this.decorate((0, decorators_2.eq)(target, resolvedOptions)); } eq(propertyOrMeta, options) { return this.equals(propertyOrMeta, options); } different(propertyOrMeta, options) { const { target, options: resolvedOptions } = this.resolveComparison(propertyOrMeta, constants_1.ValidationKeys.DIFF, options); return this.decorate((0, decorators_2.diff)(target, resolvedOptions)); } diff(propertyOrMeta, options) { return this.different(propertyOrMeta, options); } lessThan(propertyOrMeta, options) { const { target, options: resolvedOptions } = this.resolveComparison(propertyOrMeta, constants_1.ValidationKeys.LESS_THAN, options); return this.decorate((0, decorators_2.lt)(target, resolvedOptions)); } lt(propertyOrMeta, options) { return this.lessThan(propertyOrMeta, options); } lessThanOrEqual(propertyOrMeta, options) { const { target, options: resolvedOptions } = this.resolveComparison(propertyOrMeta, constants_1.ValidationKeys.LESS_THAN_OR_EQUAL, options); return this.decorate((0, decorators_2.lte)(target, resolvedOptions)); } lte(propertyOrMeta, options) { return this.lessThanOrEqual(propertyOrMeta, options); } greaterThan(propertyOrMeta, options) { const { target, options: resolvedOptions } = this.resolveComparison(propertyOrMeta, constants_1.ValidationKeys.GREATER_THAN, options); return this.decorate((0, decorators_2.gt)(target, resolvedOptions)); } gt(propertyOrMeta, options) { return this.greaterThan(propertyOrMeta, options); } greaterThanOrEqual(propertyOrMeta, options) { const { target, options: resolvedOptions } = this.resolveComparison(propertyOrMeta, constants_1.ValidationKeys.GREATER_THAN_OR_EQUAL, options); return this.decorate((0, decorators_2.gte)(target, resolvedOptions)); } gte(propertyOrMeta, options) { return this.greaterThanOrEqual(propertyOrMeta, options); } description(desc) { return this.decorate((0, decoration_1.description)(desc)); } /** * Applies the attribute metadata and decorators to the provided constructor. */ build(constructor) { const target = constructor.prototype; const propKey = this.attr; if (!Object.getOwnPropertyDescriptor(target, propKey)) { Object.defineProperty(target, propKey, { configurable: true, enumerable: true, writable: true, value: undefined, }); } if (this.declaredType) { Reflect.defineMetadata(decoration_1.DecorationKeys.DESIGN_TYPE, this.declaredType, target, propKey); } (0, decoration_1.prop)()(target, propKey); this.decorators.forEach((decorator) => { try { decorator(target, propKey); } catch (e) { throw new Error(`Failed to apply decorator to property "${this.attr}": ${e}`); } }); } } exports.AttributeBuilder = AttributeBuilder; class ListAttributeBuilder { constructor(parent, attribute, collection) { this.parent = parent; this.attribute = attribute; this.collection = collection; } ofPrimitives(clazz, message) { this.attribute.list(clazz, this.collection, message); return this.parent; } ofModel() { const nestedBuilder = ModelBuilder.builder(); const originalBuild = nestedBuilder.build; let cachedConstructor; const factory = (() => { return function () { if (!cachedConstructor) { cachedConstructor = Reflect.apply(originalBuild, nestedBuilder, []); } return cachedConstructor; }; })(); this.attribute.list(factory, this.collection); nestedBuilder.build = new Proxy(originalBuild, { apply: (target, thisArg, argArray) => { cachedConstructor = Reflect.apply(target, thisArg, argArray); return this.parent; }, }); return nestedBuilder; } } class ModelBuilder extends typed_object_accumulator_1.ObjectAccumulator { constructor() { super(...arguments); this.attributes = new Map(); } setName(name) { this._name = name; return this; } description(desc) { this._description = desc; return this; } attribute(attr, type) { const existing = this.attributes.get(attr); if (existing) { if (existing.declaredType !== type) throw new Error(`Attribute "${String(attr)}" already exists with a different type`); return existing; } const attributeBuilder = new AttributeBuilder(this, attr, type); this.attributes.set(attr, attributeBuilder); return attributeBuilder; } string(attr) { return this.attribute(attr, String); } number(attr) { return this.attribute(attr, Number); } date(attr) { return this.attribute(attr, Date); } bigint(attr) { return this.attribute(attr, BigInt); } instance(clazz, attr) { return this.attribute(attr, clazz); } model(attr) { const mm = new ModelBuilder(); mm.build = new Proxy(mm.build, { apply: (target, thisArg, argArray) => { const built = Reflect.apply(target, thisArg, argArray); return this.instance(built, attr); }, }); return mm; } listOf(attr, collection = "Array") { const listType = (collection === "Set" ? Set : Array); const attribute = this.attribute(attr, listType); return new ListAttributeBuilder(this, attribute, collection); } build() { if (!this._name) throw new Error("name is required"); const Parent = this._parent ?? Model_1.Model; class DynamicBuiltClass extends Parent { constructor(arg) { super(arg); } } Object.defineProperty(DynamicBuiltClass, "name", { value: this._name, writable: false, }); for (const attribute of this.attributes.values()) { attribute.build(DynamicBuiltClass); } let result = (0, decorators_1.model)()(DynamicBuiltClass); if (this._description) result = (0, decoration_1.description)(this._description)(result); return result; } static builder() { return new ModelBuilder(); } static from(meta, name) { if (!meta) throw new Error("metadata is required"); const builder = ModelBuilder.builder(); const derivedName = name ?? `GeneratedModel${Date.now()}`; builder.setName(derivedName); const properties = meta.properties || {}; const validations = meta.validation || {}; for (const [prop, designType] of Object.entries(properties)) { const attribute = builder.attribute(prop, designType || Object); const propValidation = validations[prop]; if (propValidation) { for (const [key, validationMeta] of Object.entries(propValidation)) { const handler = attribute[key]; if (typeof handler === "function") { handler.call(attribute, validationMeta); continue; } try { const decoratorFactory = Validation_1.Validation.decoratorFromKey(key); const decorator = typeof decoratorFactory === "function" ? decoratorFactory(validationMeta) : decoratorFactory; attribute.decorate(decorator); } catch { // ignore unknown decorators } } } } return builder.build(); } } exports.ModelBuilder = ModelBuilder; //# sourceMappingURL=Builder.js.map