spinny:~/writing $ less trigger-dev-background-jobs-guide.md
12تحتاج معظم تطبيقات الإنتاج إلى عمل لا يتناسب مع دورة الطلب/الاستجابة: إرسال رسائل البريد الإلكتروني ومعالجة التحميلات وتشغيل خطوط أنابيب الذكاء الاصطناعي ومزامنة بيانات الطرف الثالث وإنشاء التقارير. الإجابة التقليدية هي قائمة انتظار (Redis، SQS، RabbitMQ)، أسطول من العمال، مجدول، وكومة هشة من رمز الترابط الذي ينكسر في كل عملية نشر.34[Trigger.dev](https://trigger.dev) يطوي تلك الحزمة في SDK واحد TypeScript. تكتب الدوال، وتستدعيها من أي مكان، وتتعامل المنصة مع الطوابير وإعادة المحاولات والمراقبة والجدولة والتنفيذ الدائم. تعمل المهام لأطول فترة تحتاجها - لا توجد مهلة بدون خادم لمدة 10 ثوانٍ، ولا عمل ضائع عند إعادة النشر.56## لماذا Trigger.dev78التحول في 2026 هو التنفيذ الدائم. يجب أن تنجو سير العمل من إعادة التشغيل والأعطال وعمليات النشر وحدود المعدل. يجب عليهم أيضًا دفق التقدم إلى واجهة المستخدم في الوقت الفعلي والإيقاف المؤقت لإدخال الإنسان. تم إعادة بناء Trigger.dev حول هذه المتطلبات مع الإصدار 3 ويواصل توسيع سطح البنية التحتية للذكاء الاصطناعي الخاص به.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[تدفق Realtime]17 Realtime --> UI[React UI]18 Worker --> Storage[تخزين حالة Run]19```2021النموذج بسيط: تحدد المهام كصادرات، يلتقطها SDK، تجدول المنصة وتشغلها في حاويات معزولة، ويتم الاحتفاظ بحالة التشغيل حتى تتمكن من الاستئناف وإعادة المحاولة والمراقبة.2223## البدء2425### تهيئة مشروع2627```bash28npx trigger.dev@latest login29npx trigger.dev@latest init30```3132هذا ينشئ ملف `trigger.config.ts` ودليل `trigger/` مع مهام مثال. ملف التكوين هو مصدر الحقيقة لمشروعك: ما هي الأدلة التي تحتوي على مهام، وإعدادات البناء، وخطافات دورة الحياة، وخيارات وقت التشغيل.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### تشغيل المهام محليًا5758```bash59npx trigger.dev@latest dev60```6162يتصل خادم dev بالسحابة، ويسجل مهامك، ويدفق التشغيلات من خلال رمزك المحلي. تضع نقاط توقف في محررك وتصيبها على المشغلات الحقيقية - نفس الحلقة التي ستستخدمها في أي مشروع Node.js عادي.6364## تعريف Task6566المهمة هي كائن يتم تصديره بمعرف `id` فريد ودالة `run`. يفحص SDK الصادرات عبر `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. **لا توجد مهلة في جسم run.** تدير المنصة وقت التنفيذ عبر `maxDuration` في التكوين، وليس وقت التشغيل.1002. **الإلقاءات هي إعادة محاولات.** يلتقط SDK الاستثناءات ويعيد التشغيل مع تراجع أسي وفقًا لسياسة `retry`.1013. **يتم الاحتفاظ بقيمة الإرجاع.** يمكن للمهام الأخرى وواجهتك الأمامية قراءة `run.output` من أي مكان.102103## تشغيل المهام104105تستدعي مهمة من الواجهة الخلفية أو مسارات API الخاصة بك أو مهمة أخرى.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`** - إذا كان التشغيل بنفس المفتاح موجودًا بالفعل، فإن SDK يعيد المقبض الموجود بدلاً من تكرار العمل.127- **`concurrencyKey`** - يسلسل عمليات التشغيل التي تشترك في المفتاح حتى لا تتجاوز حد المعدل لكل مستأجر.128- **`queue.concurrencyLimit`** - الحد الأقصى العالمي للطابور عبر جميع المفاتيح.129- **`delay`** - يجدول التشغيل لوقت مستقبلي.130- **`ttl`** - إذا لم يبدأ التشغيل بحلول ذلك الوقت، فقم بانتهاء صلاحيته تلقائيًا.131132### Batch trigger133134لأعباء العمل المتفرعة، يقبل `batchTrigger` ما يصل إلى 500 عنصر لكل مكالمة وينشئ تشغيلًا واحدًا لكل عنصر.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## المهام المجدولة146147تصبح وظائف Cron إعلانات من الدرجة الأولى. الجدول الزمني نفسه هو كائن منفصل يمكنك إرفاقه بمهمة عدة مرات.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للجداول الزمنية لكل مستأجر - لنقل، 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: إعادة تشغيل نفس الرمز في وقت النشر لا يكدس الجداول الزمنية المكررة.182183## الطوابير والتزامن والإمكانية المتكررة184185ثلاثة بدائيات تغطي معظم احتياجات الحد من المعدل والترتيب.186187```mermaid188graph TB189 Trigger[trigger payload] --> IK{idempotencyKey<br/>شوهد؟}190 IK -->|نعم| Reuse[إرجاع run موجود]191 IK -->|لا| CK[سلة concurrencyKey]192 CK --> Q[طابور مع<br/>concurrencyLimit]193 Q -->|فتحة متاحة| Run[تشغيل task]194 Q -->|فتحات ممتلئة| Wait[الانتظار في الطابور]195```196197نمط شائع: طابور لكل مستأجر مع تزامن صغير لكل مفتاح لاحترام حد معدل البائع، بالإضافة إلى مفتاح الإمكانية المتكررة لجعل إعادة المحاولات آمنة.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## الانتظار والعمل طويل الأمد211212يمكن للمهام أن تتوقف مؤقتًا دون الاحتفاظ باتصال أو حرق الحوسبة. تحتفظ المنصة بالحالة وتستأنف الوظيفة عند اكتمال الانتظار.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` هي الميزة القاتلة: تطلق مهمة فرعية وتعلق الأصل حتى تكتمل العملية الفرعية. تكتب المهام مثل دوال async، لكن التنسيق يعمل بشكل دائم عبر أيام أو أسابيع.230231### Human-in-the-loop مع `wait.forToken`232233لتدفقات الموافقة وبوابات الذكاء الاصطناعي، يتوقف `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يفتح المحرر واجهة، ويراجع المسودة، وينقر فوق موافقة، ويكمل الواجهة الخلفية الرمز المميز. تستأنف المهمة من حيث توقفت - حتى لو مرت ساعات أو أيام.259260## خطافات دورة الحياة261262يمكنك إرفاق `init` و `onStart` و `onSuccess` و `onFailure` بمهمة أو على مستوى عالمي في `trigger.config.ts`. استخدمها للتتبع والإبلاغ عن الأخطاء والإعداد المشترك.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` يعمل مرة واحدة لكل حاوية عاملة عند التمهيد، وليس لكل تشغيل، لذا فهو المكان الصحيح لإعداد العملاء والتجمعات.280281## الوقت الفعلي في الواجهة الأمامية282283ينشر Trigger.dev تغييرات حالة التشغيل - الحالة، البيانات الوصفية، الإخراج - عبر API دفق. تشترك خطافات React في هذا الدفق وتعيد العرض تلقائيًا.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تقوم بإنشاء رمز الوصول العام من جانب الخادم، مع نطاق محدد لتشغيل معين، وتشحنه إلى العميل. يتعامل الخطاف مع المصادقة وإعادة الاتصال والتحديثات التزايدية.337338للتشغيل والاشتراك في خطوة واحدة: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## وكلاء الذكاء الاصطناعي والدفق354355أصبح Trigger.dev وقت تشغيل شائع لوكلاء الذكاء الاصطناعي لأن نفس البدائيات - التنفيذ الدائم، إعادة المحاولات، الانتظار، البيانات الوصفية في الوقت الفعلي، human-in-the-loop - هي بالضبط ما يحتاجه الوكلاء. تقوم بدفق الرموز من مزود نموذج إلى `metadata` بينما يحدث التشغيل، تقدمها الواجهة الأمامية مباشرة، ويبقى التشغيل على قيد الحياة من خلال استدعاءات الأدوات طويلة المدى دون حرق مهلة بدون خادم.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` لتقديم الاستجابة المتدفقة، بنفس الطريقة التي ستقدم بها إكمال محادثة - باستثناء أن هذا يبقى على قيد الحياة من خلال إعادة تحميل الصفحة بالكامل.385386## النشر387388تقوم عمليات النشر بترجمة مهامك إلى حزمة مُصدارة، وبناء حاوية، وتبديل حركة المرور بشكل ذري. تستمر عمليات التشغيل القديمة قيد التنفيذ في استخدام الإصدار السابق.389390```bash391npx trigger.dev@latest deploy --env prod392```393394في CI، عادةً ما تربط هذا في نفس سير العمل الذي يشحن تطبيقك: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لبيئات المعاينة، قم بتمرير `--env preview --branch ${{ github.head_ref }}` ويقوم Trigger.dev بإنشاء بيئة معزولة لكل فرع، مما يعكس كيفية تعامل Vercel مع عمليات نشر المعاينة.405406## الاستضافة الذاتية مقابل السحابة407408Trigger.dev مفتوح المصدر بموجب ترخيص Apache 2.0. يمكنك استضافة ذاتية على أي منصة حاوية (Docker Compose، Kubernetes، Fly.io) أو استخدام السحابة المدارة على trigger.dev.409410| الجانب | السحابة | الاستضافة الذاتية |411|--------|-------|-------------|412| **الإعداد** | الاشتراك، تشغيل `init` | تشغيل docker-compose أو Helm chart |413| **التحجيم** | تلقائي | مسؤوليتك |414| **التسعير** | لكل تشغيل + لكل حساب | تكلفة البنية التحتية فقط |415| **الامتثال** | SOC 2 | ما توفره بيئتك |416| **الأنسب لـ** | معظم الفرق | إقامة بيانات صارمة، بنية تحتية مخصصة |417418SDK و CLI متطابقان بين الأوضاع - تقوم بتغيير علامة ملف التعريف وتشير إلى مثيلك الخاص.419420## أفضل الممارسات421422### 1. حافظ على حمولات صغيرة وقابلة للتسلسل423424قم بتمرير المعرفات والمراجع، وليس الكائنات الكاملة. اسحب البيانات داخل المهمة. هذا يحافظ على الطابور صغيرًا، والحمولات رخيصة للتسجيل، ويتيح لك تغيير مصدر البيانات دون إعادة التشغيل.425426### 2. مفاتيح Idempotency على كل استدعاء خارجي427428اجمع `idempotencyKey` على مشغل المهمة مع مفاتيح idempotency في واجهات API للبائعين (Stripe، OpenAI، إلخ). ستكون إعادة المحاولات آمنة من البداية إلى النهاية.429430### 3. استخدم `triggerAndWait` للتنسيق، وليس `Promise.all` من المشغلات431432أحد الوالدين الذي يستدعي `triggerAndWait` يؤلف بشكل دائم مهام فرعية. أحد الوالدين الذي يطلق ويحل على الفور يفقد إمكانية مراقبة السلسلة.433434### 4. ضع علامات على عمليات التشغيل435436أضف `tags` إلى المشغلات (`tags: ["user:123", "feature:onboarding"]`) حتى تتمكن من تصفية لوحة المعلومات وواجهة management API حسب أبعاد العمل.437438### 5. حافظ على `init` idempotent439440يعمل عند كل بداية باردة. تجنب الترحيلات أو الآثار الجانبية لمرة واحدة هناك.441442## الخاتمة443444يزيل Trigger.dev فئات العمل التي اعتادت أن تتطلب بناء نظام وظائف من الصفر. تكتب async TypeScript، تستدعيها من أي مكان، وتمنحك المنصة التنفيذ الدائم والجدولة والطوابير وإعادة المحاولات وتحديثات الوقت الفعلي وأنماط human-in-the-loop خارج الصندوق.445446نفس السطح الذي يشغل cron ليلي هو السطح الذي يشغل وكيل ذكاء اصطناعي متعدد الخطوات يدفق إلى الواجهة الأمامية ويتوقف للمراجعة. هذا التقارب هو ما يجعل الإطار يستحق نظرة جادة في 2026، سواء كنت تدير SaaS يحتاج إلى عمل خلفي موثوق أو تشحن ميزات الذكاء الاصطناعي التي تعيش بعد مهلة بدون خادم.447448> **قائمة التحقق للبدء:**449>450> - [x] التسجيل في trigger.dev أو تشغيل حزمة Docker للاستضافة الذاتية451> - [x] `npx trigger.dev@latest init` في مشروعك452> - [x] حدد مهمتك الأولى باستخدام `task({ id, run })`453> - [x] قم بتشغيلها من API الخاص بك وشاهد التشغيل في لوحة المعلومات454> - [x] أضف `idempotencyKey` و `concurrencyKey` لأمان الإنتاج455> - [x] قم بتوصيل `useRealtimeRun` بمكون الحالة456> - [x] انشر باستخدام `trigger.dev deploy --env prod` من CI457
:Trigger.dev: مهام خلفية دائمة وسير عمل ذكاء اصطناعي في TypeScriptlines 1-457 (END) — press q to close