spinny:~/writing $ less trigger-dev-background-jobs-guide.md
12Karamihan sa mga production application ay nangangailangan ng trabaho na hindi kasya sa request/response cycle: pagpapadala ng email, pagpoproseso ng uploads, pagpapatakbo ng AI pipelines, pag-sync ng third-party data, paggawa ng reports. Ang tradisyonal na sagot ay isang queue (Redis, SQS, RabbitMQ), isang fleet ng workers, isang scheduler, at isang marupok na hilera ng glue code na nasisira sa bawat deploy.34Pinipiga ng [Trigger.dev](https://trigger.dev) ang stack na iyon sa isang TypeScript SDK. Sumusulat ka ng functions, tinatawag mo sila kahit saan, at hinahawakan ng platform ang queueing, retries, observability, scheduling, at durable execution. Gumagana ang tasks hangga't kinakailangan - walang 10-second serverless timeout, walang nawawalang trabaho sa redeploys.56## Bakit Trigger.dev78Ang shift sa 2026 ay durable execution. Dapat makaligtas ang workflows sa restarts, crashes, deploys, at rate limits. Dapat din nilang i-stream ang progress sa UI nang real time at i-pause para sa human input. Muling itinayo ang Trigger.dev sa paligid ng mga kinakailangang ito sa version 3 at patuloy na pinapalawak ang AI infrastructure surface nito.910```mermaid11graph LR12 App[Iyong App] -->|trigger| API[Trigger.dev API]13 API --> Queue[Durable Queue]14 Queue --> Worker[Worker Container]15 Worker -->|run task| Task[Iyong Task code]16 Task -->|metadata| Realtime[Realtime Stream]17 Realtime --> UI[React UI]18 Worker --> Storage[Run State Store]19```2021Simple lang ang model: tinutukoy mo ang tasks bilang exports, kinukuha sila ng SDK, ini-schedule at pinapatakbo sila ng platform sa isolated containers, at ang run state ay napapanatili upang maaari mong ipagpatuloy, subukang muli, at obserbahan.2223## Pagsisimula2425### Initialize ng project2627```bash28npx trigger.dev@latest login29npx trigger.dev@latest init30```3132Lumilikha ito ng isang `trigger.config.ts` file at isang `trigger/` directory na may sample na tasks. Ang config file ang pinagmumulan ng katotohanan para sa iyong project: aling mga directory ang naglalaman ng tasks, build settings, lifecycle hooks, at runtime options.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### Patakbuhin ang tasks lokal5758```bash59npx trigger.dev@latest dev60```6162Kumokonekta ang dev server sa cloud, nagrerehistro ng iyong tasks, at nag-stream ng runs sa pamamagitan ng iyong lokal na code. Naglalagay ka ng breakpoints sa iyong editor at tinatamaan ang mga ito sa totoong triggers - ang parehong loop na gagamitin mo sa anumang normal na Node.js project.6364## Pagdedefine ng Task6566Ang Task ay isang object na ini-export gamit ang isang natatanging `id` at isang `run` function. Sinusuri ng SDK ang exports sa kabuuan ng `dirs` at awtomatiko silang ini-rehistro.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```9697Tatlong bagay na dapat tandaan:98991. **Walang timeout sa run body.** Pinamamahalaan ng platform ang oras ng pagpapatupad sa pamamagitan ng `maxDuration` sa config, hindi sa runtime.1002. **Throws ay retries.** Hinuhuli ng SDK ang mga exception at muling pinapatakbo nang exponential backoff ayon sa `retry` policy.1013. **Pinapanatili ang return value.** Ang ibang tasks at ang frontend mo ay maaaring magbasa ng `run.output` mula sa kahit saan.102103## Pag-trigger ng Tasks104105Tatawagin mo ang task mula sa iyong backend, mga API route mo, o ibang task.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 - gamitin para subaybayan o ipakita ang progress122```123124Ang mga opsyon ay nagbubukas ng maraming gawi sa isang tawag:125126- **`idempotencyKey`** - kung mayroon nang run na may parehong key, ibinabalik ng SDK ang umiiral na handle imbes na duplicate ang trabaho.127- **`concurrencyKey`** - ini-serialize ang runs na nagbabahagi ng key upang hindi mo lalagpas ang per-tenant rate limit.128- **`queue.concurrencyLimit`** - global cap para sa queue sa lahat ng keys.129- **`delay`** - inaiskedyul ang run sa isang oras sa hinaharap.130- **`ttl`** - kung hindi nagsimula ang run sa oras na iyon, awtomatikong mag-e-expire.131132### Batch trigger133134Para sa fan-out workloads, tumatanggap ang `batchTrigger` ng hanggang 500 items kada tawag at lumilikha ng isang run kada item.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## Naka-iskedyul na Tasks146147Ang Cron jobs ay nagiging first-class declarations. Ang schedule mismo ay isang hiwalay na object na maaari mong ikabit sa task nang maraming beses.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```166167Para sa per-tenant schedules - sabihin nating, isang cron kada customer - dynamic mong nilikha sila sa pamamagitan ng 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```180181Ginagawang idempotent ng `deduplicationKey` ang tawag: ang muling pagpapatakbo ng parehong code sa deploy time ay hindi nag-iipon ng duplicate na schedules.182183## Queues, Concurrency, at Idempotency184185Tatlong primitives ang sumasaklaw sa karamihan ng pangangailangan ng rate-limiting at ordering.186187```mermaid188graph TB189 Trigger[trigger payload] --> IK{idempotencyKey<br/>nakita?}190 IK -->|oo| Reuse[Ibalik ang umiiral na run]191 IK -->|hindi| CK[concurrencyKey bucket]192 CK --> Q[Queue na may<br/>concurrencyLimit]193 Q -->|slot available| Run[Patakbuhin ang task]194 Q -->|slots puno| Wait[Maghintay sa queue]195```196197Karaniwang pattern: isang queue kada tenant na may maliit na per-key concurrency upang igalang ang rate limit ng vendor, plus isang idempotency key upang gawing ligtas ang retries.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## Waits at Long-Running Work211212Ang Tasks ay maaaring i-pause nang hindi nagpapanatili ng connection o nagsusunog ng compute. Pinapanatili ng platform ang state at ipinagpapatuloy ang function kapag natapos ang wait.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```228229Ang `triggerAndWait` ay ang killer feature: nagti-trigger ito ng isang child task at sini-suspend ang parent hanggang sa matapos ang child. Bumubuo ka ng tasks gaya ng async functions, ngunit ang orchestration ay tumatakbo nang matatag sa loob ng mga araw o linggo.230231### Human-in-the-loop sa `wait.forToken`232233Para sa approval flows at AI gates, ang `wait.forToken` ay nag-pa-pause hanggang sa tumawag pabalik ang iyong application na may resulta.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```257258Binubuksan ng editor ang isang UI, sinusuri ang draft, ki-click ang Approve, at kinukumpleto ng iyong backend ang token. Ipinagpapatuloy ng task kung saan ito tumigil - kahit na lumipas ang oras o mga araw.259260## Lifecycle Hooks261262Maaari mong ikabit ang `init`, `onStart`, `onSuccess`, at `onFailure` sa isang task o globally sa `trigger.config.ts`. Gamitin sila para sa tracing, error reporting, at shared setup.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```278279Tumatakbo ang `init` minsan kada worker container sa boot, hindi kada run, kaya ito ang tamang lugar upang i-set up ang clients at pools.280281## Realtime sa Frontend282283Ang Trigger.dev ay nag-pu-publish ng mga pagbabago ng run state - status, metadata, output - sa pamamagitan ng streaming API. Ang React hooks ay nag-su-subscribe sa stream na iyon at awtomatikong nagre-render muli.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```335336Bumubuo ka ng public access token sa server-side, scoped sa isang partikular na run, at ipinapadala ito sa client. Hinahawakan ng hook ang auth, reconnection, at incremental updates.337338Para sa trigger-and-subscribe sa isang hakbang: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## AI Agents at Streaming354355Ang Trigger.dev ay naging isang popular na runtime para sa AI agents dahil ang parehong primitives - durable execution, retries, waits, real-time metadata, human-in-the-loop - ay eksaktong kailangan ng agents. Nag-stream ka ng tokens mula sa isang model provider sa `metadata` habang nangyayari ang run, ire-render ang frontend ang mga ito nang live, at nakaligtas ang run sa long-running tool calls nang hindi nagsusunog ng serverless timeout.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```383384Gumagamit ang frontend ng `useRealtimeRun` at binabasa ang `run.metadata.partial` upang i-render ang streaming response, sa parehong paraan na ire-render mo ang isang chat completion - maliban na ang isang ito ay nakaligtas sa isang full page reload.385386## Pag-deploy387388Ang mga deploys ay nagko-compile ng iyong tasks sa isang versioned bundle, bumubuo ng container, at atomically pinagbabago ang trapiko. Ang lumang in-flight runs ay patuloy na gumagamit ng nakaraang version.389390```bash391npx trigger.dev@latest deploy --env prod392```393394Sa CI ay karaniwang ikinakabit mo ito sa parehong workflow na nagpapadala ng iyong app: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```403404Para sa preview environments, ipasa ang `--env preview --branch ${{ github.head_ref }}` at ang Trigger.dev ay lumilikha ng isolated environment kada branch, na sumasalamin sa kung paano hawakan ng Vercel ang preview deployments.405406## Self-Hosting vs Cloud407408Ang Trigger.dev ay open source sa ilalim ng Apache 2.0 license. Maaari kang mag-self-host sa anumang container platform (Docker Compose, Kubernetes, Fly.io) o gamitin ang managed cloud sa trigger.dev.409410| Aspeto | Cloud | Self-hosted |411|--------|-------|-------------|412| **Setup** | Mag-sign up, patakbuhin ang `init` | Patakbuhin ang docker-compose o Helm chart |413| **Scaling** | Awtomatiko | Iyong responsibilidad |414| **Pricing** | Kada run + kada compute | Gastos lamang ng infra |415| **Compliance** | SOC 2 | Kahit anong ibinibigay ng iyong environment |416| **Pinakamahusay para sa** | Karamihan ng teams | Mahigpit na data residency, custom infra |417418Ang SDK at CLI ay magkapareho sa pagitan ng modes - binabago mo ang isang profile flag at itinuturo sa iyong sariling instance.419420## Best Practices421422### 1. Panatilihing maliit at serializable ang payloads423424Ipasa ang IDs at references, hindi buong objects. Hilahin ang data sa loob ng task. Pinapanatili nitong maliit ang queue, mura ang payloads na i-log, at pinapayagan kang baguhin ang data source nang hindi muling nag-tri-trigger.425426### 2. Idempotency keys sa bawat external call427428Pagsamahin ang `idempotencyKey` sa task trigger sa idempotency keys sa iyong vendor APIs (Stripe, OpenAI, atbp.). Magiging ligtas ang retries sa end-to-end.429430### 3. Gamitin ang `triggerAndWait` para sa orchestration, hindi `Promise.all` ng triggers431432Ang isang parent na tumatawag ng `triggerAndWait` ay durable na nagsasama-sama ng child tasks. Ang isang parent na nag-tri-trigger at nag-re-resolve agad ay nawawalan ng observability ng chain.433434### 4. I-tag ang runs435436Magdagdag ng `tags` sa triggers (`tags: ["user:123", "feature:onboarding"]`) upang ma-filter mo ang dashboard at management API ayon sa business dimensions.437438### 5. Panatilihing idempotent ang `init`439440Tumatakbo ito sa bawat cold start. Iwasan ang migrations o one-shot side effects doon.441442## Konklusyon443444Inaalis ng Trigger.dev ang mga kategorya ng trabaho na dating nangangailangan ng pagbuo ng job system mula sa simula. Sumusulat ka ng async TypeScript, tinatawag mo ito kahit saan, at binibigyan ka ng platform ng durable execution, scheduling, queues, retries, real-time updates, at human-in-the-loop patterns out of the box.445446Ang parehong surface na nagpapatakbo ng nightly cron ay ang surface na nagpapatakbo ng multi-step AI agent na nag-stream sa frontend at nag-pa-pause para sa review. Ang convergence na iyon ang nagbibigay ng halaga sa framework para sa seryosong tingin sa 2026, kahit nagpapatakbo ka ng SaaS na nangangailangan ng maaasahang background work o nagpapadala ng AI features na nakaligtas sa serverless timeout.447448> **Checklist sa Pagsisimula:**449>450> - [x] Mag-sign up sa trigger.dev o patakbuhin ang self-hosted Docker stack451> - [x] `npx trigger.dev@latest init` sa iyong project452> - [x] Tukuyin ang iyong unang task gamit ang `task({ id, run })`453> - [x] I-trigger ito mula sa iyong API at panoorin ang run sa dashboard454> - [x] Magdagdag ng `idempotencyKey` at `concurrencyKey` para sa kaligtasan ng production455> - [x] Ikabit ang `useRealtimeRun` sa isang status component456> - [x] I-deploy gamit ang `trigger.dev deploy --env prod` mula sa CI457
:Trigger.dev: Matatag na Background Jobs at AI Workflows sa TypeScriptlines 1-457 (END) — press q to close