import Objects = require("ts-mortar/utils/Objects"); /** Contains a set of model definitions. * Dto Models are designed around server-side object property names and values * being formatted differently than local model property names and values. * Each model contains a list of properties with meta-attributes defining data-type, * default value, and source code template expressions for converting properties to and from service objects. * @author TeamworkGuy2 * @since 2015-12-16 */ class ModelDefinitionsSet implements ModelDefinitions { private static EMPTY_ARRAY = Object.freeze([]); private cloneDeep: (obj: T1) => T1; public modelNames: string[]; private modelDefs: { [id: string]: DataCollectionModel }; private modelsFuncs: { [id: string]: DtoFuncs }; // generate model information the first time this JS module loads constructor(dataModels: StringMap)>, cloneDeep: (obj: T1) => T1 = Objects.cloneDeep) { this.cloneDeep = cloneDeep; var { modelDefs, modelsFuncs } = ModelDefinitionsSet.modelDefsToCollectionModelDefs(dataModels, >>dataModels); this.modelDefs = modelDefs; this.modelsFuncs = modelsFuncs; this.modelNames = Object.keys(dataModels); } public addModel(modelName: string, model: DtoModel, modelFuncs: DtoFuncs): { modelDef: DataCollectionModel; modelFuncs: DtoFuncs } { if (this.modelDefs[modelName] != null) { throw new Error("model named '" + modelName + "' already exists, cannot add new model by that name"); } // clone the model first, so we're not modifying it when we attach the name var modelCopy = Objects.assign(this.cloneDeep(model), { name: modelName }); var modelFuncsCopy = Objects.assign({}, modelFuncs); var collModel = ModelDefinitionsSet.modelDefToCollectionModelDef(modelName, modelCopy, modelFuncsCopy); this.modelNames.push(modelName); this.modelDefs[modelName] = collModel.modelDef; this.modelsFuncs[modelName] = collModel.modelFuncs; return collModel; } /** Get names of primary key properties of a model * @param modelName: the name of the model * @return an array of primary key names in the specified model, * or an empty string if the 'modelName' is not recognized */ public getPrimaryKeys(modelName: string): string[] { var modelDef = this.modelDefs[modelName]; return modelDef && modelDef.primaryKeys || ModelDefinitionsSet.EMPTY_ARRAY; } /** Get names of auto-generated properties of a model * @param modelName: the name of the model * @return an array of auto-generated property names in the * specified model, or an empty string if the 'modelName' is not recognized */ public getAutoGeneratedKeys(modelName: string): string[] { var modelDef = this.modelDefs[modelName]; return modelDef && modelDef.autoGeneratedKeys || ModelDefinitionsSet.EMPTY_ARRAY; } public getFields(modelName: string): string[] { var modelDef = this.modelDefs[modelName]; return modelDef && modelDef.fieldNames || ModelDefinitionsSet.EMPTY_ARRAY; } public getModel(modelName: string): DataCollectionModel { return this.modelDefs[modelName]; } public getCopyFunc(modelName: string): (obj: any) => any { var modelFuncs = this.modelsFuncs[modelName]; return modelFuncs && modelFuncs.copyFunc || null; } public getDataModelFuncs(modelName: string): DtoFuncs { return this.modelsFuncs[modelName]; } public static fromCollectionModels(dataModels: StringMap)>, cloneDeep: (obj: T1) => T1 = Objects.cloneDeep): ModelDefinitionsSet { var inst = new ModelDefinitionsSet(dataModels, cloneDeep); return inst; } } module ModelDefinitionsSet { export function extendModelTemplate(parent: StringMap, child: StringMap, cloneDeep?: (obj: T1) => T1): StringMap { var res = Objects.map(parent, null, (k, v) => cloneDtoPropertyTemplate(v)); for (var childProp in child) { res[childProp] = cloneDtoPropertyTemplate(child[childProp], cloneDeep); } return res; } /** the last argument is the child class, each previous argument is the parent that the child will extend. * equivalent to 'extendModelDef(inheritanceChain[0], extendModelDef(inheritanceChain[1], extendModelDef(...)))' * @return the original child class (last argument) extended by all other arguments */ export function multiExtendModelTemplate(...inheritanceChain: StringMap[]): StringMap { var childClass = inheritanceChain[inheritanceChain.length - 1]; for (var i = inheritanceChain.length - 2; i > -1; i--) { childClass = extendModelTemplate(inheritanceChain[i], childClass); } return childClass; } // creates maps of model names to primary key property and auto-generate property names export function modelDefToCollectionModelDef(collectionName: string, dataModel: DtoModel, modelFunc: DtoFuncs): { modelDef: DataCollectionModel; modelFuncs: DtoFuncs } { var dataModels: StringMap = {}; dataModels[collectionName] = dataModel; var modelFuncs: StringMap> = {}; modelFuncs[collectionName] = modelFunc; var res = modelDefsToCollectionModelDefs(dataModels, modelFuncs, [collectionName]); return { modelDef: res.modelDefs[collectionName], modelFuncs: res.modelsFuncs[collectionName] }; } // creates maps of model names to primary key property and auto-generate property names export function modelDefsToCollectionModelDefs(dataModels: StringMap, modelFuncs: StringMap>, modelNames?: string[]): { modelDefs: StringMap>; modelsFuncs: StringMap> } { var modelDefs: StringMap> = {}; var modelsFuncs: StringMap> = {}; modelNames = modelNames || Object.keys(dataModels); for (var i = 0, size = modelNames.length; i < size; i++) { var modelName = modelNames[i]; var table = dataModels[modelName]; var tableFuncs = modelFuncs[modelName] || >{}; var tableProps = table.properties; // setup mapping of model names to collection names and vice versa var primaryKeys: string[] = []; var autoGeneratedKeys: string[] = []; var fieldNames = Object.keys(tableProps); for (var ii = 0, sizeI = fieldNames.length; ii < sizeI; ii++) { var propName = fieldNames[ii]; var propVal = tableProps[propName]; if (propVal.primaryKey === true) { primaryKeys.push(propName); } if (propVal.autoGenerate === true) { autoGeneratedKeys.push(propName); } } modelDefs[modelName] = { fieldNames, primaryKeys, autoGeneratedKeys }; modelsFuncs[modelName] = { copyFunc: tableFuncs.copyFunc, toLocalObject: tableFuncs.toLocalObject, toSvcObject: tableFuncs.toSvcObject }; } return { modelDefs, modelsFuncs }; } export function cloneDtoPropertyTemplate(prop: DtoPropertyTemplate & PropertyConversionTemplate, cloneDeep: (obj: T1) => T1 = Objects.cloneDeep): DtoPropertyTemplate & PropertyConversionTemplate { var res = { autoGenerate: prop.autoGenerate, //defaultValue: prop.defaultValue != null ? cloneDeep(prop.defaultValue) : prop.defaultValue, primaryKey: prop.primaryKey, readOnly: prop.readOnly, required: prop.required, server: prop.server == null ? null : { autoGenerate: prop.server.autoGenerate, //defaultValue: prop.server.defaultValue != null ? cloneDeep(prop.server.defaultValue) : prop.server.defaultValue, name: prop.server.name, primaryKey: prop.server.primaryKey, readOnly: prop.server.readOnly, required: prop.server.required, toLocal: prop.server.toLocal, type: prop.server.type, }, toLocal: prop.toLocal, toService: prop.toService, type: prop.type, }; if (Object.prototype.hasOwnProperty.call(prop, "defaultValue")) { (res).defaultValue = prop.defaultValue != null ? cloneDeep(prop.defaultValue) : prop.defaultValue; } if (res.server && Object.prototype.hasOwnProperty.call(prop.server, "defaultValue")) { (res.server).defaultValue = (prop.server).defaultValue != null ? cloneDeep((prop.server).defaultValue) : (prop.server).defaultValue; } return res; } } export = ModelDefinitionsSet;