Files
toir-automatization/IMPLEMENTATION_EXAMPLES.md
2026-04-07 19:40:41 +03:00

18 KiB
Raw Permalink Blame History

💻 Практические примеры реализации оптимизаций

⚠️ Status: Partial Implementation

The following optimizations have been moved to actual code implementation via the Claude Code prompt and removed from this file:

  • Graceful Error Handling → CLAUDE_CODE_PROMPT.md Phase 1
  • Performance Monitoring → CLAUDE_CODE_PROMPT.md Phase 4

This document now focuses on remaining optimization examples that are still in design/planning phase and waiting for implementation.


1. Incremental Generation с Caching

1.1 Система кэширования (cache-manager.ts)

// tools/cache-manager.ts
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';

interface CacheEntry {
  hash: string;
  timestamp: number;
  value: any;
}

export class GenerationCache {
  private cacheDir = './.generation-cache';

  constructor() {
    if (!fs.existsSync(this.cacheDir)) {
      fs.mkdirSync(this.cacheDir, { recursive: true });
    }
  }

  /**
   * Вычислить хеш входных данных
   */
  computeHash(input: any): string {
    const content = JSON.stringify(input, Object.keys(input).sort());
    return crypto.createHash('sha256').update(content).digest('hex');
  }

  /**
   * Получить значение из кэша
   */
  get<T>(key: string, input: any): T | null {
    const hash = this.computeHash(input);
    const cachePath = path.join(this.cacheDir, `${key}.json`);

    if (!fs.existsSync(cachePath)) {
      return null;
    }

    try {
      const cached: CacheEntry = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
      if (cached.hash === hash) {
        console.log(`✅ Cache hit for ${key}`);
        return cached.value;
      }
    } catch (e) {
      // Cache file corrupted, regenerate
    }

    return null;
  }

  /**
   * Сохранить значение в кэш
   */
  set(key: string, input: any, value: any): void {
    const hash = this.computeHash(input);
    const cachePath = path.join(this.cacheDir, `${key}.json`);

    const entry: CacheEntry = {
      hash,
      timestamp: Date.now(),
      value,
    };

    fs.writeFileSync(cachePath, JSON.stringify(entry, null, 2));
  }

  /**
   * Очистить старые кэши (>7 дней)
   */
  cleanup(maxAgeDays = 7): void {
    const maxAge = maxAgeDays * 24 * 60 * 60 * 1000;
    const now = Date.now();

    fs.readdirSync(this.cacheDir).forEach(file => {
      const cachePath = path.join(this.cacheDir, file);
      const cached: CacheEntry = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));

      if (now - cached.timestamp > maxAge) {
        fs.unlinkSync(cachePath);
        console.log(`🗑️  Deleted old cache: ${file}`);
      }
    });
  }
}

export const cache = new GenerationCache();

1.2 Использование в orchestrator.ts

// tools/orchestrator.ts (updated)
import { cache } from './cache-manager';

async function orchestrate(task: string): Promise<void> {
  const dslContent = readDSL('domain/toir.api.dsl');

  // Проверить кэш
  const cachedContract = cache.get('frozen-contract', dslContent);
  let contract = cachedContract || freezeContract(dslContent);

  if (!cachedContract) {
    cache.set('frozen-contract', dslContent, contract);
  }

  // Для Prisma: проверить кэш перед генерацией
  const cachedPrismaSchema = cache.get('prisma-schema', contract);
  let prismaOutput;

  if (cachedPrismaSchema) {
    prismaOutput = cachedPrismaSchema;
  } else {
    prismaOutput = await agent('generator_prisma', {
      contract,
      task: 'Generate Prisma schema'
    });
    cache.set('prisma-schema', contract, prismaOutput);
  }

  // ... аналогично для других генераторов
}

2. Параллельная генерация

2.1 Параллельный orchestrator

