UNPKG

@decaf-ts/decorator-validation

Version:
296 lines 12.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ModelRegistryManager = void 0; exports.bulkModelRegister = bulkModelRegister; const decoration_1 = require("@decaf-ts/decoration"); const constants_1 = require("./constants.cjs"); const index_1 = require("./../validation/index.cjs"); let modelBuilderFunction; let actingModelRegistry; /** * @description Registry manager for model constructors that enables serialization and rebuilding * @summary The ModelRegistryManager implements the ModelRegistry interface and provides * functionality for registering, retrieving, and building model instances. It maintains * a cache of model constructors indexed by name, allowing for efficient lookup and instantiation. * This class is essential for the serialization and deserialization of model objects. * * @param {function(Record<string, any>): boolean} [testFunction] - Function to test if an object is a model, defaults to {@link Model#isModel} * * @class ModelRegistryManager * @template M Type of model that can be registered, must extend Model * @implements ModelRegistry<M> * @category Model * * @example * ```typescript * // Create a model registry * const registry = new ModelRegistryManager(); * * // Register a model class * registry.register(User); * * // Retrieve a model constructor by name * const UserClass = registry.get("User"); * * // Build a model instance from a plain object * const userData = { name: "John", age: 30 }; * const user = registry.build(userData, "User"); * ``` * * @mermaid * sequenceDiagram * participant C as Client * participant R as ModelRegistryManager * participant M as Model Class * * C->>R: new ModelRegistryManager(testFunction) * C->>R: register(ModelClass) * R->>R: Store in cache * C->>R: get("ModelName") * R-->>C: ModelClass constructor * C->>R: build(data, "ModelName") * R->>R: Get constructor from cache * R->>M: new ModelClass(data) * M-->>R: Model instance * R-->>C: Model instance */ class ModelRegistryManager { constructor(testFunction = decoration_1.Metadata.isModel) { this.cache = {}; this.testFunction = testFunction; } /** * @description Registers a model constructor with the registry * @summary Adds a model constructor to the registry cache, making it available for * later retrieval and instantiation. If no name is provided, the constructor's name * property is used as the key in the registry. * * @param {ModelConstructor<M>} constructor - The model constructor to register * @param {string} [name] - Optional name to register the constructor under, defaults to constructor.name * @return {void} * @throws {Error} If the constructor is not a function */ register(constructor, name) { if (typeof constructor !== "function") throw new Error("Model registering failed. Missing Class name or constructor"); name = name || constructor.name; this.cache[name] = constructor; } /** * @summary Gets a registered Model {@link ModelConstructor} * @param {string} name */ get(name) { try { return this.cache[name]; // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return undefined; } } /** * @param {Record<string, any>} obj * @param {string} [clazz] when provided, it will attempt to find the matching constructor * * @throws Error If clazz is not found, or obj is not a {@link Model} meaning it has no {@link ModelKeys.ANCHOR} property */ build(obj = {}, clazz) { if (!clazz && !this.testFunction(obj)) throw new Error("Provided obj is not a Model object"); const name = clazz || decoration_1.Metadata.modelName(obj.constructor); if (!(name in this.cache)) throw new Error(`Provided class ${name} is not a registered Model object`); return new this.cache[name](obj); } /** * @description Configures the global model builder function * @summary Sets the Global {@link ModelBuilderFunction} used for building model instances * * @param {ModelBuilderFunction} [builder] - The builder function to set as the global builder * @return {void} */ static setBuilder(builder) { modelBuilderFunction = builder; } /** * @description Retrieves the currently configured global model builder function * @summary Returns the current global {@link ModelBuilderFunction} used for building model instances * * @return {ModelBuilderFunction | undefined} - The current global builder function or undefined if not set */ static getBuilder() { return modelBuilderFunction || ModelRegistryManager.fromModel; } /** * @description Copies and rebuilds properties from a source object to a model instance, handling nested models * @summary Repopulates the instance with properties from the new Model Object, recursively rebuilding nested models * * @template T * @param {T} self - The target model instance to update * @param {T | Record<string, any>} [obj] - The source object containing properties to copy * @return {T} - The updated model instance with rebuilt nested models * * @mermaid * sequenceDiagram * participant C as Client * participant M as Model.fromModel * participant B as Model.build * participant R as Reflection * * C->>M: fromModel(self, obj) * M->>M: Get attributes from self * loop For each property * M->>M: Copy property from obj to self * alt Property is a model * M->>M: Check if property is a model * M->>B: build(property, modelType) * B-->>M: Return built model * else Property is a complex type * M->>R: Get property decorators * R-->>M: Return decorators * M->>M: Filter type decorators * alt Property is Array/Set with list decorator * M->>M: Process each item in collection * loop For each item * M->>B: build(item, itemModelType) * B-->>M: Return built model * end * else Property is another model type * M->>B: build(property, propertyType) * B-->>M: Return built model * end * end * end * M-->>C: Return updated self */ static fromModel(self, obj) { if (!obj) obj = {}; let decorators; const props = decoration_1.Metadata.getAttributes(self); const proto = Object.getPrototypeOf(self); let descriptor; for (const prop of props) { try { self[prop] = obj[prop] ?? self[prop] ?? undefined; } catch (e) { descriptor = Object.getOwnPropertyDescriptor(proto, prop); if (!descriptor || descriptor.writable) throw new Error(`Unable to write property ${prop} to model: ${e}`); } if (typeof self[prop] !== "object") continue; const propM = decoration_1.Metadata.isPropertyModel(self, prop); if (propM) { try { self[prop] = ModelRegistryManager.getRegistry().build(self[prop], typeof propM === "string" ? propM : undefined); } catch (e) { console.log(e); } continue; } decorators = decoration_1.Metadata.allowedTypes(self.constructor, prop); if (!decorators || !decorators.length) throw new Error(`failed to find decorators for property ${prop}`); const clazz = decorators.map((t) => typeof t === "function" && !t.name ? t() : t); const reserved = Object.values(constants_1.ReservedModels); clazz.forEach((c) => { if (!reserved.includes(c)) try { switch (c.name) { case "Array": case "Set": { const validation = decoration_1.Metadata.validationFor(self.constructor, prop); if (!validation || !validation[index_1.ValidationKeys.LIST]) break; const listDec = validation[index_1.ValidationKeys.LIST]; const clazzName = listDec.clazz .map((t) => typeof t === "function" && !t.name ? t() : t) .find((t) => !constants_1.jsTypes.includes(t.name)); if (c.name === "Array") self[prop] = self[prop].map((el) => { return ["object", "function"].includes(typeof el) && clazzName ? ModelRegistryManager.getRegistry().build(el, clazzName.name) : el; }); if (c.name === "Set") { const s = new Set(); for (const v of self[prop]) { if (["object", "function"].includes(typeof v) && clazzName) { s.add(ModelRegistryManager.getRegistry().build(v, clazzName.name)); } else { s.add(v); } } self[prop] = s; } break; } default: if (typeof self[prop] !== "undefined" && ModelRegistryManager.getRegistry().get(c.name)) self[prop] = ModelRegistryManager.getRegistry().build(self[prop], c.name); } } catch (e) { console.log(e); // do nothing. we have no registry of this class } }); } return self; } /** * @description Provides access to the current model registry * @summary Returns the current {@link ModelRegistryManager} instance, creating one if it doesn't exist * * @return {ModelRegistry<any>} - The current model registry, defaults to a new {@link ModelRegistryManager} if not set * @private */ static getRegistry() { if (!actingModelRegistry) actingModelRegistry = new ModelRegistryManager(); return actingModelRegistry; } /** * @description Configures the model registry to be used by the Model system * @summary Sets the current model registry to a custom implementation * * @param {BuilderRegistry<any>} modelRegistry - The new implementation of Registry to use * @return {void} */ static setRegistry(modelRegistry) { actingModelRegistry = modelRegistry; } } exports.ModelRegistryManager = ModelRegistryManager; /** * @summary Bulk Registers Models * @description Useful when using bundlers that might not evaluate all the code at once * * @template M extends Model * @param {Array<Constructor<M>> | Array<{name: string, constructor: Constructor<M>}>} [models] * * @memberOf module:decorator-validation * @category Model */ function bulkModelRegister(...models) { models.forEach((m) => { const constructor = (m.constructor ? m.constructor : m); ModelRegistryManager.getRegistry().register(constructor, m.name); }); } //# sourceMappingURL=ModelRegistry.js.map