rebase generation
This commit is contained in:
637
IMPLEMENTATION_EXAMPLES.md
Normal file
637
IMPLEMENTATION_EXAMPLES.md
Normal 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 сервер без
|
||||
внешних зависимостей).
|
||||
Reference in New Issue
Block a user