spinny:~/writing $ less trigger-dev-background-jobs-guide.md
12Wiekszosc aplikacji produkcyjnych potrzebuje pracy, ktora nie pasuje do cyklu zadanie/odpowiedz: wysylanie e-maili, przetwarzanie uploadow, uruchamianie pipeline'ow AI, synchronizacja danych zewnetrznych, generowanie raportow. Tradycyjna odpowiedz to kolejka (Redis, SQS, RabbitMQ), flota workerow, harmonogram i kruchy stos kodu spawajacego, ktory rozpada sie przy kazdym deployu.34[Trigger.dev](https://trigger.dev) skraca ten stack do jednego SDK TypeScript. Piszesz funkcje, wywolujesz je z dowolnego miejsca i platforma zajmuje sie kolejkowaniem, ponowieniami, obserwowalnoscia, harmonogramowaniem i trwalym wykonywaniem. Tasks dzialaja tak dlugo, jak potrzeba - zaden 10-sekundowy timeout serverless, zaden zgubiony przy redeployu.56## Dlaczego Trigger.dev78Zmiana w 2026 to trwale wykonywanie. Przeplywy musza przetrwac restarty, awarie, deploye i rate limity. Musza tez transmitowac postep do UI w czasie rzeczywistym i zatrzymywac sie na input czlowieka. Trigger.dev zostal przebudowany wokol tych wymagan w wersji 3 i nadal rozszerza swoja powierzchnie infrastruktury AI.910```mermaid11graph LR12 App[Twoja App] -->|trigger| API[API Trigger.dev]13 API --> Queue[Trwala Queue]14 Queue --> Worker[Container Worker]15 Worker -->|run task| Task[Twoj kod Task]16 Task -->|metadata| Realtime[Stream Realtime]17 Realtime --> UI[React UI]18 Worker --> Storage[Store stanu Run]19```2021Model jest prosty: definiujesz tasks jako exports, SDK je odbiera, platforma planuje i uruchamia je w izolowanych kontenerach, a stan run jest przechowywany, abys mogl wznawiac, ponawiac i obserwowac.2223## Pierwsze kroki2425### Inicjalizuj projekt2627```bash28npx trigger.dev@latest login29npx trigger.dev@latest init30```3132To tworzy plik `trigger.config.ts` i katalog `trigger/` z przykladowymi tasks. Plik konfiguracyjny jest zrodlem prawdy dla twojego projektu: ktore katalogi zawieraja tasks, ustawienia builda, lifecycle hooks i opcje runtime.3334```typescript35// trigger.config.ts36import { defineConfig } from "@trigger.dev/sdk";3738export 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```5556### Uruchamiaj tasks lokalnie5758```bash59npx trigger.dev@latest dev60```6162Server dev laczy sie z chmura, rejestruje twoje tasks i transmituje runs przez twoj lokalny kod. Ustawiasz breakpointy w edytorze i trafiasz w nie na prawdziwych triggerach - ta sama petla, ktorej uzywalbys w jakimkolwiek normalnym projekcie Node.js.6364## Definiowanie Task6566Task to obiekt eksportowany z unikatowym `id` i funkcja `run`. SDK inspekcjonuje exporty w `dirs` i automatycznie je rejestruje.6768```typescript69// trigger/send-welcome-email.ts70import { task } from "@trigger.dev/sdk";71import { Resend } from "resend";7273const resend = new Resend(process.env.RESEND_API_KEY);7475export 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 });9091 if (error) throw error;92 return { messageId: data?.id };93 },94});95```9697Trzy rzeczy do zauwazenia:98991. **Brak timeoutu w ciele run.** Platforma zarzadza czasem wykonania przez `maxDuration` w configu, nie w runtime.1002. **Throws to retries.** SDK lapie wyjatki i ponawia z exponential backoff zgodnie z polityka `retry`.1013. **Wartosc zwracana jest przechowywana.** Inne tasks i twoj frontend moga czytac `run.output` skadkolwiek.102103## Triggerowanie Tasks104105Wywolujesz task z backendu, route'ow API lub innego task'a.106107```typescript108import { sendWelcomeEmail } from "@/trigger/send-welcome-email";109110const 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);120121console.log(handle.id); // run_xyz - uzyj do sledzenia lub wyswietlania postepu122```123124Opcje odblokowuja wiele zachowania w jednym wywolaniu:125126- **`idempotencyKey`** - jesli run z ta sama kluczem juz istnieje, SDK zwraca istniejacy uchwyt zamiast duplikowac prace.127- **`concurrencyKey`** - serializuje runs udostepniajace klucz, abys nie przekroczyl rate limitu na tenanta.128- **`queue.concurrencyLimit`** - globalna granica dla kolejki we wszystkich kluczach.129- **`delay`** - planuje run na przyszly czas.130- **`ttl`** - jesli run nie rozpoczal sie do tego czasu, automatycznie wygasa.131132### Batch trigger133134Dla obciazen fan-out, `batchTrigger` przyjmuje do 500 elementow na wywolanie i tworzy jeden run na element.135136```typescript137await sendWelcomeEmail.batchTrigger(138 newUsers.map((u) => ({139 payload: { email: u.email, name: u.name },140 options: { idempotencyKey: `welcome-${u.id}` },141 }))142);143```144145## Tasks zaplanowane146147Cron joby staja sie deklaracjami pierwszej klasy. Sam harmonogram to oddzielny obiekt, ktory mozesz przylaczyc do task'a wiele razy.148149```typescript150// trigger/daily-digest.ts151import { schedules } from "@trigger.dev/sdk";152153export 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);161162 await sendDigestForDate(payload.timestamp);163 },164});165```166167Dla harmonogramow na tenanta - powiedzmy, jeden cron na klienta - tworzysz je dynamicznie przez management API.168169```typescript170import { schedules } from "@trigger.dev/sdk";171172await 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```180181`deduplicationKey` czyni wywolanie idempotentnym: ponowne uruchomienie tego samego kodu w czasie deployu nie nawarstwia zduplikowanych harmonogramow.182183## Kolejki, Wspolbieznosc i Idempotentnosc184185Trzy primitywy pokrywaja wiekszosc potrzeb rate-limitingu i porzadkowania.186187```mermaid188graph TB189 Trigger[trigger payload] --> IK{idempotencyKey<br/>widziany?}190 IK -->|tak| Reuse[Zwroc istniejacy run]191 IK -->|nie| CK[bucket concurrencyKey]192 CK --> Q[Kolejka z<br/>concurrencyLimit]193 Q -->|slot dostepny| Run[Uruchom task]194 Q -->|sloty pelne| Wait[Czekaj w kolejce]195```196197Powszechny wzorzec: jedna kolejka na tenanta z mala wspolbieznoscia na klucz, aby uszanowac rate limit dostawcy, plus klucz idempotentnosci, aby retries byly bezpieczne.198199```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```209210## Oczekiwania i praca dlugoterminowa211212Tasks moga sie zatrzymac bez utrzymywania polaczenia czy spalania compute. Platforma utrzymuje stan i wznawia funkcje, gdy oczekiwanie sie konczy.213214```typescript215import { wait } from "@trigger.dev/sdk";216217export 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```228229`triggerAndWait` to zabojcza funkcja: triggeruje task dziecka i zawiesza rodzica az dziecko sie zakonczy. Komponujesz tasks jak funkcje async, ale orkiestracja dziala trwale przez dni lub tygodnie.230231### Human-in-the-loop z `wait.forToken`232233Dla przeplywow zatwierdzania i bramek AI, `wait.forToken` zatrzymuje sie az twoja aplikacja oddzwoni z wynikiem.234235```typescript236import { task, wait } from "@trigger.dev/sdk";237238export const publishPost = task({239 id: "publish-post",240 run: async (payload: { draftId: string }) => {241 const draft = await generateAIContent(payload.draftId);242243 const token = await wait.createToken({ timeout: "7d" });244 await notifyEditor({ draftId: draft.id, token: token.id });245246 const decision = await wait.forToken<{ approved: boolean; notes?: string }>(247 token.id248 );249250 if (decision.approved) {251 return await publish(draft);252 }253 return await applyFeedback(draft, decision.notes);254 },255});256```257258Edytor otwiera UI, przeglada draft, klika Zatwierdz i twoj backend kompletuje token. Task wznawia od miejsca, w ktorym sie zatrzymal - nawet jesli minely godziny lub dni.259260## Lifecycle Hooks261262Mozesz przylaczyc `init`, `onStart`, `onSuccess` i `onFailure` do task'a lub globalnie w `trigger.config.ts`. Uzywaj ich do tracingu, error reportingu i wspolnego setupu.263264```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```278279`init` dziala raz na worker container przy bootcie, nie na run, wiec jest to dobre miejsce do skonfigurowania klientow i pul.280281## Realtime w Frontendzie282283Trigger.dev publikuje zmiany stanu run - status, metadata, output - przez streaming API. Hooki React subskrybuja ten strumien i automatycznie re-renderuja.284285```typescript286// trigger/process-video.ts287import { task, metadata } from "@trigger.dev/sdk";288289export const processVideo = task({290 id: "process-video",291 run: async (payload: { videoId: string }) => {292 metadata.set("stage", "transcoding");293 await transcode(payload.videoId);294295 metadata.set("stage", "thumbnails");296 await generateThumbnails(payload.videoId);297298 metadata.set("stage", "uploading");299 const url = await uploadToCDN(payload.videoId);300301 return { url };302 },303});304```305306```tsx307// components/VideoStatus.tsx308"use client";309import { useRealtimeRun } from "@trigger.dev/react-hooks";310import type { processVideo } from "@/trigger/process-video";311312export 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 });322323 if (error) return <p>Error: {error.message}</p>;324 if (!run) return <p>Loading...</p>;325326 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```335336Generujesz publiczny access token po stronie serwera, scoped do konkretnego run, i wysylasz do klienta. Hook obsluguje auth, ponowne polaczenie i przyrostowe aktualizacje.337338Dla trigger-and-subscribe w jednym kroku:339340```tsx341import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";342343const { submit, run, isLoading } = useRealtimeTaskTrigger<typeof processVideo>(344 "process-video",345 { accessToken: publicAccessToken }346);347348<button onClick={() => submit({ videoId })} disabled={isLoading}>349 Process video350</button>;351```352353## Agenci AI i Streaming354355Trigger.dev stal sie popularnym runtime dla agentow AI poniewaz te same primitywy - trwale wykonywanie, retries, oczekiwania, real-time metadata, human-in-the-loop - to dokladnie to, czego potrzebuja agenci. Strumieniujesz tokeny od dostawcy modelu do `metadata` podczas gdy run sie dzieje, frontend renderuje je na zywo, a run przezywa dlugotrwale tool calls bez spalania serverless timeoutu.356357```typescript358import { task, metadata } from "@trigger.dev/sdk";359import { streamText } from "ai";360import { anthropic } from "@ai-sdk/anthropic";361362export 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 });372373 let fullText = "";374 for await (const chunk of result.textStream) {375 fullText += chunk;376 metadata.set("partial", fullText);377 }378379 return { answer: fullText, usage: await result.usage };380 },381});382```383384Frontend uzywa `useRealtimeRun` i czyta `run.metadata.partial`, aby renderowac odpowiedz strumieniowa, w ten sam sposob, w jaki renderowalbys chat completion - z wyjatkiem tego, ze ta przezywa pelny reload strony.385386## Deploying387388Deploye kompiluja twoje tasks do bundla z wersja, buduja kontener i atomowo zamieniaja ruch. Stare runs w locie nadal uzywaja poprzedniej wersji.389390```bash391npx trigger.dev@latest deploy --env prod392```393394W CI zwykle podlaczasz to do tego samego workflow, ktory wysyla twoja aplikacje:395396```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```403404Dla srodowisk preview, przekaz `--env preview --branch ${{ github.head_ref }}` i Trigger.dev tworzy izolowane srodowisko na branch, odzwierciedlajac sposob, w jaki Vercel obsluguje preview deploymenty.405406## Self-Hosting vs Cloud407408Trigger.dev jest open source na licencji Apache 2.0. Mozesz self-hostowac na dowolnej platformie kontenerowej (Docker Compose, Kubernetes, Fly.io) lub uzywac zarzadzanej chmury w trigger.dev.409410| Aspekt | Cloud | Self-hosted |411|--------|-------|-------------|412| **Setup** | Rejestracja, uruchom `init` | Uruchom docker-compose lub Helm chart |413| **Skalowanie** | Automatyczne | Twoja odpowiedzialnosc |414| **Pricing** | Za run + za compute | Tylko koszt infrastruktury |415| **Compliance** | SOC 2 | Co dostarcza twoje srodowisko |416| **Najlepsze dla** | Wiekszosc zespolow | Surowa rezydencja danych, infra niestandardowa |417418SDK i CLI sa identyczne miedzy trybami - zmieniasz flage profilu i wskazujesz na wlasna instancje.419420## Best Practices421422### 1. Trzymaj payloady male i serializowalne423424Przekazuj IDki i referencje, nie pelne obiekty. Pobieraj dane wewnatrz task'a. Utrzymuje to kolejke mala, payloady tanio loggable i pozwala zmieniac zrodlo danych bez ponownego triggerowania.425426### 2. Klucze idempotentnosci na kazdym wywolaniu zewnetrznym427428Polacz `idempotencyKey` na triggerze task'a z kluczami idempotentnosci na API dostawcow (Stripe, OpenAI, itp.). Retries beda bezpieczne end-to-end.429430### 3. Uzywaj `triggerAndWait` do orkiestracji, a nie `Promise.all` triggerow431432Rodzic, ktory wywoluje `triggerAndWait`, trwale komponuje task dzieci. Rodzic, ktory triggeruje i resolvuje natychmiast, traci obserwowalnosc lancucha.433434### 4. Taguj runs435436Dodaj `tags` do triggerow (`tags: ["user:123", "feature:onboarding"]`), abys mogl filtrowac dashboard i management API wedlug wymiarow biznesowych.437438### 5. Trzymaj `init` idempotentnym439440Dziala przy kazdym cold starcie. Unikaj migracji lub jednorazowych skutkow ubocznych tam.441442## Wniosek443444Trigger.dev usuwa kategorie pracy, ktore wczesniej wymagaly zbudowania systemu jobow od zera. Piszesz async TypeScript, wywolujesz go skadkolwiek i platforma daje ci trwale wykonywanie, harmonogramowanie, kolejki, retries, aktualizacje real-time i wzorce human-in-the-loop out of the box.445446Ta sama powierzchnia, ktora napedza nocnego crona, jest powierzchnia, ktora napedza wieloetapowego agenta AI, ktory strumieniuje do frontendu i zatrzymuje sie na review. Ta konwergencja jest tym, co czyni framework wartym powaznego spojrzenia w 2026, niezaleznie od tego, czy zarzadzasz SaaS, ktory potrzebuje niezawodnej pracy w tle, czy wysylasz funkcje AI, ktore przezywaja serverless timeout.447448> **Checklist Pierwszych Krokow:**449>450> - [x] Zarejestruj sie na trigger.dev lub uruchom self-hosted Docker stack451> - [x] `npx trigger.dev@latest init` w twoim projekcie452> - [x] Zdefiniuj swoj pierwszy task z `task({ id, run })`453> - [x] Triggeruj go z twojego API i ogladaj run w dashboardzie454> - [x] Dodaj `idempotencyKey` i `concurrencyKey` dla bezpieczenstwa produkcyjnego455> - [x] Polacz `useRealtimeRun` z komponentem statusu456> - [x] Deployuj z `trigger.dev deploy --env prod` z CI457
:Trigger.dev: Trwale zadania w tle i przeplywy AI w TypeScriptlines 1-457 (END) — press q to close