spinny:~/writing $ vim trigger-dev-background-jobs-guide.md
1~2अधिकांश प्रोडक्शन एप्लिकेशन को ऐसे काम की ज़रूरत होती है जो रिक्वेस्ट/रेस्पॉन्स साइकल मे फ़िट नही होता: ईमेल भेजना, अपलोड प्रोसेस करना, AI पाइपलाइन चलाना, थर्ड-पार्टी डेटा सिंक करना, रिपोर्ट जनरेट करना। पारंपरिक उत्तर एक क्यू (Redis, SQS, RabbitMQ), वर्कर फ़्लीट, एक शेड्यूलर, और ग्लू कोड का एक नाज़ुक ढेर है जो हर डिप्लॉय पर टूट जाता है।3~4[Trigger.dev](https://trigger.dev) उस स्टैक को एक एकल TypeScript SDK मे संकुचित करता है। आप फ़ंक्शन लिखते है, उन्हे कही से भी कॉल करते है, और प्लेटफ़ॉर्म क्यूइंग, रिट्राई, ऑब्ज़र्वेबिलिटी, शेड्यूलिंग और टिकाऊ निष्पादन को संभालता है। टास्क उतने समय तक चलते है जितनी ज़रूरत हो - कोई 10-सेकंड सर्वरलेस टाइमआउट नही, रीडिप्लॉय पर कोई खोया हुआ काम नही।5~6## क्यो Trigger.dev7~82026 मे शिफ़्ट टिकाऊ निष्पादन है। वर्कफ़्लो को रीस्टार्ट, क्रैश, डिप्लॉय और रेट लिमिट से बचना होगा। उन्हे प्रगति को रियलटाइम मे UI मे स्ट्रीम करना और मानवीय इनपुट के लिए रुकना भी होगा। Trigger.dev को संस्करण 3 के साथ इन आवश्यकताओ के आसपास फिर से बनाया गया था और AI इन्फ्रास्ट्रक्चर सतह का विस्तार जारी रखता है।9~10```mermaid11graph LR12 App[आपकी App] -->|trigger| API[Trigger.dev API]13 API --> Queue[टिकाऊ क्यू]14 Queue --> Worker[Worker कंटेनर]15 Worker -->|run task| Task[आपका Task कोड]16 Task -->|metadata| Realtime[रियलटाइम स्ट्रीम]17 Realtime --> UI[React UI]18 Worker --> Storage[Run स्टेट स्टोर]19```20~21मॉडल सरल है: आप टास्क को एक्सपोर्ट के रूप मे परिभाषित करते है, SDK उन्हे उठाता है, प्लेटफ़ॉर्म उन्हे अलग कंटेनरो मे शेड्यूल और चलाता है, और रन स्टेट को बनाए रखा जाता है ताकि आप फिर से शुरू कर सके, रिट्राई कर सके और निरीक्षण कर सके।22~23## शुरू करना24~25### प्रोजेक्ट इनिशियलाइज़ करे26~27```bash28npx trigger.dev@latest login29npx trigger.dev@latest init30```31~32यह एक `trigger.config.ts` फ़ाइल और उदाहरण टास्क के साथ एक `trigger/` डायरेक्टरी बनाता है। कॉन्फ़िग फ़ाइल आपके प्रोजेक्ट के लिए सत्य का स्रोत है: कौन सी डायरेक्टरीज़ मे टास्क है, बिल्ड सेटिंग्स, lifecycle hooks और रनटाइम विकल्प।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### टास्क लोकल मे चलाए57~58```bash59npx trigger.dev@latest dev60```61~62dev सर्वर क्लाउड से कनेक्ट होता है, आपके टास्क रजिस्टर करता है, और आपके लोकल कोड के माध्यम से रन स्ट्रीम करता है। आप अपने एडिटर मे ब्रेकपॉइंट सेट करते है और उन्हे असली ट्रिगर्स पर हिट करते है - वही लूप जो आप किसी सामान्य Node.js प्रोजेक्ट मे उपयोग करेगे।63~64## एक Task परिभाषित करना65~66टास्क एक एक्सपोर्टेड ऑब्जेक्ट है जिसमे एक यूनीक `id` और एक `run` फ़ंक्शन है। SDK `dirs` के माध्यम से एक्सपोर्ट का निरीक्षण करता है और उन्हे स्वचालित रूप से रजिस्टर करता है।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~97ध्यान देने योग्य तीन बाते:98~991. **रन बॉडी मे कोई टाइमआउट नही।** प्लेटफ़ॉर्म रनटाइम मे नही, कॉन्फ़िग मे `maxDuration` के माध्यम से निष्पादन समय का प्रबंधन करता है।1002. **Throws रिट्राई है।** SDK अपवादो को पकड़ता है और `retry` पॉलिसी के अनुसार exponential backoff के साथ पुनः चलाता है।1013. **रिटर्न वैल्यू बनी रहती है।** अन्य टास्क और आपका फ्रंटएंड कही से भी `run.output` पढ़ सकते है।102~103## Tasks ट्रिगर करना104~105आप अपने बैकएंड, अपने API रूट्स, या किसी अन्य टास्क से एक टास्क कॉल करते है।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 - इसका उपयोग प्रगति को ट्रैक या प्रदर्शित करने के लिए करे122```123~124विकल्प एक कॉल मे बहुत सारे व्यवहार खोलते है:125~126- **`idempotencyKey`** - यदि उसी key के साथ एक रन पहले से मौजूद है, तो SDK काम को डुप्लिकेट करने के बजाय मौजूदा हैंडल लौटाता है।127- **`concurrencyKey`** - उस key को साझा करने वाले रन को क्रमबद्ध करता है ताकि आप प्रति-tenant रेट लिमिट को न पार करे।128- **`queue.concurrencyLimit`** - सभी keys मे क्यू के लिए वैश्विक कैप।129- **`delay`** - रन को भविष्य के समय के लिए शेड्यूल करता है।130- **`ttl`** - यदि रन तब तक शुरू नही हुआ है, तो इसे स्वचालित रूप से समाप्त कर दे।131~132### Batch trigger133~134फ़ैन-आउट वर्कलोड के लिए, `batchTrigger` प्रति कॉल 500 आइटम तक स्वीकार करता है और प्रति आइटम एक रन बनाता है।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## शेड्यूल किए गए Tasks146~147Cron जॉब्स प्रथम-श्रेणी की घोषणाएँ बन जाती है। शेड्यूल स्वयं एक अलग ऑब्जेक्ट है जिसे आप कई बार किसी टास्क मे संलग्न कर सकते है।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~167प्रति-tenant शेड्यूल के लिए - मान लीजिए, प्रति ग्राहक एक cron - आप उन्हे 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` कॉल को आइडेम्पोटेन्ट बनाता है: डिप्लॉय समय पर एक ही कोड को फिर से चलाने से डुप्लिकेट शेड्यूल नही बनते।182~183## क्यू, कन्करन्सी और आइडेम्पोटेन्सी184~185तीन प्रिमिटिव्स अधिकांश रेट-लिमिटिंग और ऑर्डरिंग आवश्यकताओ को कवर करते है।186~187```mermaid188graph TB189 Trigger[trigger payload] --> IK{idempotencyKey<br/>देखी?}190 IK -->|हाँ| Reuse[मौजूदा run लौटाएं]191 IK -->|नही| CK[concurrencyKey बकेट]192 CK --> Q[concurrencyLimit के साथ<br/>क्यू]193 Q -->|स्लॉट उपलब्ध| Run[task चलाएं]194 Q -->|स्लॉट भरे| Wait[क्यू मे प्रतीक्षा करे]195```196~197एक सामान्य पैटर्न: एक vendor की रेट लिमिट का सम्मान करने के लिए प्रति tenant एक क्यू, छोटी प्रति-key concurrency के साथ, साथ ही रिट्राई को सुरक्षित बनाने के लिए एक आइडेम्पोटेन्सी key।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## प्रतीक्षा और लंबे समय तक चलने वाला काम211~212टास्क एक कनेक्शन को बनाए रखे या कंप्यूट को जलाए बिना रुक सकते है। प्लेटफ़ॉर्म स्टेट को बनाए रखता है और जब प्रतीक्षा पूरी होती है तो फ़ंक्शन को फिर से शुरू करता है।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` मारक फ़ीचर है: यह एक चाइल्ड टास्क को ट्रिगर करता है और चाइल्ड के पूरा होने तक पैरेंट को सस्पेंड करता है। आप टास्क को async फ़ंक्शन की तरह कंपोज़ करते है, लेकिन ऑर्केस्ट्रेशन दिनो या हफ़्तो तक टिकाऊ रूप से चलता है।230~231### `wait.forToken` के साथ Human-in-the-loop232~233अनुमोदन प्रवाह और AI गेट्स के लिए, `wait.forToken` तब तक रुकता है जब तक आपका एप्लिकेशन परिणाम के साथ कॉलबैक न करे।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~258संपादक एक UI खोलता है, ड्राफ्ट की समीक्षा करता है, अनुमोदन पर क्लिक करता है, और आपका बैकएंड टोकन पूरा करता है। टास्क वही से जारी रहता है जहाँ छूटा था - भले ही घंटे या दिन बीत गए हो।259~260## Lifecycle Hooks261~262आप `init`, `onStart`, `onSuccess` और `onFailure` को एक टास्क या वैश्विक रूप से `trigger.config.ts` मे संलग्न कर सकते है। उन्हे ट्रेसिंग, error reporting और साझा सेटअप के लिए उपयोग करे।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` बूट पर प्रति वर्कर कंटेनर एक बार चलता है, प्रति रन नही, इसलिए यह क्लाइंट और पूल सेट अप करने के लिए सही जगह है।280~281## फ्रंटएंड मे रियलटाइम282~283Trigger.dev रन स्टेट परिवर्तन - status, metadata, output - एक स्ट्रीमिंग API पर प्रकाशित करता है। React हुक उस स्ट्रीम की सदस्यता लेते है और स्वचालित रूप से री-रेंडर करते है।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~336आप सर्वर-साइड पर सार्वजनिक एक्सेस टोकन उत्पन्न करते है, जो किसी विशिष्ट रन के लिए स्कोप किया जाता है, और इसे क्लाइंट को भेजते है। हुक auth, पुनः कनेक्शन और incremental updates को संभालता है।337~338एक चरण मे trigger-and-subscribe के लिए: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 एजेंट्स और स्ट्रीमिंग354~355Trigger.dev AI एजेंटो के लिए एक लोकप्रिय रनटाइम बन गया है क्योकि वही प्रिमिटिव्स - टिकाऊ निष्पादन, रिट्राई, प्रतीक्षा, रियलटाइम मेटाडेटा, human-in-the-loop - बिल्कुल वही है जो एजेंटो को चाहिए। आप रन के होने के दौरान एक मॉडल प्रदाता से टोकन को `metadata` मे स्ट्रीम करते है, फ्रंटएंड उन्हे लाइव रेंडर करता है, और रन सर्वरलेस टाइमआउट को जलाए बिना लंबे समय तक चलने वाले टूल कॉल को जीवित रहता है।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~384फ्रंटएंड `useRealtimeRun` का उपयोग करता है और स्ट्रीमिंग प्रतिक्रिया को रेंडर करने के लिए `run.metadata.partial` पढ़ता है, उसी तरह जैसे आप एक चैट कम्प्लीशन को रेंडर करेगे - सिवाय इसके कि यह एक पूर्ण पेज रीलोड को जीवित रखता है।385~386## डिप्लॉय करना387~388डिप्लॉय आपके टास्क को एक संस्करण बंडल मे संकलित करते है, एक कंटेनर बनाते है, और ट्रैफ़िक को परमाणु रूप से स्वैप करते है। पुराने इन-फ्लाइट रन पिछले संस्करण का उपयोग करते रहते है।389~390```bash391npx trigger.dev@latest deploy --env prod392```393~394CI मे आप आमतौर पर इसे उसी वर्कफ़्लो मे जोड़ते है जो आपकी ऐप शिप करता है: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~404प्रीव्यू वातावरणो के लिए, `--env preview --branch ${{ github.head_ref }}` पास करे और Trigger.dev प्रति शाखा एक अलग वातावरण बनाता है, यह दर्शाता है कि Vercel प्रीव्यू डिप्लॉयमेंट को कैसे संभालता है।405~406## Self-Hosting बनाम Cloud407~408Trigger.dev Apache 2.0 लाइसेंस के तहत ओपन सोर्स है। आप किसी भी कंटेनर प्लेटफ़ॉर्म (Docker Compose, Kubernetes, Fly.io) पर self-host कर सकते है या trigger.dev पर प्रबंधित क्लाउड का उपयोग कर सकते है।409~410| पहलू | Cloud | Self-hosted |411|--------|-------|-------------|412| **सेटअप** | साइन अप, `init` चलाएं | docker-compose या Helm chart चलाएं |413| **स्केलिंग** | स्वचालित | आपकी ज़िम्मेदारी |414| **मूल्य निर्धारण** | प्रति run + प्रति compute | केवल इन्फ्रा लागत |415| **अनुपालन** | SOC 2 | जो आपका वातावरण प्रदान करता है |416| **के लिए सर्वोत्तम** | अधिकांश टीमे | सख्त डेटा निवास, custom इन्फ्रा |417~418SDK और CLI मोड के बीच समान है - आप एक प्रोफ़ाइल फ़्लैग बदलते है और अपने स्वयं के इंस्टेंस की ओर इशारा करते है।419~420## सर्वोत्तम प्रथाएँ421~422### 1. payloads को छोटा और serializable रखे423~424IDs और संदर्भ पास करे, पूर्ण ऑब्जेक्ट नही। टास्क के अंदर डेटा खीचे। यह क्यू को छोटा रखता है, payloads को log करना सस्ता बनाता है, और आपको पुनः ट्रिगर किए बिना डेटा स्रोत बदलने देता है।425~426### 2. हर बाहरी कॉल पर idempotency keys427~428टास्क ट्रिगर पर `idempotencyKey` को अपने vendor APIs (Stripe, OpenAI, आदि) पर idempotency keys के साथ संयोजित करे। रिट्राई end-to-end सुरक्षित होगे।429~430### 3. ऑर्केस्ट्रेशन के लिए `triggerAndWait` का उपयोग करे, ट्रिगर्स के `Promise.all` का नही431~432एक पैरेंट जो `triggerAndWait` कॉल करता है, टिकाऊ रूप से चाइल्ड टास्क को कंपोज़ करता है। एक पैरेंट जो ट्रिगर करता है और तुरंत हल हो जाता है, चेन की ऑब्ज़र्वेबिलिटी खो देता है।433~434### 4. Runs को टैग करे435~436ट्रिगर्स मे `tags` जोड़े (`tags: ["user:123", "feature:onboarding"]`) ताकि आप व्यावसायिक आयामो द्वारा डैशबोर्ड और management API को फ़िल्टर कर सके।437~438### 5. `init` को idempotent रखे439~440यह हर cold start पर चलता है। वहाँ migrations या one-shot side effects से बचे।441~442## निष्कर्ष443~444Trigger.dev उन कार्य श्रेणियो को हटाता है जिनके लिए पहले स्क्रैच से एक जॉब सिस्टम बनाने की आवश्यकता होती थी। आप async TypeScript लिखते है, इसे कही से भी कॉल करते है, और प्लेटफ़ॉर्म आपको out of the box टिकाऊ निष्पादन, शेड्यूलिंग, क्यू, रिट्राई, रियलटाइम अपडेट और human-in-the-loop पैटर्न देता है।445~446वही सतह जो रात के cron को शक्ति देती है, वही सतह है जो एक मल्टी-स्टेप AI एजेंट को शक्ति देती है जो फ्रंटएंड पर स्ट्रीम करता है और समीक्षा के लिए रुकता है। यह अभिसरण ही है जो फ्रेमवर्क को 2026 मे एक गंभीर नज़र के लायक बनाता है, चाहे आप विश्वसनीय बैकग्राउंड कार्य की आवश्यकता वाले SaaS का संचालन कर रहे हो या सर्वरलेस टाइमआउट से बाहर रहने वाले AI फ़ीचर शिप कर रहे हो।447~448> **शुरुआत चेकलिस्ट:**449>450> - [x] trigger.dev पर साइन अप करे या self-hosted Docker स्टैक चलाएं451> - [x] अपने प्रोजेक्ट मे `npx trigger.dev@latest init`452> - [x] `task({ id, run })` के साथ अपना पहला टास्क परिभाषित करे453> - [x] अपने API से इसे ट्रिगर करे और डैशबोर्ड मे रन देखे454> - [x] प्रोडक्शन सुरक्षा के लिए `idempotencyKey` और `concurrencyKey` जोड़े455> - [x] `useRealtimeRun` को एक status component मे जोड़े456> - [x] CI से `trigger.dev deploy --env prod` के साथ डिप्लॉय करे457~
NORMAL · trigger-dev-background-jobs-guide.md [readonly]457 lines · :q to close