spinny:~/writing $ less trigger-dev-background-jobs-guide.md
12רוב יישומי הייצור צריכים עבודה שלא מתאימה למחזור request/response: שליחת מיילים, עיבוד העלאות, הרצת pipelines של AI, סנכרון נתוני צד שלישי, יצירת דוחות. התשובה המסורתית היא תור (Redis, SQS, RabbitMQ), צי workers, scheduler וערימה שברירית של קוד glue שנשבר בכל deploy.34[Trigger.dev](https://trigger.dev) מקפל את הסטאק הזה ל-SDK יחיד של TypeScript. אתה כותב פונקציות, קורא להן מכל מקום, והפלטפורמה מטפלת ב-queueing, retries, observability, scheduling וביצוע עמיד. Tasks רצים כל עוד נדרש - אין timeout serverless של 10 שניות, אין עבודה אבודה ב-redeploys.56## למה Trigger.dev78השינוי ב-2026 הוא ביצוע עמיד. Workflows חייבים לשרוד restarts, crashes, deploys ו-rate limits. הם גם חייבים לשדר התקדמות ל-UI בזמן אמת ולהשהות עבור קלט אנושי. Trigger.dev נבנה מחדש סביב הדרישות האלה עם גרסה 3 וממשיך להרחיב את שטח התשתית של AI שלו.910```mermaid11graph LR12 App[האפליקציה שלך] -->|trigger| API[Trigger.dev API]13 API --> Queue[תור עמיד]14 Queue --> Worker[מיכל Worker]15 Worker -->|run task| Task[קוד Task שלך]16 Task -->|metadata| Realtime[Stream Realtime]17 Realtime --> UI[React UI]18 Worker --> Storage[מאגר מצב Run]19```2021המודל פשוט: אתה מגדיר tasks כ-exports, ה-SDK אוסף אותם, הפלטפורמה מתזמנת ומריצה אותם במיכלים מבודדים, ומצב run נשמר כדי שתוכל לחדש, לנסות שוב ולצפות.2223## התחלה2425### אתחל פרויקט2627```bash28npx trigger.dev@latest login29npx trigger.dev@latest init30```3132זה יוצר קובץ `trigger.config.ts` וספריית `trigger/` עם tasks לדוגמה. קובץ ה-config הוא מקור האמת לפרויקט שלך: אילו ספריות מכילות tasks, הגדרות build, lifecycle hooks ואפשרויות 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### הרץ tasks מקומית5758```bash59npx trigger.dev@latest dev60```6162שרת ה-dev מתחבר לענן, רושם את ה-tasks שלך ומשדר runs דרך הקוד המקומי שלך. אתה מגדיר breakpoints בעורך שלך ופוגע בהם ב-triggers אמיתיים - אותו לולאה שתשתמש בה בכל פרויקט Node.js רגיל.6364## הגדרת Task6566Task הוא אובייקט שמיוצא עם `id` ייחודי ופונקציית `run`. ה-SDK בודק את ה-exports ב-`dirs` ורושם אותם אוטומטית.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. **אין timeout בגוף ה-run.** הפלטפורמה מנהלת את זמן הביצוע דרך `maxDuration` ב-config, לא ב-runtime.1002. **Throws הם retries.** ה-SDK תופס חריגים ומריץ מחדש עם exponential backoff בהתאם למדיניות `retry`.1013. **ערך ההחזרה נשמר.** Tasks אחרים והפרונטאנד שלך יכולים לקרוא את `run.output` מכל מקום.102103## הפעלת Tasks104105אתה קורא ל-task מהבקאנד שלך, ממסלולי ה-API או מ-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 - השתמש לעקוב או להציג את ההתקדמות122```123124האפשרויות פותחות הרבה התנהגות בקריאה אחת:125126- **`idempotencyKey`** - אם run עם אותו מפתח כבר קיים, ה-SDK מחזיר את ה-handle הקיים במקום לשכפל עבודה.127- **`concurrencyKey`** - מסדר runs ששוטפים את המפתח כך שלא תחרוג מ-rate limit לפי tenant.128- **`queue.concurrencyLimit`** - מקסימום גלובלי לתור על פני כל המפתחות.129- **`delay`** - מתזמן את ה-run לזמן עתידי.130- **`ttl`** - אם ה-run לא התחיל עד אז, פוקע אוטומטית.131132### Batch trigger133134עבור עומסי fan-out, `batchTrigger` מקבל עד 500 פריטים לקריאה ויוצר 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## Tasks מתוזמנים146147Cron jobs הופכים להצהרות מהמעלה הראשונה. ה-schedule עצמו הוא אובייקט נפרד שאתה יכול לצרף ל-task מספר פעמים.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```166167עבור schedules לפי tenant - נניח, 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` הופך את הקריאה ל-idempotent: הרצה חוזרת של אותו קוד בזמן deploy לא ערימה schedules כפולים.182183## תורים, Concurrency ו-Idempotency184185שלושה primitives מכסים את רוב הצרכים של rate-limiting וסידור.186187```mermaid188graph TB189 Trigger[trigger payload] --> IK{idempotencyKey<br/>נראה?}190 IK -->|כן| Reuse[החזר run קיים]191 IK -->|לא| CK[bucket concurrencyKey]192 CK --> Q[תור עם<br/>concurrencyLimit]193 Q -->|slot זמין| Run[הרץ task]194 Q -->|slots מלאים| Wait[המתן בתור]195```196197תבנית נפוצה: תור אחד ל-tenant עם concurrency קטן לפי מפתח כדי לכבד את rate limit של ספק, בתוספת מפתח idempotency כדי להפוך את ה-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## המתנות ועבודה לטווח ארוך211212Tasks יכולים להשהות בלי להחזיק חיבור או לשרוף compute. הפלטפורמה שומרת את המצב ומחדשת את הפונקציה כשההמתנה מסתיימת.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: היא מפעילה task ילד ומשהה את ההורה עד שהילד מסתיים. אתה מרכיב tasks כפונקציות async, אבל ה-orchestration רץ עמיד על פני ימים או שבועות.230231### Human-in-the-loop עם `wait.forToken`232233עבור זרימות אישור ו-AI gates, `wait.forToken` משהה עד שהאפליקציה שלך תחזור עם תוצאה.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```257258העורך פותח UI, סוקר את הטיוטה, לוחץ על אישור והבקאנד שלך משלים את ה-token. ה-task ממשיך מהמקום שבו הוא הפסיק - גם אם עברו שעות או ימים.259260## Lifecycle Hooks261262אתה יכול לצרף `init`, `onStart`, `onSuccess` ו-`onFailure` ל-task או באופן גלובלי ב-`trigger.config.ts`. השתמש בהם ל-tracing, error reporting והגדרה משותפת.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` רץ פעם אחת ל-worker container ב-boot, לא לכל run, אז זה המקום הנכון להגדיר clients ו-pools.280281## Realtime בפרונטאנד282283Trigger.dev מפרסם שינויי מצב run - status, metadata, output - דרך streaming API. ה-React hooks נרשמים לזרם הזה ועושים 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אתה יוצר את ה-public access token בצד השרת, scoped ל-run ספציפי, ושולח אותו ללקוח. ה-hook מטפל ב-auth, התחברות מחדש ועדכונים מצטברים.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 הפך ל-runtime פופולרי עבור AI agents כי אותם primitives - ביצוע עמיד, retries, המתנות, real-time metadata, human-in-the-loop - הם בדיוק מה שסוכנים צריכים. אתה משדר tokens מספק מודל ל-`metadata` בזמן שה-run מתרחש, הפרונטאנד מציג אותם בזמן אמת, וה-run שורד tool calls ארוכי טווח בלי לשרוף timeout serverless.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```383384הפרונטאנד משתמש ב-`useRealtimeRun` וקורא את `run.metadata.partial` כדי להציג את התגובה ה-streaming, באותה דרך שהיית מציג chat completion - מלבד שזה שורד reload מלא של דף.385386## פריסה387388הפריסות מהדרות את ה-tasks שלך ל-bundle עם גרסה, בונות מיכל ומחליפות תעבורה אטומית. runs ישנים בטיסה ממשיכים להשתמש בגרסה הקודמת.389390```bash391npx trigger.dev@latest deploy --env prod392```393394ב-CI אתה בדרך כלל מחבר את זה ל-workflow שמשלח את האפליקציה שלך: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```403404עבור סביבות preview, העבר `--env preview --branch ${{ github.head_ref }}` ו-Trigger.dev יוצר סביבה מבודדת לכל branch, משקף את הדרך שבה Vercel מטפל בפריסות preview.405406## Self-Hosting לעומת Cloud407408Trigger.dev הוא קוד פתוח תחת רישיון Apache 2.0. אתה יכול ל-self-host על כל פלטפורמת מיכלים (Docker Compose, Kubernetes, Fly.io) או להשתמש בענן המנוהל ב-trigger.dev.409410| היבט | Cloud | Self-hosted |411|--------|-------|-------------|412| **Setup** | הרשמה, הרצת `init` | הרצת docker-compose או Helm chart |413| **Scaling** | אוטומטי | האחריות שלך |414| **Pricing** | לכל run + לכל compute | רק עלות תשתית |415| **Compliance** | SOC 2 | מה שהסביבה שלך מספקת |416| **הכי טוב ל** | רוב הצוותים | data residency מחמיר, תשתית מותאמת |417418ה-SDK וה-CLI זהים בין מצבים - אתה משנה דגל profile ומכוון ל-instance שלך.419420## Best Practices421422### 1. שמור על payloads קטנים וניתנים לסריאליזציה423424העבר IDs והפניות, לא אובייקטים מלאים. משוך נתונים בתוך ה-task. זה שומר על התור קטן, payloads זולים ל-log ומאפשר לך לשנות מקור נתונים בלי לעורר מחדש.425426### 2. מפתחות Idempotency על כל קריאה חיצונית427428שלב את `idempotencyKey` על trigger ה-task עם מפתחות idempotency על APIs של ספקים (Stripe, OpenAI וכו'). Retries יהיו בטוחים end-to-end.429430### 3. השתמש ב-`triggerAndWait` ל-orchestration, לא ב-`Promise.all` של triggers431432הורה שקורא ל-`triggerAndWait` מרכיב באופן עמיד tasks ילד. הורה ש-trigger ופותר מיד מאבד את היכולת להתבונן בשרשרת.433434### 4. תייג runs435436הוסף `tags` ל-triggers (`tags: ["user:123", "feature:onboarding"]`) כדי שתוכל לסנן את ה-dashboard ואת ה-management API לפי מימדים עסקיים.437438### 5. שמור על `init` כ-idempotent439440הוא רץ בכל cold start. הימנע ממיגרציות או תופעות לוואי one-shot שם.441442## סיכום443444Trigger.dev מסיר את הקטגוריות של עבודה שדרשו פעם בניית מערכת job מאפס. אתה כותב async TypeScript, קורא לו מכל מקום, והפלטפורמה נותנת לך ביצוע עמיד, scheduling, תורים, retries, עדכונים בזמן אמת ודפוסי human-in-the-loop מחוץ לקופסה.445446אותו משטח שמפעיל cron לילי הוא המשטח שמפעיל AI agent רב-שלבי שמשדר ל-frontend ומשהה לסקירה. ההתכנסות הזו היא מה שהופך את ה-framework ראוי למבט רציני ב-2026, בין אם אתה מנהל SaaS שצריך עבודת רקע אמינה או שולח תכונות AI ששורדות timeout serverless.447448> **רשימת בדיקה להתחלה:**449>450> - [x] הירשם ב-trigger.dev או הרץ stack Docker self-hosted451> - [x] `npx trigger.dev@latest init` בפרויקט שלך452> - [x] הגדר את ה-task הראשון שלך עם `task({ id, run })`453> - [x] הפעל אותו מה-API שלך וצפה ב-run ב-dashboard454> - [x] הוסף `idempotencyKey` ו-`concurrencyKey` לבטיחות בייצור455> - [x] חבר את `useRealtimeRun` לרכיב סטטוס456> - [x] פרוס עם `trigger.dev deploy --env prod` מ-CI457
:Trigger.dev: משימות רקע עמידות וזרימות עבודה של AI ב-TypeScriptlines 1-457 (END) — press q to close