UNPKG

@gftdcojp/gftd-orm

Version:

Enterprise-grade real-time data platform with ksqlDB, inspired by Supabase architecture

345 lines (341 loc) 13.2 kB
"use strict"; /** * TypeScript型生成機能 - ksqlDBスキーマから自動型定義生成 * * 完全実装済み機能: * - TypeScript型定義の自動生成 ✅ * - マッパー関数の自動生成 ✅ * - カラムメタデータの生成 ✅ * - 配列/マップ型対応 ✅ * - インターフェース名の自動生成 ✅ * - 複数テーブル一括生成 ✅ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.listAllTables = listAllTables; exports.getTableSchema = getTableSchema; exports.convertKsqlTypeToTypeScript = convertKsqlTypeToTypeScript; exports.generateTypeScriptInterface = generateTypeScriptInterface; exports.generateMapperFunction = generateMapperFunction; exports.generateColumnMetadata = generateColumnMetadata; exports.generateCompleteTypeDefinition = generateCompleteTypeDefinition; exports.generateInterfaceName = generateInterfaceName; exports.generateMapperFunctionName = generateMapperFunctionName; exports.generateColumnConstantName = generateColumnConstantName; exports.generateTypesForTables = generateTypesForTables; const ksqldb_client_1 = require("./ksqldb-client"); const logger_1 = require("./utils/logger"); /** * ksqlDB データ型のマッピング */ const KSQL_TYPE_TO_TYPESCRIPT = { 'STRING': 'string', 'VARCHAR': 'string', 'INTEGER': 'number', 'INT': 'number', 'BIGINT': 'number', 'DOUBLE': 'number', 'BOOLEAN': 'boolean', 'TIMESTAMP': 'string', // ISO 8601 文字列として扱う 'DATE': 'string', 'TIME': 'string', 'DECIMAL': 'number', 'BYTES': 'Uint8Array', }; /** * 全テーブル/ストリーム一覧を取得 * @returns テーブル・ストリーム情報の配列 */ async function listAllTables() { try { // テーブル一覧を取得 const tablesResult = await (0, ksqldb_client_1.executeQuery)('LIST TABLES EXTENDED;'); const streamsResult = await (0, ksqldb_client_1.executeQuery)('LIST STREAMS EXTENDED;'); const tables = []; // テーブル情報を処理 if (tablesResult && Array.isArray(tablesResult)) { for (const result of tablesResult) { if (result.tables && Array.isArray(result.tables)) { for (const table of result.tables) { tables.push({ name: table.name, type: 'TABLE', topic: table.topic || '', keyFormat: table.keyFormat || 'KAFKA', valueFormat: table.valueFormat || 'JSON', isWindowed: table.isWindowed || false, }); } } } } // ストリーム情報を処理 if (streamsResult && Array.isArray(streamsResult)) { for (const result of streamsResult) { if (result.streams && Array.isArray(result.streams)) { for (const stream of result.streams) { tables.push({ name: stream.name, type: 'STREAM', topic: stream.topic || '', keyFormat: stream.keyFormat || 'KAFKA', valueFormat: stream.valueFormat || 'JSON', isWindowed: false, // ストリームはwindowedではない }); } } } } return tables; } catch (error) { logger_1.log.error('Failed to list tables and streams:', error); throw new Error(`Failed to list tables and streams: ${error}`); } } /** * テーブル/ストリームのスキーマ情報を取得 * @param tableName - テーブル/ストリーム名 * @returns スキーマ情報 */ async function getTableSchema(tableName) { try { logger_1.log.debug(`Getting schema for table: ${tableName}`); // テーブル詳細情報を取得 const describeResult = await (0, ksqldb_client_1.describeTable)(tableName); logger_1.log.debug(`Describe result for ${tableName}:`, describeResult); if (!describeResult || !Array.isArray(describeResult) || describeResult.length === 0) { throw new Error(`No schema information found for table: ${tableName}`); } const sourceDescription = describeResult[0]?.sourceDescription; if (!sourceDescription) { throw new Error(`Invalid schema information for table: ${tableName}`); } // カラム情報を解析 const columns = []; if (sourceDescription.fields && Array.isArray(sourceDescription.fields)) { for (const field of sourceDescription.fields) { columns.push({ name: field.name, type: field.schema?.type || 'STRING', nullable: field.schema?.isOptional !== false, key: field.schema?.isKey === true, }); } } // テーブル情報を構築 const tableInfo = { name: sourceDescription.name || tableName, type: sourceDescription.type === 'STREAM' ? 'STREAM' : 'TABLE', topic: sourceDescription.topic || '', keyFormat: sourceDescription.keyFormat || 'KAFKA', valueFormat: sourceDescription.valueFormat || 'JSON', isWindowed: sourceDescription.windowType !== undefined, }; return { tableName, columns, tableInfo, }; } catch (error) { logger_1.log.error(`Failed to get schema for table ${tableName}:`, error); throw new Error(`Failed to get schema for table ${tableName}: ${error}`); } } /** * ksqlDB型をTypeScript型に変換 * @param ksqlType - ksqlDBのデータ型 * @param nullable - null許可フラグ * @returns TypeScript型文字列 */ function convertKsqlTypeToTypeScript(ksqlType, nullable = true) { // 配列型の処理 (ARRAY<TYPE>) const arrayMatch = ksqlType.match(/^ARRAY<(.+)>$/i); if (arrayMatch) { const innerType = convertKsqlTypeToTypeScript(arrayMatch[1], false); const baseType = `${innerType}[]`; return nullable ? `${baseType} | null` : baseType; } // マップ型の処理 (MAP<STRING, TYPE>) const mapMatch = ksqlType.match(/^MAP<\s*STRING\s*,\s*(.+)\s*>$/i); if (mapMatch) { const valueType = convertKsqlTypeToTypeScript(mapMatch[1], false); const baseType = `Record<string, ${valueType}>`; return nullable ? `${baseType} | null` : baseType; } // 基本型の変換 const upperType = ksqlType.toUpperCase(); const tsType = KSQL_TYPE_TO_TYPESCRIPT[upperType] || 'any'; return nullable ? `${tsType} | null` : tsType; } /** * TypeScript インターフェースを生成 * @param schema - スキーマ情報 * @returns 生成されたインターフェースコード */ function generateTypeScriptInterface(schema) { const interfaceName = generateInterfaceName(schema.tableName); const properties = schema.columns .map(column => { const tsType = convertKsqlTypeToTypeScript(column.type, column.nullable); const propertyName = column.name.toLowerCase(); return ` /** ${column.type}${column.nullable ? ' (nullable)' : ''} */\n ${propertyName}: ${tsType};`; }) .join('\n'); return `/** * Generated TypeScript interface for ksqlDB table: ${schema.tableName} * Generated on: ${new Date().toISOString()} */ export interface ${interfaceName} { ${properties} }`; } /** * マッパー関数を生成 * @param schema - スキーマ情報 * @returns 生成されたマッパー関数コード */ function generateMapperFunction(schema) { const interfaceName = generateInterfaceName(schema.tableName); const functionName = generateMapperFunctionName(schema.tableName); const mappings = schema.columns .map((column, index) => { const propertyName = column.name.toLowerCase(); return ` ${propertyName}: row[${index}]`; }) .join(',\n'); return `/** * Generated mapper function for converting ksqlDB array response to ${interfaceName} object * @param row - Array response from ksqlDB pull query * @returns ${interfaceName} object */ export function ${functionName}(row: any[]): ${interfaceName} { return { ${mappings} }; }`; } /** * カラムメタデータを生成 * @param schema - スキーマ情報 * @returns 生成されたメタデータコード */ function generateColumnMetadata(schema) { const constantName = generateColumnConstantName(schema.tableName); const columnNames = schema.columns.map(col => `'${col.name}'`).join(', '); const columnTypes = schema.columns.map(col => `'${col.type}'`).join(', '); return `/** * Generated column metadata for ksqlDB table: ${schema.tableName} */ export const ${constantName} = { names: [${columnNames}], types: [${columnTypes}], nullable: [${schema.columns.map(col => col.nullable).join(', ')}], keyColumns: [${schema.columns.map(col => col.key).join(', ')}] } as const;`; } /** * 完全なTypeScript型定義ファイルを生成 * @param schema - スキーマ情報 * @returns 生成された完全な型定義情報 */ function generateCompleteTypeDefinition(schema) { const interfaceName = generateInterfaceName(schema.tableName); const interfaceCode = generateTypeScriptInterface(schema); const mapperFunctionName = generateMapperFunctionName(schema.tableName); const mapperCode = generateMapperFunction(schema); const columnMetadata = generateColumnMetadata(schema); const fullCode = `// Generated TypeScript definitions for ksqlDB table: ${schema.tableName} // Generated on: ${new Date().toISOString()} // DO NOT EDIT - This file is auto-generated ${interfaceCode} ${mapperCode} ${columnMetadata} // Re-export for convenience export type ${schema.tableName.toUpperCase()}_TYPE = ${interfaceName}; `; return { interfaceName, interfaceCode, mapperFunctionName, mapperCode, columnMetadata, fullCode, }; } /** * テーブル名からインターフェース名を生成 * @param tableName - テーブル名 * @returns インターフェース名 */ function generateInterfaceName(tableName) { // TABLE_table のような重複を修正 const cleanName = tableName.replace(/_table$/i, '').replace(/_stream$/i, ''); // 特殊文字をアンダースコアに変換 const normalizedName = cleanName.replace(/[^a-zA-Z0-9_]/g, '_'); return normalizedName .split('_') .filter(word => word.length > 0) // 空の文字列を除外 .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join('') + 'Table'; } /** * テーブル名からマッパー関数名を生成 * @param tableName - テーブル名 * @returns マッパー関数名 */ function generateMapperFunctionName(tableName) { const interfaceName = generateInterfaceName(tableName); return `map${interfaceName}Row`; } /** * テーブル名からカラム定数名を生成 * @param tableName - テーブル名 * @returns カラム定数名 */ function generateColumnConstantName(tableName) { // TABLE_table のような重複を修正 const cleanName = tableName.replace(/_table$/i, '').replace(/_stream$/i, ''); // 特殊文字をアンダースコアに変換 const normalizedName = cleanName.replace(/[^a-zA-Z0-9_]/g, '_'); return `${normalizedName.toUpperCase()}_COLUMNS`; } /** * 複数テーブルの型定義を一括生成 * @param tableNames - テーブル名の配列(省略時は全テーブル) * @returns 生成された型定義情報の配列 */ async function generateTypesForTables(tableNames) { try { let tables; if (tableNames && tableNames.length > 0) { tables = tableNames; } else { // 全テーブルを取得 const allTables = await listAllTables(); tables = allTables.map(t => t.name); } const results = []; for (const tableName of tables) { try { logger_1.log.info(`Generating types for table: ${tableName}`); const schema = await getTableSchema(tableName); const typeInfo = generateCompleteTypeDefinition(schema); results.push(typeInfo); logger_1.log.info(`Successfully generated types for table: ${tableName}`); } catch (error) { logger_1.log.error(`Failed to generate types for table ${tableName}:`, error); // 個別テーブルの失敗は全体を止めない continue; } } return results; } catch (error) { logger_1.log.error('Failed to generate types for tables:', error); throw new Error(`Failed to generate types for tables: ${error}`); } } //# sourceMappingURL=type-generator.js.map