لماذا Trigger.dev v4؟
أنهت Trigger.dev v3 دورة حياتها رسمياً في 1 يوليو 2026. إذا كان مشروعك لا يزال يستورد من @trigger.dev/sdk/v3، فقد توقفت مهام الخلفية عن العمل — البنية التحتية لسحابة v3 مُغلقة بالكامل.
v4 ليست مجرد تحديث شكلي. فهي تُعيد كتابة واجهة برمجة الـ hooks، وتُضيف نظام middleware، وتُدخل تدفقات الموافقة wait-for-token، وتُقدّم schemaTask — جسر يحوّل أي مهمة خلفية إلى أداة من الدرجة الأولى قابلة للاستخدام مع Vercel AI SDK. هذا الدرس يرشدك خلال مسار الهجرة ويبني خط معالجة مستندات متكاملاً يعرض كل ميزة رئيسية في v4.
المتطلبات المسبقة
- Node.js 20 أو أحدث
- مشروع Next.js 15 (App Router)
- حساب على Trigger.dev — مجاني على trigger.dev
- إلمام أساسي بـ TypeScript غير المتزامن
ما الذي ستبنيه
خط استيعاب مستندات بأربع مراحل:
- الاستيعاب — قبول الملفات المرفوعة وإضافة معالجتها إلى قائمة الانتظار
- التحليل — استخراج المحتوى بالذكاء الاصطناعي مع التحكم في التزامن
- الموافقة — الإيقاف المؤقت وانتظار مراجع بشري قبل النشر
- الإشعار — الاستئناف وإرسال تأكيد عند الموافقة
الخطوة 1: تثبيت SDK الإصدار v4
npm install @trigger.dev/sdk
npx trigger.dev@latest initيكتب أمر init ملف trigger.config.ts في جذر المشروع ويُنشئ مجلد trigger/ لملفات المهام.
إذا كنت تُهاجر من v3، فأول تغيير هو مسار الاستيراد:
// قبل (v3)
import { task } from "@trigger.dev/sdk/v3";
// بعد (v4)
import { task } from "@trigger.dev/sdk";اضبط مشروعك في trigger.config.ts:
// trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk/config";
export default defineConfig({
project: "proj_معرف_مشروعك",
runtime: "node",
maxDuration: 3600,
dirs: ["./trigger"],
});أضف مفتاح API الخاص بك في .env.local:
TRIGGER_SECRET_KEY=tr_dev_xxxxxxxxxxxxالخطوة 2: مهمتك الأولى بـ v4
أبرز تغيير جذري في v4 هو واجهة برمجة معاملات الـ hook. في v3، كانت الـ hooks تستقبل وسائط إيجابية منفصلة. في v4، كل hook تستقبل كائناً مُفككاً واحداً.
// trigger/document-tasks.ts
import { task, logger } from "@trigger.dev/sdk";
export const ingestDocument = task({
id: "ingest-document",
// v4: معامل كائن واحد مُفكَّك لجميع الـ hooks
onStartAttempt: ({ payload, ctx }) => {
logger.info("بدء محاولة الاستيعاب", { runId: ctx.run.id });
},
onSuccess: ({ payload, output, ctx }) => {
logger.info("نجح الاستيعاب", { docId: output.documentId });
},
onFailure: ({ payload, error, ctx }) => {
logger.error("فشل الاستيعاب", { message: error.message });
},
// توقيع run() لم يتغير عن v3
run: async (payload: { filename: string; url: string }, { ctx }) => {
logger.info("معالجة المستند", { filename: payload.filename });
await new Promise(resolve => setTimeout(resolve, 1000));
return {
documentId: `doc_${Date.now()}`,
filename: payload.filename,
status: "ingested",
};
},
});ملاحظة:
onStartAttemptهو خلَف v4 للـ hook المُهمَلonStart. استخدمonStartAttemptفي كل الأكواد الجديدة.
الخطوة 3: التشغيل من Next.js
Server Action
// app/actions/document.ts
"use server";
import { tasks } from "@trigger.dev/sdk";
import type { ingestDocument } from "@/trigger/document-tasks";
export async function submitDocument(filename: string, url: string) {
try {
const handle = await tasks.trigger<typeof ingestDocument>(
"ingest-document",
{ filename, url }
);
return { runId: handle.id };
} catch (error) {
return { error: "فشل إضافة المستند إلى قائمة الانتظار" };
}
}مسار API
// app/api/documents/route.ts
import { tasks } from "@trigger.dev/sdk";
import { NextResponse } from "next/server";
import type { ingestDocument } from "@/trigger/document-tasks";
export async function POST(request: Request) {
const { filename, url } = await request.json();
const handle = await tasks.trigger<typeof ingestDocument>(
"ingest-document",
{ filename, url }
);
return NextResponse.json({ runId: handle.id });
}الاستيراد من نوع type فقط يضمن عدم تسرب كود المهمة إلى حزمة العميل.
الخطوة 4: قوائم الانتظار والتزامن
استخدم queue() للتحكم في عدد التشغيلات المتزامنة — أمر حيوي لحماية قاعدة البيانات أو واجهات برمجة التطبيقات الخارجية.
// trigger/analysis-tasks.ts
import { task, queue } from "@trigger.dev/sdk";
const processingQueue = queue({
name: "document-processing",
concurrencyLimit: 5,
});
export const analyzeDocument = task({
id: "analyze-document",
queue: processingQueue,
retry: {
maxAttempts: 3,
minTimeoutInMs: 1000,
maxTimeoutInMs: 30000,
factor: 2,
},
run: async (payload: { documentId: string; url: string }) => {
const text = await fetch(payload.url).then(r => r.text());
const wordCount = text.split(/\s+/).length;
return {
documentId: payload.documentId,
wordCount,
extractedAt: new Date().toISOString(),
};
},
});يمكنك أيضاً تجاوز قائمة الانتظار عند التشغيل لتوجيه الأولويات:
// توجيه المستخدمين المميزين إلى قائمة انتظار بتزامن أعلى
const handle = await analyzeDocument.trigger(
{ documentId, url },
{ queue: "premium-users" }
);الخطوة 5: Wait-for-Token — تدفق الموافقة البشرية
أقوى بدائية جديدة في v4 هي رمز نقطة الانتظار (waitpoint token). تتوقف المهمة مؤقتاً في منتصف تنفيذها ولا تستأنف إلا عندما يُكمل نظام خارجي ذلك الرمز — سواء بعد دقائق أو ساعات أو أيام — دون احتلال خيط خادم.
// trigger/approval-tasks.ts
import { task, wait } from "@trigger.dev/sdk";
export const awaitDocumentApproval = task({
id: "await-document-approval",
maxDuration: 86400, // توقف مؤقت حتى 24 ساعة
run: async (payload: { documentId: string; reviewerEmail: string }) => {
// إنشاء رمز مرتبط بهذه التشغيلة
const token = await wait.createToken({
timeout: "24h",
tags: [`doc:${payload.documentId}`],
});
// إرسال بريد إلكتروني يحتوي على رابط يتضمن token.id
await notifyReviewer(payload.reviewerEmail, payload.documentId, token.id);
// المهمة تتوقف هنا — تستأنف عند اكتمال الرمز
const result = await wait.forToken<{ approved: boolean; notes: string }>(token);
if (!result.output.approved) {
throw new Error(`رُفض المستند: ${result.output.notes}`);
}
return { approved: true, notes: result.output.notes };
},
});
async function notifyReviewer(email: string, docId: string, tokenId: string) {
// إرسال بريد مع رابط: /api/review?token=tokenId&doc=docId
console.log(`تم إرسال رابط الموافقة إلى ${email} للمستند ${docId}`);
}إكمال الرمز من مسار API
// app/api/review/route.ts
import { runs } from "@trigger.dev/sdk";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const { tokenId, approved, notes } = await request.json();
// إكمال نقطة الانتظار — المهمة تستأنف فوراً
await runs.completeToken(tokenId, { approved, notes });
return NextResponse.json({ ok: true });
}بدائية wait.forToken تتعامل مع أي تسليم غير متزامن كان سيتطلب تقليدياً polling أو webhooks.
الخطوة 6: schemaTask كأداة ذكاء اصطناعي
تُقدّم v4 schemaTask — نوعاً من المهام يُصرّح بمخطط Zod. هذا يُفعّل ai.toolExecute، الذي يحوّل أي مهمة خلفية إلى أداة يمكن لنماذج اللغة استدعاؤها عبر Vercel AI SDK.
// trigger/ai-tasks.ts
import { schemaTask } from "@trigger.dev/sdk";
import { z } from "zod";
export const summarizeDocument = schemaTask({
id: "summarize-document",
description: "استخراج ملخص والموضوعات الرئيسية من رابط مستند",
schema: z.object({
documentId: z.string(),
url: z.string().url(),
maxWords: z.number().int().min(50).max(500).default(200),
}),
run: async ({ documentId, url, maxWords }) => {
const text = await fetch(url).then(r => r.text());
const words = text.split(/\s+/).slice(0, maxWords);
return {
documentId,
summary: words.join(" "),
topicCount: Math.min(3, Math.floor(words.length / 50)),
};
},
});ربطه كأداة Vercel AI SDK
// lib/document-agent.ts
import { generateText, tool } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { ai } from "@trigger.dev/sdk/ai";
import { summarizeDocument } from "@/trigger/ai-tasks";
const summarizeTool = tool({
description: summarizeDocument.description ?? "تلخيص مستند",
inputSchema: summarizeDocument.schema!,
execute: ai.toolExecute(summarizeDocument),
});
export async function runDocumentAgent(userPrompt: string) {
const { text } = await generateText({
model: anthropic("claude-sonnet-5"),
prompt: userPrompt,
tools: { summarizeDocument: summarizeTool },
maxSteps: 5,
});
return text;
}عندما يستدعي النموذج summarizeDocument، تعمل مهمة Trigger.dev في الخلفية — مع إعادة المحاولات والتحكم في التزامن والمراقبة في الوقت الفعلي — بدلاً من التنفيذ المباشر داخل مسار API.
الخطوة 7: المهام المجدولة (Cron)
// trigger/scheduled-tasks.ts
import { schedules } from "@trigger.dev/sdk";
export const dailyCleanup = schedules.task({
id: "daily-cleanup",
cron: "0 2 * * *", // 02:00 UTC كل يوم
run: async (payload) => {
const { timestamp, lastTimestamp } = payload;
const cutoff = new Date(timestamp);
cutoff.setDate(cutoff.getDate() - 30);
// payload.lastTimestamp غير معرّف في التشغيل الأول
const since = lastTimestamp ?? cutoff;
const deleted = await deleteDocumentsOlderThan(cutoff);
return { deleted, runAt: timestamp.toISOString(), since: since.toISOString() };
},
});
async function deleteDocumentsOlderThan(cutoff: Date): Promise<number> {
// استعلام قاعدة البيانات هنا
return 0;
}لا حاجة لأي إعداد إضافي — حقل cron يُسجّل الجدول الزمني تلقائياً عند النشر.
الخطوة 8: hooks دورة الحياة الجديدة في v4
تُضيف v4 hooks تُطلَق حول نقاط الانتظار، مما يمنحك رؤية على التشغيلات المتوقفة مؤقتاً:
// trigger/monitored-task.ts
import { task, logger } from "@trigger.dev/sdk";
export const monitoredTask = task({
id: "monitored-task",
// تُطلَق عند دخول التشغيلة في نقطة انتظار
onWait: ({ payload, ctx }) => {
logger.info("المهمة متوقفة مؤقتاً عند نقطة انتظار", { runId: ctx.run.id });
},
// تُطلَق عند حل نقطة الانتظار واستئناف التشغيلة
onResume: ({ payload, ctx }) => {
logger.info("المهمة استؤنفت من نقطة الانتظار", { runId: ctx.run.id });
},
// تُطلَق بعد العودة الناجحة من دالة run
onComplete: ({ payload, output, ctx }) => {
logger.info("اكتملت المهمة", { runId: ctx.run.id, output });
},
// تُطلَق عند إلغاء التشغيلة عبر لوحة التحكم أو API
onCancel: ({ payload, ctx }) => {
logger.warn("تم إلغاء المهمة", { runId: ctx.run.id });
},
run: async (payload: { documentId: string }) => {
return { processed: payload.documentId };
},
});تُكمّل هذه الـ hooks الـ hooks الموجودة onSuccess وonFailure وonStartAttempt، وهي مفيدة لإرسال المقاييس إلى Datadog أو PostHog أو أي نظام مراقبة.
الخطوة 9: التشغيل الدُفعي (تغيير API في v4)
تغيّرت API الدُفعية في v4. الآن تُستعاد النتائج باستخدام batch.retrieve() بدلاً من الوصول المباشر إلى batchHandle.runs:
import { tasks, batch } from "@trigger.dev/sdk";
import type { ingestDocument } from "@/trigger/document-tasks";
const documents = [
{ filename: "تقرير-أ.pdf", url: "https://example.com/a.pdf" },
{ filename: "تقرير-ب.pdf", url: "https://example.com/b.pdf" },
{ filename: "تقرير-ج.pdf", url: "https://example.com/c.pdf" },
];
const batchHandle = await tasks.batchTrigger(
documents.map(doc => [ingestDocument, doc] as const)
);
// v3: batchHandle.runs — مُزال في v4
// v4: استرجاع منفصل
const batchResult = await batch.retrieve(batchHandle.batchId);
console.log(`تم إضافة ${batchResult.runs.length} تشغيلات إلى قائمة الانتظار`);الخطوة 10: التطوير والنشر
التطوير المحلي
npx trigger.dev@latest devيدفق هذا الأمر سجلات المهام إلى طرفيتك ويُعيد التحميل تلقائياً عند حفظ ملفات المهام. خادم Next.js dev يعمل بشكل منفصل.
النشر على سحابة Trigger.dev
npx trigger.dev@latest deployGitHub Actions CI
- name: نشر مهام Trigger.dev
run: npx trigger.dev@latest deploy --ci
env:
TRIGGER_SECRET_KEY: ${{ secrets.TRIGGER_SECRET_KEY }}مرجع الهجرة: من v3 إلى v4
| ما الذي تغيّر | v3 | v4 |
|---|---|---|
| مسار الاستيراد | @trigger.dev/sdk/v3 | @trigger.dev/sdk |
| معاملات الـ hook | وسائط إيجابية | كائن مُفكَّك واحد |
| اسم الـ hook | onStart | onStartAttempt (مُفضَّل) |
| نتائج الدُفعة | batchHandle.runs | batch.retrieve(id).runs |
| تدفقات الموافقة | غير متاحة | wait.createToken وwait.forToken |
| جسر أدوات الذكاء الاصطناعي | غير متاح | schemaTask وai.toolExecute |
| hooks الإيقاف/الاستئناف | غير متاحة | onWait وonResume وonComplete وonCancel |
استكشاف الأخطاء وإصلاحها
المهمة لا تعمل: تحقق من أن TRIGGER_SECRET_KEY يطابق مشروعك. شغّل npx trigger.dev@latest whoami للتحقق من الاتصال.
التشغيلة عالقة في حالة "waiting": تم إنشاء رمز نقطة انتظار لكن لم يُكتمل. اتصل بـ runs.completeToken من نقطة نهاية الموافقة، أو ألغِ التشغيلة من لوحة تحكم Trigger.dev.
أخطاء TypeScript على معاملات الـ hook: لا تزال تستخدم الأسلوب الإيجابي لـ v3. اجمعها في كائن مُفكَّك واحد كما هو موضح في الخطوة 2.
batchHandle.runs غير معرَّف: تستخدم API الدُفعية القديمة لـ v3. انتقل إلى batch.retrieve(batchHandle.batchId) كما هو موضح في الخطوة 9.
الخطوات التالية
- بحث LanceDB المتجهي في Next.js — تخزين تضمينات المستندات المستخرجة بالذكاء الاصطناعي والاستعلام عنها
- AI SDK 7 HarnessAgent — تنسيق أدوات
schemaTaskمن Trigger.dev معHarnessAgent - بوابة LiteLLM Proxy — توجيه استدعاءات الذكاء الاصطناعي عبر بوابة مستضافة ذاتياً بميزانيات لكل فريق
خلاصة
يُقدّم Trigger.dev v4 تحسينات جوهرية على v3: API hooks أنظف، وwait-for-token لتدفقات الإنسان في الحلقة غير المتزامنة، وschemaTask لتكامل أدوات الذكاء الاصطناعي، وhooks دورة حياة جديدة للمراقبة حول نقاط الانتظار. مع إغلاق v3 بالكامل منذ يوليو 2026، الهجرة ليست اختيارية — لكن الترقية مباشرة، والميزات الجديدة تستحق الجهد.