Dokümanlar menüsü
Saga / compensation (geri sarma)
Bir çalıştırmanın geri sarılması gerektiğinde, compensateRun yürütülmüş araç kayıtlarını YÜRÜTME SIRASININ TERSİNDE dolaşır ve her aracın compensate hook'unu çağırır — compensation başlamadan önce bir condemn tombstone'ı yazarak çalıştırmanın bir daha asla resume edilememesini sağlar.
Ne işe yarar / ne zaman kullanılır#
Çok adımlı bir çalıştırma, birkaç yan-etkili araç zaten başarılı olduktan SONRA başarısız olabilir (ya da basitçe geri alınması gerekebilir) — bir tahsilat gerçekleşmiş, bir e-posta gönderilmiş, bir sevkiyat oluşturulmuş olabilir. compensateRun, açık, opt-in bir saga/geri-alma mekanizmasıdır: başarısızlıkta ASLA otomatik olarak tetiklenmez. Onu bilerek çağırırsınız ve o da zaten olanı geri sarmak için her aracın kendi compensate hook'unu (bir iade, bir iptal e-postası, bir sevkiyat iptali) çağırır.
Nasıl çalışır#
compensateRun, yürütülmüş (succeeded/failed/running) araç kayıtlarını ters yürütme sırasında geri sarar; rapordaki entry'ler de aynı geri-sarma sırasındadır. İlk failed ya da busy compensation'da durur — kalan daha önceki adımlar not-attempted olarak raporlanır. Her entry'nin status'u şunlardan biridir:
compensatedCompensate hook başarıyla çalıştı.
already-compensatedBu adım daha önceki bir compensateRun çağrısında zaten compensate edilmiş.
would-compensatedryRun: true altında — adım compensate EDİLİRDİ, ama hiçbir şey çalışmadı.
skipped-no-hookAracın compensate hook'u yok, dolayısıyla geri alınacak bir şey yok.
skipped-not-executedAraç çağrısı aslında hiç yürütülmedi (örn. hiç ulaşılmadı).
uncertainCompensate çağrısının sonucu belirlenemedi.
busyCompensate çağrısı şu anda başka bir yerde devam ediyor — geri sarma burada durur.
failedCompensate hook hata fırlattı — geri sarma burada durur.
not-attemptedGeri sarmadaki daha önceki bir adım; denenmeden bırakıldı çünkü yukarıdaki (ters sıradaki) daha sonraki bir adım zaten başarısız oldu ya da meşguldü.
Kurulum / import#
compensateRun ve hata türü @gnldev/durable içinde yaşar:
import { createGnl, compensateRun, CompensatedRunError } from '@gnldev/durable';Adım adım kullanım#
Yan-etkili bir araca bir compensate kancası verin — compensate?(input, output, opts); burada opts idempotencyKey, toolCallId ve runId taşır. Eşleşen iade veya geri-alma işlemini başlatmak için idempotencyKey'i kullanın:
// 'charge' refunds using the idempotencyKey when compensated.
const charge = {
execute: async (input, opts) => billing.charge(input, opts.idempotencyKey),
compensate: async (input, output, opts) => {
// opts: { idempotencyKey, toolCallId, runId }
return billing.refund(opts.idempotencyKey);
},
};
await gnl.run('pay', { runId: 'order-7', prompt: 'charge the customer', tools: { charge } });compensateRun(runId, opts)'u journal ve araçlarınızla çağırın (dryRun varsayılanı false; journal.readRun mevcut olmalı, yoksa fırlatır). Çalıştırmayı ters yürütme sırasında geri sarar — sonucu görmek için report.condemned ve report.entries'i inceleyin:
// Unwind the run in reverse execution order.
const report = await compensateRun('order-7', {
journal: storage.runs,
tools: { charge },
});
console.log(report.condemned); // true — a tombstone was written first
console.log(report.entries.map((e) => e.status));Yan etki olmadan bir plan almak için dryRun: true geçin: statuler would-compensate olur, ve çalıştırma condemn EDİLMEZ ya da yürütülmez:
// dryRun: true only plans the unwind — no tombstone, no execution.
const plan = await compensateRun('order-7', {
journal: storage.runs,
tools: { charge },
dryRun: true,
});
console.log(plan.entries.map((e) => e.status)); // e.g. ['would-compensate']API referansı#
compensateRun(runId, opts)compensateRun(runId: string, opts: { journal: Journal & Partial<JournalReader>; tools?: Record<string, AnyTool>; dryRun?: boolean }): Promise<CompensationReport>. journal.readRun gerektirir (yoksa hata fırlatır). tools varsayılan {}, dryRun varsayılan false.
AnyTool.compensate?(input, output, opts)Opsiyonel araç-başına hook: compensate?(input, output, opts): Promise<unknown>, burada opts = { idempotencyKey, toolCallId, runId }. YALNIZCA açık bir compensateRun tarafından çağrılır — başarısızlıkta asla otomatik olarak değil.
CompensationReport{ runId, dryRun, condemned, entries: CompensationEntry[] }. entries geri-sarma (ters yürütme) sırasındadır.
CompensationEntry{ suffix, toolCallId?, toolName?, status, error? }. status: 'compensated' | 'already-compensated' | 'would-compensate' | 'skipped-no-hook' | 'skipped-not-executed' | 'uncertain' | 'busy' | 'failed' | 'not-attempted'.
CompensatedRunErrorCondemn edilmiş bir çalıştırma resume edildiğinde assertNotCompensated (runDurable/streamDurable/forkRun tarafından kullanılır) tarafından fırlatılır. detail: { runId }.
runCompensated(journal, runId)runCompensated(journal, runId): Promise<boolean> — bir çalıştırmanın condemn/compensate edilip edilmediğini kontrol eder.
assertNotCompensated(journal, runId)assertNotCompensated(journal, runId): Promise<void> — çalıştırma condemn edilmişse CompensatedRunError fırlatır.
compensateRun, herhangi bir şey geri alınmadan ÖNCE bir tombstone yazar (bir dryRun sırasında asla). Condemn edildikten sonra çalıştırma resume'u reddeder: runDurable/streamDurable/forkRun, condemn edilmiş bir çalıştırma için CompensatedRunError fırlatan assertNotCompensated'ı çağırır.failed ya da busy ise, tüm önceki adımlar (N-1, N-2, …) sıra dışı denenmek yerine not-attempted olarak raporlanır.exactly-once-tools'a, compensateRun'ın dolaştığı yürütülmüş adımları journal'ın nasıl kaydettiği için deterministic-replay'e, ve askıya alınmış bir çağrının kaderine otomatik bir geri sarma yerine bir insanın karar verdiği kardeş akış için human-in-loop-approvals'a bakın.