rebase generation

This commit is contained in:
MaKarin
2026-04-07 19:40:41 +03:00
parent 73ddb1a948
commit aab7bfa691
180 changed files with 15512 additions and 364 deletions

637
IMPLEMENTATION_EXAMPLES.md Normal file
View File

@@ -0,0 +1,637 @@
# 💻 Практические примеры реализации оптимизаций
## ⚠️ 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 сервер без
внешних зависимостей).