GNL
Dokümanlar menüsü
Core · Ücretsiz@gnldev/durable

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:

constcompensated

Compensate hook başarıyla çalıştı.

constalready-compensated

Bu adım daha önceki bir compensateRun çağrısında zaten compensate edilmiş.

constwould-compensate

dryRun: true altında — adım compensate EDİLİRDİ, ama hiçbir şey çalışmadı.

constskipped-no-hook

Aracın compensate hook'u yok, dolayısıyla geri alınacak bir şey yok.

constskipped-not-executed

Araç çağrısı aslında hiç yürütülmedi (örn. hiç ulaşılmadı).

constuncertain

Compensate çağrısının sonucu belirlenemedi.

constbusy

Compensate çağrısı şu anda başka bir yerde devam ediyor — geri sarma burada durur.

constfailed

Compensate hook hata fırlattı — geri sarma burada durur.

constnot-attempted

Geri 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ı#

fncompensateRun(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.

fnAnyTool.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.

typeCompensationReport

{ runId, dryRun, condemned, entries: CompensationEntry[] }. entries geri-sarma (ters yürütme) sırasındadır.

typeCompensationEntry

{ suffix, toolCallId?, toolName?, status, error? }. status: 'compensated' | 'already-compensated' | 'would-compensate' | 'skipped-no-hook' | 'skipped-not-executed' | 'uncertain' | 'busy' | 'failed' | 'not-attempted'.

classCompensatedRunError

Condemn 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 }.

fnrunCompensated(journal, runId)

runCompensated(journal, runId): Promise<boolean> — bir çalıştırmanın condemn/compensate edilip edilmediğini kontrol eder.

fnassertNotCompensated(journal, runId)

assertNotCompensated(journal, runId): Promise<void> — çalıştırma condemn edilmişse CompensatedRunError fırlatır.

Condemn-first
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.
Not
Geri sarma kesinlikle ters sıradadır ve ilk başarısızlıkta durur: eğer N. adımın compensate çağrısı failed ya da busy ise, tüm önceki adımlar (N-1, N-2, …) sıra dışı denenmek yerine not-attempted olarak raporlanır.
İlgili
Compensate hook'larına kararlı bir idempotencyKey veren çalıştırma-başına exactly-once garantisi için 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.