// tools/orchestrator-parallel.ts
async function orchestrateParallel(task: string): Promise<GenerationResult> {
  const contract = freezeContract(readDSL('domain/toir.api.dsl'));

  // Независимые генерации - запустить в параллель
  const [prismaOutput, nestOutput, reactOutput] = await Promise.all([
    // Prisma не зависит ни от чего
    agent('generator_prisma', {
      contract,
      task: 'Generate Prisma schema'
    }),

    // NestJS зависит от Prisma (но не блокирующая зависимость)
    agent('generator_nest_resources', {
      contract,
      // Может получить Prisma результаты позже
      task: 'Generate NestJS resources'
    }),

    // React независим от NestJS на этапе генерации ресурсов
    agent('generator_react_admin_resources', {
      contract,
      task: 'Generate React Admin resources'
    })
  ]);

  // ПОТОМ: когда все завершены, интегрировать и валидировать
  const dataAccessOutput = await agent('generator_data_access', {
    contract,
    nestModules: nestOutput,
    task: 'Integrate data access'
  });

  // Финальная валидация
  const reviewed = await agent('reviewer', {
    outputs: {
      prisma: prismaOutput,
      nest: nestOutput,
      react: reactOutput,
      dataAccess: dataAccessOutput
    },
    task: 'Validate all outputs'
  });

  return reviewed;
}

3. Plugin System

3.1 Plugin Interface

// types/generator-plugin.ts
export interface GeneratorHooks {
  /**
   * Вызывается перед началом всей генерации
   */
  beforeGeneration?: (contract: FrozenContract) => Promise<void>;

  /**
   * После фриза контракта, но перед первой генерацией
   */
  afterContractFreeze?: (contract: FrozenContract) => Promise<FrozenContract>;

  /**
   * После генерации Prisma schema
   */
  afterPrismaGeneration?: (schema: string) => Promise<string>;

  /**
   * После генерации NestJS модулей
   */
  afterNestResourcesGeneration?: (modules: GeneratedModule[]) => Promise<GeneratedModule[]>;

  /**
   * После генерации React ресурсов
   */
  afterReactResourcesGeneration?: (resources: GeneratedResource[]) => Promise<GeneratedResource[]>;

  /**
   * Финальная интеграция перед валидацией
   */
  beforeReview?: (outputs: AllOutputs) => Promise<AllOutputs>;

  /**
   * На ошибку в любом агенте
   */
  onGenerationError?: (error: Error, stage: string) => Promise<void>;
}

export interface GeneratorPlugin {
  name: string;
  version: string;
  description: string;
  hooks: GeneratorHooks;
}

3.2 Plugin Manager

// tools/plugin-manager.ts
export class PluginManager {
  private plugins: GeneratorPlugin[] = [];

  loadPlugins(pluginDir: string): void {
    const files = fs.readdirSync(pluginDir);

    files.forEach(file => {
      if (file.endsWith('.plugin.ts') || file.endsWith('.plugin.js')) {
        const pluginPath = path.join(pluginDir, file);
        const plugin = require(pluginPath).default as GeneratorPlugin;

        this.plugins.push(plugin);
        console.log(`📦 Loaded plugin: ${plugin.name} v${plugin.version}`);
      }
    });
  }

  async executeHook(
    hookName: keyof GeneratorHooks,
    input: any,
    context: { stage?: string; error?: Error }
  ): Promise<any> {
    let result = input;

    for (const plugin of this.plugins) {
      const hook = plugin.hooks[hookName];
      if (hook) {
        try {
          result = await hook(result);
          console.log(`✅ ${plugin.name}.${hookName}`);
        } catch (e) {
          console.error(`❌ ${plugin.name}.${hookName} failed:`, e);
          if (plugin.hooks.onGenerationError) {
            await plugin.hooks.onGenerationError(e as Error, context.stage || 'unknown');
          }
        }
      }
    }

    return result;
  }
}

export const pluginManager = new PluginManager();

3.3 Пример plugin'а

// plugins/oauth2-plugin.plugin.ts
import { GeneratorPlugin } from '../types/generator-plugin';

