mongo-pipeline-kit
Version:
A feature-rich MongoDB pipeline builder kit for creating, reusing, and managing aggregation pipelines with enhanced JSON support and advanced utilities
171 lines • 5.88 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PipelineComposer = void 0;
const types_1 = require("./types");
/**
* Class for composing and reusing MongoDB pipeline segments
*/
class PipelineComposer {
constructor() {
this.pipeline = [];
}
/**
* Compose multiple pipelines into one
* @param pipelines The pipelines to compose
*/
compose(...pipelines) {
if (pipelines.length === 0) {
throw new types_1.CompositionError("At least one pipeline must be provided for composition");
}
// Validate all pipelines before composition
pipelines.forEach((pipeline, index) => {
if (!this.validate(pipeline)) {
throw new types_1.CompositionError(`Invalid pipeline at index ${index}`);
}
});
// Flatten and combine all pipelines
this.pipeline = pipelines.reduce((acc, curr) => [...acc, ...curr], []);
return this;
}
/**
* Extend an existing pipeline with another pipeline
* @param basePipeline The base pipeline to extend
* @param extension The pipeline to extend with
*/
extend(basePipeline, extension) {
if (!this.validate(basePipeline)) {
throw new types_1.CompositionError("Invalid base pipeline");
}
if (!this.validate(extension)) {
throw new types_1.CompositionError("Invalid extension pipeline");
}
this.pipeline = [...basePipeline, ...extension];
return this;
}
/**
* Validate a pipeline structure
* @param pipeline The pipeline to validate
* @param options Validation options
*/
validate(pipeline, options = {}) {
const { strict = true, allowEmpty = false, maxStages = 1000 } = options;
// Check if pipeline is an array
if (!Array.isArray(pipeline)) {
throw new types_1.ValidationError("Pipeline must be an array");
}
// Check for empty pipeline
if (pipeline.length === 0 && !allowEmpty) {
throw new types_1.ValidationError("Pipeline cannot be empty");
}
// Check for maximum stages
if (pipeline.length > maxStages) {
throw new types_1.ValidationError(`Pipeline exceeds maximum stage limit of ${maxStages}`);
}
// Validate each stage
for (const stage of pipeline) {
if (!stage || typeof stage !== "object") {
throw new types_1.ValidationError("Invalid pipeline stage: must be an object");
}
if (strict) {
// Check that each stage has exactly one operator
const operators = Object.keys(stage);
if (operators.length !== 1) {
throw new types_1.ValidationError("Each pipeline stage must have exactly one operator");
}
// Check that the operator starts with $
const operator = operators[0];
if (!operator.startsWith("$")) {
throw new types_1.ValidationError("Pipeline operators must start with $");
}
}
}
return true;
}
/**
* Build the final composed pipeline
*/
build() {
return [...this.pipeline];
}
/**
* Clear the current pipeline
*/
clear() {
this.pipeline = [];
return this;
}
/**
* Get the current number of stages in the pipeline
*/
getStageCount() {
return this.pipeline.length;
}
/**
* Get a specific stage from the pipeline
* @param index The index of the stage to retrieve
*/
getStage(index) {
if (index < 0 || index >= this.pipeline.length) {
throw new Error("Invalid stage index");
}
return this.pipeline[index];
}
/**
* Check if the pipeline contains a specific stage type
* @param operator The stage operator to check for (e.g., '$match', '$group')
*/
hasStage(operator) {
return this.pipeline.some((stage) => Object.keys(stage)[0] === operator);
}
/**
* Get all stages of a specific type
* @param operator The stage operator to filter by
*/
getStagesByType(operator) {
return this.pipeline.filter((stage) => Object.keys(stage)[0] === operator);
}
/**
* Get the pipeline as a JSON string
* @param pretty Whether to format the JSON with indentation
* @returns JSON string representation of the pipeline
*/
toJSON(pretty = false) {
const pipeline = this.build();
return pretty ? JSON.stringify(pipeline, null, 2) : JSON.stringify(pipeline);
}
/**
* Get the pipeline as a plain object (not stringified)
* @returns Object representation of the pipeline
*/
toObject() {
return this.build();
}
/**
* Export pipeline with metadata
* @param metadata Additional metadata to include
* @returns Object with pipeline and metadata
*/
exportWithMetadata(metadata = {}) {
return {
pipeline: this.build(),
metadata: {
stageCount: this.getStageCount(),
createdAt: new Date().toISOString(),
...metadata,
},
};
}
/**
* Get a human-readable string representation of the pipeline
* @returns Formatted string showing pipeline stages
*/
toString() {
const stages = this.pipeline.map((stage, index) => {
const operator = Object.keys(stage)[0];
return `${index + 1}. ${operator}`;
});
return `Composed Pipeline with ${this.pipeline.length} stages:\n${stages.join('\n')}`;
}
}
exports.PipelineComposer = PipelineComposer;
//# sourceMappingURL=PipelineComposer.js.map