Files
elasticsearch/ts/examples/schema/schema-example.ts

380 lines
11 KiB
TypeScript
Raw Normal View History

/**
* 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);
});