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:
418
ts/examples/query/query-builder-example.ts
Normal file
418
ts/examples/query/query-builder-example.ts
Normal file
@@ -0,0 +1,418 @@
|
||||
/**
|
||||
* Comprehensive Query Builder Example
|
||||
*
|
||||
* Demonstrates type-safe query construction with the QueryBuilder
|
||||
*/
|
||||
|
||||
import {
|
||||
createConfig,
|
||||
ElasticsearchConnectionManager,
|
||||
LogLevel,
|
||||
} from '../../core/index.js';
|
||||
import { DocumentManager } from '../../domain/documents/index.js';
|
||||
import { QueryBuilder, createQuery } from '../../domain/query/index.js';
|
||||
|
||||
interface Product {
|
||||
name: string;
|
||||
description: string;
|
||||
category: string;
|
||||
brand: string;
|
||||
price: number;
|
||||
rating: number;
|
||||
stock: number;
|
||||
tags: string[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('=== Query Builder Example ===\n');
|
||||
|
||||
// ============================================================================
|
||||
// Step 1: Configuration
|
||||
// ============================================================================
|
||||
|
||||
console.log('Step 1: Configuring Elasticsearch connection...');
|
||||
const config = createConfig()
|
||||
.fromEnv()
|
||||
.nodes(process.env.ELASTICSEARCH_URL || 'http://localhost:9200')
|
||||
.basicAuth(
|
||||
process.env.ELASTICSEARCH_USERNAME || 'elastic',
|
||||
process.env.ELASTICSEARCH_PASSWORD || 'changeme'
|
||||
)
|
||||
.timeout(30000)
|
||||
.retries(3)
|
||||
.logLevel(LogLevel.INFO)
|
||||
.enableMetrics(true)
|
||||
.enableTracing(true, { serviceName: 'query-example', serviceVersion: '1.0.0' })
|
||||
.build();
|
||||
|
||||
// ============================================================================
|
||||
// Step 2: Initialize Connection
|
||||
// ============================================================================
|
||||
|
||||
console.log('Step 2: Initializing connection manager...');
|
||||
const connectionManager = ElasticsearchConnectionManager.getInstance(config);
|
||||
await connectionManager.initialize();
|
||||
console.log('✓ Connection manager initialized\n');
|
||||
|
||||
// ============================================================================
|
||||
// Step 3: Setup Sample Data
|
||||
// ============================================================================
|
||||
|
||||
console.log('Step 3: Setting up sample data...');
|
||||
const products = new DocumentManager<Product>({
|
||||
index: 'products-query-example',
|
||||
autoCreateIndex: true,
|
||||
});
|
||||
await products.initialize();
|
||||
|
||||
// Create sample products
|
||||
const sampleProducts: Array<{ id: string; data: Product }> = [
|
||||
{
|
||||
id: 'laptop-1',
|
||||
data: {
|
||||
name: 'Professional Laptop Pro',
|
||||
description: 'High-performance laptop for professionals',
|
||||
category: 'Electronics',
|
||||
brand: 'TechBrand',
|
||||
price: 1299.99,
|
||||
rating: 4.5,
|
||||
stock: 15,
|
||||
tags: ['laptop', 'professional', 'high-end'],
|
||||
createdAt: new Date('2024-01-15'),
|
||||
updatedAt: new Date('2024-01-20'),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'laptop-2',
|
||||
data: {
|
||||
name: 'Budget Laptop Basic',
|
||||
description: 'Affordable laptop for everyday use',
|
||||
category: 'Electronics',
|
||||
brand: 'ValueBrand',
|
||||
price: 499.99,
|
||||
rating: 3.8,
|
||||
stock: 30,
|
||||
tags: ['laptop', 'budget', 'student'],
|
||||
createdAt: new Date('2024-02-01'),
|
||||
updatedAt: new Date('2024-02-05'),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'phone-1',
|
||||
data: {
|
||||
name: 'Smartphone X',
|
||||
description: 'Latest flagship smartphone',
|
||||
category: 'Electronics',
|
||||
brand: 'PhoneBrand',
|
||||
price: 899.99,
|
||||
rating: 4.7,
|
||||
stock: 25,
|
||||
tags: ['smartphone', 'flagship', '5g'],
|
||||
createdAt: new Date('2024-01-20'),
|
||||
updatedAt: new Date('2024-01-25'),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'tablet-1',
|
||||
data: {
|
||||
name: 'Tablet Pro',
|
||||
description: 'Professional tablet for creative work',
|
||||
category: 'Electronics',
|
||||
brand: 'TechBrand',
|
||||
price: 799.99,
|
||||
rating: 4.6,
|
||||
stock: 20,
|
||||
tags: ['tablet', 'creative', 'professional'],
|
||||
createdAt: new Date('2024-02-10'),
|
||||
updatedAt: new Date('2024-02-15'),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'monitor-1',
|
||||
data: {
|
||||
name: '4K Monitor',
|
||||
description: 'Ultra HD monitor for gaming and design',
|
||||
category: 'Electronics',
|
||||
brand: 'DisplayBrand',
|
||||
price: 599.99,
|
||||
rating: 4.4,
|
||||
stock: 12,
|
||||
tags: ['monitor', '4k', 'gaming'],
|
||||
createdAt: new Date('2024-01-25'),
|
||||
updatedAt: new Date('2024-01-30'),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Index sample data
|
||||
const session = products.session();
|
||||
session.start();
|
||||
for (const product of sampleProducts) {
|
||||
session.upsert(product.id, product.data);
|
||||
}
|
||||
await session.commit();
|
||||
console.log(`✓ Indexed ${sampleProducts.length} sample products\n`);
|
||||
|
||||
// Wait for indexing to complete
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// ============================================================================
|
||||
// Step 4: Simple Queries
|
||||
// ============================================================================
|
||||
|
||||
console.log('Step 4: Running simple queries...\n');
|
||||
|
||||
// 4.1: Match query - search by name
|
||||
console.log('4.1: Match query - search for "laptop"');
|
||||
const laptopResults = await createQuery<Product>('products-query-example')
|
||||
.match('name', 'laptop')
|
||||
.size(10)
|
||||
.execute();
|
||||
console.log(`Found ${laptopResults.hits.total.value} laptops`);
|
||||
console.log('Laptops:', laptopResults.hits.hits.map((h) => h._source.name));
|
||||
console.log();
|
||||
|
||||
// 4.2: Term query - exact match on category
|
||||
console.log('4.2: Term query - exact category match');
|
||||
const electronicsResults = await createQuery<Product>('products-query-example')
|
||||
.term('category.keyword', 'Electronics')
|
||||
.execute();
|
||||
console.log(`Found ${electronicsResults.hits.total.value} electronics`);
|
||||
console.log();
|
||||
|
||||
// 4.3: Range query - price between 500 and 1000
|
||||
console.log('4.3: Range query - price between $500 and $1000');
|
||||
const midPriceResults = await createQuery<Product>('products-query-example')
|
||||
.range('price', { gte: 500, lte: 1000 })
|
||||
.sort('price', 'asc')
|
||||
.execute();
|
||||
console.log(`Found ${midPriceResults.hits.total.value} products in price range`);
|
||||
midPriceResults.hits.hits.forEach((hit) => {
|
||||
console.log(` - ${hit._source.name}: $${hit._source.price}`);
|
||||
});
|
||||
console.log();
|
||||
|
||||
// 4.4: Multi-match query - search across multiple fields
|
||||
console.log('4.4: Multi-match query - search "professional" in name and description');
|
||||
const professionalResults = await createQuery<Product>('products-query-example')
|
||||
.multiMatch('professional', ['name', 'description'])
|
||||
.execute();
|
||||
console.log(`Found ${professionalResults.hits.total.value} professional products`);
|
||||
console.log();
|
||||
|
||||
// ============================================================================
|
||||
// Step 5: Boolean Queries
|
||||
// ============================================================================
|
||||
|
||||
console.log('Step 5: Running boolean queries...\n');
|
||||
|
||||
// 5.1: Must + Filter - combine multiple conditions
|
||||
console.log('5.1: Boolean query - TechBrand products over $700');
|
||||
const techBrandResults = await createQuery<Product>('products-query-example')
|
||||
.term('brand.keyword', 'TechBrand')
|
||||
.range('price', { gte: 700 })
|
||||
.sort('price', 'desc')
|
||||
.execute();
|
||||
console.log(`Found ${techBrandResults.hits.total.value} matching products`);
|
||||
techBrandResults.hits.hits.forEach((hit) => {
|
||||
console.log(` - ${hit._source.name} (${hit._source.brand}): $${hit._source.price}`);
|
||||
});
|
||||
console.log();
|
||||
|
||||
// 5.2: Should clause - match any condition
|
||||
console.log('5.2: Should query - products matching "laptop" OR "tablet"');
|
||||
const laptopOrTabletResults = await new QueryBuilder<Product>('products-query-example')
|
||||
.should({ match: { name: { query: 'laptop' } } })
|
||||
.should({ match: { name: { query: 'tablet' } } })
|
||||
.minimumMatch(1)
|
||||
.execute();
|
||||
console.log(`Found ${laptopOrTabletResults.hits.total.value} laptops or tablets`);
|
||||
console.log();
|
||||
|
||||
// 5.3: Must not - exclude results
|
||||
console.log('5.3: Must not query - electronics excluding laptops');
|
||||
const noLaptopsResults = await createQuery<Product>('products-query-example')
|
||||
.term('category.keyword', 'Electronics')
|
||||
.mustNot({ match: { name: { query: 'laptop' } } })
|
||||
.execute();
|
||||
console.log(`Found ${noLaptopsResults.hits.total.value} non-laptop electronics`);
|
||||
console.log();
|
||||
|
||||
// ============================================================================
|
||||
// Step 6: Aggregations
|
||||
// ============================================================================
|
||||
|
||||
console.log('Step 6: Running aggregations...\n');
|
||||
|
||||
// 6.1: Terms aggregation - group by brand
|
||||
console.log('6.1: Terms aggregation - products by brand');
|
||||
const brandAggResults = await createQuery<Product>('products-query-example')
|
||||
.matchAll()
|
||||
.size(0) // We only want aggregations, not documents
|
||||
.aggregations((agg) => {
|
||||
agg.terms('brands', 'brand.keyword', { size: 10 });
|
||||
})
|
||||
.execute();
|
||||
if (brandAggResults.aggregations && 'brands' in brandAggResults.aggregations) {
|
||||
const brandsAgg = brandAggResults.aggregations.brands as { buckets: Array<{ key: string; doc_count: number }> };
|
||||
console.log('Products by brand:');
|
||||
brandsAgg.buckets.forEach((bucket) => {
|
||||
console.log(` - ${bucket.key}: ${bucket.doc_count} products`);
|
||||
});
|
||||
}
|
||||
console.log();
|
||||
|
||||
// 6.2: Metric aggregations - price statistics
|
||||
console.log('6.2: Metric aggregations - price statistics');
|
||||
const priceStatsResults = await createQuery<Product>('products-query-example')
|
||||
.matchAll()
|
||||
.size(0)
|
||||
.aggregations((agg) => {
|
||||
agg.stats('price_stats', 'price');
|
||||
agg.avg('avg_rating', 'rating');
|
||||
agg.sum('total_stock', 'stock');
|
||||
})
|
||||
.execute();
|
||||
if (priceStatsResults.aggregations) {
|
||||
console.log('Price statistics:', priceStatsResults.aggregations.price_stats);
|
||||
console.log('Average rating:', priceStatsResults.aggregations.avg_rating);
|
||||
console.log('Total stock:', priceStatsResults.aggregations.total_stock);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// 6.3: Nested aggregations - brands with average price
|
||||
console.log('6.3: Nested aggregations - average price per brand');
|
||||
const nestedAggResults = await createQuery<Product>('products-query-example')
|
||||
.matchAll()
|
||||
.size(0)
|
||||
.aggregations((agg) => {
|
||||
agg.terms('brands', 'brand.keyword', { size: 10 }).subAggregation('avg_price', (sub) => {
|
||||
sub.avg('avg_price', 'price');
|
||||
});
|
||||
})
|
||||
.execute();
|
||||
if (nestedAggResults.aggregations && 'brands' in nestedAggResults.aggregations) {
|
||||
const brandsAgg = nestedAggResults.aggregations.brands as {
|
||||
buckets: Array<{ key: string; doc_count: number; avg_price: { value: number } }>;
|
||||
};
|
||||
console.log('Average price by brand:');
|
||||
brandsAgg.buckets.forEach((bucket) => {
|
||||
console.log(` - ${bucket.key}: $${bucket.avg_price.value.toFixed(2)} (${bucket.doc_count} products)`);
|
||||
});
|
||||
}
|
||||
console.log();
|
||||
|
||||
// ============================================================================
|
||||
// Step 7: Advanced Features
|
||||
// ============================================================================
|
||||
|
||||
console.log('Step 7: Advanced query features...\n');
|
||||
|
||||
// 7.1: Pagination
|
||||
console.log('7.1: Pagination - page 1 of results (2 per page)');
|
||||
const page1Results = await createQuery<Product>('products-query-example')
|
||||
.matchAll()
|
||||
.paginate(1, 2)
|
||||
.sort('price', 'asc')
|
||||
.execute();
|
||||
console.log(`Page 1: ${page1Results.hits.hits.length} results`);
|
||||
page1Results.hits.hits.forEach((hit) => {
|
||||
console.log(` - ${hit._source.name}: $${hit._source.price}`);
|
||||
});
|
||||
console.log();
|
||||
|
||||
// 7.2: Source filtering - only return specific fields
|
||||
console.log('7.2: Source filtering - only name and price');
|
||||
const filteredResults = await createQuery<Product>('products-query-example')
|
||||
.matchAll()
|
||||
.fields(['name', 'price'])
|
||||
.size(3)
|
||||
.execute();
|
||||
console.log('Filtered results:');
|
||||
filteredResults.hits.hits.forEach((hit) => {
|
||||
console.log(` - Name: ${hit._source.name}, Price: ${hit._source.price}`);
|
||||
});
|
||||
console.log();
|
||||
|
||||
// 7.3: Count documents
|
||||
console.log('7.3: Count documents matching query');
|
||||
const count = await createQuery<Product>('products-query-example')
|
||||
.range('price', { gte: 500 })
|
||||
.count();
|
||||
console.log(`Count of products over $500: ${count}`);
|
||||
console.log();
|
||||
|
||||
// 7.4: Get only sources (convenience method)
|
||||
console.log('7.4: Get sources only');
|
||||
const sources = await createQuery<Product>('products-query-example')
|
||||
.term('brand.keyword', 'TechBrand')
|
||||
.executeAndGetSources();
|
||||
console.log(`TechBrand products: ${sources.map((s) => s.name).join(', ')}`);
|
||||
console.log();
|
||||
|
||||
// ============================================================================
|
||||
// Step 8: Complex Real-World Query
|
||||
// ============================================================================
|
||||
|
||||
console.log('Step 8: Complex real-world query...\n');
|
||||
|
||||
console.log('Finding high-rated electronics in stock, sorted by best deals:');
|
||||
const complexResults = await createQuery<Product>('products-query-example')
|
||||
.term('category.keyword', 'Electronics')
|
||||
.range('rating', { gte: 4.0 })
|
||||
.range('stock', { gt: 0 })
|
||||
.range('price', { lte: 1000 })
|
||||
.sort('rating', 'desc')
|
||||
.size(5)
|
||||
.aggregations((agg) => {
|
||||
agg.terms('top_brands', 'brand.keyword', { size: 5 });
|
||||
agg.avg('avg_price', 'price');
|
||||
agg.max('max_rating', 'rating');
|
||||
})
|
||||
.execute();
|
||||
|
||||
console.log(`Found ${complexResults.hits.total.value} matching products`);
|
||||
console.log('\nTop results:');
|
||||
complexResults.hits.hits.forEach((hit, index) => {
|
||||
console.log(` ${index + 1}. ${hit._source.name}`);
|
||||
console.log(` Brand: ${hit._source.brand}`);
|
||||
console.log(` Price: $${hit._source.price}`);
|
||||
console.log(` Rating: ${hit._source.rating}⭐`);
|
||||
console.log(` Stock: ${hit._source.stock} units`);
|
||||
});
|
||||
|
||||
if (complexResults.aggregations) {
|
||||
console.log('\nAggregated insights:');
|
||||
console.log(' Average price:', complexResults.aggregations.avg_price);
|
||||
console.log(' Max rating:', complexResults.aggregations.max_rating);
|
||||
if ('top_brands' in complexResults.aggregations) {
|
||||
const topBrands = complexResults.aggregations.top_brands as { buckets: Array<{ key: string; doc_count: number }> };
|
||||
console.log(' Top brands:');
|
||||
topBrands.buckets.forEach((bucket) => {
|
||||
console.log(` - ${bucket.key}: ${bucket.doc_count} products`);
|
||||
});
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
|
||||
// ============================================================================
|
||||
// Step 9: Cleanup
|
||||
// ============================================================================
|
||||
|
||||
console.log('Step 9: Cleanup...');
|
||||
await products.deleteIndex();
|
||||
console.log('✓ Test index deleted');
|
||||
|
||||
await connectionManager.destroy();
|
||||
console.log('✓ Connection closed\n');
|
||||
|
||||
console.log('=== Query Builder Example Complete ===');
|
||||
}
|
||||
|
||||
// Run the example
|
||||
main().catch((error) => {
|
||||
console.error('Example failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user