330 lines
11 KiB
TypeScript
330 lines
11 KiB
TypeScript
|
|
/**
|
||
|
|
* Complete Example - Enterprise Elasticsearch Client
|
||
|
|
*
|
||
|
|
* This example demonstrates:
|
||
|
|
* - Configuration with environment variables
|
||
|
|
* - Connection management with health checks
|
||
|
|
* - Document operations with sessions
|
||
|
|
* - Snapshot functionality
|
||
|
|
* - Error handling and observability
|
||
|
|
*/
|
||
|
|
|
||
|
|
import {
|
||
|
|
createConfig,
|
||
|
|
ElasticsearchConnectionManager,
|
||
|
|
LogLevel,
|
||
|
|
defaultLogger,
|
||
|
|
defaultMetricsCollector,
|
||
|
|
} from '../../core/index.js';
|
||
|
|
import { DocumentManager } from '../../domain/documents/index.js';
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// Type Definitions
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
interface Product {
|
||
|
|
name: string;
|
||
|
|
description: string;
|
||
|
|
price: number;
|
||
|
|
category: string;
|
||
|
|
inStock: boolean;
|
||
|
|
tags: string[];
|
||
|
|
createdAt: Date;
|
||
|
|
updatedAt: Date;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface ProductSnapshot {
|
||
|
|
totalProducts: number;
|
||
|
|
averagePrice: number;
|
||
|
|
categoryCounts: Record<string, number>;
|
||
|
|
outOfStockCount: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// Main Example
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
// Step 1: Configuration
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
console.log('🔧 Step 1: Creating configuration...\n');
|
||
|
|
|
||
|
|
const config = createConfig()
|
||
|
|
// Load from environment variables (ELASTICSEARCH_URL, etc.)
|
||
|
|
.fromEnv()
|
||
|
|
// Or specify directly
|
||
|
|
.nodes(process.env.ELASTICSEARCH_URL || 'http://localhost:9200')
|
||
|
|
.basicAuth(
|
||
|
|
process.env.ELASTICSEARCH_USERNAME || 'elastic',
|
||
|
|
process.env.ELASTICSEARCH_PASSWORD || 'changeme'
|
||
|
|
)
|
||
|
|
// Request settings
|
||
|
|
.timeout(30000)
|
||
|
|
.retries(3)
|
||
|
|
.compression(true)
|
||
|
|
// Connection pool
|
||
|
|
.poolSize(10, 2)
|
||
|
|
// Observability
|
||
|
|
.logLevel(LogLevel.INFO)
|
||
|
|
.enableRequestLogging(true)
|
||
|
|
.enableMetrics(true)
|
||
|
|
.enableTracing(true, {
|
||
|
|
serviceName: 'product-catalog',
|
||
|
|
serviceVersion: '1.0.0',
|
||
|
|
})
|
||
|
|
.build();
|
||
|
|
|
||
|
|
console.log('✅ Configuration created successfully\n');
|
||
|
|
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
// Step 2: Initialize Connection Manager
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
console.log('🔌 Step 2: Initializing connection manager...\n');
|
||
|
|
|
||
|
|
const connectionManager = ElasticsearchConnectionManager.getInstance(config);
|
||
|
|
await connectionManager.initialize();
|
||
|
|
|
||
|
|
console.log('✅ Connection established');
|
||
|
|
console.log(` Health Status: ${connectionManager.getHealthStatus()}`);
|
||
|
|
console.log(` Circuit State: ${connectionManager.getCircuitState()}\n`);
|
||
|
|
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
// Step 3: Create Document Manager
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
console.log('📦 Step 3: Creating document manager...\n');
|
||
|
|
|
||
|
|
const productManager = new DocumentManager<Product>({
|
||
|
|
index: 'products',
|
||
|
|
connectionManager,
|
||
|
|
autoCreateIndex: true,
|
||
|
|
});
|
||
|
|
|
||
|
|
await productManager.initialize();
|
||
|
|
console.log('✅ Document manager initialized\n');
|
||
|
|
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
// Step 4: Individual Document Operations
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
console.log('📝 Step 4: Individual document operations...\n');
|
||
|
|
|
||
|
|
// Create a product
|
||
|
|
await productManager.create('prod-001', {
|
||
|
|
name: 'Premium Widget',
|
||
|
|
description: 'A high-quality widget for all your needs',
|
||
|
|
price: 99.99,
|
||
|
|
category: 'widgets',
|
||
|
|
inStock: true,
|
||
|
|
tags: ['premium', 'bestseller'],
|
||
|
|
createdAt: new Date(),
|
||
|
|
updatedAt: new Date(),
|
||
|
|
});
|
||
|
|
console.log(' ✓ Created product prod-001');
|
||
|
|
|
||
|
|
// Upsert (create or update)
|
||
|
|
await productManager.upsert('prod-002', {
|
||
|
|
name: 'Deluxe Gadget',
|
||
|
|
description: 'The ultimate gadget',
|
||
|
|
price: 149.99,
|
||
|
|
category: 'gadgets',
|
||
|
|
inStock: true,
|
||
|
|
tags: ['deluxe', 'featured'],
|
||
|
|
createdAt: new Date(),
|
||
|
|
updatedAt: new Date(),
|
||
|
|
});
|
||
|
|
console.log(' ✓ Upserted product prod-002');
|
||
|
|
|
||
|
|
// Get a product
|
||
|
|
const product = await productManager.get('prod-001');
|
||
|
|
console.log(` ✓ Retrieved product: ${product?._source.name}`);
|
||
|
|
|
||
|
|
// Update a product
|
||
|
|
await productManager.update('prod-001', {
|
||
|
|
price: 89.99, // Price reduction!
|
||
|
|
updatedAt: new Date(),
|
||
|
|
});
|
||
|
|
console.log(' ✓ Updated product prod-001\n');
|
||
|
|
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
// Step 5: Session-Based Batch Operations
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
console.log('🔄 Step 5: Session-based batch operations...\n');
|
||
|
|
|
||
|
|
const session = productManager.session({
|
||
|
|
cleanupStale: true, // Delete documents not in this session
|
||
|
|
batchSize: 100,
|
||
|
|
});
|
||
|
|
|
||
|
|
const batchResult = await session
|
||
|
|
.start()
|
||
|
|
.upsert('prod-003', {
|
||
|
|
name: 'Standard Widget',
|
||
|
|
description: 'A reliable widget',
|
||
|
|
price: 49.99,
|
||
|
|
category: 'widgets',
|
||
|
|
inStock: true,
|
||
|
|
tags: ['standard'],
|
||
|
|
createdAt: new Date(),
|
||
|
|
updatedAt: new Date(),
|
||
|
|
})
|
||
|
|
.upsert('prod-004', {
|
||
|
|
name: 'Mini Gadget',
|
||
|
|
description: 'Compact and efficient',
|
||
|
|
price: 29.99,
|
||
|
|
category: 'gadgets',
|
||
|
|
inStock: false,
|
||
|
|
tags: ['compact', 'mini'],
|
||
|
|
createdAt: new Date(),
|
||
|
|
updatedAt: new Date(),
|
||
|
|
})
|
||
|
|
.upsert('prod-005', {
|
||
|
|
name: 'Mega Widget Pro',
|
||
|
|
description: 'Professional grade widget',
|
||
|
|
price: 199.99,
|
||
|
|
category: 'widgets',
|
||
|
|
inStock: true,
|
||
|
|
tags: ['professional', 'premium'],
|
||
|
|
createdAt: new Date(),
|
||
|
|
updatedAt: new Date(),
|
||
|
|
})
|
||
|
|
.commit();
|
||
|
|
|
||
|
|
console.log(` ✓ Batch operation completed:`);
|
||
|
|
console.log(` - Successful: ${batchResult.successful}`);
|
||
|
|
console.log(` - Failed: ${batchResult.failed}`);
|
||
|
|
console.log(` - Time: ${batchResult.took}ms\n`);
|
||
|
|
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
// Step 6: Iteration Over Documents
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
console.log('🔍 Step 6: Iterating over documents...\n');
|
||
|
|
|
||
|
|
let count = 0;
|
||
|
|
for await (const doc of productManager.iterate()) {
|
||
|
|
count++;
|
||
|
|
console.log(` ${count}. ${doc._source.name} - $${doc._source.price}`);
|
||
|
|
}
|
||
|
|
console.log(`\n ✓ Iterated over ${count} documents\n`);
|
||
|
|
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
// Step 7: Create Snapshot with Analytics
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
console.log('📸 Step 7: Creating snapshot with analytics...\n');
|
||
|
|
|
||
|
|
const snapshot = await productManager.snapshot<ProductSnapshot>(
|
||
|
|
async (iterator, previousSnapshot) => {
|
||
|
|
console.log(' 🔄 Processing snapshot...');
|
||
|
|
|
||
|
|
let totalPrice = 0;
|
||
|
|
let productCount = 0;
|
||
|
|
const categoryCounts: Record<string, number> = {};
|
||
|
|
let outOfStockCount = 0;
|
||
|
|
|
||
|
|
for await (const doc of iterator) {
|
||
|
|
productCount++;
|
||
|
|
totalPrice += doc._source.price;
|
||
|
|
|
||
|
|
const category = doc._source.category;
|
||
|
|
categoryCounts[category] = (categoryCounts[category] || 0) + 1;
|
||
|
|
|
||
|
|
if (!doc._source.inStock) {
|
||
|
|
outOfStockCount++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const analytics: ProductSnapshot = {
|
||
|
|
totalProducts: productCount,
|
||
|
|
averagePrice: productCount > 0 ? totalPrice / productCount : 0,
|
||
|
|
categoryCounts,
|
||
|
|
outOfStockCount,
|
||
|
|
};
|
||
|
|
|
||
|
|
if (previousSnapshot) {
|
||
|
|
console.log(` 📊 Previous snapshot had ${previousSnapshot.totalProducts} products`);
|
||
|
|
}
|
||
|
|
|
||
|
|
return analytics;
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
console.log('\n ✅ Snapshot created:');
|
||
|
|
console.log(` - Total Products: ${snapshot.data.totalProducts}`);
|
||
|
|
console.log(` - Average Price: $${snapshot.data.averagePrice.toFixed(2)}`);
|
||
|
|
console.log(` - Out of Stock: ${snapshot.data.outOfStockCount}`);
|
||
|
|
console.log(` - Categories:`);
|
||
|
|
for (const [category, count] of Object.entries(snapshot.data.categoryCounts)) {
|
||
|
|
console.log(` • ${category}: ${count}`);
|
||
|
|
}
|
||
|
|
console.log(` - Processing Time: ${snapshot.processingTime}ms\n`);
|
||
|
|
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
// Step 8: Health Check & Metrics
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
console.log('❤️ Step 8: Health check and metrics...\n');
|
||
|
|
|
||
|
|
const healthResult = await connectionManager.healthCheck();
|
||
|
|
console.log(' Health Check:');
|
||
|
|
console.log(` - Status: ${healthResult.status}`);
|
||
|
|
console.log(` - Cluster Health: ${healthResult.clusterHealth}`);
|
||
|
|
console.log(` - Active Nodes: ${healthResult.activeNodes}`);
|
||
|
|
console.log(` - Response Time: ${healthResult.responseTimeMs}ms\n`);
|
||
|
|
|
||
|
|
// Export metrics in Prometheus format
|
||
|
|
const metricsExport = defaultMetricsCollector.export();
|
||
|
|
console.log(' 📊 Metrics (sample):');
|
||
|
|
console.log(metricsExport.split('\n').slice(0, 20).join('\n'));
|
||
|
|
console.log(' ...\n');
|
||
|
|
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
// Step 9: Error Handling Demo
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
console.log('⚠️ Step 9: Error handling demo...\n');
|
||
|
|
|
||
|
|
try {
|
||
|
|
await productManager.get('non-existent-id');
|
||
|
|
} catch (error) {
|
||
|
|
console.log(' ✓ Gracefully handled non-existent document (returns null)\n');
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const nonExistentManager = new DocumentManager<Product>({
|
||
|
|
index: 'non-existent-index',
|
||
|
|
connectionManager,
|
||
|
|
autoCreateIndex: false,
|
||
|
|
});
|
||
|
|
await nonExistentManager.initialize();
|
||
|
|
} catch (error: any) {
|
||
|
|
console.log(` ✓ Caught expected error: ${error.message}`);
|
||
|
|
console.log(` Error Code: ${error.code}\n`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
// Step 10: Cleanup
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
console.log('🧹 Step 10: Cleanup...\n');
|
||
|
|
|
||
|
|
// Optional: Delete the index
|
||
|
|
// await productManager.deleteIndex();
|
||
|
|
// console.log(' ✓ Index deleted');
|
||
|
|
|
||
|
|
// Close connections
|
||
|
|
await connectionManager.destroy();
|
||
|
|
console.log(' ✓ Connections closed\n');
|
||
|
|
|
||
|
|
console.log('✨ Example completed successfully!\n');
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// Run Example
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
||
|
|
main().catch((error) => {
|
||
|
|
console.error('❌ Example failed:', error);
|
||
|
|
defaultLogger.error('Example failed', error);
|
||
|
|
process.exit(1);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
export { main };
|