@gsb-core/core
Version:
GSB core services and classes for platform-independent web applications
635 lines • 31.4 kB
JavaScript
"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