/** * Comprehensive Schema Management Example * * Demonstrates index mapping management, templates, and migrations */ import { createConfig, ElasticsearchConnectionManager, LogLevel, createSchemaManager, type IndexSchema, type SchemaMigration, type IndexTemplate, } from '../../index.js'; async function main() { console.log('=== Schema Management 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) .build(); // ============================================================================ // Step 2: Initialize // ============================================================================ console.log('Step 2: Initializing connection and schema manager...'); const connectionManager = ElasticsearchConnectionManager.getInstance(config); await connectionManager.initialize(); const schemaManager = createSchemaManager({ historyIndex: '.test_migrations', dryRun: false, strict: true, enableLogging: true, enableMetrics: true, validateBeforeApply: true, }); await schemaManager.initialize(); console.log('✓ Connection and schema manager initialized\n'); // ============================================================================ // Step 3: Create Index with Schema // ============================================================================ console.log('Step 3: Creating index with schema...'); const productSchema: IndexSchema = { name: 'products-v1', version: 1, settings: { number_of_shards: 1, number_of_replicas: 0, refresh_interval: '1s', analysis: { analyzer: { product_analyzer: { type: 'custom', tokenizer: 'standard', filter: ['lowercase', 'snowball'], }, }, }, }, mappings: { dynamic: 'strict', properties: { id: { type: 'keyword' }, name: { type: 'text', analyzer: 'product_analyzer', properties: { keyword: { type: 'keyword', ignore_above: 256 }, }, }, description: { type: 'text' }, price: { type: 'scaled_float', scaling_factor: 100 }, category: { type: 'keyword' }, tags: { type: 'keyword' }, inStock: { type: 'boolean' }, createdAt: { type: 'date' }, updatedAt: { type: 'date' }, }, }, aliases: { products: { is_write_index: true }, }, metadata: { description: 'Product catalog index', owner: 'catalog-team', tags: ['catalog', 'products'], }, }; // Validate schema first const validation = schemaManager.validateSchema(productSchema); console.log(` Schema validation: ${validation.valid ? 'PASSED' : 'FAILED'}`); if (validation.warnings.length > 0) { console.log(' Warnings:'); for (const warning of validation.warnings) { console.log(` - ${warning.field}: ${warning.message}`); } } // Create the index await schemaManager.createIndex(productSchema); console.log(' ✓ Index created with mappings and aliases\n'); // ============================================================================ // Step 4: Schema Migrations // ============================================================================ console.log('Step 4: Running schema migrations...'); const migrations: SchemaMigration[] = [ { version: 1, name: 'add_rating_field', description: 'Add product rating field', type: 'update', index: 'products-v1', changes: { mappings: { properties: { rating: { type: 'float' }, reviewCount: { type: 'integer' }, }, }, }, rollback: { // Note: Can't remove fields in ES, but document for reference }, metadata: { author: 'dev-team', ticket: 'PROD-123', }, }, { version: 2, name: 'add_brand_field', description: 'Add brand field with keyword type', type: 'update', index: 'products-v1', changes: { mappings: { properties: { brand: { type: 'keyword' }, sku: { type: 'keyword' }, }, }, }, metadata: { author: 'dev-team', ticket: 'PROD-456', }, }, { version: 3, name: 'add_inventory_object', description: 'Add nested inventory object', type: 'update', index: 'products-v1', changes: { mappings: { properties: { inventory: { type: 'object', properties: { quantity: { type: 'integer' }, warehouse: { type: 'keyword' }, lastRestocked: { type: 'date' }, }, }, }, }, }, metadata: { author: 'inventory-team', ticket: 'INV-789', }, }, ]; const migrationResults = await schemaManager.migrate(migrations); console.log(' Migration results:'); for (const result of migrationResults) { console.log(` v${result.version} (${result.name}): ${result.status} (${result.duration}ms)`); } console.log(); // ============================================================================ // Step 5: Get Applied Migrations // ============================================================================ console.log('Step 5: Viewing migration history...'); const appliedMigrations = await schemaManager.getAppliedMigrations(); console.log(' Applied migrations:'); for (const migration of appliedMigrations) { console.log(` v${migration.version}: ${migration.name} (${migration.status})`); } console.log(); // ============================================================================ // Step 6: Compare Schemas // ============================================================================ console.log('Step 6: Comparing schemas...'); const oldSchema = productSchema; const newSchema: IndexSchema = { ...productSchema, version: 2, mappings: { ...productSchema.mappings, properties: { ...productSchema.mappings.properties, rating: { type: 'float' }, discount: { type: 'float' }, }, }, }; const diff = schemaManager.diffSchemas(oldSchema, newSchema); console.log(' Schema diff:'); console.log(` Identical: ${diff.identical}`); console.log(` Added fields: ${diff.added.join(', ') || 'none'}`); console.log(` Removed fields: ${diff.removed.join(', ') || 'none'}`); console.log(` Modified fields: ${diff.modified.length}`); console.log(` Breaking changes: ${diff.breakingChanges.length}`); console.log(); // ============================================================================ // Step 7: Index Templates // ============================================================================ console.log('Step 7: Managing index templates...'); const template: IndexTemplate = { name: 'products-template', index_patterns: ['products-*'], priority: 100, version: 1, template: { settings: { number_of_shards: 1, number_of_replicas: 0, }, mappings: { dynamic: 'strict', properties: { id: { type: 'keyword' }, name: { type: 'text' }, createdAt: { type: 'date' }, }, }, aliases: { 'all-products': {}, }, }, _meta: { description: 'Template for product indices', }, }; await schemaManager.putTemplate(template); console.log(' ✓ Index template created'); // Get template const retrievedTemplate = await schemaManager.getTemplate('products-template'); console.log(` Template patterns: ${retrievedTemplate?.index_patterns.join(', ')}`); console.log(); // ============================================================================ // Step 8: Alias Management // ============================================================================ console.log('Step 8: Managing aliases...'); // Add alias await schemaManager.addAlias('products-v1', 'products-read', { filter: { term: { inStock: true } }, }); console.log(' ✓ Added filtered read alias'); // Get schema to see aliases const currentSchema = await schemaManager.getSchema('products-v1'); console.log(` Current aliases: ${Object.keys(currentSchema?.aliases || {}).join(', ')}`); console.log(); // ============================================================================ // Step 9: Update Settings // ============================================================================ console.log('Step 9: Updating index settings...'); await schemaManager.updateSettings('products-v1', { refresh_interval: '5s', max_result_window: 20000, }); console.log(' ✓ Settings updated'); console.log(); // ============================================================================ // Step 10: Statistics // ============================================================================ console.log('Step 10: Schema manager statistics...\n'); const stats = schemaManager.getStats(); console.log('Schema Manager Statistics:'); console.log(` Total migrations: ${stats.totalMigrations}`); console.log(` Successful: ${stats.successfulMigrations}`); console.log(` Failed: ${stats.failedMigrations}`); console.log(` Rolled back: ${stats.rolledBackMigrations}`); console.log(` Total indices: ${stats.totalIndices}`); console.log(` Total templates: ${stats.totalTemplates}`); console.log(` Avg migration duration: ${stats.avgMigrationDuration.toFixed(2)}ms`); console.log(); // ============================================================================ // Step 11: Cleanup // ============================================================================ console.log('Step 11: Cleanup...'); await schemaManager.deleteTemplate('products-template'); await schemaManager.deleteIndex('products-v1'); await connectionManager.destroy(); console.log('✓ Cleanup complete\n'); console.log('=== Schema Management Example Complete ==='); console.log('\nKey Features Demonstrated:'); console.log(' ✓ Index creation with mappings and settings'); console.log(' ✓ Schema validation before apply'); console.log(' ✓ Versioned migrations with history tracking'); console.log(' ✓ Schema diff and comparison'); console.log(' ✓ Index templates'); console.log(' ✓ Alias management with filters'); console.log(' ✓ Settings updates'); console.log(' ✓ Migration rollback support'); console.log(' ✓ Dry run mode'); console.log(' ✓ Comprehensive statistics'); } // Run the example main().catch((error) => { console.error('Example failed:', error); process.exit(1); });