export default {
  name: 'oauth2-auth',
  version: '1.0.0',
  description: 'Add OAuth2 support to generated schema and auth',

  hooks: {
    afterPrismaGeneration: async (schema: string) => {
      // Добавить OAuth2 таблицы
      const oauthTables = `
        model OAuth2Provider {
          id        String    @id @default(cuid())
          name      String    @unique
          clientId  String
          clientSecret String
          createdAt DateTime @default(now())
        }

        model OAuth2Token {
          id        String @id @default(cuid())
          userId    String
          providerId String
          accessToken String
          refreshToken String?
          expiresAt DateTime
          user User @relation(fields: [userId], references: [id])
        }
      `;

      return schema + '\n' + oauthTables;
    },

    afterNestResourcesGeneration: async (modules) => {
      // Добавить OAuth2 контроллер
      const oauth2Module = {
        name: 'oauth2',
        files: {
          'oauth2.controller.ts': `
            import { Controller, Get, Query } from '@nestjs/common';

            @Controller('auth/oauth2')
            export class OAuth2Controller {
              @Get('callback')
              async handleCallback(@Query('code') code: string) {
                // OAuth2 callback logic
              }
            }
          `
        }
      };

      return [...modules, oauth2Module];
    }
  }
} as GeneratorPlugin;

4. OpenAPI as Intermediate Representation

4.1 DSL → OpenAPI Generator

// tools/dsl-to-openapi.ts
import { FrozenContract } from './types';
import { OpenAPI, Info, Paths } from 'openapi3-ts/oas30';

export function dslToOpenAPI(contract: FrozenContract): OpenAPI {
  const openapi: OpenAPI = {
    openapi: '3.1.0',
    info: {
      title: contract.appName || 'Generated API',
      version: contract.version || '1.0.0',
      description: contract.description || ''
    } as Info,
    paths: {} as Paths,
    components: {
      schemas: {}
    }
  };

  // Для каждого entity - генерировать endpoints
  contract.entities.forEach(entity => {
    const basePath = `/${entity.plural || entity.name}`;

    // GET /entities
    openapi.paths![`${basePath}`] = {
      get: {
        operationId: `list${entity.name}`,
        tags: [entity.name],
        responses: {
          '200': {
            description: `List of ${entity.plural}`,
            content: {
              'application/json': {
                schema: {
                  type: 'array',
                  items: { $ref: `#/components/schemas/${entity.name}` }
                }
              }
            }
          }
        }
      },
      post: {
        operationId: `create${entity.name}`,
        tags: [entity.name],
        requestBody: {
          content: {
            'application/json': {
              schema: { $ref: `#/components/schemas/${entity.name}CreateDTO` }
            }
          }
        },
        responses: {
          '201': {
            description: `Created ${entity.name}`,
            content: {
              'application/json': {
                schema: { $ref: `#/components/schemas/${entity.name}` }
              }
            }
          }
        }
      }
    };

    // GET /entities/{id}
    openapi.paths![`${basePath}/{id}`] = {
      get: {
        operationId: `get${entity.name}`,
        parameters: [
          { name: 'id', in: 'path', required: true, schema: { type: 'string' } }
        ],
        tags: [entity.name],
        responses: {
          '200': {
            description: `${entity.name} details`,
            content: {
              'application/json': {
                schema: { $ref: `#/components/schemas/${entity.name}` }
              }
            }
          }
        }
      },
      put: {
        operationId: `update${entity.name}`,
        parameters: [
          { name: 'id', in: 'path', required: true, schema: { type: 'string' } }
        ],
        tags: [entity.name],
        requestBody: {
          content: {
            'application/json': {
              schema: { $ref: `#/components/schemas/${entity.name}UpdateDTO` }
            }
          }
        },
        responses: {
          '200': {
            description: `Updated ${entity.name}`,
            content: {
              'application/json': {
                schema: { $ref: `#/components/schemas/${entity.name}` }
              }
            }
          }
        }
      },
      delete: {
        operationId: `delete${entity.name}`,
        parameters: [
          { name: 'id', in: 'path', required: true, schema: { type: 'string' } }
        ],
        tags: [entity.name],
        responses: {
          '204': {
            description: 'Deleted successfully'
          }
        }
      }
    };

    // Добавить schema для entity
    openapi.components!.schemas![entity.name] = {
      type: 'object',
      properties: Object.fromEntries(
        entity.fields.map(field => [
          field.name,
          { type: mapFieldTypeToOpenAPI(field.type) }
        ])
      ),
      required: entity.fields.filter(f => f.required).map(f => f.name)
    };
  });

  return openapi;
}

