UNPKG

@gsb-core/core

Version:

GSB core services and classes for platform-independent web applications

635 lines 31.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EntityDefService = void 0; const gsb_entity_def_model_1 = require("../../models/gsb-entity-def.model"); const gsb_entity_service_1 = require("./gsb-entity.service"); const query_params_1 = require("../../types/query-params"); const query_1 = require("../../types/query"); const gsb_config_1 = require("../../config/gsb-config"); const gsb_utils_1 = require("../../utils/gsb-utils"); const gsb_cache_service_1 = require("../cache/gsb-cache.service"); const entity_def_validator_service_1 = require("./entity-def-validator.service"); /** * Service for managing Entity Definitions (Database Tables) in the application */ class EntityDefService { constructor(useCache = true) { this.ENTITY_NAME = 'GsbEntityDef'; this.entityService = gsb_entity_service_1.GsbEntityService.getInstance(useCache); this.cacheService = gsb_cache_service_1.GsbCacheService.getInstance(); this.validatorService = entity_def_validator_service_1.EntityDefValidatorService.getInstance(); this.useCache = useCache; } static getInstance(useCache = true) { if (!EntityDefService.instance) { EntityDefService.instance = new EntityDefService(useCache); } return EntityDefService.instance; } /** * Get an entity definition by ID or name * @param def The entity definition to get * @param token Optional custom token to use for this request * @param tenantCode Optional custom tenant code to use for this request * @returns The entity definition or null if not found */ async getEntityDef(def, token, tenantCode) { if (!def.id && !def.name) { throw new Error('Entity definition must have an id or name'); } if (this.useCache) { const entityDef = await this.cacheService.getEntityDef(def, token, tenantCode); return entityDef; } else { const entityDefResponse = await this.entityService.get(new query_params_1.QueryParams(this.ENTITY_NAME).type(query_params_1.QueryType.FullNonPersonal).filter(def.id ? new query_1.Filter('id', def.id) : new query_1.Filter('name', def.name)).include(p => p.properties).self, token || (0, gsb_config_1.getGsbToken)(), tenantCode || (0, gsb_config_1.getGsbTenantCode)()); return entityDefResponse === null || entityDefResponse === void 0 ? void 0 : entityDefResponse.entity; } } /** * Search for entity definitions * @param searchTerm The search term * @param page The page number (starting from 1) * @param pageSize The number of items per page * @param token Optional custom token to use for this request * @param tenantCode Optional custom tenant code to use for this request * @returns An array of entity definitions and the total count */ async searchEntityDefs(searchTerm, page = 1, pageSize = 10, includeSystemDefs = false, token, tenantCode) { try { if (page <= 0) page = 1; if (pageSize <= 0) pageSize = 10; // If all definitions are cached, we can perform the search locally for better performance if (this.useCache) { const allDefinitions = this.cacheService.getAllCachedEntityDefinitions(); // Perform local filtering const lowercaseSearchTerm = searchTerm.toLowerCase(); let filteredResults = allDefinitions.filter(def => (def.name && def.name.toLowerCase().includes(lowercaseSearchTerm)) || (def.title && def.title.toLowerCase().includes(lowercaseSearchTerm)) || (def.description && def.description.toLowerCase().includes(lowercaseSearchTerm))); if (!includeSystemDefs) { filteredResults = filteredResults.filter(def => !def.isSystem); } // Apply pagination const startIndex = (page - 1) * pageSize; const endIndex = startIndex + pageSize; const paginatedResults = filteredResults.slice(startIndex, endIndex).map(def => ({ name: def.name, title: def.title, description: def.description, dbTableName: def.dbTableName, id: def.id })); return { entityDefs: paginatedResults, totalCount: filteredResults.length }; } // If not cached, or search term is empty, use server-side search const query = new query_params_1.QueryParams(this.ENTITY_NAME); query.searchText = searchTerm; query.startIndex = (page - 1) * pageSize; query.count = pageSize; query.calcTotalCount = true; query.select(['name', 'title', 'description', 'dbTableName', 'id']); if (!includeSystemDefs) { //exclude nulls and false query.filter(new query_1.Filter('isSystem', true).not()); } const response = await this.entityService.query(query, token || (0, gsb_config_1.getGsbToken)(), tenantCode || (0, gsb_config_1.getGsbTenantCode)()); return { entityDefs: (response.entities || []), totalCount: response.totalCount || 0 }; } catch (error) { console.error('Error searching entity definitions:', error); // Log the error in more detail if (error instanceof Error) { console.error('Error details:', error.message); console.error('Error stack:', error.stack); } return { entityDefs: [], totalCount: 0 }; } } /** * Create a new entity definition with enhanced validation and advanced property mapping * @param entityDef The entity definition to create * @param token Optional custom token to use for this request * @param tenantCode Optional custom tenant code to use for this request * @returns The created entity definition ID * @throws Error with message from API if the request fails */ async createEntityDef(entityDef, token, tenantCode) { try { // Validate entity definition this.validatorService.validateEntityDef(entityDef, true); // Check name uniqueness if (!entityDef.name) { throw new Error('Entity name is required'); } const isNameUnique = await this.validatorService.isEntityNameUnique(entityDef.name, undefined, token, tenantCode); if (!isNameUnique) { throw new Error('Entity name is already taken: ' + entityDef.name); } // Check table name uniqueness if provided if (entityDef.dbTableName) { const isTableNameUnique = await this.validatorService.isTableNameUnique(entityDef.dbTableName, undefined, token, tenantCode); if (!isTableNameUnique) { throw new Error('Database table name is already taken: ' + entityDef.dbTableName); } } // Normalize and generate missing values const normalizedEntityDef = this.validatorService.normalizeEntityDef(entityDef, tenantCode !== null && tenantCode !== void 0 ? tenantCode : (0, gsb_config_1.getGsbTenantCode)()); const request = { entDefName: this.ENTITY_NAME, entity: normalizedEntityDef }; const response = await this.entityService.save(request, token || (0, gsb_config_1.getGsbToken)(), tenantCode || (0, gsb_config_1.getGsbTenantCode)()); if (!response) { throw new Error('No response received from the server: ' + entityDef.name + ' ' + entityDef.id); } if (!response.id) { const errorMsg = response.message || 'Failed to create entity definition: ' + entityDef.name + ' ' + entityDef.id; throw new Error(errorMsg); } normalizedEntityDef.id = response.id; if (this.useCache) { this.cacheService.setEntityDef(normalizedEntityDef, token || (0, gsb_config_1.getGsbToken)(), tenantCode || (0, gsb_config_1.getGsbTenantCode)()); } return normalizedEntityDef; } catch (error) { console.error('Error creating entity definition:' + entityDef.name + ' ' + entityDef.id, error); throw error; } } async createOrUpdateEntityDef(entityDef, token, tenantCode) { let existingDef; if (this.useCache) { const allDefs = this.cacheService.getAllCachedEntityDefinitions(); existingDef = allDefs.find(def => def.name === entityDef.name); } else if (entityDef.name || entityDef.id) { existingDef = await this.getEntityDef(entityDef, token, tenantCode); } else { existingDef; } if (existingDef) { return this.updateEntityDef(entityDef, token, tenantCode); } return this.createEntityDef(entityDef, token, tenantCode); } /** * Get default properties for a new entity definition. * Note: lastUpdateDate and createDate properties are included here for display in the UI * but the actual values of these fields are automatically managed by the GSB system. * * @param defName The name of the entity definition * @returns Array of default GsbProperty objects */ getDefaultProperties(defName) { return [ { "name": "id", "title": "Id", "isSearchable": false, "isUnique": true, "isPrimaryKey": true, "isIndexed": true, "formModes": 263172, "definition_id": "5C0AA76F-9C32-4E7E-A4BC-B56E93877883", "orderNumber": 0, "description": "The unique identifier for the entity" }, { "name": "title", "title": "Title", "isRequired": false, "isSearchable": true, "isMultiLingual": true, "listScreens": 7, "definition_id": "C6C34BF3-F51B-4E69-A689-B09847BE74B9", "formModes": 66049, "orderNumber": 2, "description": "The display title of the entity" }, { "name": "createdBy", "title": "Created By", "listScreens": 7, "formModes": 262658, "definition_id": "924ACBA8-58C5-4881-940D-472EC01EBA5F", "refType": 2, "refEntPropName": ("created" + defName), "refEntDef_id": "98CDC0E8-58D6-4923-B22E-591430E52606", "orderNumber": 3, "description": "The user who created the entity, automatically set by the system, can not be changed" }, { "name": "lastUpdatedBy", "title": "Last Updated By", "formModes": 262658, "definition_id": "924ACBA8-58C5-4881-940D-472EC01EBA5F", "refType": 2, "refEntPropName": ("updated" + defName), "refEntDef_id": "98CDC0E8-58D6-4923-B22E-591430E52606", "orderNumber": 4, "description": "The user who last updated the entity, automatically set by the system, can not be changed" }, { "name": "lastUpdateDate", "title": "Last Update Date", "formModes": 262658, "definition_id": "12E647E0-EBD2-4EC2-A4E3-82C1DFE07DA2", "orderNumber": 5, "description": "The date and time the entity was last updated, automatically set by the system, can not be changed" }, { "name": "createDate", "title": "Create Date", "listScreens": gsb_entity_def_model_1.ScreenType.All, "formModes": 262658, "definition_id": "12E647E0-EBD2-4EC2-A4E3-82C1DFE07DA2", "orderNumber": 6, "description": "The date and time the entity was created, automatically set by the system, can not be changed" } ]; } /** * Update an existing entity definition * @param entityDef The entity definition to update * @param token Optional custom token to use for this request * @param tenantCode Optional custom tenant code to use for this request * @returns True if updated successfully, false otherwise * @throws Error with message from API if the request fails */ async updateEntityDef(entityDef, token, tenantCode) { try { let updatedDef = { ...entityDef }; if (!updatedDef.id && !updatedDef.name) { throw new Error('Entity definition ID or name is required for updates'); } // Validate entity definition this.validatorService.validateEntityDef(entityDef, false); // Check name uniqueness (excluding current entity) if (entityDef.name) { const isNameUnique = await this.validatorService.isEntityNameUnique(entityDef.name, entityDef.id || undefined, token, tenantCode); if (!isNameUnique) { throw new Error('Entity name is already taken: ' + entityDef.name); } } // Check table name uniqueness if provided (excluding current entity) if (entityDef.dbTableName) { const isTableNameUnique = await this.validatorService.isTableNameUnique(entityDef.dbTableName, entityDef.id || undefined, token, tenantCode); if (!isTableNameUnique) { throw new Error('Database table name is already taken: ' + entityDef.dbTableName); } } const request = { entDefName: this.ENTITY_NAME, entity: entityDef }; const response = await this.entityService.save(request, token || (0, gsb_config_1.getGsbToken)(), tenantCode || (0, gsb_config_1.getGsbTenantCode)()); if (!response) { throw new Error('No response received from the server: ' + entityDef.name + ' ' + entityDef.id); } if (!response.id) { const errorMsg = response.message || 'Failed to update entity definition: ' + entityDef.name + ' ' + entityDef.id; throw new Error(errorMsg); } entityDef.id = response.id; if (this.useCache) { const cachedDef = await this.cacheService.getEntityDef(entityDef, token, tenantCode); if (cachedDef) { Object.assign(cachedDef, entityDef); } } return updatedDef; } catch (error) { console.error('Error updating entity definition:' + entityDef.name + ' ' + entityDef.id, error); throw error; } } /** * Delete an entity definition (soft delete) * @param id The entity definition ID to delete * @param token Optional custom token to use for this request * @param tenantCode Optional custom tenant code to use for this request * @returns True if deleted successfully * @throws Error with message from API if the request fails */ async deleteEntityDef(id, token, tenantCode) { try { // Get existing entity definition if (!id) { throw new Error('Entity definition ID is required'); } const request = { entDefName: this.ENTITY_NAME, entityId: id }; const response = await this.entityService.delete(request, token || (0, gsb_config_1.getGsbToken)(), tenantCode || (0, gsb_config_1.getGsbTenantCode)()); if (!response) { throw new Error('No response received from the server'); } if (!response.affectedRowCount) { const errorMsg = response.message || 'Failed to delete entity definition'; throw new Error(errorMsg); } if (this.useCache) { this.cacheService.removeEntityDef(id, token, tenantCode); } return true; } catch (error) { console.error('Error deleting entity definition:' + id, error); throw error; } } /** * Check uniqueness of name and dbTableName * @param nameToCheck The name to check for uniqueness * @param token Optional custom token to use for this request * @param tenantCode Optional custom tenant code to use for this request * @returns Matching entity defs with only name and dbTableName fields */ async checkNameUniqueness(nameToCheck, token, tenantCode) { try { const query = new query_params_1.QueryParams(this.ENTITY_NAME); // Set search filter if (nameToCheck) { query.filter('name', nameToCheck); } // Only select name and dbTableName fields for efficiency query.select(['name', 'dbTableName', 'id']); // Set pagination - only need a few results query.startIndex = 0; query.count = 20; query.calcTotalCount = true; const response = await this.entityService.query(query, token || (0, gsb_config_1.getGsbToken)(), tenantCode || (0, gsb_config_1.getGsbTenantCode)()); return { entityDefs: (response.entities || []), totalCount: response.totalCount || 0 }; } catch (error) { console.error('Error checking name uniqueness:' + nameToCheck, error); return { entityDefs: [], totalCount: 0 }; } } /** * Check if reference property name is already used in the referenced entity * @param refPropName Reference property name to check * @param refEntityId ID of the referenced entity * @returns Promise with validation result */ async checkRefPropNameUniqueness(refPropName, refEntityId) { var _a; if (!refPropName || !refEntityId) { return { isValid: true, validationMessage: "" }; } try { const req = new query_params_1.QueryParams("GsbProperty").filter(p => p.ownerEntDef_id, refEntityId).filter(p => p.name, refPropName).select('name'); const response = await this.entityService.query(req); if ((_a = response.entities) === null || _a === void 0 ? void 0 : _a.length) { return { isValid: false, validationMessage: `GsbProperty name "${refPropName}" already exists in the referenced entity.` }; } return { isValid: true, validationMessage: "" }; } catch (error) { console.error("Error checking reference property name:" + refPropName + ' ' + refEntityId, error); // Default to valid if we can't check return { isValid: true, validationMessage: "" }; } } /** * Add a column to an existing entity definition * @param entityDefId The entity definition ID * @param property The column properties * @param token Optional custom token to use for this request * @param tenantCode Optional custom tenant code to use for this request * @returns true if successful */ async addProperty(property, entityDef, token, tenantCode) { var _a; try { let saveProperty = property; let cachedProperty = property; let cachedDef = entityDef || { id: property.ownerEntDef_id }; if (!property) { throw new Error('Column properties are required'); } if (!entityDef && !property.ownerEntDef_id) { throw new Error('Either entityDef or ownerEntDef_id in property is required'); } // Validate the property this.validatorService.validateProperty(property, true); if (!property.ownerEntDef_id && (entityDef === null || entityDef === void 0 ? void 0 : entityDef.id)) { property.ownerEntDef_id = entityDef.id; } if (this.useCache) { // Get existing entity definition cachedDef = await this.getEntityDef(cachedDef, token, tenantCode) || null; if (cachedDef) { // Add defaults to the column properties saveProperty = this.validatorService.normalizeProperty({ ...property, orderNumber: property.orderNumber || Math.max(...(((_a = cachedDef.properties) === null || _a === void 0 ? void 0 : _a.map(p => p.orderNumber || 0)) || [0])) + 1 }); } } else { // Check if property name is unique within the entity if (property.name && entityDef && !this.validatorService.isPropertyNameUnique(entityDef, property.name)) { throw new Error(`Column "${property.name}" already exists`); } } // Create save request for the property const request = { entDefName: "GsbProperty", entity: saveProperty, }; const response = await this.entityService.save(request, token || (0, gsb_config_1.getGsbToken)(), tenantCode || (0, gsb_config_1.getGsbTenantCode)()); if (!response || !response.id) { throw new Error('Failed to add column'); } cachedProperty = { ...cachedProperty, ...saveProperty }; cachedProperty.id = response.id; if (this.useCache) { cachedProperty = { ...cachedProperty, ...saveProperty }; if (cachedDef && cachedDef.properties && !cachedDef.properties.find(p => p.id === cachedProperty.id)) { cachedDef.properties = [...(cachedDef.properties || []), cachedProperty]; } } return cachedProperty; } catch (error) { console.error('Error adding column:' + (property === null || property === void 0 ? void 0 : property.name) + ' ' + (property === null || property === void 0 ? void 0 : property.id), error); throw error; } } /** * Update a column in an existing entity definition * @param entityDefId The entity definition ID * @param property The column properties * @param token Optional custom token to use for this request * @param tenantCode Optional custom tenant code to use for this request * @returns true if successful */ async updateProperty(property, entityDef, token, tenantCode) { var _a; try { let saveProperty = property; let cachedProperty = property; let cachedDef = entityDef || { id: property.ownerEntDef_id }; if (!property) { throw new Error('Column properties are required'); } // Validate the property this.validatorService.validateProperty(property, false); if (!property.ownerEntDef_id && (entityDef === null || entityDef === void 0 ? void 0 : entityDef.id)) { property.ownerEntDef_id = entityDef.id; } if (this.useCache) { // Get existing entity definition cachedDef = await this.getEntityDef(cachedDef, token, tenantCode) || null; if (cachedDef) { cachedProperty = ((_a = cachedDef.properties) === null || _a === void 0 ? void 0 : _a.find(p => p.id === property.id)) || null; } } else { // Check if property name is unique within the entity if (property.name && !this.validatorService.isPropertyNameUnique(cachedDef, property.name, property.id)) { throw new Error(`Column "${property.name}" already exists`); } } // Create save request for the property const request = { entDefName: "GsbProperty", entity: saveProperty, }; const response = await this.entityService.save(request, token || (0, gsb_config_1.getGsbToken)(), tenantCode || (0, gsb_config_1.getGsbTenantCode)()); if (!response || !response.id) { throw new Error('Failed to add column'); } cachedProperty = { ...cachedProperty, ...saveProperty }; cachedProperty.id = response.id; if (this.useCache) { cachedProperty = { ...cachedProperty, ...saveProperty }; if (cachedDef && cachedDef.properties && !cachedDef.properties.find(p => p.id === (cachedProperty === null || cachedProperty === void 0 ? void 0 : cachedProperty.id))) { cachedDef.properties = [...(cachedDef.properties || []), cachedProperty]; } } return cachedProperty; } catch (error) { console.error('Error updating column:' + (property === null || property === void 0 ? void 0 : property.name) + ' ' + (property === null || property === void 0 ? void 0 : property.id), error); throw error; } } /** * Remove a column from an entity definition * @param columnIdentifier The column ID or name to remove * @param entityDefId The entity definition ID * @param token Optional custom token to use for this request * @param tenantCode Optional custom tenant code to use for this request * @returns true if successful */ async removeProperty(property, entityDef, token, tenantCode) { var _a, _b; try { if (!property) { throw new Error('Property is required'); } let columnIdentifier = property.id; if (!columnIdentifier) { let resp = await this.entityService.query(new query_params_1.QueryParams("GsbProperty"). filter(p => p.name, property.name).filter(p => p.ownerEntDef_id, entityDef === null || entityDef === void 0 ? void 0 : entityDef.id).select('id')); if ((_a = resp.entities) === null || _a === void 0 ? void 0 : _a.length) { columnIdentifier = resp.entities[0].id; } else { throw new Error('Either ownerEntDef_id and name, or property id is required'); } } // Create remove request with required properties const request = { entDefName: "GsbProperty", entityId: columnIdentifier, }; // Remove using removeMappedItems const response = await this.entityService.delete(request, token || (0, gsb_config_1.getGsbToken)(), tenantCode || (0, gsb_config_1.getGsbTenantCode)()); if (!response || !response.affectedRowCount) { throw new Error('Failed to remove column'); } if (this.useCache) { let cachedDef; if (property.ownerEntDef_id) { cachedDef = await this.getEntityDef({ id: property.ownerEntDef_id }, token, tenantCode); } else { let defs = await this.getAllDefinitions(token, tenantCode); cachedDef = defs.find(d => { var _a; return (_a = d.properties) === null || _a === void 0 ? void 0 : _a.find(p => p.id === columnIdentifier); }) || null; } // Remove from cache if (cachedDef) { cachedDef.properties = (_b = cachedDef.properties) === null || _b === void 0 ? void 0 : _b.filter(p => p.id !== columnIdentifier); } } return true; } catch (error) { console.error('Error removing column:' + (property === null || property === void 0 ? void 0 : property.name) + ' ' + (property === null || property === void 0 ? void 0 : property.id), error); throw error; } } /** * Get all entity definitions metadata with their properties * * This method caches the results and returns from cache if available * @param token Optional custom token to use for this request * @param tenantCode Optional custom tenant code to use for this request * @returns Array of entity definitions with their properties */ async getAllDefinitions(token, tenantCode) { try { return await this.cacheService.getAllDefinitions(token, tenantCode); } catch (error) { console.error('Error getting all entity definitions metadata:', error); return []; } } } exports.EntityDefService = EntityDefService; // Centralized property definitions EntityDefService.PROPERTY_DEFINITIONS = [ { id: gsb_utils_1.GsbUtils.StringPropertyDefinitionId, name: "String", title: "String", dataType: gsb_entity_def_model_1.DataType.StringUnicode }, { id: gsb_utils_1.GsbUtils.NumberPropertyDefinitionId, name: "Number", title: "Number", dataType: gsb_entity_def_model_1.DataType.Decimal }, { id: gsb_utils_1.GsbUtils.BooleanPropertyDefinitionId, name: "Boolean", title: "Boolean", dataType: gsb_entity_def_model_1.DataType.Bool }, { id: gsb_utils_1.GsbUtils.DatePropertyDefinitionId, name: "Date", title: "Date", dataType: gsb_entity_def_model_1.DataType.DateTime }, { id: gsb_utils_1.GsbUtils.ReferencePropertyDefinitionId, name: "Reference", title: "Reference", dataType: gsb_entity_def_model_1.DataType.Reference }, { id: gsb_utils_1.GsbUtils.EnumPropertyDefinitionId, name: "Enum", title: "Enum (Select List)", dataType: gsb_entity_def_model_1.DataType.Enum }, { id: gsb_utils_1.GsbUtils.IDPropertyDefinitionId, name: "ID", title: "ID", dataType: gsb_entity_def_model_1.DataType.Guid }, { id: gsb_utils_1.GsbUtils.RichTextPropertyDefinitionId, name: "RichText", title: "RichText", dataType: gsb_entity_def_model_1.DataType.Raw }, { id: gsb_utils_1.GsbUtils.EmailPropertyDefinitionId, name: "Email", title: "Email", dataType: gsb_entity_def_model_1.DataType.StringUnicode }, { id: gsb_utils_1.GsbUtils.PasswordPropertyDefinitionId, name: "Password", title: "Password", dataType: gsb_entity_def_model_1.DataType.StringUnicode } ]; //# sourceMappingURL=entity-def.service.js.map