bb-inspired
Version:
Core library for BB-inspired NestJS backend
209 lines • 9.77 kB
JavaScript
"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 __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var SynchronizationService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SynchronizationService = void 0;
const common_1 = require("@nestjs/common");
const schedule_1 = require("@nestjs/schedule");
const prisma_service_1 = require("./prisma.service");
const mongodb_service_1 = require("./mongodb.service");
const logger_1 = require("../../utils/logger");
let SynchronizationService = SynchronizationService_1 = class SynchronizationService {
constructor(options, prismaService, mongodbService) {
this.options = options;
this.prismaService = prismaService;
this.mongodbService = mongodbService;
this.logger = new logger_1.AppLogger(SynchronizationService_1.name);
this.isInitialSyncDone = false;
const syncOptions = options.synchronization || { enabled: false, strategy: 'scheduled', direction: 'pg-to-mongo' };
this.syncEnabled = syncOptions.enabled;
this.syncStrategy = syncOptions.strategy;
this.syncDirection = syncOptions.direction;
this.excludedCollections = syncOptions.excludedCollections || [];
}
async onModuleInit() {
if (!this.syncEnabled) {
this.logger.log('Database synchronization is disabled');
return;
}
if (!this.prismaService || !this.mongodbService) {
this.logger.error('Synchronization requires both Prisma and MongoDB services');
return;
}
this.logger.log(`Database synchronization enabled with strategy: ${this.syncStrategy}, direction: ${this.syncDirection}`);
if (this.syncStrategy === 'scheduled' || this.syncStrategy === 'immediate') {
try {
await this.performInitialSync();
this.isInitialSyncDone = true;
}
catch (error) {
this.logger.error(`Initial synchronization failed: ${error.message}`, error.stack);
}
}
}
async performInitialSync() {
this.logger.log('Starting initial database synchronization');
const prismaModels = await this.getPrismaModels();
if (this.syncDirection === 'pg-to-mongo' || this.syncDirection === 'bidirectional') {
await this.syncPrismaToMongo(prismaModels);
}
if (this.syncDirection === 'mongo-to-pg' || this.syncDirection === 'bidirectional') {
await this.syncMongoToPrisma();
}
this.logger.log('Initial database synchronization completed');
}
async getPrismaModels() {
const dmmf = this.prismaService._baseDmmf;
if (!dmmf || !dmmf.datamodel || !dmmf.datamodel.models) {
return [];
}
return dmmf.datamodel.models
.map(model => model.name)
.filter(name => !this.excludedCollections.includes(name));
}
async syncPrismaToMongo(models) {
for (const model of models) {
try {
const records = await this.prismaService[model].findMany();
if (records.length === 0) {
continue;
}
const collection = this.mongodbService.getCollection(model);
await collection.deleteMany({});
if (records.length > 0) {
await collection.insertMany(records.map(record => ({
...record,
_id: record.id,
_prisma_version: record.version || 1,
_last_sync: new Date()
})));
}
this.logger.verbose(`Synchronized ${records.length} records from PostgreSQL to MongoDB for model ${model}`);
}
catch (error) {
this.logger.error(`Failed to sync model ${model}: ${error.message}`, error.stack);
}
}
}
async syncMongoToPrisma() {
const db = this.mongodbService.getDatabase();
const collections = await db.collections();
for (const collection of collections) {
const collectionName = collection.collectionName;
if (this.excludedCollections.includes(collectionName)) {
continue;
}
try {
if (!this.prismaService[collectionName]) {
continue;
}
const documents = await collection.find({}).toArray();
if (documents.length === 0) {
continue;
}
for (const doc of documents) {
const { _id, _prisma_version, _last_sync, ...data } = doc;
try {
const exists = await this.prismaService[collectionName].findUnique({
where: { id: _id.toString() }
});
if (exists) {
await this.prismaService[collectionName].update({
where: { id: _id.toString() },
data: {
...data,
version: (exists.version || 0) + 1
}
});
}
else {
await this.prismaService[collectionName].create({
data: {
...data,
id: _id.toString(),
version: 1
}
});
}
}
catch (docError) {
this.logger.error(`Failed to sync document ${_id} to PostgreSQL: ${docError.message}`, docError.stack);
}
}
this.logger.verbose(`Synchronized ${documents.length} records from MongoDB to PostgreSQL for model ${collectionName}`);
}
catch (error) {
this.logger.error(`Failed to sync collection ${collectionName}: ${error.message}`, error.stack);
}
}
}
async scheduledSync() {
if (!this.syncEnabled || this.syncStrategy !== 'scheduled' || !this.isInitialSyncDone) {
return;
}
this.logger.log('Starting scheduled database synchronization');
try {
const prismaModels = await this.getPrismaModels();
if (this.syncDirection === 'pg-to-mongo' || this.syncDirection === 'bidirectional') {
await this.syncPrismaToMongo(prismaModels);
}
if (this.syncDirection === 'mongo-to-pg' || this.syncDirection === 'bidirectional') {
await this.syncMongoToPrisma();
}
this.logger.log('Scheduled database synchronization completed');
}
catch (error) {
this.logger.error(`Scheduled synchronization failed: ${error.message}`, error.stack);
}
}
async triggerSync(models = []) {
if (!this.syncEnabled || !this.isInitialSyncDone) {
return;
}
this.logger.log(`Triggering manual synchronization for models: ${models.join(', ') || 'all'}`);
try {
let prismaModels = await this.getPrismaModels();
if (models.length > 0) {
prismaModels = prismaModels.filter(model => models.includes(model));
}
if (this.syncDirection === 'pg-to-mongo' || this.syncDirection === 'bidirectional') {
await this.syncPrismaToMongo(prismaModels);
}
if (this.syncDirection === 'mongo-to-pg' || this.syncDirection === 'bidirectional') {
await this.syncMongoToPrisma();
}
this.logger.log('Manual synchronization completed');
}
catch (error) {
this.logger.error(`Manual synchronization failed: ${error.message}`, error.stack);
throw error;
}
}
};
exports.SynchronizationService = SynchronizationService;
__decorate([
(0, schedule_1.Cron)('0 * * * *'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], SynchronizationService.prototype, "scheduledSync", null);
exports.SynchronizationService = SynchronizationService = SynchronizationService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, common_1.Inject)('DATABASE_OPTIONS')),
__param(1, (0, common_1.Optional)()),
__param(2, (0, common_1.Optional)()),
__metadata("design:paramtypes", [Object, prisma_service_1.PrismaService,
mongodb_service_1.MongodbService])
], SynchronizationService);
//# sourceMappingURL=synchronization.service.js.map