spinny:~/writing $ vim trigger-dev-background-jobs-guide.md
1~2বেশিরভাগ প্রোডাকশন অ্যাপ্লিকেশনের কিছু কাজের প্রয়োজন যা request/response সাইকেলে ফিট হয় না: ইমেইল পাঠানো, আপলোড প্রক্রিয়াকরণ, AI পাইপলাইন চালানো, third-party ডেটা সিঙ্ক, রিপোর্ট তৈরি। ঐতিহ্যবাহী উত্তর হল একটি কিউ (Redis, SQS, RabbitMQ), একটি ওয়ার্কার ফ্লিট, একটি শিডিউলার এবং glue কোডের একটি ভঙ্গুর স্তূপ যা প্রতিটি ডিপ্লয়ে ভেঙে যায়।3~4[Trigger.dev](https://trigger.dev) সেই স্ট্যাকটিকে একটি একক TypeScript SDK-তে সংকুচিত করে। আপনি ফাংশন লেখেন, যেকোন জায়গা থেকে সেগুলিকে কল করেন এবং প্ল্যাটফর্ম কিউইং, রিট্রাই, observability, শিডিউলিং এবং টেকসই এক্সিকিউশন পরিচালনা করে। টাস্কগুলি যতক্ষণ প্রয়োজন ততক্ষণ চলে - কোন 10-সেকেন্ড সার্ভারলেস টাইমআউট নেই, রিডিপ্লয়ে কোন কাজ হারানো নেই।5~6## কেন Trigger.dev7~82026-এর পরিবর্তন হল টেকসই এক্সিকিউশন। ওয়ার্কফ্লোগুলি অবশ্যই restart, crashes, deploys এবং rate limit-এ বেঁচে থাকতে হবে। তাদের রিয়েল টাইমে 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মডেলটি সহজ: আপনি export হিসেবে টাস্কগুলি সংজ্ঞায়িত করেন, SDK সেগুলি তুলে নেয়, প্ল্যাটফর্ম সেগুলিকে আলাদা কন্টেইনারে শিডিউল এবং চালায় এবং run স্টেট সংরক্ষিত হয় যাতে আপনি resume, retry এবং observe করতে পারেন।22~23## শুরু করা24~25### একটি প্রজেক্ট ইনিশিয়ালাইজ করুন26~27```bash28npx trigger.dev@latest login29npx trigger.dev@latest init30```31~32এটি একটি `trigger.config.ts` ফাইল এবং উদাহরণ টাস্ক সহ একটি `trigger/` ডিরেক্টরি তৈরি করে। কনফিগ ফাইলটি আপনার প্রজেক্টের সত্যের উৎস: কোন ডিরেক্টরিতে টাস্ক রয়েছে, build settings, 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 সার্ভার ক্লাউডের সাথে সংযুক্ত হয়, আপনার টাস্ক রেজিস্টার করে এবং আপনার লোকাল কোডের মাধ্যমে রান স্ট্রিম করে। আপনি আপনার এডিটরে breakpoint সেট করেন এবং সেগুলি বাস্তব ট্রিগারে আঘাত করেন - একই লুপ যা আপনি যেকোন সাধারণ Node.js প্রজেক্টে ব্যবহার করবেন।63~64## একটি Task সংজ্ঞায়িত করা65~66একটি টাস্ক হল একটি export করা অবজেক্ট যার একটি অনন্য `id` এবং একটি `run` ফাংশন রয়েছে। SDK `dirs`-এর মধ্যে export পরিদর্শন করে এবং স্বয়ংক্রিয়ভাবে নিবন্ধন করে।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. **run বডিতে কোন টাইমআউট নেই।** প্ল্যাটফর্ম রানটাইমে নয়, কনফিগে `maxDuration`-এর মাধ্যমে এক্সিকিউশন সময় পরিচালনা করে।1002. **Throws হল retry।** 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`** - যদি একই কী সহ একটি run ইতিমধ্যে বিদ্যমান থাকে, SDK কাজ ডুপ্লিকেট করার পরিবর্তে বিদ্যমান হ্যান্ডেল ফেরত দেয়।127- **`concurrencyKey`** - কী শেয়ার করা runs serialize করে যাতে আপনি per-tenant rate limit ছাড়িয়ে না যান।128- **`queue.concurrencyLimit`** - সমস্ত কী জুড়ে কিউয়ের জন্য বৈশ্বিক ক্যাপ।129- **`delay`** - ভবিষ্যতের সময়ের জন্য run শিডিউল করে।130- **`ttl`** - যদি run ততক্ষণ পর্যন্ত শুরু না হয়, এটি স্বয়ংক্রিয়ভাবে মেয়াদ শেষ করে।131~132### Batch trigger133~134fan-out workload-এর জন্য, `batchTrigger` প্রতি কলে 500 আইটেম পর্যন্ত গ্রহণ করে এবং প্রতি আইটেম একটি run তৈরি করে।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 জব প্রথম-শ্রেণীর ঘোষণা হয়ে ওঠে। schedule নিজেই একটি পৃথক অবজেক্ট যা আপনি একটি টাস্কের সাথে একাধিকবার সংযুক্ত করতে পারেন।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~167per-tenant schedule-এর জন্য - বলুন, প্রতি গ্রাহক একটি 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` কলটিকে idempotent করে: deploy সময়ে একই কোড পুনরায় চালালে ডুপ্লিকেট schedule স্ট্যাক হয় না।182~183## কিউ, কনকারেন্সি এবং আইডেমপটেন্সি184~185তিনটি প্রিমিটিভ বেশিরভাগ rate-limiting এবং ordering প্রয়োজন কভার করে।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-এর rate limit সম্মান করতে প্রতি tenant একটি কিউ ছোট per-key concurrency সহ, প্লাস retries নিরাপদ করতে একটি idempotency কী।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টাস্কগুলি একটি সংযোগ ধরে রাখা বা compute পোড়ানো ছাড়াই pause করতে পারে। প্ল্যাটফর্ম স্টেট সংরক্ষণ করে এবং অপেক্ষা সম্পূর্ণ হলে ফাংশন resume করে।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` killer feature: এটি একটি child task ট্রিগার করে এবং child সম্পূর্ণ হওয়া পর্যন্ত parent suspend করে। আপনি async ফাংশনের মতো task compose করেন, কিন্তু orchestration দিন বা সপ্তাহ ধরে টেকসইভাবে চলে।230~231### `wait.forToken` সহ Human-in-the-loop232~233approval flows এবং AI gates-এর জন্য, `wait.forToken` pause করে যতক্ষণ না আপনার অ্যাপ্লিকেশন একটি ফলাফল সহ callback করে।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 খোলেন, খসড়া পর্যালোচনা করেন, Approve ক্লিক করেন এবং আপনার ব্যাকএন্ড টোকেন সম্পূর্ণ করে। টাস্কটি যেখানে ছেড়েছিল সেখান থেকে চলে - এমনকি ঘন্টা বা দিন কেটে গেলেও।259~260## Lifecycle Hooks261~262আপনি একটি টাস্ক বা বিশ্বব্যাপী `trigger.config.ts`-এ `init`, `onStart`, `onSuccess` এবং `onFailure` সংযুক্ত করতে পারেন। tracing, error reporting এবং shared setup-এর জন্য ব্যবহার করুন।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` boot-এ প্রতি worker container একবার চলে, প্রতি run নয়, তাই এটি client এবং pool সেট আপ করার সঠিক জায়গা।280~281## ফ্রন্টএন্ডে রিয়েলটাইম282~283Trigger.dev একটি streaming API-এর মাধ্যমে run state পরিবর্তন - status, metadata, output - প্রকাশ করে। React hooks সেই stream-এ subscribe করে এবং স্বয়ংক্রিয়ভাবে re-render করে।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আপনি একটি নির্দিষ্ট run-এ scoped server-side public access token তৈরি করেন এবং ক্লায়েন্টে পাঠান। হুক auth, reconnection এবং 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 এজেন্টদের জন্য একটি জনপ্রিয় runtime হয়ে উঠেছে কারণ একই প্রিমিটিভ - টেকসই এক্সিকিউশন, retries, waits, real-time metadata, human-in-the-loop - ঠিক যা এজেন্টদের প্রয়োজন। আপনি run চলাকালীন একটি model provider থেকে token-গুলিকে `metadata`-তে stream করেন, frontend সেগুলি live render করে এবং run serverless timeout পোড়ানো ছাড়াই দীর্ঘ-চলমান tool calls থেকে বেঁচে থাকে।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 `useRealtimeRun` ব্যবহার করে এবং streaming response render করতে `run.metadata.partial` পড়ে, একইভাবে যেমন আপনি একটি chat completion render করবেন - শুধুমাত্র এটি একটি সম্পূর্ণ page reload থেকে বেঁচে থাকে।385~386## ডিপ্লয়িং387~388ডিপ্লয় আপনার টাস্ক একটি versioned bundle-এ কম্পাইল করে, একটি container তৈরি করে এবং পারমাণবিকভাবে traffic swap করে। পুরানো in-flight runs পূর্ববর্তী version ব্যবহার চালিয়ে যায়।389~390```bash391npx trigger.dev@latest deploy --env prod392```393~394CI-তে আপনি সাধারণত এটিকে একই workflow-এ যুক্ত করেন যা আপনার অ্যাপ পাঠায়: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~404preview environments-এর জন্য, `--env preview --branch ${{ github.head_ref }}` পাস করুন এবং Trigger.dev প্রতি branch একটি isolated environment তৈরি করে, Vercel কীভাবে preview deployments পরিচালনা করে তা mirror করে।405~406## Self-Hosting বনাম Cloud407~408Trigger.dev Apache 2.0 লাইসেন্সের অধীনে open source। আপনি যেকোন container platform (Docker Compose, Kubernetes, Fly.io)-এ self-host করতে পারেন বা trigger.dev-এ managed cloud ব্যবহার করতে পারেন।409~410| দিক | Cloud | Self-hosted |411|--------|-------|-------------|412| **Setup** | সাইন আপ, `init` চালান | docker-compose বা Helm chart চালান |413| **Scaling** | স্বয়ংক্রিয় | আপনার দায়িত্ব |414| **Pricing** | প্রতি run + প্রতি compute | শুধু infra খরচ |415| **Compliance** | SOC 2 | আপনার environment যা প্রদান করে |416| **সর্বোত্তম জন্য** | বেশিরভাগ team | কঠোর data residency, custom infra |417~418SDK এবং CLI mode-গুলির মধ্যে অভিন্ন - আপনি একটি profile flag পরিবর্তন করেন এবং আপনার নিজস্ব instance-এ point করেন।419~420## Best Practices421~422### 1. payloads ছোট এবং serializable রাখুন423~424ID এবং reference পাস করুন, সম্পূর্ণ object নয়। task-এর ভিতরে data টানুন। এটি queue ছোট রাখে, payloads log করতে সস্তা এবং re-trigger না করে data source পরিবর্তন করতে দেয়।425~426### 2. প্রতি external call-এ idempotency keys427~428task trigger-এ `idempotencyKey` আপনার vendor APIs (Stripe, OpenAI, ইত্যাদি)-তে idempotency keys-এর সাথে একত্রিত করুন। retries end-to-end নিরাপদ হবে।429~430### 3. orchestration-এর জন্য `triggerAndWait` ব্যবহার করুন, triggers-এর `Promise.all` নয়431~432`triggerAndWait` কল করা একটি parent টেকসইভাবে child tasks compose করে। trigger করে এবং অবিলম্বে resolve করা parent chain-এর observability হারায়।433~434### 4. runs ট্যাগ করুন435~436triggers-এ `tags` যোগ করুন (`tags: ["user:123", "feature:onboarding"]`) যাতে আপনি ব্যবসায়িক dimension দ্বারা dashboard এবং management API filter করতে পারেন।437~438### 5. `init` idempotent রাখুন439~440এটি প্রতি cold start-এ চলে। সেখানে migrations বা one-shot side effect এড়ান।441~442## উপসংহার443~444Trigger.dev কাজের সেই বিভাগগুলি সরিয়ে দেয় যেগুলির জন্য একসময় scratch থেকে একটি job system তৈরি করতে হত। আপনি async TypeScript লেখেন, যেকোন জায়গা থেকে কল করেন এবং প্ল্যাটফর্ম আপনাকে box-এর বাইরে টেকসই execution, scheduling, queues, retries, real-time updates এবং human-in-the-loop pattern দেয়।445~446একই surface যা একটি রাতের cron-কে শক্তি দেয় তা সেই surface যা frontend-এ stream করা এবং review-এর জন্য pause করা একটি multi-step AI agent-কে শক্তি দেয়। সেই অভিসৃতিই framework-কে 2026-এ একটি গুরুতর দৃষ্টিভঙ্গির যোগ্য করে তোলে, আপনি একটি SaaS পরিচালনা করছেন কিনা যার নির্ভরযোগ্য background work প্রয়োজন বা serverless timeout-এর বাইরে বেঁচে থাকা AI features পাঠাচ্ছেন।447~448> **শুরু করার চেকলিস্ট:**449>450> - [x] trigger.dev-এ সাইন আপ করুন বা self-hosted Docker stack চালান451> - [x] আপনার প্রজেক্টে `npx trigger.dev@latest init`452> - [x] `task({ id, run })` দিয়ে আপনার প্রথম task সংজ্ঞায়িত করুন453> - [x] আপনার API থেকে এটি trigger করুন এবং dashboard-এ run দেখুন454> - [x] production নিরাপত্তার জন্য `idempotencyKey` এবং `concurrencyKey` যোগ করুন455> - [x] `useRealtimeRun` একটি status component-এ যুক্ত করুন456> - [x] CI থেকে `trigger.dev deploy --env prod` দিয়ে deploy করুন457~
NORMAL · trigger-dev-background-jobs-guide.md [readonly]457 lines · :q to close