spinny:~/writing $ less trigger-dev-background-jobs-guide.md
12زیادہ تر پروڈکشن ایپلی کیشنز کو ایسے کام کی ضرورت ہوتی ہے جو request/response cycle میں فٹ نہیں ہوتا: ای میلز بھیجنا، اپلوڈز پر کارروائی کرنا، AI pipelines چلانا، تیسرے فریق کا ڈیٹا synchronize کرنا، رپورٹس بنانا۔ روایتی جواب ایک queue (Redis، SQS، RabbitMQ)، ایک worker fleet، ایک scheduler، اور glue code کا ایک نازک ڈھیر ہے جو ہر deploy پر ٹوٹ جاتا ہے۔34[Trigger.dev](https://trigger.dev) اس stack کو ایک واحد TypeScript SDK میں سمیٹ دیتا ہے۔ آپ functions لکھتے ہیں، انہیں کہیں سے بھی call کرتے ہیں، اور platform queueing، retries، observability، scheduling اور پائیدار execution سنبھالتا ہے۔ Tasks جتنی دیر تک ضرورت ہو چلتے ہیں - کوئی 10 سیکنڈ serverless timeout نہیں، redeploys پر کوئی کھویا ہوا کام نہیں۔56## کیوں Trigger.dev782026 کی تبدیلی پائیدار execution ہے۔ Workflows کو restarts، crashes، deploys اور rate limits سے بچنا ہوگا۔ انہیں progress کو realtime میں UI پر stream اور انسانی input کے لیے pause بھی کرنا ہوگا۔ Trigger.dev ورژن 3 کے ساتھ ان تقاضوں کے گرد دوبارہ تعمیر کیا گیا اور اپنے AI infrastructure surface کو وسیع کرتا رہتا ہے۔910```mermaid11graph LR12 App[آپ کی App] -->|trigger| API[Trigger.dev API]13 API --> Queue[پائیدار Queue]14 Queue --> Worker[Worker Container]15 Worker -->|run task| Task[آپ کا Task کوڈ]16 Task -->|metadata| Realtime[Realtime Stream]17 Realtime --> UI[React UI]18 Worker --> Storage[Run State Store]19```2021ماڈل سادہ ہے: آپ tasks کو exports کے طور پر definite کرتے ہیں، SDK انہیں اٹھاتا ہے، platform انہیں isolated containers میں schedule اور run کرتا ہے اور run state برقرار رہتی ہے تاکہ آپ resume، retry اور observe کر سکیں۔2223## شروعات2425### پراجیکٹ initialize کریں2627```bash28npx trigger.dev@latest login29npx trigger.dev@latest init30```3132یہ ایک `trigger.config.ts` فائل اور مثال tasks کے ساتھ ایک `trigger/` directory بناتا ہے۔ Config فائل آپ کے پراجیکٹ کے لیے سچائی کا ذریعہ ہے: کون سی directories میں tasks ہیں، build settings، lifecycle hooks اور 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### Tasks مقامی طور پر چلائیں5758```bash59npx trigger.dev@latest dev60```6162dev server cloud سے connect ہوتا ہے، آپ کے tasks register کرتا ہے اور آپ کے local code کے ذریعے runs stream کرتا ہے۔ آپ اپنے editor میں breakpoints سیٹ کرتے ہیں اور انہیں حقیقی triggers پر hit کرتے ہیں - وہی loop جو آپ کسی بھی عام Node.js پراجیکٹ میں استعمال کرتے ہیں۔6364## ایک Task کی تعریف6566Task ایک exported object ہے جس کا منفرد `id` اور `run` function ہوتا ہے۔ SDK `dirs` کے ذریعے exports کا معائنہ کرتا ہے اور انہیں خودکار طور پر register کرتا ہے۔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```9697نوٹ کرنے کی تین چیزیں:98991. **Run body میں کوئی timeout نہیں۔** Platform runtime میں نہیں، config میں `maxDuration` کے ذریعے execution time کو منظم کرتا ہے۔1002. **Throws retries ہیں۔** SDK exceptions پکڑتا ہے اور `retry` policy کے مطابق exponential backoff کے ساتھ دوبارہ چلاتا ہے۔1013. **Return value برقرار رہتی ہے۔** دوسرے tasks اور آپ کا frontend کہیں سے بھی `run.output` پڑھ سکتے ہیں۔102103## Tasks کو trigger کرنا104105آپ اپنے backend، اپنے API routes یا کسی اور task سے ایک task کو call کرتے ہیں۔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 - اس کا استعمال progress کو track یا display کرنے کے لیے کریں122```123124Options ایک ہی call میں بہت ساری behavior unlock کرتی ہیں:125126- **`idempotencyKey`** - اگر اسی key کے ساتھ run پہلے سے موجود ہے، تو SDK کام کو duplicate کرنے کی بجائے موجودہ handle لوٹا دیتا ہے۔127- **`concurrencyKey`** - key share کرنے والے runs کو serialize کرتا ہے تاکہ آپ per-tenant rate limit سے زیادہ نہ ہوں۔128- **`queue.concurrencyLimit`** - تمام keys میں queue کے لیے global cap۔129- **`delay`** - run کو مستقبل کے وقت کے لیے schedule کرتا ہے۔130- **`ttl`** - اگر run تب تک شروع نہیں ہوا ہے، تو خودکار طور پر expire کر دیتا ہے۔131132### Batch trigger133134Fan-out workloads کے لیے، `batchTrigger` فی call 500 items تک قبول کرتا ہے اور فی item ایک run بناتا ہے۔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## شیڈولڈ Tasks146147Cron jobs first-class declarations بن جاتے ہیں۔ Schedule خود ایک علیحدہ object ہے جسے آپ کسی task پر متعدد بار attach کر سکتے ہیں۔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```166167Per-tenant schedules کے لیے - مان لیں، فی customer ایک cron - آپ انہیں 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` call کو idempotent بناتا ہے: deploy time پر وہی code دوبارہ چلانا duplicate schedules کو نہیں روکتا۔182183## Queues، Concurrency اور Idempotency184185تین primitives زیادہ تر rate-limiting اور ordering کی ضروریات کا احاطہ کرتے ہیں۔186187```mermaid188graph TB189 Trigger[trigger payload] --> IK{idempotencyKey<br/>دیکھی؟}190 IK -->|ہاں| Reuse[موجودہ run لوٹائیں]191 IK -->|نہیں| CK[concurrencyKey bucket]192 CK --> Q[concurrencyLimit کے ساتھ<br/>Queue]193 Q -->|slot دستیاب| Run[task چلائیں]194 Q -->|slots بھرے| Wait[Queue میں انتظار کریں]195```196197ایک عام pattern: vendor کی rate limit کا احترام کرنے کے لیے فی tenant ایک queue چھوٹی per-key concurrency کے ساتھ، نیز retries کو محفوظ بنانے کے لیے ایک idempotency key۔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 اور طویل دورانیے کا کام211212Tasks ایک connection برقرار رکھے یا compute جلائے بغیر pause کر سکتے ہیں۔ Platform state کو برقرار رکھتا ہے اور wait مکمل ہونے پر function کو دوبارہ شروع کرتا ہے۔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` killer feature ہے: یہ ایک child task کو trigger کرتا ہے اور child مکمل ہونے تک parent کو suspend کرتا ہے۔ آپ tasks کو async functions کی طرح compose کرتے ہیں، لیکن orchestration دنوں یا ہفتوں تک پائیدار طور پر چلتا ہے۔230231### `wait.forToken` کے ساتھ Human-in-the-loop232233Approval flows اور AI gates کے لیے، `wait.forToken` تب تک pause کرتا ہے جب تک کہ آپ کی application نتیجے کے ساتھ callback نہ کرے۔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```257258Editor ایک UI کھولتا ہے، draft کا جائزہ لیتا ہے، Approve پر click کرتا ہے، اور آپ کا backend token مکمل کرتا ہے۔ Task جہاں چھوڑا تھا وہاں سے continue ہوتا ہے - چاہے گھنٹے یا دن گزر گئے ہوں۔259260## Lifecycle Hooks261262آپ `init`، `onStart`، `onSuccess` اور `onFailure` کو ایک task یا global طور پر `trigger.config.ts` میں attach کر سکتے ہیں۔ tracing، error reporting اور 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```278279`init` boot پر فی worker container ایک بار چلتا ہے، فی run نہیں، لہذا یہ clients اور pools سیٹ اپ کرنے کے لیے صحیح جگہ ہے۔280281## Frontend میں Realtime282283Trigger.dev streaming API پر run state changes - status، metadata، output - شائع کرتا ہے۔ React hooks اس stream کو subscribe کرتے ہیں اور خودکار طور پر re-render کرتے ہیں۔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```335336آپ مخصوص run کے لیے scoped server-side public access token تیار کرتے ہیں اور اسے client کو بھیجتے ہیں۔ Hook auth، reconnection اور incremental updates کو سنبھالتا ہے۔337338ایک قدم میں trigger-and-subscribe کے لیے: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 اور Streaming354355Trigger.dev AI agents کے لیے ایک مقبول runtime بن گیا ہے کیونکہ وہی primitives - پائیدار execution، retries، waits، realtime metadata، human-in-the-loop - بالکل وہی ہیں جن کی agents کو ضرورت ہے۔ آپ run کے دوران model provider سے tokens کو `metadata` میں stream کرتے ہیں، frontend انہیں live render کرتا ہے، اور run serverless timeout جلائے بغیر طویل دورانیے کے tool calls سے بچ جاتا ہے۔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 `useRealtimeRun` استعمال کرتا ہے اور streaming response render کرنے کے لیے `run.metadata.partial` پڑھتا ہے، اسی طرح جیسے آپ chat completion render کرتے - سوائے اس کے کہ یہ مکمل page reload سے بچ جاتا ہے۔385386## Deploying387388Deploys آپ کے tasks کو ایک versioned bundle میں compile کرتے ہیں، ایک container بناتے ہیں اور traffic کو atomic طور پر swap کرتے ہیں۔ پرانے in-flight runs پچھلا version استعمال کرتے رہتے ہیں۔389390```bash391npx trigger.dev@latest deploy --env prod392```393394CI میں آپ عام طور پر اسے اسی workflow میں جوڑتے ہیں جو آپ کی 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```403404Preview environments کے لیے، `--env preview --branch ${{ github.head_ref }}` پاس کریں اور Trigger.dev فی branch ایک isolated environment بناتا ہے، Vercel کس طرح preview deployments کو سنبھالتا ہے اس کی عکاسی کرتا ہے۔405406## Self-Hosting بمقابلہ Cloud407408Trigger.dev Apache 2.0 license کے تحت open source ہے۔ آپ کسی بھی container platform (Docker Compose، Kubernetes، Fly.io) پر self-host کر سکتے ہیں یا trigger.dev پر managed cloud استعمال کر سکتے ہیں۔409410| پہلو | Cloud | Self-hosted |411|--------|-------|-------------|412| **Setup** | سائن اپ، `init` چلائیں | docker-compose یا Helm chart چلائیں |413| **Scaling** | خودکار | آپ کی ذمہ داری |414| **Pricing** | فی run + فی compute | صرف infra لاگت |415| **Compliance** | SOC 2 | جو آپ کا environment فراہم کرتا ہے |416| **بہترین** | زیادہ تر teams | سخت data residency، custom infra |417418SDK اور CLI modes کے درمیان یکساں ہیں - آپ ایک profile flag تبدیل کرتے ہیں اور اپنے instance کی طرف اشارہ کرتے ہیں۔419420## Best Practices421422### 1. payloads کو چھوٹا اور serializable رکھیں423424IDs اور references پاس کریں، مکمل objects نہیں۔ task کے اندر data کھینچیں۔ یہ queue کو چھوٹا رکھتا ہے، payloads log کرنے کے لیے سستے، اور آپ کو re-trigger کیے بغیر data source تبدیل کرنے دیتا ہے۔425426### 2. ہر external call پر idempotency keys427428Task trigger پر `idempotencyKey` کو اپنے vendor APIs (Stripe، OpenAI، وغیرہ) پر idempotency keys کے ساتھ ملائیں۔ Retries end-to-end محفوظ ہوں گے۔429430### 3. orchestration کے لیے `triggerAndWait` استعمال کریں، triggers کا `Promise.all` نہیں431432Parent جو `triggerAndWait` call کرتا ہے پائیدار طور پر child tasks compose کرتا ہے۔ Parent جو trigger کرتا ہے اور فوری طور پر resolve کرتا ہے chain کی observability کھو دیتا ہے۔433434### 4. runs کو tag کریں435436Triggers میں `tags` شامل کریں (`tags: ["user:123", "feature:onboarding"]`) تاکہ آپ کاروباری dimensions کے ذریعے dashboard اور management API کو filter کر سکیں۔437438### 5. `init` کو idempotent رکھیں439440یہ ہر cold start پر چلتا ہے۔ وہاں migrations یا one-shot side effects سے بچیں۔441442## نتیجہ443444Trigger.dev ان کام کی categories کو ہٹاتا ہے جنہیں پہلے scratch سے ایک job system بنانے کی ضرورت ہوتی تھی۔ آپ async TypeScript لکھتے ہیں، اسے کہیں سے بھی call کرتے ہیں، اور platform آپ کو out of the box پائیدار execution، scheduling، queues، retries، realtime updates اور human-in-the-loop patterns دیتا ہے۔445446وہی surface جو رات کا cron چلاتا ہے وہی surface ہے جو ایک multi-step AI agent چلاتا ہے جو frontend پر stream کرتا ہے اور review کے لیے pause ہوتا ہے۔ یہ یکجائی ہی ہے جو framework کو 2026 میں ایک سنجیدہ نظر کے قابل بناتی ہے، چاہے آپ ایک SaaS چلا رہے ہوں جسے قابل اعتماد background work کی ضرورت ہے یا serverless timeout سے بچنے والی AI features بھیج رہے ہوں۔447448> **شروعاتی Checklist:**449>450> - [x] trigger.dev پر سائن اپ کریں یا self-hosted Docker stack چلائیں451> - [x] اپنے پراجیکٹ میں `npx trigger.dev@latest init`452> - [x] `task({ id, run })` کے ساتھ اپنا پہلا task definite کریں453> - [x] اپنے API سے اسے trigger کریں اور dashboard میں run دیکھیں454> - [x] production safety کے لیے `idempotencyKey` اور `concurrencyKey` شامل کریں455> - [x] `useRealtimeRun` کو ایک status component میں جوڑیں456> - [x] CI سے `trigger.dev deploy --env prod` کے ساتھ deploy کریں457
:Trigger.dev: TypeScript میں پائیدار بیک گراؤنڈ جابز اور AI ورک فلوزlines 1-457 (END) — press q to close