spinny:~/writing $ vim trigger-dev-background-jobs-guide.md
1~2Vetsina produkcnich aplikaci potrebuje praci, ktera nezapadne do cyklu pozadavek/odpoved: posilani emailu, zpracovani uploadu, spousteni AI pipelines, synchronizace dat tretich stran, generovani reportu. Tradicni odpoved je fronta (Redis, SQS, RabbitMQ), flotila workeru, planovac a krechka hromada lepidlovych kodu, ktery se rozpadne pri kazdem deployi.3~4[Trigger.dev](https://trigger.dev) zhusti tento stack do jedineho TypeScript SDK. Pisete funkce, volate je odkudkoliv a platforma se stara o frontoupravu, retries, observability, planovani a trvale provadeni. Ulohy bezi tak dlouho, jak je potreba - zadny 10sekundovy serverless timeout, zadna ztracena prace pri redeploy.5~6## Proc Trigger.dev7~8Posun v roce 2026 je trvale provadeni. Workflows musi prezit restarty, krachy, deploye a rate limity. Musi take streamovat pokrok do UI v realnem case a pozastavit se pro lidsky vstup. Trigger.dev byl prebudovan kolem techto pozadavku ve verzi 3 a pokracuje v rozsireni sve AI infrastruktury.9~10```mermaid11graph LR12 App[Vase App] -->|trigger| API[Trigger.dev API]13 API --> Queue[Trvala Fronta]14 Queue --> Worker[Worker Container]15 Worker -->|run task| Task[Vas Task kod]16 Task -->|metadata| Realtime[Realtime Stream]17 Realtime --> UI[React UI]18 Worker --> Storage[Run State Store]19```20~21Model je jednoduchy: definujete tasks jako exporty, SDK je vyzvedne, platforma je planuje a spousti v izolovanych kontejnerech a stav run je zachovan, abyste mohli pokracovat, opakovat a sledovat.22~23## Zacnete24~25### Inicializujte projekt26~27```bash28npx trigger.dev@latest login29npx trigger.dev@latest init30```31~32To vytvori soubor `trigger.config.ts` a adresar `trigger/` s ukazkovymi tasks. Konfiguracni soubor je zdrojem pravdy pro vas projekt: ktere adresare obsahuji tasks, nastaveni buildu, lifecycle hooks a runtime moznosti.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### Spustte tasks lokalne57~58```bash59npx trigger.dev@latest dev60```61~62Dev server se pripoji k cloudu, zaregistruje vase tasks a streamuje runs pres vas lokalni kod. Nastavujete breakpointy ve svem editoru a trefite je na skutecnych triggerech - stejna smycka, kterou byste pouzili v jakemkoli normalnim Node.js projektu.63~64## Definovani Task65~66Task je objekt exportovany s unikatnim `id` a funkci `run`. SDK kontroluje exporty v `dirs` a registruje je automaticky.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~97Tri veci k povsimnuti:98~991. **Zadny timeout v tele run.** Platforma spravuje cas provadeni pres `maxDuration` v konfiguraci, ne v runtime.1002. **Throws jsou retries.** SDK zachycuje vyjimky a opakuje s exponencialnim backoff podle politiky `retry`.1013. **Navratova hodnota je zachovana.** Ostatni tasks a vas frontend mohou cist `run.output` odkudkoliv.102~103## Triggerovani Tasks104~105Volate task ze sveho backendu, API rout nebo jineho tasku.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 - pouzijte pro sledovani nebo zobrazeni pokroku122```123~124Moznosti odemykaji hodne chovani v jednom volani:125~126- **`idempotencyKey`** - pokud run se stejnym klicem jiz existuje, SDK vrati existujici handle misto duplikace prace.127- **`concurrencyKey`** - serializuje runs sdileici klic, abyste neprekrocili rate limit per-tenant.128- **`queue.concurrencyLimit`** - globalni cap pro frontu napric vsemi klici.129- **`delay`** - planuje run na budouci cas.130- **`ttl`** - pokud run do te doby nezacal, automaticky vyprsi.131~132### Batch trigger133~134Pro fan-out workloady `batchTrigger` prijima az 500 polozek na volani a vytvari jeden run na polozku.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## Naplanovane Tasks146~147Cron joby se stavaji deklaracemi prvni tridy. Schedule sam je samostatny objekt, ktery muzete pripojit k tasku vicekrat.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~167Pro per-tenant schedules - rekneme, jeden cron na zakaznika - je vytvarite dynamicky pres management API.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` cini volani idempotentni: opakovane spusteni stejneho kodu pri deploy nestackovari duplicitni schedules.182~183## Fronty, soubeznost a idempotence184~185Tri primitiva pokryvaji vetsinu potreb rate-limitingu a serazeni.186~187```mermaid188graph TB189 Trigger[trigger payload] --> IK{idempotencyKey<br/>videno?}190 IK -->|ano| Reuse[Vratit existujici run]191 IK -->|ne| CK[bucket concurrencyKey]192 CK --> Q[Fronta s<br/>concurrencyLimit]193 Q -->|slot dostupny| Run[Spustit task]194 Q -->|sloty plne| Wait[Cekat ve fronte]195```196~197Bezny vzor: jedna fronta na tenant s malou per-key soubeznosti pro respektovani rate limitu vendora, plus klic idempotence pro zajisteni bezpecnosti retries.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## Cekani a dlouhotrvajici prace211~212Tasks se mohou pozastavit bez drzeni spojeni nebo spalovani compute. Platforma zachovava stav a obnovuje funkci, kdyz cekani skonci.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` je zabijacka funkce: spusti detsky task a pozastavi rodice, dokud dite neni hotove. Komponujete tasks jako async funkce, ale orchestrace bezi trvale po dnech nebo tydnech.230~231### Human-in-the-loop s `wait.forToken`232~233Pro schvalovaci toky a AI gates `wait.forToken` pozastavuje, dokud vase aplikace neodvola s vysledkem.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 otevre UI, prohlizi koncept, klikne na Schvalit a vas backend dokoncuje token. Task pokracuje od mista, kde skoncil - i kdyz uplynuly hodiny nebo dny.259~260## Lifecycle Hooks261~262Muzete pripojit `init`, `onStart`, `onSuccess` a `onFailure` k tasku nebo globalne v `trigger.config.ts`. Pouzijte je pro tracing, error reporting a sdilene nastaveni.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` bezi jednou na worker container pri bootu, ne na run, takze je to spravne misto pro nastaveni klientu a poolu.280~281## Realtime ve Frontendu282~283Trigger.dev publikuje zmeny stavu run - status, metadata, output - pres streaming API. React hooks se prihlasuji k tomuto streamu a automaticky se prerenderuji.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~336Generujete public access token na strane serveru, scoped na specificky run, a posilate jej klientovi. Hook spravuje auth, opakovane pripojeni a inkrementalni aktualizace.337~338Pro trigger-and-subscribe v jednom kroku: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 Agenti a Streaming354~355Trigger.dev se stal popularnim runtime pro AI agenty, protoze stejna primitiva - trvale provadeni, retries, cekani, real-time metadata, human-in-the-loop - jsou presne to, co agenti potrebuji. Streamujete tokeny od poskytovatele modelu do `metadata`, zatimco run probiha, frontend je renderuje zive a run prezije dlouhotrvajici tool calls bez spaleni serverless timeoutu.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 pouziva `useRealtimeRun` a cte `run.metadata.partial`, aby renderoval streaming odpoved, stejnym zpusobem, jakym byste renderoval chat completion - krome toho, ze tato prezije plne nacteni stranky.385~386## Deployment387~388Deploye kompiluji vase tasks do versionovaneho bundlu, sestavuji kontejner a atomicky prepinaji provoz. Stare in-flight runs nadale pouzivaji predchozi verzi.389~390```bash391npx trigger.dev@latest deploy --env prod392```393~394V CI typicky pripojite toto ke stejnemu workflow, ktery posila vasi aplikaci: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~404Pro preview prostredi predejte `--env preview --branch ${{ github.head_ref }}` a Trigger.dev vytvori izolovane prostredi na vetev, odrazejici, jak Vercel zachazi s preview deploymenty.405~406## Self-Hosting vs Cloud407~408Trigger.dev je open source pod licenci Apache 2.0. Muzete self-host na jakekoliv kontejnerove platforme (Docker Compose, Kubernetes, Fly.io) nebo pouzivat spravovany cloud na trigger.dev.409~410| Aspekt | Cloud | Self-hosted |411|--------|-------|-------------|412| **Setup** | Registrace, spusteni `init` | Spusteni docker-compose nebo Helm chart |413| **Skalovani** | Automaticke | Vase odpovednost |414| **Pricing** | Per run + per compute | Pouze naklady na infru |415| **Compliance** | SOC 2 | Cokoliv vase prostredi poskytuje |416| **Idealni pro** | Vetsinu tymu | Pristnou rezidenci dat, vlastni infru |417~418SDK a CLI jsou identicke mezi rezimy - menite profile flag a smerujete na vlastni instanci.419~420## Best Practices421~422### 1. Drzte payloads male a serializovatelne423~424Predavejte ID a reference, ne plne objekty. Tahnete data uvnitr tasku. To drzi frontu malou, payloads levne k logovani a umoznuje vam menit zdroj dat bez re-triggerovani.425~426### 2. Idempotency keys na kazdem externim volani427~428Kombinujte `idempotencyKey` na trigger tasku s idempotency klici na vendor API (Stripe, OpenAI, atd.). Retries budou bezpecne end-to-end.429~430### 3. Pouzivejte `triggerAndWait` pro orchestraci, ne `Promise.all` triggeru431~432Rodic, ktery vola `triggerAndWait`, trvale komponuje detske tasks. Rodic, ktery triggerne a hned vyresi, ztraci pozorovatelnost retezu.433~434### 4. Tagujte runs435~436Pridejte `tags` k triggerum (`tags: ["user:123", "feature:onboarding"]`), abyste mohli filtrovat dashboard a management API podle business dimenzi.437~438### 5. Drzte `init` idempotentni439~440Bezi pri kazdem cold startu. Vyhnete se tam migracim nebo jednorazovym side effects.441~442## Zaver443~444Trigger.dev odstranuje kategorie prace, ktere drive vyzadovaly stavbu job systemu od nuly. Pisete async TypeScript, volate jej odkudkoliv a platforma vam dava trvale provadeni, planovani, fronty, retries, real-time aktualizace a vzory human-in-the-loop out of the box.445~446Stejna povrch, ktera pohani nocni cron, je povrch, ktera pohani multi-step AI agenta, ktery streamuje do frontendu a pozastavuje se pro review. Tato konvergence je to, co cini framework hodnym vazneho pohledu v roce 2026, at uz provozujete SaaS, ktery potrebuje spolehlivou praci na pozadi, nebo dodavate AI funkce, ktere prezivaji serverless timeout.447~448> **Checklist Zacatku:**449>450> - [x] Zaregistrujte se na trigger.dev nebo spustte self-hosted Docker stack451> - [x] `npx trigger.dev@latest init` ve vasem projektu452> - [x] Definujte svuj prvni task s `task({ id, run })`453> - [x] Triggerne jej z vaseho API a sledujte run v dashboardu454> - [x] Pridejte `idempotencyKey` a `concurrencyKey` pro produkcni bezpecnost455> - [x] Pripojte `useRealtimeRun` k status komponente456> - [x] Deployujte s `trigger.dev deploy --env prod` z CI457~
NORMAL · trigger-dev-background-jobs-guide.md [readonly]457 lines · :q to close