| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- import {Service} from '@e22m4u/js-service';
- import {DataType} from './properties/index.js';
- import {cloneDeep} from '../../utils/index.js';
- import {excludeObjectKeys} from '../../utils/index.js';
- import {EmptyValuesDefiner} from './properties/index.js';
- import {InvalidArgumentError} from '../../errors/index.js';
- import {DefinitionRegistry} from '../definition-registry.js';
- /**
- * Default primary key property name.
- *
- * @type {string}
- */
- export const DEFAULT_PRIMARY_KEY_PROPERTY_NAME = 'id';
- /**
- * Model definition utils.
- */
- export class ModelDefinitionUtils extends Service {
- /**
- * Get primary key as property name.
- *
- * @param {string} modelName
- * @returns {string}
- */
- getPrimaryKeyAsPropertyName(modelName) {
- const propDefs =
- this.getPropertiesDefinitionInBaseModelHierarchy(modelName);
- const propNames = Object.keys(propDefs).filter(propName => {
- const propDef = propDefs[propName];
- return propDef && typeof propDef === 'object' && propDef.primaryKey;
- });
- if (propNames.length < 1) {
- const isDefaultPrimaryKeyAlreadyInUse = Object.keys(propDefs).includes(
- DEFAULT_PRIMARY_KEY_PROPERTY_NAME,
- );
- if (isDefaultPrimaryKeyAlreadyInUse)
- throw new InvalidArgumentError(
- 'The property name %v of the model %v is defined as a regular property. ' +
- 'In this case, a primary key should be defined explicitly. ' +
- 'Do use the option "primaryKey" to specify the primary key.',
- DEFAULT_PRIMARY_KEY_PROPERTY_NAME,
- modelName,
- );
- return DEFAULT_PRIMARY_KEY_PROPERTY_NAME;
- }
- return propNames[0];
- }
- /**
- * Get primary key as column name.
- *
- * @param {string} modelName
- * @returns {string}
- */
- getPrimaryKeyAsColumnName(modelName) {
- const pkPropName = this.getPrimaryKeyAsPropertyName(modelName);
- let pkColName;
- try {
- pkColName = this.getColumnNameByPropertyName(modelName, pkPropName);
- } catch (error) {
- if (!(error instanceof InvalidArgumentError)) throw error;
- }
- if (pkColName === undefined) return pkPropName;
- return pkColName;
- }
- /**
- * Get table name by model name.
- *
- * @param {string} modelName
- * @returns {string}
- */
- getTableNameByModelName(modelName) {
- const modelDef = this.getService(DefinitionRegistry).getModel(modelName);
- return modelDef.tableName ?? modelName;
- }
- /**
- * Get column name by property name.
- *
- * @param {string} modelName
- * @param {string} propertyName
- * @returns {string}
- */
- getColumnNameByPropertyName(modelName, propertyName) {
- const propDefs =
- this.getPropertiesDefinitionInBaseModelHierarchy(modelName);
- const propDef = propDefs[propertyName];
- if (!propDef)
- throw new InvalidArgumentError(
- 'The model %v does not have the property %v.',
- modelName,
- propertyName,
- );
- if (propDef && typeof propDef === 'object')
- return propDef.columnName ?? propertyName;
- return propertyName;
- }
- /**
- * Get default property value.
- *
- * @param {string} modelName
- * @param {string} propertyName
- * @returns {*}
- */
- getDefaultPropertyValue(modelName, propertyName) {
- const propDefs =
- this.getPropertiesDefinitionInBaseModelHierarchy(modelName);
- const propDef = propDefs[propertyName];
- if (!propDef)
- throw new InvalidArgumentError(
- 'The model %v does not have the property %v.',
- modelName,
- propertyName,
- );
- if (propDef && typeof propDef === 'object')
- return propDef.default instanceof Function
- ? propDef.default()
- : propDef.default;
- }
- /**
- * Set default values for empty properties.
- *
- * @param {string} modelName
- * @param {object} modelData
- * @param {boolean|undefined} onlyProvidedProperties
- * @returns {object}
- */
- setDefaultValuesToEmptyProperties(
- modelName,
- modelData,
- onlyProvidedProperties = false,
- ) {
- const propDefs =
- this.getPropertiesDefinitionInBaseModelHierarchy(modelName);
- const propNames = onlyProvidedProperties
- ? Object.keys(modelData)
- : Object.keys(propDefs);
- const extendedData = cloneDeep(modelData);
- const emptyValueDefiner = this.getService(EmptyValuesDefiner);
- propNames.forEach(propName => {
- const propDef = propDefs[propName];
- const propValue = extendedData[propName];
- const propType =
- propDef != null
- ? this.getDataTypeFromPropertyDefinition(propDef)
- : DataType.ANY;
- const isEmpty = emptyValueDefiner.isEmpty(propType, propValue);
- if (!isEmpty) return;
- if (
- propDef &&
- typeof propDef === 'object' &&
- propDef.default !== undefined
- ) {
- extendedData[propName] = this.getDefaultPropertyValue(
- modelName,
- propName,
- );
- }
- });
- return extendedData;
- }
- /**
- * Convert property names to column names.
- *
- * @param {string} modelName
- * @param {object} modelData
- * @returns {object}
- */
- convertPropertyNamesToColumnNames(modelName, modelData) {
- const propDefs =
- this.getPropertiesDefinitionInBaseModelHierarchy(modelName);
- const propNames = Object.keys(propDefs);
- const convertedData = cloneDeep(modelData);
- propNames.forEach(propName => {
- if (!(propName in convertedData)) return;
- const colName = this.getColumnNameByPropertyName(modelName, propName);
- let propValue = convertedData[propName];
- // если значением является объект, то проверяем
- // тип свойства и наличие модели для замены
- // полей данного объекта
- const propDef = propDefs[propName];
- if (
- propValue !== null &&
- typeof propValue === 'object' &&
- !Array.isArray(propValue) &&
- propDef !== null &&
- typeof propDef === 'object' &&
- propDef.type === DataType.OBJECT &&
- propDef.model
- ) {
- propValue = this.convertPropertyNamesToColumnNames(
- propDef.model,
- propValue,
- );
- }
- // если значением является массив, то проверяем
- // тип свойства и наличие модели элементов массива
- // для замены полей каждого объекта
- if (
- Array.isArray(propValue) &&
- propDef !== null &&
- typeof propDef === 'object' &&
- propDef.type === DataType.ARRAY &&
- propDef.itemModel
- ) {
- propValue = propValue.map(el => {
- // если элементом массива является объект,
- // то конвертируем поля согласно модели
- return el !== null && typeof el === 'object' && !Array.isArray(el)
- ? this.convertPropertyNamesToColumnNames(propDef.itemModel, el)
- : el;
- });
- }
- delete convertedData[propName];
- convertedData[colName] = propValue;
- });
- return convertedData;
- }
- /**
- * Convert column names to property names.
- *
- * @param {string} modelName
- * @param {object} tableData
- * @returns {object}
- */
- convertColumnNamesToPropertyNames(modelName, tableData) {
- const propDefs =
- this.getPropertiesDefinitionInBaseModelHierarchy(modelName);
- const propNames = Object.keys(propDefs);
- const convertedData = cloneDeep(tableData);
- propNames.forEach(propName => {
- const colName = this.getColumnNameByPropertyName(modelName, propName);
- if (!(colName in convertedData)) return;
- let colValue = convertedData[colName];
- // если значением является объект, то проверяем
- // тип свойства и наличие модели для замены
- // полей данного объекта
- const propDef = propDefs[propName];
- if (
- colValue !== null &&
- typeof colValue === 'object' &&
- !Array.isArray(colValue) &&
- propDef !== null &&
- typeof propDef === 'object' &&
- propDef.type === DataType.OBJECT &&
- propDef.model
- ) {
- colValue = this.convertColumnNamesToPropertyNames(
- propDef.model,
- colValue,
- );
- }
- // если значением является массив, то проверяем
- // тип свойства и наличие модели элементов массива
- // для замены полей каждого объекта
- if (
- Array.isArray(colValue) &&
- propDef !== null &&
- typeof propDef === 'object' &&
- propDef.type === DataType.ARRAY &&
- propDef.itemModel
- ) {
- colValue = colValue.map(el => {
- // если элементом массива является объект,
- // то конвертируем поля согласно модели
- return el !== null && typeof el === 'object' && !Array.isArray(el)
- ? this.convertColumnNamesToPropertyNames(propDef.itemModel, el)
- : el;
- });
- }
- delete convertedData[colName];
- convertedData[propName] = colValue;
- });
- return convertedData;
- }
- /**
- * Get data type by property name.
- *
- * @param {string} modelName
- * @param {string} propertyName
- * @returns {string}
- */
- getDataTypeByPropertyName(modelName, propertyName) {
- const propDefs =
- this.getPropertiesDefinitionInBaseModelHierarchy(modelName);
- const propDef = propDefs[propertyName];
- if (!propDef) {
- const pkPropName = this.getPrimaryKeyAsPropertyName(modelName);
- if (pkPropName === propertyName) return DataType.ANY;
- throw new InvalidArgumentError(
- 'The model %v does not have the property %v.',
- modelName,
- propertyName,
- );
- }
- if (typeof propDef === 'string') return propDef;
- return propDef.type;
- }
- /**
- * Get data type from property definition.
- *
- * @param {object} propDef
- * @returns {string}
- */
- getDataTypeFromPropertyDefinition(propDef) {
- if (
- (!propDef || typeof propDef !== 'object') &&
- !Object.values(DataType).includes(propDef)
- ) {
- throw new InvalidArgumentError(
- 'The argument "propDef" of the ModelDefinitionUtils.getDataTypeFromPropertyDefinition ' +
- 'should be an Object or the DataType enum, but %v given.',
- propDef,
- );
- }
- if (typeof propDef === 'string') return propDef;
- const dataType = propDef.type;
- if (!Object.values(DataType).includes(dataType))
- throw new InvalidArgumentError(
- 'The given Object to the ModelDefinitionUtils.getDataTypeFromPropertyDefinition ' +
- 'should have the "type" property with one of values: %l, but %v given.',
- Object.values(DataType),
- propDef.type,
- );
- return dataType;
- }
- /**
- * Get own properties definition of primary keys.
- *
- * @param {string} modelName
- * @returns {object}
- */
- getOwnPropertiesDefinitionOfPrimaryKeys(modelName) {
- const modelDef = this.getService(DefinitionRegistry).getModel(modelName);
- const propDefs = modelDef.properties ?? {};
- const pkPropNames = Object.keys(propDefs).filter(propName => {
- const propDef = propDefs[propName];
- return typeof propDef === 'object' && propDef.primaryKey;
- });
- return pkPropNames.reduce((a, k) => ({...a, [k]: propDefs[k]}), {});
- }
- /**
- * Get own properties definition without primary keys.
- *
- * @param {string} modelName
- * @returns {object}
- */
- getOwnPropertiesDefinitionWithoutPrimaryKeys(modelName) {
- const modelDef = this.getService(DefinitionRegistry).getModel(modelName);
- const propDefs = modelDef.properties ?? {};
- return Object.keys(propDefs).reduce((result, propName) => {
- const propDef = propDefs[propName];
- if (typeof propDef === 'object' && propDef.primaryKey) return result;
- return {...result, [propName]: propDef};
- }, {});
- }
- /**
- * Get properties definition in base model hierarchy.
- *
- * @param {string} modelName
- * @returns {object}
- */
- getPropertiesDefinitionInBaseModelHierarchy(modelName) {
- let result = {};
- let pkPropDefs = {};
- const recursion = (currModelName, prevModelName = undefined) => {
- if (currModelName === prevModelName)
- throw new InvalidArgumentError(
- 'The model %v has a circular inheritance.',
- currModelName,
- );
- if (Object.keys(pkPropDefs).length === 0) {
- pkPropDefs =
- this.getOwnPropertiesDefinitionOfPrimaryKeys(currModelName);
- result = {...result, ...pkPropDefs};
- }
- const regularPropDefs =
- this.getOwnPropertiesDefinitionWithoutPrimaryKeys(currModelName);
- result = {...regularPropDefs, ...result};
- const modelDef =
- this.getService(DefinitionRegistry).getModel(currModelName);
- if (modelDef.base) recursion(modelDef.base, currModelName);
- };
- recursion(modelName);
- return result;
- }
- /**
- * Get own relations definition.
- *
- * @param {string} modelName
- * @returns {object}
- */
- getOwnRelationsDefinition(modelName) {
- const modelDef = this.getService(DefinitionRegistry).getModel(modelName);
- return modelDef.relations ?? {};
- }
- /**
- * Get relations definition in base model hierarchy.
- *
- * @param {string} modelName
- * @returns {object}
- */
- getRelationsDefinitionInBaseModelHierarchy(modelName) {
- let result = {};
- const recursion = (currModelName, prevModelName = undefined) => {
- if (currModelName === prevModelName)
- throw new InvalidArgumentError(
- 'The model %v has a circular inheritance.',
- currModelName,
- );
- const modelDef =
- this.getService(DefinitionRegistry).getModel(currModelName);
- const ownRelDefs = modelDef.relations ?? {};
- result = {...ownRelDefs, ...result};
- if (modelDef.base) recursion(modelDef.base, currModelName);
- };
- recursion(modelName);
- return result;
- }
- /**
- * Get relation definition by name.
- *
- * @param {string} modelName
- * @param {string} relationName
- * @returns {object}
- */
- getRelationDefinitionByName(modelName, relationName) {
- const relDefs = this.getRelationsDefinitionInBaseModelHierarchy(modelName);
- const relNames = Object.keys(relDefs);
- let foundDef;
- for (const relName of relNames) {
- if (relName === relationName) {
- foundDef = relDefs[relName];
- break;
- }
- }
- if (!foundDef)
- throw new InvalidArgumentError(
- 'The model %v does not have relation name %v.',
- modelName,
- relationName,
- );
- return foundDef;
- }
- /**
- * Exclude object keys by relation names.
- *
- * @param {string} modelName
- * @param {object} modelData
- * @returns {object}
- */
- excludeObjectKeysByRelationNames(modelName, modelData) {
- if (!modelData || typeof modelData !== 'object' || Array.isArray(modelData))
- throw new InvalidArgumentError(
- 'The second argument of ModelDefinitionUtils.excludeObjectKeysByRelationNames ' +
- 'should be an Object, but %v given.',
- modelData,
- );
- const relDefs = this.getRelationsDefinitionInBaseModelHierarchy(modelName);
- const relNames = Object.keys(relDefs);
- return excludeObjectKeys(modelData, relNames);
- }
- /**
- * Get model name of property value if defined.
- *
- * @param {string} modelName
- * @param {string} propertyName
- * @returns {undefined|string}
- */
- getModelNameOfPropertyValueIfDefined(modelName, propertyName) {
- if (!modelName || typeof modelName !== 'string')
- throw new InvalidArgumentError(
- 'Parameter "modelName" of ' +
- 'ModelDefinitionUtils.getModelNameOfPropertyValueIfDefined ' +
- 'requires a non-empty String, but %v given.',
- modelName,
- );
- if (!propertyName || typeof propertyName !== 'string')
- throw new InvalidArgumentError(
- 'Parameter "propertyName" of ' +
- 'ModelDefinitionUtils.getModelNameOfPropertyValueIfDefined ' +
- 'requires a non-empty String, but %v given.',
- propertyName,
- );
- // если определение свойства не найдено,
- // то возвращаем undefined
- const propDefs =
- this.getPropertiesDefinitionInBaseModelHierarchy(modelName);
- const propDef = propDefs[propertyName];
- if (!propDef) return undefined;
- // если определение свойства является
- // объектом, то проверяем тип и возвращаем
- // название модели
- if (propDef && typeof propDef === 'object') {
- // если тип свойства является объектом,
- // то возвращаем значение опции "model",
- // или undefined
- if (propDef.type === DataType.OBJECT) return propDef.model || undefined;
- // если тип свойства является массивом,
- // то возвращаем значение опции "itemModel",
- // или undefined
- if (propDef.type === DataType.ARRAY)
- return propDef.itemModel || undefined;
- }
- // если определение свойства не является
- // объектом, то возвращаем undefined
- return undefined;
- }
- }
|