spinny:~/writing $ vim trigger-dev-background-jobs-guide.md
1~2Cogu production uygulamasi istek/yanit dongusune sigmayan ise ihtiyac duyar: e-posta gondermek, yuklemeleri islemek, AI pipeline'larini calistirmak, ucuncu taraf verileri senkronize etmek, raporlar uretmek. Geleneksel cevap bir kuyruktur (Redis, SQS, RabbitMQ), bir worker filosu, bir scheduler ve her deploy'da kirilan kirilgan bir glue kod yiginidir.3~4[Trigger.dev](https://trigger.dev) bu yigini tek bir TypeScript SDK'sina sikistirir. Fonksiyonlari yazarsiniz, herhangi bir yerden cagirirsiniz ve platform queueing, retries, observability, scheduling ve dayanikli yurutmeyi yonetir. Tasks ihtiyac duyduklari sure boyunca calisir - 10 saniyelik serverless timeout yok, redeploy'larda kaybolan is yok.5~6## Neden Trigger.dev7~82026'daki kayma dayanikli yurutme. Is akislari yeniden baslatmalardan, kazalardan, deploy'lardan ve rate limit'lerden kurtulmali. Ayni zamanda ilerlemeyi gercek zamanli olarak UI'a stream etmeli ve insan girdisi icin duraklamali. Trigger.dev surum 3 ile bu gereksinimler etrafinda yeniden insa edildi ve AI altyapi yuzeyini genisletmeye devam ediyor.9~10```mermaid11graph LR12 App[Uygulamaniz] -->|trigger| API[Trigger.dev API]13 API --> Queue[Dayanikli Queue]14 Queue --> Worker[Worker Container]15 Worker -->|run task| Task[Task Kodunuz]16 Task -->|metadata| Realtime[Realtime Stream]17 Realtime --> UI[React UI]18 Worker --> Storage[Run Durum Store]19```20~21Model basit: tasks'lari export olarak tanimlarsiniz, SDK onlari alir, platform onlari izole konteynerlerde planlar ve calistirir ve run durumu kalici olarak korunur, boylece devam edebilir, yeniden deneyebilir ve gozlemleyebilirsiniz.22~23## Baslarken24~25### Bir proje baslatin26~27```bash28npx trigger.dev@latest login29npx trigger.dev@latest init30```31~32Bu, bir `trigger.config.ts` dosyasi ve ornek tasks'larla bir `trigger/` dizini olusturur. Config dosyasi projenizin gercek kaynaktir: hangi dizinler tasks icerir, build ayarlari, lifecycle hooks ve runtime secenekleri.33~34```typescript35// trigger.config.ts36import { defineConfig } from "@trigger.dev/sdk";37~38export default defineConfig({39 project: "proj_abc123",40 runtime: "node",41 logLevel: "log",42 maxDuration: 3600,43 retries: {44 enabledInDev: true,45 default: {46 maxAttempts: 3,47 factor: 2,48 minTimeoutInMs: 1000,49 maxTimeoutInMs: 30_000,50 },51 },52 dirs: ["./trigger"],53});54```55~56### Tasks'lari yerel olarak calistirin57~58```bash59npx trigger.dev@latest dev60```61~62dev server bulutla baglantilar, tasks'larinizi kaydeder ve runs'lari yerel kodunuz uzerinden stream eder. Editorunuzde breakpoint'ler ayarlarsiniz ve gercek tetikleyicilerde bunlara ulasirsiniz - herhangi bir normal Node.js projesinde kullanacaginiz ayni dongu.63~64## Bir Task Tanimlamak65~66Bir task, benzersiz bir `id` ve bir `run` fonksiyonu ile dista aktarilan bir nesnedir. SDK, `dirs` boyunca dista aktarmalari inceler ve otomatik olarak kaydeder.67~68```typescript69// trigger/send-welcome-email.ts70import { task } from "@trigger.dev/sdk";71import { Resend } from "resend";72~73const resend = new Resend(process.env.RESEND_API_KEY);74~75export const sendWelcomeEmail = task({76 id: "send-welcome-email",77 retry: {78 maxAttempts: 5,79 factor: 1.8,80 minTimeoutInMs: 500,81 maxTimeoutInMs: 30_000,82 },83 run: async (payload: { email: string; name: string }) => {84 const { data, error } = await resend.emails.send({85 from: "hello@spinny.dev",86 to: payload.email,87 subject: `Welcome, ${payload.name}`,88 html: `<p>Glad you are here, ${payload.name}.</p>`,89 });90~91 if (error) throw error;92 return { messageId: data?.id };93 },94});95```96~97Dikkat edilecek uc sey:98~991. **Run govdesinde timeout yok.** Platform yurutme suresini runtime'da degil, config'deki `maxDuration` araciligiyla yonetir.1002. **Throws retry'dir.** SDK istisnalari yakalar ve `retry` politikasina gore exponential backoff ile yeniden calistirir.1013. **Donus degeri kalici olarak korunur.** Diger tasks ve frontend'iniz herhangi bir yerden `run.output` okuyabilir.102~103## Tasks'lari Tetikleme104~105Bir task'i backend'inizden, API rotalarinizdan veya baska bir task'tan cagirirsiniz.106~107```typescript108import { sendWelcomeEmail } from "@/trigger/send-welcome-email";109~110const handle = await sendWelcomeEmail.trigger(111 { email: "user@example.com", name: "Alex" },112 {113 idempotencyKey: `welcome-${userId}`,114 concurrencyKey: `tenant-${tenantId}`,115 queue: { name: "emails", concurrencyLimit: 50 },116 delay: "30s",117 ttl: "10m",118 }119);120~121console.log(handle.id); // run_xyz - ilerlemeyi izlemek veya goruntulemek icin kullanin122```123~124Secenekler tek bir cagrida cok fazla davranis acar:125~126- **`idempotencyKey`** - ayni anahtara sahip bir run zaten varsa, SDK isi cogaltmak yerine mevcut hosgeldiniz dondurur.127- **`concurrencyKey`** - anahtar paylasan runs'lari serilestirir, boylece tenant basi rate limit asilmaz.128- **`queue.concurrencyLimit`** - tum anahtarlar boyunca queue icin global cap.129- **`delay`** - run'i gelecek bir zaman icin planlar.130- **`ttl`** - run o zamana kadar baslamamissa, otomatik olarak suresini doldurur.131~132### Batch trigger133~134Fan-out yukleri icin, `batchTrigger` cagri basina 500 ogeye kadar kabul eder ve oge basina bir run olusturur.135~136```typescript137await sendWelcomeEmail.batchTrigger(138 newUsers.map((u) => ({139 payload: { email: u.email, name: u.name },140 options: { idempotencyKey: `welcome-${u.id}` },141 }))142);143```144~145## Planlanmis Tasks146~147Cron isleri birinci sinif bildirimler haline gelir. Schedule'in kendisi, bir task'a birden cok kez ekleyebileceginiz ayri bir nesnedir.148~149```typescript150// trigger/daily-digest.ts151import { schedules } from "@trigger.dev/sdk";152~153export const dailyDigest = schedules.task({154 id: "daily-digest",155 cron: "0 9 * * *",156 run: async (payload) => {157 console.log("Scheduled at:", payload.timestamp);158 console.log("Last run:", payload.lastTimestamp);159 console.log("Timezone:", payload.timezone);160 console.log("Next 5 runs:", payload.upcoming);161~162 await sendDigestForDate(payload.timestamp);163 },164});165```166~167Tenant basi schedules icin - diyelim ki, musteri basi bir cron - bunlari management API uzerinden dinamik olarak olusturursunuz.168~169```typescript170import { schedules } from "@trigger.dev/sdk";171~172await schedules.create({173 task: "daily-digest",174 cron: "0 9 * * *",175 timezone: "America/New_York",176 externalId: `customer_${customerId}`,177 deduplicationKey: `digest-${customerId}`,178});179```180~181`deduplicationKey` cagriyi idempotent yapar: deploy zamanin ayni kodu yeniden calistirmak yinelenen schedules yigmaz.182~183## Queues, Concurrency ve Idempotency184~185Uc primitive cogu rate-limiting ve siralama ihtiyacini karsilar.186~187```mermaid188graph TB189 Trigger[trigger payload] --> IK{idempotencyKey<br/>gorulen?}190 IK -->|evet| Reuse[Mevcut run'u dondur]191 IK -->|hayir| CK[concurrencyKey kova]192 CK --> Q[concurrencyLimit ile<br/>Queue]193 Q -->|slot mevcut| Run[task'i calistir]194 Q -->|slotlar dolu| Wait[Queue'da bekle]195```196~197Yaygin bir kalip: bir vendor'un rate limit'ine saygi gostermek icin tenant basi bir queue kucuk anahtar basi concurrency ile, ayrica retry'leri guvenli kilmak icin bir idempotency anahtari.198~199```typescript200await syncShopifyOrders.trigger(201 { shopId },202 {203 queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 },204 concurrencyKey: shopId,205 idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`,206 }207);208```209~210## Waits ve Uzun Suren Is211~212Tasks bir baglanti tutmadan veya compute yakmadan duraklatilabilir. Platform durumu kalici olarak korur ve wait tamamlandiginda fonksiyonu devam ettirir.213~214```typescript215import { wait } from "@trigger.dev/sdk";216~217export const onboarding = task({218 id: "onboarding",219 run: async (payload: { userId: string }) => {220 await sendWelcomeEmail.triggerAndWait({ userId: payload.userId });221 await wait.for({ days: 1 });222 await sendTipsEmail.trigger({ userId: payload.userId });223 await wait.until({ date: oneWeekFromSignup(payload.userId) });224 await sendUpgradeOffer.trigger({ userId: payload.userId });225 },226});227```228~229`triggerAndWait` katil ozelliktir: bir cocuk task'i tetikler ve cocuk tamamlanana kadar parent'i askiya alir. Tasks'lari async fonksiyonlar gibi olusturursunuz, ancak orkestrasyon gunler veya haftalar boyunca dayanikli olarak calisir.230~231### `wait.forToken` ile Human-in-the-loop232~233Onay akislari ve AI gates icin, `wait.forToken` uygulamaniz bir sonucla geri arayana kadar duraklatir.234~235```typescript236import { task, wait } from "@trigger.dev/sdk";237~238export const publishPost = task({239 id: "publish-post",240 run: async (payload: { draftId: string }) => {241 const draft = await generateAIContent(payload.draftId);242~243 const token = await wait.createToken({ timeout: "7d" });244 await notifyEditor({ draftId: draft.id, token: token.id });245~246 const decision = await wait.forToken<{ approved: boolean; notes?: string }>(247 token.id248 );249~250 if (decision.approved) {251 return await publish(draft);252 }253 return await applyFeedback(draft, decision.notes);254 },255});256```257~258Editor bir UI acar, taslagi inceler, Onayla'ya tiklar ve backend'iniz token'i tamamlar. Task kaldigi yerden devam eder - saatler veya gunler gecmis olsa bile.259~260## Lifecycle Hooks261~262`init`, `onStart`, `onSuccess` ve `onFailure`'i bir task'a veya `trigger.config.ts`'de global olarak ekleyebilirsiniz. Bunlari tracing, error reporting ve paylasilan setup icin kullanin.263~264```typescript265// trigger.config.ts266export default defineConfig({267 // ...268 init: async () => {269 Sentry.init({ dsn: process.env.SENTRY_DSN });270 },271 onFailure: async ({ error, ctx }) => {272 Sentry.captureException(error, {273 tags: { taskId: ctx.task.id, runId: ctx.run.id },274 });275 },276});277```278~279`init` boot'ta worker container basina bir kez calisir, run basina degil, bu yuzden istemcileri ve havuzlari ayarlamak icin dogru yerdir.280~281## Frontend'de Realtime282~283Trigger.dev run durumu degisikliklerini - status, metadata, output - bir streaming API uzerinde yayinlar. React hooks bu stream'e abone olur ve otomatik olarak yeniden render eder.284~285```typescript286// trigger/process-video.ts287import { task, metadata } from "@trigger.dev/sdk";288~289export const processVideo = task({290 id: "process-video",291 run: async (payload: { videoId: string }) => {292 metadata.set("stage", "transcoding");293 await transcode(payload.videoId);294~295 metadata.set("stage", "thumbnails");296 await generateThumbnails(payload.videoId);297~298 metadata.set("stage", "uploading");299 const url = await uploadToCDN(payload.videoId);300~301 return { url };302 },303});304```305~306```tsx307// components/VideoStatus.tsx308"use client";309import { useRealtimeRun } from "@trigger.dev/react-hooks";310import type { processVideo } from "@/trigger/process-video";311~312export function VideoStatus({313 runId,314 publicAccessToken,315}: {316 runId: string;317 publicAccessToken: string;318}) {319 const { run, error } = useRealtimeRun<typeof processVideo>(runId, {320 accessToken: publicAccessToken,321 });322~323 if (error) return <p>Error: {error.message}</p>;324 if (!run) return <p>Loading...</p>;325~326 return (327 <div>328 <p>Status: {run.status}</p>329 <p>Stage: {String(run.metadata?.stage ?? "queued")}</p>330 {run.output?.url && <video src={run.output.url} controls />}331 </div>332 );333}334```335~336Belirli bir run'a kapsam belirlenmis sunucu tarafinda public access token uretirsiniz ve istemciye gondersiniz. Hook auth, yeniden baglanmayi ve artisli guncellemeleri yonetir.337~338Tek seferde trigger-and-subscribe icin:339~340```tsx341import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";342~343const { submit, run, isLoading } = useRealtimeTaskTrigger<typeof processVideo>(344 "process-video",345 { accessToken: publicAccessToken }346);347~348<button onClick={() => submit({ videoId })} disabled={isLoading}>349 Process video350</button>;351```352~353## AI Agents ve Streaming354~355Trigger.dev AI agents icin populer bir runtime haline geldi cunku ayni primitives - dayanikli yurutme, retries, waits, gercek zamanli metadata, human-in-the-loop - tam olarak agents'lerin ihtiyac duydugu seydir. Run gerceklesirken bir model saglayicisindan token'lari `metadata`'a stream edersiniz, frontend bunlari canli olarak render eder ve run, serverless timeout yakmadan uzun suren tool calls'tan hayatta kalir.356~357```typescript358import { task, metadata } from "@trigger.dev/sdk";359import { streamText } from "ai";360import { anthropic } from "@ai-sdk/anthropic";361~362export const researchAgent = task({363 id: "research-agent",364 maxDuration: 1800,365 run: async (payload: { question: string }) => {366 const result = streamText({367 model: anthropic("claude-opus-4-7"),368 system: "You are a research assistant. Use the web.",369 prompt: payload.question,370 tools: { webSearch },371 });372~373 let fullText = "";374 for await (const chunk of result.textStream) {375 fullText += chunk;376 metadata.set("partial", fullText);377 }378~379 return { answer: fullText, usage: await result.usage };380 },381});382```383~384Frontend `useRealtimeRun` kullanir ve streaming yanitini render etmek icin `run.metadata.partial` okur, bir chat completion'i render edeceginiz gibi - bu sadece tam bir sayfa yenileme'den sag cikar.385~386## Deploying387~388Deploy'lar tasks'larinizi versiyonlu bir bundle'a derler, bir container olusturur ve trafigi atomik olarak degistirir. Eski ucustaki runs onceki versiyonu kullanmaya devam eder.389~390```bash391npx trigger.dev@latest deploy --env prod392```393~394CI'da bunu genellikle uygulamanizi gonderen ayni workflow'a baglarsiniz:395~396```yaml397# .github/workflows/deploy.yml398- name: Deploy Trigger.dev399 env:400 TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }}401 run: npx trigger.dev@latest deploy --env prod402```403~404Onizleme ortamlari icin, `--env preview --branch ${{ github.head_ref }}` gecirin ve Trigger.dev branch basina izole bir ortam olusturur, bu Vercel'in onizleme deploy'larini nasil ele aldigini yansitir.405~406## Self-Hosting vs Cloud407~408Trigger.dev Apache 2.0 lisansi altinda acik kaynaktir. Herhangi bir container platformunda (Docker Compose, Kubernetes, Fly.io) self-host edebilir veya trigger.dev'de yonetilen bulutu kullanabilirsiniz.409~410| Yon | Cloud | Self-hosted |411|--------|-------|-------------|412| **Setup** | Kayit ol, `init` calistir | docker-compose veya Helm chart calistir |413| **Olcekleme** | Otomatik | Sizin sorumlulugunuz |414| **Pricing** | Run basi + compute basi | Sadece infra maliyeti |415| **Compliance** | SOC 2 | Ortaminizin sagladigi her sey |416| **En iyisi** | Cogu takim | Sıkı veri ikamet, ozel infra |417~418SDK ve CLI modlar arasinda aynidir - bir profile flag'i degistirirsiniz ve kendi instance'iniza yonlendirirsiniz.419~420## En Iyi Uygulamalar421~422### 1. Payloads'lari kucuk ve serilestirilebilir tutun423~424ID'ler ve referanslari gecirin, tam objeler degil. Verileri task icinde cekin. Bu queue'yu kucuk tutar, payloads loglamak icin ucuz olur ve veri kaynagini yeniden tetiklemeden degistirmenize olanak tanir.425~426### 2. Her dis cagride idempotency keys427~428Task tetikleyicisindeki `idempotencyKey`'i vendor API'larinizdaki (Stripe, OpenAI, vb.) idempotency keys ile birlestirin. Retries uctan uca guvenli olacaktir.429~430### 3. Orkestrasyon icin `triggerAndWait` kullanin, tetikleyicilerin `Promise.all`'i degil431~432`triggerAndWait` cagiran bir parent dayanikli olarak cocuk tasks'lari olusturur. Tetikleyen ve hemen cozen bir parent zincirin gozlemlenebilirligini kaybeder.433~434### 4. Runs'lari etiketleyin435~436Tetikleyicilere `tags` ekleyin (`tags: ["user:123", "feature:onboarding"]`) boylece dashboard ve management API'yi is boyutlarina gore filtreleyebilirsiniz.437~438### 5. `init`'i idempotent tutun439~440Her cold start'ta calisir. Orada migrations veya tek seferlik yan etkilerden kacinin.441~442## Sonuc443~444Trigger.dev eskiden sifirdan bir job sistemi olusturmayi gerektiren is kategorilerini ortadan kaldirir. Async TypeScript yazarsiniz, herhangi bir yerden cagirirsiniz ve platform size kutu disinda dayanikli yurutme, scheduling, queues, retries, gercek zamanli guncellemeler ve human-in-the-loop kaliplari verir.445~446Bir gece cron'unu calistiran ayni yuzey, frontend'e stream eden ve inceleme icin duraklatan cok asamali bir AI agent'i calistiran yuzeydir. Bu yakinsama, framework'u 2026'da ciddi bir bakisa deger kilan seydir, ister guvenilir arka plan isi gerektiren bir SaaS isletiyor olun, ister bir serverless timeout'tan sag cikan AI ozellikleri gonderiyor olun.447~448> **Baslama Kontrol Listesi:**449>450> - [x] trigger.dev'de kayit olun veya self-hosted Docker stack'i calistirin451> - [x] Projenizde `npx trigger.dev@latest init`452> - [x] `task({ id, run })` ile ilk task'inizi tanimlayin453> - [x] Bunu API'nizden tetikleyin ve dashboard'da run'i izleyin454> - [x] Production guvenligi icin `idempotencyKey` ve `concurrencyKey` ekleyin455> - [x] `useRealtimeRun`'i bir status component'ine baglayin456> - [x] CI'dan `trigger.dev deploy --env prod` ile deploy edin457~
NORMAL · trigger-dev-background-jobs-guide.md [readonly]457 lines · :q to close