import type { AggregationDSL, TermsAggregation, MetricAggregation, StatsAggregation, ExtendedStatsAggregation, PercentilesAggregation, DateHistogramAggregation, HistogramAggregation, RangeAggregation, FilterAggregation, TopHitsAggregation, QueryDSL, SortOrder, SortField, } from './types.js'; /** * Fluent aggregation builder for type-safe Elasticsearch aggregations * * @example * ```typescript * const query = new QueryBuilder('products') * .aggregations((agg) => { * agg.terms('categories', 'category.keyword', { size: 10 }) * .subAggregation('avg_price', (sub) => sub.avg('price')); * }); * ``` */ export class AggregationBuilder { private aggregations: Record = {}; private currentAggName?: string; /** * Add a terms aggregation */ terms( name: string, field: string, options?: { size?: number; order?: Record; missing?: string | number; } ): this { const termsAgg: TermsAggregation = { terms: { field, ...options, }, }; this.aggregations[name] = termsAgg; this.currentAggName = name; return this; } /** * Add an average metric aggregation */ avg(name: string, field: string, missing?: number): this { const avgAgg: MetricAggregation = { avg: { field, ...(missing !== undefined && { missing }), }, }; this.aggregations[name] = avgAgg; this.currentAggName = name; return this; } /** * Add a sum metric aggregation */ sum(name: string, field: string, missing?: number): this { const sumAgg: MetricAggregation = { sum: { field, ...(missing !== undefined && { missing }), }, }; this.aggregations[name] = sumAgg; this.currentAggName = name; return this; } /** * Add a min metric aggregation */ min(name: string, field: string, missing?: number): this { const minAgg: MetricAggregation = { min: { field, ...(missing !== undefined && { missing }), }, }; this.aggregations[name] = minAgg; this.currentAggName = name; return this; } /** * Add a max metric aggregation */ max(name: string, field: string, missing?: number): this { const maxAgg: MetricAggregation = { max: { field, ...(missing !== undefined && { missing }), }, }; this.aggregations[name] = maxAgg; this.currentAggName = name; return this; } /** * Add a cardinality metric aggregation */ cardinality(name: string, field: string): this { const cardinalityAgg: MetricAggregation = { cardinality: { field, }, }; this.aggregations[name] = cardinalityAgg; this.currentAggName = name; return this; } /** * Add a stats aggregation */ stats(name: string, field: string): this { const statsAgg: StatsAggregation = { stats: { field, }, }; this.aggregations[name] = statsAgg; this.currentAggName = name; return this; } /** * Add an extended stats aggregation */ extendedStats(name: string, field: string): this { const extendedStatsAgg: ExtendedStatsAggregation = { extended_stats: { field, }, }; this.aggregations[name] = extendedStatsAgg; this.currentAggName = name; return this; } /** * Add a percentiles aggregation */ percentiles(name: string, field: string, percents?: number[]): this { const percentilesAgg: PercentilesAggregation = { percentiles: { field, ...(percents && { percents }), }, }; this.aggregations[name] = percentilesAgg; this.currentAggName = name; return this; } /** * Add a date histogram aggregation */ dateHistogram( name: string, field: string, options: { calendar_interval?: string; fixed_interval?: string; format?: string; time_zone?: string; min_doc_count?: number; } ): this { const dateHistogramAgg: DateHistogramAggregation = { date_histogram: { field, ...options, }, }; this.aggregations[name] = dateHistogramAgg; this.currentAggName = name; return this; } /** * Add a histogram aggregation */ histogram( name: string, field: string, interval: number, options?: { min_doc_count?: number; } ): this { const histogramAgg: HistogramAggregation = { histogram: { field, interval, ...options, }, }; this.aggregations[name] = histogramAgg; this.currentAggName = name; return this; } /** * Add a range aggregation */ range( name: string, field: string, ranges: Array<{ from?: number; to?: number; key?: string }> ): this { const rangeAgg: RangeAggregation = { range: { field, ranges, }, }; this.aggregations[name] = rangeAgg; this.currentAggName = name; return this; } /** * Add a filter aggregation */ filterAgg(name: string, filter: QueryDSL): this { const filterAgg: FilterAggregation = { filter, }; this.aggregations[name] = filterAgg; this.currentAggName = name; return this; } /** * Add a top hits aggregation */ topHits( name: string, options?: { size?: number; sort?: Array; _source?: boolean | { includes?: string[]; excludes?: string[] }; } ): this { const topHitsAgg: TopHitsAggregation = { top_hits: { ...options, }, }; this.aggregations[name] = topHitsAgg; this.currentAggName = name; return this; } /** * Add a sub-aggregation to the last defined aggregation */ subAggregation(name: string, configure: (builder: AggregationBuilder) => void): this { if (!this.currentAggName) { throw new Error('Cannot add sub-aggregation: no parent aggregation defined'); } const parentAgg = this.aggregations[this.currentAggName]; const subBuilder = new AggregationBuilder(); configure(subBuilder); // Add aggs field to parent aggregation if ('terms' in parentAgg) { (parentAgg as TermsAggregation).aggs = subBuilder.build(); } else if ('date_histogram' in parentAgg) { (parentAgg as DateHistogramAggregation).aggs = subBuilder.build(); } else if ('histogram' in parentAgg) { (parentAgg as HistogramAggregation).aggs = subBuilder.build(); } else if ('range' in parentAgg) { (parentAgg as RangeAggregation).aggs = subBuilder.build(); } else if ('filter' in parentAgg) { (parentAgg as FilterAggregation).aggs = subBuilder.build(); } return this; } /** * Add a custom aggregation DSL */ custom(name: string, aggregation: AggregationDSL): this { this.aggregations[name] = aggregation; this.currentAggName = name; return this; } /** * Build the aggregations object */ build(): Record { return this.aggregations; } } /** * Create a new aggregation builder */ export function createAggregationBuilder(): AggregationBuilder { return new AggregationBuilder(); }