@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
JavaScript
;
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