@gftdcojp/gftd-cli
Version:
GFTD Schema Management CLI - Drizzle-like schema management for Kafka and ksqlDB
343 lines • 13.8 kB
JavaScript
;
/**
* 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