UNPKG

@gftdcojp/gftd-cli

Version:

GFTD Schema Management CLI - Drizzle-like schema management for Kafka and ksqlDB

343 lines 13.8 kB
"use strict"; /** * GFTD Categorical Manager - Category-theoretic unified configuration */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.CategoricalManager = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const types_1 = require("../types"); const YAML = require('yaml'); class CategoricalManager { constructor(config) { this.config = config; this.naming = new types_1.CategoricalNaming(config.naming); } /** * 統合設定ファイルの初期化 */ static async initUnified(options) { const configPath = path.join(process.cwd(), CategoricalManager.UNIFIED_CONFIG_FILE); if (fs.existsSync(configPath) && !options.interactive) { throw new Error('Unified configuration file already exists. Use --force to overwrite.'); } const unifiedConfig = { org: options.orgId, project: options.project, connections: { kafka: { endpoint: process.env.KAFKA_ENDPOINT || '${KAFKA_ENDPOINT}', credentials: [ process.env.KAFKA_API_KEY || '${KAFKA_API_KEY}', process.env.KAFKA_API_SECRET || '${KAFKA_API_SECRET}' ] }, ksqldb: { endpoint: process.env.KSQLDB_ENDPOINT || '${KSQLDB_ENDPOINT}', credentials: [ process.env.KSQLDB_API_KEY || '${KSQLDB_API_KEY}', process.env.KSQLDB_API_SECRET || '${KSQLDB_API_SECRET}' ] } }, schema: { version: '1.0.0', topics: { users: { partitions: 3, replication: 3, config: { 'cleanup.policy': 'compact', 'retention.ms': -1, 'compression.type': 'snappy' } }, events: { partitions: 5, replication: 3, config: { 'cleanup.policy': 'delete', 'retention.ms': 604800000, 'compression.type': 'lz4' } } }, streams: { users: { format: ['JSON', 'KAFKA'], schema: { 'ID': 'VARCHAR(PK)', 'NAME': 'VARCHAR', 'EMAIL': 'VARCHAR', 'CREATED_AT': 'BIGINT', 'UPDATED_AT': 'BIGINT' } }, events: { format: ['JSON'], schema: { 'EVENT_ID': 'VARCHAR', 'USER_ID': 'VARCHAR', 'EVENT_TYPE': 'VARCHAR', 'PAYLOAD': 'VARCHAR', 'TIMESTAMP': 'BIGINT' } } }, tables: { users: { source: 'users', aggregation: `SELECT ID, LATEST_BY_OFFSET(NAME) AS NAME, LATEST_BY_OFFSET(EMAIL) AS EMAIL, LATEST_BY_OFFSET(CREATED_AT) AS CREATED_AT, LATEST_BY_OFFSET(UPDATED_AT) AS UPDATED_AT FROM {stream} GROUP BY ID` } } }, naming: { topic: '{name}', stream: '{ORG}_{NAME}', table: '{ORG}_{NAME}' }, migration: { strategy: 'colimit', output: './migrations', dependency_order: ['topics', 'streams', 'tables'] } }; const configYaml = YAML.stringify(unifiedConfig, { indent: 2 }); fs.writeFileSync(configPath, configYaml); console.log(`✅ Initialized GFTD unified project:`); console.log(` Organization: ${options.orgId}`); if (options.project) { console.log(` Project: ${options.project}`); } console.log(` Unified Config: ${configPath}`); console.log(''); console.log('📝 Next steps:'); console.log(' 1. Edit the unified configuration with your credentials'); console.log(' 2. Customize the schema definitions'); console.log(' 3. Run: gftd migrate'); } /** * 統合設定ファイルの読み込み */ static load(configPath) { const filePath = configPath || path.join(process.cwd(), CategoricalManager.UNIFIED_CONFIG_FILE); if (!fs.existsSync(filePath)) { throw new Error(`Unified configuration file not found: ${filePath}\n` + 'Run "gftd init --unified" to initialize a new unified project.'); } const content = fs.readFileSync(filePath, 'utf8'); let config = YAML.parse(content); // 環境変数の解決 config = CategoricalManager.resolveEnvironmentVariables(config); CategoricalManager.validate(config); return new CategoricalManager(config); } /** * 環境変数の解決 */ static resolveEnvironmentVariables(config) { const resolved = JSON.parse(JSON.stringify(config)); // Kafka connections if (process.env.KAFKA_ENDPOINT) { resolved.connections.kafka.endpoint = process.env.KAFKA_ENDPOINT; } if (process.env.KAFKA_API_KEY && process.env.KAFKA_API_SECRET) { resolved.connections.kafka.credentials = [ process.env.KAFKA_API_KEY, process.env.KAFKA_API_SECRET ]; } // ksqlDB connections if (process.env.KSQLDB_ENDPOINT) { resolved.connections.ksqldb.endpoint = process.env.KSQLDB_ENDPOINT; } if (process.env.KSQLDB_API_KEY && process.env.KSQLDB_API_SECRET) { resolved.connections.ksqldb.credentials = [ process.env.KSQLDB_API_KEY, process.env.KSQLDB_API_SECRET ]; } return resolved; } /** * 設定の検証 */ static validate(config) { const errors = []; if (!config.org) errors.push('org is required'); if (!config.connections?.kafka?.endpoint) errors.push('connections.kafka.endpoint is required'); if (!config.connections?.kafka?.credentials?.[0]) errors.push('connections.kafka.credentials[0] (API key) is required'); if (!config.connections?.kafka?.credentials?.[1]) errors.push('connections.kafka.credentials[1] (API secret) is required'); if (!config.connections?.ksqldb?.endpoint) errors.push('connections.ksqldb.endpoint is required'); if (!config.connections?.ksqldb?.credentials?.[0]) errors.push('connections.ksqldb.credentials[0] (API key) is required'); if (!config.connections?.ksqldb?.credentials?.[1]) errors.push('connections.ksqldb.credentials[1] (API secret) is required'); if (errors.length > 0) { throw new Error(`Unified configuration validation failed:\n${errors.map(e => ` - ${e}`).join('\n')}`); } } /** * 統合設定をレガシー形式に変換 */ toLegacyConfig() { return { schema: '', tables: {}, orgId: this.config.org, project: this.config.project, kafka: { endpoint: this.config.connections.kafka.endpoint, apiKey: this.config.connections.kafka.credentials[0], apiSecret: this.config.connections.kafka.credentials[1] }, ksqlDB: { endpoint: this.config.connections.ksqldb.endpoint, apiKey: this.config.connections.ksqldb.credentials[0], apiSecret: this.config.connections.ksqldb.credentials[1] }, migrationPath: this.config.migration.output }; } /** * 統合スキーマをレガシー形式に変換 */ toLegacySchema() { const legacyTopics = {}; const legacyStreams = {}; const legacyTables = {}; // Topics transformation Object.entries(this.config.schema.topics).forEach(([key, topic]) => { legacyTopics[key] = { partitions: topic.partitions, replicationFactor: topic.replication, configEntries: Object.entries(topic.config).map(([name, value]) => ({ name, value: String(value) })) }; }); // Streams transformation Object.entries(this.config.schema.streams).forEach(([key, stream]) => { const formatConfig = types_1.SchemaParser.parseFormat(stream.format); const columns = Object.entries(stream.schema).map(([name, definition]) => { const parsed = types_1.SchemaParser.parseColumnType(definition); return { name, type: parsed.type, key: parsed.key }; }); legacyStreams[key] = { valueFormat: formatConfig.valueFormat, keyFormat: formatConfig.keyFormat, columns }; }); // Tables transformation Object.entries(this.config.schema.tables).forEach(([key, table]) => { legacyTables[key] = { aggregation: table.aggregation }; }); return { orgId: this.config.org, project: this.config.project, version: this.config.schema.version, topics: legacyTopics, streams: legacyStreams, tables: legacyTables }; } /** * プロジェクト情報の表示 */ showInfo() { console.log('📊 GFTD Categorical Project Information:'); console.log(` Organization: ${this.config.org}`); if (this.config.project) { console.log(` Project: ${this.config.project}`); } console.log(` Schema Version: ${this.config.schema.version}`); console.log(` Migration Strategy: ${this.config.migration.strategy}`); console.log(` Migration Output: ${this.config.migration.output}`); console.log(''); console.log('🔗 Connections:'); console.log(` Kafka: ${this.config.connections.kafka.endpoint}`); console.log(` ksqlDB: ${this.config.connections.ksqldb.endpoint}`); console.log(''); console.log('📝 Category Structure:'); console.log(` Topics (Objects): ${Object.keys(this.config.schema.topics).length}`); console.log(` Streams (Morphisms): ${Object.keys(this.config.schema.streams).length}`); console.log(` Tables (Morphisms): ${Object.keys(this.config.schema.tables).length}`); console.log(''); console.log('🏷️ Naming Templates:'); console.log(` Topic: ${this.config.naming.topic}`); console.log(` Stream: ${this.config.naming.stream}`); console.log(` Table: ${this.config.naming.table}`); console.log(''); console.log('📝 Example transformations:'); console.log(` Topic 'users' → ${this.naming.generateTopicName(this.config.org, 'users')}`); console.log(` Stream 'users' → ${this.naming.generateStreamName(this.config.org, 'users')}`); console.log(` Table 'users' → ${this.naming.generateTableName(this.config.org, 'users')}`); } /** * 設定オブジェクトの取得 */ getConfig() { return this.config; } /** * 命名規則の取得 */ getNaming() { return this.naming; } } exports.CategoricalManager = CategoricalManager; CategoricalManager.UNIFIED_CONFIG_FILE = 'gftd.yml'; //# sourceMappingURL=categorical-manager.js.map