UNPKG

@nodeboot/starter-persistence

Version:

Nodeboot starter package for persistence. Supports data access layer auto-configuration providing features like database initialization, consistency check, entity mapping, repository pattern, transactions, paging, migrations, persistence listeners, persis

263 lines 12.8 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var PersistenceConfiguration_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.PersistenceConfiguration = void 0; const core_1 = require("@nodeboot/core"); const typeorm_1 = require("typeorm"); const context_1 = require("@nodeboot/context"); const PersistenceContext_1 = require("../PersistenceContext"); const di_1 = require("@nodeboot/di"); const MongoDriver_1 = require("typeorm/driver/mongodb/MongoDriver"); const mongodb_1 = require("mongodb"); /** * The PersistenceConfiguration class is responsible for configuring the persistence layer of the application. * It defines beans for DataSource and EntityManager, manages database initialization, * migrations, synchronization, repository bindings, and ensures persistence consistency. * * Main functionalities include: * - Configuring the DataSource bean * - Initializing DataSource and running migrations or synchronization if enabled * - Injecting dependencies into subscribers and MongoDB client * - Binding data repositories to the DI container * - Validating the persistence layer consistency * * @author Manuel Santos <https://github.com/manusant> */ let PersistenceConfiguration = PersistenceConfiguration_1 = class PersistenceConfiguration { /** * Configures and provides the DataSource bean for the persistence layer. * Initializes the DataSource, injects dependencies, runs migrations or database synchronization * based on configuration, and validates database consistency. * * @param {BeansContext} context - Context containing iocContainer, logger, and lifecycleBridge. * @returns {DataSource} The initialized and configured TypeORM DataSource instance. * * @throws {Error} If DataSource initialization fails. * * @author Manuel Santos <https://github.com/manusant> */ dataSource({ iocContainer, logger, lifecycleBridge }) { logger.info("Configuring persistence DataSource"); const datasourceConfig = iocContainer.get("datasource-config"); const entities = PersistenceContext_1.PersistenceContext.get().repositories.map(repository => repository.entity); const dataSource = new typeorm_1.DataSource({ ...datasourceConfig, entities, }); dataSource .initialize() .then(() => { logger.info("Persistence DataSource successfully initialized"); const { synchronizeDatabase, migrationsRun } = PersistenceContext_1.PersistenceContext.get(); if (datasourceConfig.type === "mongodb") { PersistenceConfiguration_1.injectMongoClient(logger, dataSource, iocContainer); } // Inject dependencies into Subscriber instances PersistenceConfiguration_1.setupInjection(logger, dataSource, iocContainer); // Bind Data Repositories if DI container is configured PersistenceConfiguration_1.bindDataRepositories(logger); // For SQL like databases if (datasourceConfig.type !== "mongodb") { const initializationPromises = []; // Run migrations if enabled if (migrationsRun) { initializationPromises.push(PersistenceConfiguration_1.runMigration(logger, dataSource)); } if (synchronizeDatabase) { initializationPromises.push(PersistenceConfiguration_1.runDatabaseSync(logger, dataSource)); } // Validate database consistency Promise.all(initializationPromises).then(_ => PersistenceConfiguration_1.ensureDatabase(logger, dataSource)); } }) .catch(err => { logger.error("Error during Persistence DataSource initialization:", err); process.exit(1); }) .finally(() => { lifecycleBridge.publish("persistence.started"); }); logger.info("DataSource bean provided successfully"); return dataSource; } /** * Provides an instance of the EntityManager bean for database operations. * * @param {BeansContext} context - Context containing iocContainer and logger. * @returns {EntityManager} The EntityManager instance from the configured DataSource. * * @author Manuel Santos <https://github.com/manusant> */ entityManager({ iocContainer, logger }) { logger.info("Providing EntityManager"); const dataSource = iocContainer.get(typeorm_1.DataSource); logger.info("EntityManager bean provided successfully"); return dataSource.manager; } /** * Sets up dependency injection for persistence event subscribers. * Injects required dependencies into subscriber instances based on metadata. * * @param {Logger} logger - Logger instance for logging messages. * @param {DataSource} dataSource - The DataSource containing subscribers. * @param {IocContainer<unknown>} iocContainer - The IoC container for resolving dependencies. * * @author Manuel Santos <https://github.com/manusant> */ static setupInjection(logger, dataSource, iocContainer) { const subscribers = dataSource.subscribers; logger.info(`Setting up dependency injection for ${subscribers.length} persistence event subscribers`); for (const subscriber of subscribers) { for (const fieldToInject of Reflect.getMetadata(di_1.REQUIRES_FIELD_INJECTION_KEY, subscriber) || []) { // Extract type metadata for field injection const propertyType = Reflect.getMetadata("design:type", subscriber, fieldToInject); subscriber[fieldToInject] = iocContainer.get(propertyType); } } logger.info(`${subscribers.length} persistence event subscribers successfully injected`); } /** * Injects the MongoDB client into the IoC container. * Retrieves MongoClient from the TypeORM MongoDriver and registers it for DI. * * @param {Logger} logger - Logger instance for logging messages. * @param {DataSource} dataSource - TypeORM DataSource instance. * @param {IocContainer<unknown>} iocContainer - The IoC container where MongoClient will be registered. * * @author Manuel Santos <https://github.com/manusant> */ static injectMongoClient(logger, dataSource, iocContainer) { logger.info(`Setting up injection for MongoClient`); const mongoDriver = dataSource.driver; if (mongoDriver instanceof MongoDriver_1.MongoDriver) { // Force set query runner since TypeORM is not setting it for MongoDB dataSource.manager.queryRunner = mongoDriver.queryRunner; // Retrieve the MongoClient instance from the MongoDriver's query runner const mongoClient = mongoDriver.queryRunner?.databaseConnection; if (mongoClient) { // Register MongoClient in the IoC container iocContainer.set(mongodb_1.MongoClient, mongoClient); logger.info(`MongoClient was set to the DI container successfully. You can now inject it in your beans`); } else { logger.warn(`Not able to inject MongoClient. Mongo client not connected`); } } else { logger.error(`Invalid MongoDriver. Please configure mongodb properly`); } } /** * Runs database migrations using the provided DataSource. * Logs migration success or failure. * * @param {Logger} logger - Logger instance for logging messages. * @param {DataSource} dataSource - The DataSource to run migrations on. * * @author Manuel Santos <https://github.com/manusant> */ static async runMigration(logger, dataSource) { logger.info("Running migrations"); try { const migrations = await dataSource.runMigrations(); logger.info(`${migrations.length} migration was successfully executed`); } catch (error) { logger.info(`Migrations failed due to:`, error); } } /** * Binds persistence repositories to the IoC container. * Throws an error if the DI container is not configured. * * @param {Logger} logger - Logger instance for logging messages. * * @throws {Error} When diOptions or IOC container is missing. * * @author Manuel Santos <https://github.com/manusant> */ static bindDataRepositories(logger) { const context = context_1.ApplicationContext.get(); if (context.diOptions) { logger.info(`Binding persistence repositories`); context.repositoriesAdapter?.bind(context.diOptions.iocContainer); } else { throw new Error("diOptions with an IOC Container is required for Data Repositories"); } } /** * Runs database synchronization. * Logs success or errors. * * @param {Logger} logger - Logger instance for logging messages. * @param {DataSource} dataSource - The DataSource to synchronize. * * @author Manuel Santos <https://github.com/manusant> */ static async runDatabaseSync(logger, dataSource) { logger.info(`Starting database synchronization`); try { await dataSource.synchronize(); logger.info(`Database synchronization was successful`); } catch (error) { logger.error(`Error running database synchronization:`, error); } } /** * Validates database consistency by comparing registered entities with existing tables. * Logs inconsistencies and exits process if found. * * @param {Logger} logger - Logger instance for logging messages. * @param {DataSource} dataSource - The DataSource used to create a query runner. * * @author Manuel Santos <https://github.com/manusant> */ static async ensureDatabase(logger, dataSource) { const queryRunner = dataSource.createQueryRunner(); try { const tables = await queryRunner.getTables(); const entities = dataSource.entityMetadatas; logger.info(`Database validation: Running consistency validation for ${tables.length}-tables/${entities.length}-entities.`); const existingEntities = entities.filter(entity => tables.find(table => table.name.includes(entity.tableName)) !== undefined); if (existingEntities.length !== entities.length) { logger.error(`Inconsistent persistence layer: There are ${entities.length} entities registered but ${existingEntities.length} are in the database.`); logger.warn(`Please enable database sync through "persistence.synchronize: true" or activate migrations through "persistence.migrationsRun: true" to properly setup the database. This is important in order to avoid runtime errors in the application`); process.exit(1); } logger.info(`Basic database consistency validation passed`); } catch (error) { logger.error(`Error validating database:`, error); process.exit(1); } } }; exports.PersistenceConfiguration = PersistenceConfiguration; __decorate([ (0, core_1.Bean)(), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", typeorm_1.DataSource) ], PersistenceConfiguration.prototype, "dataSource", null); __decorate([ (0, core_1.Bean)(), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", typeorm_1.EntityManager) ], PersistenceConfiguration.prototype, "entityManager", null); exports.PersistenceConfiguration = PersistenceConfiguration = PersistenceConfiguration_1 = __decorate([ (0, core_1.Configuration)() ], PersistenceConfiguration); //# sourceMappingURL=PersistenceConfiguration.js.map