UNPKG

@kenniy/godeye-data-contracts

Version:

Enterprise-grade base repository architecture for GOD-EYE microservices with zero overhead and maximum code reuse

336 lines (335 loc) 13.5 kB
"use strict"; /** * Mongoose Aggregate Repository - MongoDB Implementation * * Extends BaseMongooseRepository with unified aggregation capabilities * Converts ComplexQueryConfig to MongoDB aggregation pipelines * * Key Features: * - Native MongoDB aggregation pipeline optimization * - Single query with $facet for parallel data+count * - Complex joins using $lookup stages * - Aggregation operations with $group stages * - Enterprise performance monitoring */ 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); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MongooseAggregateRepository = void 0; const common_1 = require("@nestjs/common"); const mongoose_1 = require("mongoose"); const base_mongoose_repository_1 = require("./base-mongoose.repository"); /** * Mongoose implementation of unified aggregation repository * Converts universal ComplexQueryConfig to MongoDB aggregation pipelines */ let MongooseAggregateRepository = class MongooseAggregateRepository extends base_mongoose_repository_1.BaseMongooseRepository { constructor(model) { super(model); this.model = model; } // ============================================================================ // CORE AGGREGATION METHODS // ============================================================================ /** * Execute MongoDB aggregation pipeline directly * Enhanced with performance monitoring and optimization */ async aggregate(pipeline) { const startTime = performance.now(); try { // Enterprise optimization: Enable disk usage for large aggregations const results = await this.model.aggregate(pipeline, { allowDiskUse: true, maxTimeMS: 30000, // 30 second timeout }).exec(); this.logQueryMetrics('mongooseAggregate', performance.now() - startTime, { stages: pipeline.length, pipelinePreview: JSON.stringify(pipeline).substring(0, 200), }); return results; } catch (error) { this.handleQueryError('mongooseAggregate', error, { pipeline }); throw error; } } /** * Execute aggregation with pagination using MongoDB's $facet * Single query for both data and total count - optimal performance */ async aggregateWithPagination(pipeline, pagination) { const startTime = performance.now(); const { page = 1, limit = 20 } = pagination; try { // Build paginated pipeline with $facet for parallel execution const paginatedPipeline = [ ...pipeline, { $facet: { // Data pipeline with pagination data: [ { $skip: (page - 1) * limit }, { $limit: limit } ], // Count pipeline for total totalCount: [ { $count: 'count' } ] } } ]; const [result] = await this.model.aggregate(paginatedPipeline, { allowDiskUse: true, maxTimeMS: 30000, }).exec(); const items = result.data || []; const total = result.totalCount[0]?.count || 0; const queryTime = performance.now() - startTime; this.logQueryMetrics('mongooseAggregateWithPagination', queryTime, { stages: pipeline.length, total, returned: items.length, page, limit, }); return this.formatPaginatedResult(items, total, pagination); } catch (error) { this.handleQueryError('mongooseAggregateWithPagination', error, { pipeline, pagination }); throw error; } } // ============================================================================ // TYPEORM COMPATIBILITY METHODS // ============================================================================ /** * Execute query builder function (TypeORM compatibility) * Converts query builder concepts to MongoDB aggregation pipeline */ async query(builder) { // For Mongoose, we don't have query builders, so we'll use a simple find // This is mainly for API compatibility with TypeORM console.warn('query() method is not optimized for MongoDB. Use aggregate() instead.'); try { const results = await this.model.find({}).exec(); return results; } catch (error) { this.handleQueryError('mongooseQuery', error, { builder: 'QueryBuilder not supported' }); throw error; } } /** * Execute query builder with pagination (TypeORM compatibility) */ async queryWithPagination(builder, pagination) { console.warn('queryWithPagination() method is not optimized for MongoDB. Use aggregateWithPagination() instead.'); const { page = 1, limit = 20 } = pagination; const skip = (page - 1) * limit; try { const [items, total] = await Promise.all([ this.model.find({}).skip(skip).limit(limit).exec(), this.model.countDocuments({}).exec() ]); return this.formatPaginatedResult(items, total, pagination); } catch (error) { this.handleQueryError('mongooseQueryWithPagination', error, { pagination }); throw error; } } // ============================================================================ // UNIVERSAL COMPLEX QUERY METHOD // ============================================================================ /** * Universal complex query - converts ComplexQueryConfig to MongoDB pipeline * This is the main method that replaces multiple separate queries */ async complexQuery(config) { const startTime = performance.now(); try { // Build MongoDB aggregation pipeline from config const pipeline = this.buildMongoosePipeline(config); // Execute with or without pagination const result = config.pagination ? await this.aggregateWithPagination(pipeline, config.pagination) : { items: await this.aggregate(pipeline), total: 0, page: 1, limit: 0, totalPages: 1, hasNext: false, hasPrev: false, }; this.logQueryMetrics('mongooseComplexQuery', performance.now() - startTime, { joins: config.joins?.length || 0, aggregations: config.aggregations?.length || 0, conditions: Object.keys(config.conditions || {}).length, paginated: !!config.pagination, }); return result; } catch (error) { this.handleQueryError('mongooseComplexQuery', error, { config }); throw error; } } // ============================================================================ // MONGODB PIPELINE BUILDER // ============================================================================ /** * Build MongoDB aggregation pipeline from ComplexQueryConfig * Converts universal config to MongoDB-specific pipeline stages */ buildMongoosePipeline(config) { const pipeline = []; // 1. Add match stage for conditions (WHERE equivalent) if (config.conditions && Object.keys(config.conditions).length > 0) { pipeline.push({ $match: config.conditions }); } // 2. Add lookup stages for joins (JOIN equivalent) if (config.joins?.length) { config.joins.forEach(join => { pipeline.push(this.buildLookupStage(join)); }); } // 3. Add unwind stages for joined arrays (flatten joined data) if (config.joins?.length) { config.joins.forEach(join => { pipeline.push({ $unwind: { path: `$${join.as}`, preserveNullAndEmptyArrays: true // LEFT JOIN equivalent } }); }); } // 4. Add group stage for aggregations (GROUP BY equivalent) if (config.aggregations?.length || config.groupBy?.length) { pipeline.push(this.buildGroupStage(config.aggregations, config.groupBy)); } // 5. Add project stage for field selection (SELECT equivalent) if (config.select?.length) { const projection = {}; config.select.forEach(field => { projection[field] = 1; }); pipeline.push({ $project: projection }); } // 6. Add sort stage (ORDER BY equivalent) if (config.sort && Object.keys(config.sort).length > 0) { const mongoSort = {}; Object.entries(config.sort).forEach(([key, value]) => { mongoSort[key] = value === 'ASC' ? 1 : value === 'DESC' ? -1 : value; }); pipeline.push({ $sort: mongoSort }); } return pipeline; } /** * Build MongoDB $lookup stage from JoinConfig */ buildLookupStage(join) { return { $lookup: { from: join.collection, localField: join.localField, foreignField: join.foreignField, as: join.as } }; } /** * Build MongoDB $group stage from aggregation configs */ buildGroupStage(aggregations, groupBy) { const groupStage = { $group: { _id: this.buildGroupByExpression(groupBy) } }; // Add aggregation operations aggregations?.forEach(agg => { switch (agg.operation) { case 'COUNT': if (agg.conditions) { // Conditional count groupStage.$group[agg.alias] = { $sum: { $cond: [{ $eq: [`$${agg.field}`, agg.conditions[agg.field]] }, 1, 0] } }; } else { // Simple count groupStage.$group[agg.alias] = { $sum: 1 }; } break; case 'SUM': groupStage.$group[agg.alias] = { $sum: `$${agg.field}` }; break; case 'AVG': groupStage.$group[agg.alias] = { $avg: `$${agg.field}` }; break; case 'MIN': groupStage.$group[agg.alias] = { $min: `$${agg.field}` }; break; case 'MAX': groupStage.$group[agg.alias] = { $max: `$${agg.field}` }; break; case 'GROUP_CONCAT': groupStage.$group[agg.alias] = { $push: `$${agg.field}` }; break; } }); return groupStage; } /** * Build MongoDB group by expression */ buildGroupByExpression(groupBy) { if (!groupBy?.length) { return null; // Group all documents together } if (groupBy.length === 1) { return `$${groupBy[0]}`; } // Multiple group by fields const groupExpr = {}; groupBy.forEach(field => { groupExpr[field] = `$${field}`; }); return groupExpr; } // ============================================================================ // UTILITY METHODS // ============================================================================ /** * Format paginated results consistently */ formatPaginatedResult(items, total, pagination) { const { page, limit } = pagination; const totalPages = Math.ceil(total / limit); return { items, total, page, limit, totalPages, hasNext: page < totalPages, hasPrev: page > 1, }; } }; exports.MongooseAggregateRepository = MongooseAggregateRepository; exports.MongooseAggregateRepository = MongooseAggregateRepository = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [mongoose_1.Model]) ], MongooseAggregateRepository);