function mapFieldTypeToOpenAPI(fieldType: string): string {
  const mapping: Record<string, string> = {
    'string': 'string',
    'int': 'integer',
    'float': 'number',
    'boolean': 'boolean',
    'date': 'string', // format: date
    'datetime': 'string', // format: date-time
  };
  return mapping[fieldType] || 'string';
}

4.2 Usage в package.json

{
  "scripts": {
    "generate:api-summary": "ts-node tools/api-summary-generator.ts",
    "generate:openapi": "ts-node tools/dsl-to-openapi.ts",
    "generate:docs": "npm run generate:openapi && swagger-cli bundle openapi.json --outfile docs/api.html",
    "generate:client-sdk": "openapi-generator-cli generate -i openapi.json -g typescript-fetch -o client/src/generated"
  }
}

5. Type Safety с Zod

5.1 DSL Validation Schema

// tools/validation/dsl-schema.ts
import { z } from 'zod';

export const FieldSchema = z.object({
  name: z.string().min(1),
  type: z.enum(['string', 'int', 'float', 'boolean', 'date', 'datetime']),
  required: z.boolean().default(true),
  unique: z.boolean().default(false),
  indexed: z.boolean().default(false),
});

export const EntitySchema = z.object({
  name: z.string().min(1),
  plural: z.string().optional(),
  description: z.string().optional(),
  fields: z.array(FieldSchema).min(1),
  timestamps: z.boolean().default(true),
});

export const RelationshipSchema = z.object({
  from: z.string(),
  to: z.string(),
  type: z.enum(['one-to-one', 'one-to-many', 'many-to-many']),
  cascade: z.boolean().default(false),
});

export const DSLSchema = z.object({
  appName: z.string().min(1),
  version: z.string().default('1.0.0'),
  entities: z.array(EntitySchema),
  relationships: z.array(RelationshipSchema).optional(),
  auth: z.object({
    enabled: z.boolean().default(true),
    providers: z.array(z.string()).optional(),
  }).optional(),
});

export type DSL = z.infer<typeof DSLSchema>;
export type Entity = z.infer<typeof EntitySchema>;
export type Field = z.infer<typeof FieldSchema>;

5.2 Validation в orchestrator

// tools/orchestrator.ts (with validation)
import { DSLSchema, DSL } from './validation/dsl-schema';

async function orchestrate(task: string): Promise<void> {
  const rawDSL = readDSL('domain/toir.api.dsl');

  // Валидировать перед началом
  let validatedDSL: DSL;
  try {
    validatedDSL = DSLSchema.parse(rawDSL);
    console.log('✅ DSL validation passed');
  } catch (error) {
    console.error('❌ DSL validation failed:');
    console.error(error.issues);
    process.exit(1);
  }

  // Продолжить с validated data
  const contract = freezeContract(validatedDSL);
  // ...
}

Removed: Sections 6 (Nx Caching Configuration) and 8 (placeholder) have been removed. Nx caching is tracked as a future recommendation, not a worked example. Graceful Error Handling and Performance Monitoring are now fully implemented — see tools/orchestrator.ts (generate(), GenerationOutput, runTask()) and tools/performance-monitor.ts (PerformanceMonitor class) for the production code paths instead of illustrative snippets.


Заключение

Эти примеры показывают как:

  1. Кэшировать результаты генерации
  2. Параллелизировать независимые операции
  3. Расширять систему через плагины
  4. Генерировать OpenAPI для документации и SDK
  5. Валидировать входные данные

Реализованные оптимизации (уже в репозитории):

  • Graceful error handling + parallel generationtools/orchestrator.ts (generate(), Promise.allSettled, per-stage try/catch, discriminated-union GenerationStatus).
  • Performance monitoringtools/performance-monitor.ts (PerformanceMonitor.mark/measure/summary/export), отчёт в generation-metrics.json.
  • MCP-валидаторыtools/mcp/{prisma,npm,nest}-validator.mjs + tools/mcp/lib/mcp-server.mjs (минимальный JSON-RPC stdio сервер без внешних зависимостей).