BREAKING CHANGE(core): Refactor to v3: introduce modular core/domain architecture, plugin system, observability and strict TypeScript configuration; remove legacy classes

This commit is contained in:
2025-11-29 18:32:00 +00:00
parent 53673e37cb
commit 7e89b6ebf5
68 changed files with 17020 additions and 720 deletions

View File

@@ -0,0 +1,324 @@
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<Product>('products')
* .aggregations((agg) => {
* agg.terms('categories', 'category.keyword', { size: 10 })
* .subAggregation('avg_price', (sub) => sub.avg('price'));
* });
* ```
*/
export class AggregationBuilder {
private aggregations: Record<string, AggregationDSL> = {};
private currentAggName?: string;
/**
* Add a terms aggregation
*/
terms(
name: string,
field: string,
options?: {
size?: number;
order?: Record<string, SortOrder>;
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<SortField | string>;
_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<string, AggregationDSL> {
return this.aggregations;
}
}
/**
* Create a new aggregation builder
*/
export function createAggregationBuilder(): AggregationBuilder {
return new AggregationBuilder();
}

67
ts/domain/query/index.ts Normal file
View File

@@ -0,0 +1,67 @@
/**
* Query Builder Module
*
* Type-safe query construction for Elasticsearch
*/
// Query Builder
export { QueryBuilder, createQuery } from './query-builder.js';
// Aggregation Builder
export { AggregationBuilder, createAggregationBuilder } from './aggregation-builder.js';
// Types
export type {
// Query types
QueryType,
QueryDSL,
BoolClause,
BoolQuery,
MatchQuery,
MatchPhraseQuery,
MultiMatchQuery,
TermQuery,
TermsQuery,
RangeQuery,
ExistsQuery,
PrefixQuery,
WildcardQuery,
RegexpQuery,
FuzzyQuery,
IdsQuery,
MatchAllQuery,
QueryStringQuery,
SimpleQueryStringQuery,
// Options
SearchOptions,
SortOrder,
SortField,
MatchOperator,
MultiMatchType,
RangeBounds,
// Aggregation types
AggregationType,
AggregationDSL,
TermsAggregation,
MetricAggregation,
StatsAggregation,
ExtendedStatsAggregation,
PercentilesAggregation,
DateHistogramAggregation,
HistogramAggregation,
RangeAggregation,
FilterAggregation,
TopHitsAggregation,
// Results
SearchResult,
SearchHit,
AggregationResult,
AggregationBucket,
TermsAggregationResult,
MetricAggregationResult,
StatsAggregationResult,
PercentilesAggregationResult,
} from './types.js';

View File

@@ -0,0 +1,629 @@
import type {
QueryDSL,
BoolQuery,
MatchQuery,
MatchPhraseQuery,
MultiMatchQuery,
TermQuery,
TermsQuery,
RangeQuery,
ExistsQuery,
PrefixQuery,
WildcardQuery,
RegexpQuery,
FuzzyQuery,
IdsQuery,
MatchAllQuery,
QueryStringQuery,
SimpleQueryStringQuery,
SearchOptions,
SearchResult,
SortOrder,
MatchOperator,
MultiMatchType,
RangeBounds,
SortField,
} from './types.js';
import type { AggregationBuilder } from './aggregation-builder.js';
import { createAggregationBuilder } from './aggregation-builder.js';
import { ElasticsearchConnectionManager } from '../../core/connection/connection-manager.js';
import { defaultLogger } from '../../core/observability/logger.js';
import { defaultMetrics } from '../../core/observability/metrics.js';
import { defaultTracing } from '../../core/observability/tracing.js';
/**
* Fluent query builder for type-safe Elasticsearch queries
*
* @example
* ```typescript
* const results = await new QueryBuilder<Product>('products')
* .match('name', 'laptop')
* .range('price', { gte: 100, lte: 1000 })
* .sort('price', 'asc')
* .size(20)
* .execute();
* ```
*/
export class QueryBuilder<T = unknown> {
private index: string;
private queryDSL: QueryDSL | null = null;
private boolClauses: {
must: QueryDSL[];
should: QueryDSL[];
must_not: QueryDSL[];
filter: QueryDSL[];
} = {
must: [],
should: [],
must_not: [],
filter: [],
};
private minimumShouldMatch?: number | string;
private sortFields: Array<SortField | string> = [];
private sourceFields?: string[];
private excludeSourceFields?: string[];
private resultSize: number = 10;
private resultFrom: number = 0;
private shouldTrackTotalHits: boolean | number = true;
private searchTimeout?: string;
private aggregationBuilder?: AggregationBuilder;
private highlightConfig?: SearchOptions['highlight'];
constructor(index: string) {
this.index = index;
}
/**
* Create a new query builder instance
*/
static create<T>(index: string): QueryBuilder<T> {
return new QueryBuilder<T>(index);
}
// ============================================================================
// Query Methods
// ============================================================================
/**
* Add a match query
*/
match(field: string, query: string, options?: { operator?: MatchOperator; fuzziness?: number | 'AUTO'; boost?: number }): this {
const matchQuery: MatchQuery = {
match: {
[field]: {
query,
...options,
},
},
};
this.boolClauses.must.push(matchQuery);
return this;
}
/**
* Add a match phrase query
*/
matchPhrase(field: string, query: string, options?: { slop?: number; boost?: number }): this {
const matchPhraseQuery: MatchPhraseQuery = {
match_phrase: {
[field]: {
query,
...options,
},
},
};
this.boolClauses.must.push(matchPhraseQuery);
return this;
}
/**
* Add a multi-match query
*/
multiMatch(query: string, fields: string[], options?: { type?: MultiMatchType; operator?: MatchOperator; boost?: number }): this {
const multiMatchQuery: MultiMatchQuery = {
multi_match: {
query,
fields,
...options,
},
};
this.boolClauses.must.push(multiMatchQuery);
return this;
}
/**
* Add a term query (exact match)
*/
term(field: string, value: string | number | boolean, boost?: number): this {
const termQuery: TermQuery = {
term: {
[field]: {
value,
...(boost && { boost }),
},
},
};
this.boolClauses.filter.push(termQuery);
return this;
}
/**
* Add a terms query (match any of the values)
*/
terms(field: string, values: Array<string | number | boolean>, boost?: number): this {
const termsQuery: TermsQuery = {
terms: {
[field]: values,
...(boost && { boost }),
},
};
this.boolClauses.filter.push(termsQuery);
return this;
}
/**
* Add a range query
*/
range(field: string, bounds: RangeBounds, boost?: number): this {
const rangeQuery: RangeQuery = {
range: {
[field]: {
...bounds,
...(boost && { boost }),
},
},
};
this.boolClauses.filter.push(rangeQuery);
return this;
}
/**
* Add an exists query (field must exist)
*/
exists(field: string): this {
const existsQuery: ExistsQuery = {
exists: {
field,
},
};
this.boolClauses.filter.push(existsQuery);
return this;
}
/**
* Add a prefix query
*/
prefix(field: string, value: string, boost?: number): this {
const prefixQuery: PrefixQuery = {
prefix: {
[field]: {
value,
...(boost && { boost }),
},
},
};
this.boolClauses.must.push(prefixQuery);
return this;
}
/**
* Add a wildcard query
*/
wildcard(field: string, value: string, boost?: number): this {
const wildcardQuery: WildcardQuery = {
wildcard: {
[field]: {
value,
...(boost && { boost }),
},
},
};
this.boolClauses.must.push(wildcardQuery);
return this;
}
/**
* Add a regexp query
*/
regexp(field: string, value: string, options?: { flags?: string; boost?: number }): this {
const regexpQuery: RegexpQuery = {
regexp: {
[field]: {
value,
...options,
},
},
};
this.boolClauses.must.push(regexpQuery);
return this;
}
/**
* Add a fuzzy query
*/
fuzzy(field: string, value: string, options?: { fuzziness?: number | 'AUTO'; boost?: number }): this {
const fuzzyQuery: FuzzyQuery = {
fuzzy: {
[field]: {
value,
...options,
},
},
};
this.boolClauses.must.push(fuzzyQuery);
return this;
}
/**
* Add an IDs query
*/
ids(values: string[]): this {
const idsQuery: IdsQuery = {
ids: {
values,
},
};
this.boolClauses.filter.push(idsQuery);
return this;
}
/**
* Add a query string query
*/
queryString(query: string, options?: { default_field?: string; fields?: string[]; default_operator?: MatchOperator; boost?: number }): this {
const queryStringQuery: QueryStringQuery = {
query_string: {
query,
...options,
},
};
this.boolClauses.must.push(queryStringQuery);
return this;
}
/**
* Add a simple query string query
*/
simpleQueryString(query: string, options?: { fields?: string[]; default_operator?: MatchOperator; boost?: number }): this {
const simpleQueryStringQuery: SimpleQueryStringQuery = {
simple_query_string: {
query,
...options,
},
};
this.boolClauses.must.push(simpleQueryStringQuery);
return this;
}
/**
* Match all documents
*/
matchAll(boost?: number): this {
const matchAllQuery: MatchAllQuery = {
match_all: {
...(boost && { boost }),
},
};
this.queryDSL = matchAllQuery;
return this;
}
/**
* Add a custom query to the must clause
*/
must(query: QueryDSL): this {
this.boolClauses.must.push(query);
return this;
}
/**
* Add a custom query to the should clause
*/
should(query: QueryDSL): this {
this.boolClauses.should.push(query);
return this;
}
/**
* Add a custom query to the must_not clause
*/
mustNot(query: QueryDSL): this {
this.boolClauses.must_not.push(query);
return this;
}
/**
* Add a custom query to the filter clause
*/
filter(query: QueryDSL): this {
this.boolClauses.filter.push(query);
return this;
}
/**
* Set minimum_should_match for boolean queries
*/
minimumMatch(value: number | string): this {
this.minimumShouldMatch = value;
return this;
}
/**
* Set a custom query DSL (replaces builder queries)
*/
customQuery(query: QueryDSL): this {
this.queryDSL = query;
return this;
}
// ============================================================================
// Result Shaping Methods
// ============================================================================
/**
* Add sorting
*/
sort(field: string, order: SortOrder = 'asc'): this {
this.sortFields.push({ [field]: { order } });
return this;
}
/**
* Add custom sort configuration
*/
customSort(sort: SortField | string): this {
this.sortFields.push(sort);
return this;
}
/**
* Specify fields to include in results (source filtering)
*/
fields(fields: string[]): this {
this.sourceFields = fields;
return this;
}
/**
* Specify fields to exclude from results
*/
exclude(fields: string[]): this {
this.excludeSourceFields = fields;
return this;
}
/**
* Set number of results to return
*/
size(size: number): this {
this.resultSize = size;
return this;
}
/**
* Set offset for pagination
*/
from(from: number): this {
this.resultFrom = from;
return this;
}
/**
* Set pagination (convenience method)
*/
paginate(page: number, pageSize: number): this {
this.resultFrom = (page - 1) * pageSize;
this.resultSize = pageSize;
return this;
}
/**
* Set whether to track total hits
*/
trackTotalHits(track: boolean | number): this {
this.shouldTrackTotalHits = track;
return this;
}
/**
* Set search timeout
*/
timeout(timeout: string): this {
this.searchTimeout = timeout;
return this;
}
/**
* Configure highlighting
*/
highlight(config: SearchOptions['highlight']): this {
this.highlightConfig = config;
return this;
}
// ============================================================================
// Aggregation Methods
// ============================================================================
/**
* Get aggregation builder
*/
aggregations(configure: (builder: AggregationBuilder) => void): this {
if (!this.aggregationBuilder) {
this.aggregationBuilder = createAggregationBuilder();
}
configure(this.aggregationBuilder);
return this;
}
// ============================================================================
// Build & Execute
// ============================================================================
/**
* Build the final query DSL
*/
build(): SearchOptions {
let finalQuery: QueryDSL | undefined;
// If custom query was set, use it
if (this.queryDSL) {
finalQuery = this.queryDSL;
} else {
// Otherwise, build from bool clauses
const hasAnyClauses =
this.boolClauses.must.length > 0 ||
this.boolClauses.should.length > 0 ||
this.boolClauses.must_not.length > 0 ||
this.boolClauses.filter.length > 0;
if (hasAnyClauses) {
const boolQuery: BoolQuery = {
bool: {},
};
if (this.boolClauses.must.length > 0) {
boolQuery.bool.must = this.boolClauses.must;
}
if (this.boolClauses.should.length > 0) {
boolQuery.bool.should = this.boolClauses.should;
}
if (this.boolClauses.must_not.length > 0) {
boolQuery.bool.must_not = this.boolClauses.must_not;
}
if (this.boolClauses.filter.length > 0) {
boolQuery.bool.filter = this.boolClauses.filter;
}
if (this.minimumShouldMatch !== undefined) {
boolQuery.bool.minimum_should_match = this.minimumShouldMatch;
}
finalQuery = boolQuery;
}
}
const searchOptions: SearchOptions = {
...(finalQuery && { query: finalQuery }),
...(this.sourceFields && { fields: this.sourceFields }),
...(this.excludeSourceFields && { excludeFields: this.excludeSourceFields }),
size: this.resultSize,
from: this.resultFrom,
...(this.sortFields.length > 0 && { sort: this.sortFields }),
trackTotalHits: this.shouldTrackTotalHits,
...(this.searchTimeout && { timeout: this.searchTimeout }),
...(this.highlightConfig && { highlight: this.highlightConfig }),
...(this.aggregationBuilder && { aggregations: this.aggregationBuilder.build() }),
};
return searchOptions;
}
/**
* Execute the query and return results
*/
async execute(): Promise<SearchResult<T>> {
const span = defaultTracing.createSpan('query.execute', {
'db.system': 'elasticsearch',
'db.operation': 'search',
'db.elasticsearch.index': this.index,
});
try {
const client = ElasticsearchConnectionManager.getInstance().getClient();
const searchOptions = this.build();
defaultLogger.debug('Executing query', {
index: this.index,
query: searchOptions.query,
size: searchOptions.size,
from: searchOptions.from,
});
const startTime = Date.now();
// Execute search
const result = await client.search<T>({
index: this.index,
...searchOptions,
});
const duration = Date.now() - startTime;
// Record metrics
defaultMetrics.requestsTotal.inc({ operation: 'search', index: this.index });
defaultMetrics.requestDuration.observe({ operation: 'search', index: this.index }, duration);
defaultLogger.info('Query executed successfully', {
index: this.index,
took: result.took,
hits: result.hits.total,
duration,
});
span.setAttributes({
'db.elasticsearch.took': result.took,
'db.elasticsearch.hits': typeof result.hits.total === 'object' ? result.hits.total.value : result.hits.total,
});
span.end();
return result as SearchResult<T>;
} catch (error) {
defaultMetrics.requestErrors.inc({ operation: 'search', index: this.index });
defaultLogger.error('Query execution failed', { index: this.index, error: error instanceof Error ? error.message : String(error) });
span.recordException(error as Error);
span.end();
throw error;
}
}
/**
* Execute query and return only the hits
*/
async executeAndGetHits(): Promise<SearchResult<T>['hits']['hits']> {
const result = await this.execute();
return result.hits.hits;
}
/**
* Execute query and return only the source documents
*/
async executeAndGetSources(): Promise<T[]> {
const hits = await this.executeAndGetHits();
return hits.map((hit) => hit._source);
}
/**
* Count documents matching the query
*/
async count(): Promise<number> {
const span = defaultTracing.createSpan('query.count', {
'db.system': 'elasticsearch',
'db.operation': 'count',
'db.elasticsearch.index': this.index,
});
try {
const client = ElasticsearchConnectionManager.getInstance().getClient();
const searchOptions = this.build();
const result = await client.count({
index: this.index,
...(searchOptions.query && { query: searchOptions.query }),
});
span.end();
return result.count;
} catch (error) {
span.recordException(error as Error);
span.end();
throw error;
}
}
}
/**
* Create a new query builder instance
*/
export function createQuery<T>(index: string): QueryBuilder<T> {
return new QueryBuilder<T>(index);
}

563
ts/domain/query/types.ts Normal file
View File

@@ -0,0 +1,563 @@
/**
* Query DSL type definitions for type-safe Elasticsearch queries
*/
/**
* Elasticsearch query types
*/
export type QueryType =
| 'match'
| 'match_phrase'
| 'multi_match'
| 'term'
| 'terms'
| 'range'
| 'exists'
| 'prefix'
| 'wildcard'
| 'regexp'
| 'fuzzy'
| 'ids'
| 'bool'
| 'match_all'
| 'query_string'
| 'simple_query_string';
/**
* Boolean query clause types
*/
export type BoolClause = 'must' | 'should' | 'must_not' | 'filter';
/**
* Sort order
*/
export type SortOrder = 'asc' | 'desc';
/**
* Match query operator
*/
export type MatchOperator = 'or' | 'and';
/**
* Multi-match type
*/
export type MultiMatchType =
| 'best_fields'
| 'most_fields'
| 'cross_fields'
| 'phrase'
| 'phrase_prefix'
| 'bool_prefix';
/**
* Range query bounds
*/
export interface RangeBounds {
gt?: number | string | Date;
gte?: number | string | Date;
lt?: number | string | Date;
lte?: number | string | Date;
}
/**
* Match query definition
*/
export interface MatchQuery {
match: {
[field: string]: {
query: string;
operator?: MatchOperator;
fuzziness?: number | 'AUTO';
boost?: number;
};
};
}
/**
* Match phrase query definition
*/
export interface MatchPhraseQuery {
match_phrase: {
[field: string]: {
query: string;
slop?: number;
boost?: number;
};
};
}
/**
* Multi-match query definition
*/
export interface MultiMatchQuery {
multi_match: {
query: string;
fields: string[];
type?: MultiMatchType;
operator?: MatchOperator;
boost?: number;
};
}
/**
* Term query definition
*/
export interface TermQuery {
term: {
[field: string]: {
value: string | number | boolean;
boost?: number;
};
};
}
/**
* Terms query definition
*/
export interface TermsQuery {
terms: {
[field: string]: Array<string | number | boolean>;
boost?: number;
};
}
/**
* Range query definition
*/
export interface RangeQuery {
range: {
[field: string]: RangeBounds & {
boost?: number;
};
};
}
/**
* Exists query definition
*/
export interface ExistsQuery {
exists: {
field: string;
};
}
/**
* Prefix query definition
*/
export interface PrefixQuery {
prefix: {
[field: string]: {
value: string;
boost?: number;
};
};
}
/**
* Wildcard query definition
*/
export interface WildcardQuery {
wildcard: {
[field: string]: {
value: string;
boost?: number;
};
};
}
/**
* Regexp query definition
*/
export interface RegexpQuery {
regexp: {
[field: string]: {
value: string;
flags?: string;
boost?: number;
};
};
}
/**
* Fuzzy query definition
*/
export interface FuzzyQuery {
fuzzy: {
[field: string]: {
value: string;
fuzziness?: number | 'AUTO';
boost?: number;
};
};
}
/**
* IDs query definition
*/
export interface IdsQuery {
ids: {
values: string[];
};
}
/**
* Match all query definition
*/
export interface MatchAllQuery {
match_all: {
boost?: number;
};
}
/**
* Query string query definition
*/
export interface QueryStringQuery {
query_string: {
query: string;
default_field?: string;
fields?: string[];
default_operator?: MatchOperator;
boost?: number;
};
}
/**
* Simple query string query definition
*/
export interface SimpleQueryStringQuery {
simple_query_string: {
query: string;
fields?: string[];
default_operator?: MatchOperator;
boost?: number;
};
}
/**
* Boolean query definition
*/
export interface BoolQuery {
bool: {
must?: QueryDSL[];
should?: QueryDSL[];
must_not?: QueryDSL[];
filter?: QueryDSL[];
minimum_should_match?: number | string;
boost?: number;
};
}
/**
* Union of all query types
*/
export type QueryDSL =
| MatchQuery
| MatchPhraseQuery
| MultiMatchQuery
| TermQuery
| TermsQuery
| RangeQuery
| ExistsQuery
| PrefixQuery
| WildcardQuery
| RegexpQuery
| FuzzyQuery
| IdsQuery
| MatchAllQuery
| QueryStringQuery
| SimpleQueryStringQuery
| BoolQuery;
/**
* Sort field definition
*/
export interface SortField {
[field: string]: {
order?: SortOrder;
mode?: 'min' | 'max' | 'sum' | 'avg' | 'median';
missing?: '_first' | '_last' | string;
};
}
/**
* Search request options
*/
export interface SearchOptions {
/** Query to execute */
query?: QueryDSL;
/** Fields to return (source filtering) */
fields?: string[];
/** Exclude source fields */
excludeFields?: string[];
/** Number of results to return */
size?: number;
/** Offset for pagination */
from?: number;
/** Sort order */
sort?: Array<SortField | string>;
/** Track total hits */
trackTotalHits?: boolean | number;
/** Search timeout */
timeout?: string;
/** Highlight configuration */
highlight?: {
fields: {
[field: string]: {
pre_tags?: string[];
post_tags?: string[];
fragment_size?: number;
number_of_fragments?: number;
};
};
};
/** Aggregations */
aggregations?: Record<string, AggregationDSL>;
}
/**
* Aggregation types
*/
export type AggregationType =
| 'terms'
| 'avg'
| 'sum'
| 'min'
| 'max'
| 'cardinality'
| 'stats'
| 'extended_stats'
| 'percentiles'
| 'date_histogram'
| 'histogram'
| 'range'
| 'filter'
| 'filters'
| 'nested'
| 'reverse_nested'
| 'top_hits';
/**
* Terms aggregation
*/
export interface TermsAggregation {
terms: {
field: string;
size?: number;
order?: Record<string, SortOrder>;
missing?: string | number;
};
aggs?: Record<string, AggregationDSL>;
}
/**
* Metric aggregations (avg, sum, min, max, cardinality)
*/
export interface MetricAggregation {
avg?: { field: string; missing?: number };
sum?: { field: string; missing?: number };
min?: { field: string; missing?: number };
max?: { field: string; missing?: number };
cardinality?: { field: string };
}
/**
* Stats aggregation
*/
export interface StatsAggregation {
stats: {
field: string;
};
}
/**
* Extended stats aggregation
*/
export interface ExtendedStatsAggregation {
extended_stats: {
field: string;
};
}
/**
* Percentiles aggregation
*/
export interface PercentilesAggregation {
percentiles: {
field: string;
percents?: number[];
};
}
/**
* Date histogram aggregation
*/
export interface DateHistogramAggregation {
date_histogram: {
field: string;
calendar_interval?: string;
fixed_interval?: string;
format?: string;
time_zone?: string;
min_doc_count?: number;
};
aggs?: Record<string, AggregationDSL>;
}
/**
* Histogram aggregation
*/
export interface HistogramAggregation {
histogram: {
field: string;
interval: number;
min_doc_count?: number;
};
aggs?: Record<string, AggregationDSL>;
}
/**
* Range aggregation
*/
export interface RangeAggregation {
range: {
field: string;
ranges: Array<{ from?: number; to?: number; key?: string }>;
};
aggs?: Record<string, AggregationDSL>;
}
/**
* Filter aggregation
*/
export interface FilterAggregation {
filter: QueryDSL;
aggs?: Record<string, AggregationDSL>;
}
/**
* Top hits aggregation
*/
export interface TopHitsAggregation {
top_hits: {
size?: number;
sort?: Array<SortField | string>;
_source?: boolean | { includes?: string[]; excludes?: string[] };
};
}
/**
* Union of all aggregation types
*/
export type AggregationDSL =
| TermsAggregation
| MetricAggregation
| StatsAggregation
| ExtendedStatsAggregation
| PercentilesAggregation
| DateHistogramAggregation
| HistogramAggregation
| RangeAggregation
| FilterAggregation
| TopHitsAggregation;
/**
* Search result hit
*/
export interface SearchHit<T> {
_index: string;
_id: string;
_score: number | null;
_source: T;
fields?: Record<string, unknown[]>;
highlight?: Record<string, string[]>;
sort?: Array<string | number>;
}
/**
* Aggregation bucket
*/
export interface AggregationBucket {
key: string | number;
key_as_string?: string;
doc_count: number;
[aggName: string]: unknown;
}
/**
* Terms aggregation result
*/
export interface TermsAggregationResult {
doc_count_error_upper_bound: number;
sum_other_doc_count: number;
buckets: AggregationBucket[];
}
/**
* Metric aggregation result
*/
export interface MetricAggregationResult {
value: number;
}
/**
* Stats aggregation result
*/
export interface StatsAggregationResult {
count: number;
min: number;
max: number;
avg: number;
sum: number;
}
/**
* Percentiles aggregation result
*/
export interface PercentilesAggregationResult {
values: Record<string, number>;
}
/**
* Generic aggregation result
*/
export type AggregationResult =
| TermsAggregationResult
| MetricAggregationResult
| StatsAggregationResult
| PercentilesAggregationResult
| { buckets: AggregationBucket[] }
| { value: number }
| Record<string, unknown>;
/**
* Search result
*/
export interface SearchResult<T> {
took: number;
timed_out: boolean;
_shards: {
total: number;
successful: number;
skipped: number;
failed: number;
};
hits: {
total: {
value: number;
relation: 'eq' | 'gte';
};
max_score: number | null;
hits: SearchHit<T>[];
};
aggregations?: Record<string, AggregationResult>;
}