Motor generic pentru procese cu mai multi pasi care nu incap intr-o tranzactie. Garantia nu e atomicitatea, ci compensarea in ordine inversa. - saga-registry: definitii versionate in cod, backoff exponential plafonat - saga-runner: corelator outbox->instanta, step runner, timeout, retry, compensare; revendicare cu FOR UPDATE SKIP LOCKED ca doua procese sa nu avanseze aceeasi saga simultan - idempotenta la pornire prin index unic (saga, versiune, trigger_event), nu prin SELECT-apoi-INSERT care ar avea race - compensarea are propriul retry: o compensare esuata lasa sistemul mai rau decat esecul original - monitor /v1/sagas cu instante blocate si retry manual care NU sare pasi - prima saga reala: imbogatire research brief, fara pas AI (un apel AI automat per brief ar schimba profilul de cost)
40 lines
1.3 KiB
TypeScript
40 lines
1.3 KiB
TypeScript
import { Controller, Get, Param, ParseUUIDPipe, Post, Query } from '@nestjs/common';
|
|
import { CurrentSession } from '../auth/session.decorator';
|
|
import type { AuthenticatedSession } from '../auth/tenant.guard';
|
|
import { SagasService } from './sagas.service';
|
|
|
|
const STATUSES = [
|
|
'RUNNING',
|
|
'COMPLETED',
|
|
'COMPENSATING',
|
|
'COMPENSATED',
|
|
'FAILED',
|
|
'TIMED_OUT',
|
|
] as const;
|
|
|
|
@Controller('sagas')
|
|
export class SagasController {
|
|
constructor(private readonly sagas: SagasService) {}
|
|
|
|
@Get()
|
|
overview(@CurrentSession() session: AuthenticatedSession) {
|
|
return this.sagas.overview(session);
|
|
}
|
|
|
|
@Get('instances')
|
|
list(@CurrentSession() session: AuthenticatedSession, @Query('status') status?: string) {
|
|
// Allowlist, nu interpolare: statusul ajunge intr-o comparatie SQL.
|
|
const safe = STATUSES.includes(status as (typeof STATUSES)[number]) ? status : undefined;
|
|
return this.sagas.list(session, safe);
|
|
}
|
|
|
|
@Get('instances/:id')
|
|
detail(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string) {
|
|
return this.sagas.detail(session, id);
|
|
}
|
|
|
|
@Post('instances/:id/retry')
|
|
retry(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string) {
|
|
return this.sagas.retry(session, id);
|
|
}
|
|
}
|