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

638 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 💻 Практические примеры реализации оптимизаций
## ⚠️ 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)
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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'а
```typescript
// 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
```typescript
// 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
```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
```typescript
// 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
```typescript
// 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 generation** →
`tools/orchestrator.ts` (`generate()`, `Promise.allSettled`, per-stage
try/catch, discriminated-union `GenerationStatus`).
- **Performance monitoring** → `tools/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 сервер без
внешних зависимостей).