Effect-TS: معالجة الأخطاء المُنمَّطة والخدمات والأنابيب لتطبيقات TypeScript الإنتاجية

توقف عن فقدان الأخطاء في ثقوب try-catch السوداء. تمنحك Effect-TS أخطاءً مُنمَّطة وأنابيب قابلة للتركيب وحقن تبعيات مدمجاً — كل ذلك مع البقاء في TypeScript بالكامل. في هذا الدليل، ستبني خدمة إدارة مستخدمين جاهزة للإنتاج مع أخطاء مُتتبَّعة وإعادة محاولات وتبعيات قابلة للاختبار.
ما ستتعلمه
بنهاية هذا الدليل، ستتمكن من:
- فهم ما هي Effect-TS ولماذا تكتسب انتشاراً واسعاً في 2026
- نمذجة أخطاء مُنمَّطة يتتبعها المترجم عبر تطبيقك بالكامل
- بناء أنابيب قابلة للتركيب باستخدام
Effect.pipeوالمولّدات - تنفيذ حقن التبعيات باستخدام نمط Service/Layer
- التعامل مع التزامن باستخدام بدائيات التزامن المُهيكل
- إضافة إعادة المحاولات والمُهَل الزمنية والجدولة بدون كود إضافي
- كتابة خدمات قابلة للاختبار عن طريق تبديل الطبقات
- بناء واجهة برمجة تطبيقات لإدارة المستخدمين من الصفر
المتطلبات الأساسية
قبل البدء، تأكد من توفر:
- Node.js 20+ مثبت (
node --version) - خبرة في TypeScript 5.4+ (الأنواع العامة، استنتاج الأنواع، الاتحادات المُميَّزة)
- إلمام بأنماط async/await
- فهم أساسي لمفاهيم حقن التبعيات
- محرر أكواد يدعم TypeScript (يُنصح بـ VS Code)
لماذا Effect-TS؟
معالجة الأخطاء التقليدية في TypeScript بها مشكلة جوهرية: try-catch تمحو معلومات الأنواع. عندما تلتقط خطأً، تُنمِّطه TypeScript كـ unknown — لا تعرف ما الخطأ في وقت التجميع.
// النهج التقليدي — الأخطاء غير مرئية لنظام الأنواع
async function getUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error("Failed to fetch user");
return response.json();
}
// ما الأخطاء التي يمكن أن تُرمى؟ توقيع النوع لا يخبرك.
// قد تكون: NetworkError, NotFoundError, ParseError, TimeoutError...تحل Effect-TS هذا بتشفير الأخطاء في توقيع النوع:
import { Effect } from "effect";
// Effect<User, NetworkError | NotFoundError, UserService>
// نوع النجاح ↑ أنواع الأخطاء ↑ التبعيات ↑كل دالة تعلن بالضبط ما يمكن أن يحدث خطأ وما التبعيات المطلوبة. المترجم يفرض ذلك في كل نقطة استدعاء.
الخطوة 1: إعداد المشروع
أنشئ مشروعاً جديداً وثبّت Effect:
mkdir effect-user-service && cd effect-user-service
npm init -y
npm install effect @effect/platform @effect/schema
npm install -D typescript tsx @types/nodeهيّئ TypeScript بإعدادات صارمة:
npx tsc --initحدّث ملف tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"exactOptionalPropertyTypes": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}أنشئ هيكل المشروع:
mkdir -p src/{errors,services,layers,models}أضف أمر التشغيل في package.json:
{
"scripts": {
"dev": "tsx watch src/main.ts",
"start": "tsx src/main.ts"
}
}الخطوة 2: فهم نوع Effect
جوهر Effect-TS هو نوع Effect. فكّر فيه كـ Promise مُعزَّز يتتبع ثلاثة أشياء:
// Effect<Success, Error, Requirements>
//
// Success — القيمة المُنتَجة عند النجاح
// Error — أنواع الأخطاء المحتملة (اتحاد)
// Requirements — الخدمات/التبعيات المطلوبة للتشغيلأنشئ ملف src/basics.ts لاستكشاف الأساسيات:
import { Effect, Console } from "effect";
// Effect بسيط ينجح بسلسلة نصية
const greeting = Effect.succeed("Hello, Effect!");
// Effect بسيط يفشل بخطأ
const failure = Effect.fail("Something went wrong");
// Effects كسولة — لا شيء يعمل حتى تنفّذها
// هذا مجرد وصف لعملية حسابية
// تحويل القيم باستخدام map
const loudGreeting = greeting.pipe(
Effect.map((msg) => msg.toUpperCase())
);
// ربط effects باستخدام flatMap
const program = Effect.flatMap(greeting, (msg) => {
return Console.log(msg);
});
// استخدم المولّدات لأسلوب حتمي (مُوصى به)
const generatorProgram = Effect.gen(function* () {
const msg = yield* greeting;
yield* Console.log(msg);
yield* Console.log("Effect-TS is awesome!");
});
// شغّل البرنامج
Effect.runPromise(generatorProgram).then(() => {
console.log("Done!");
});شغّله:
npx tsx src/basics.tsسترى:
Hello, Effect!
Effect-TS is awesome!
Done!
المولّدات مقابل pipe: كلا الأسلوبين متكافئان تماماً. المولّدات (Effect.gen) تعطيك كوداً يبدو حتمياً مع yield*، بينما pipe يعطيك أسلوب تركيب وظيفي. معظم الفرق تفضل المولّدات لسهولة القراءة. استخدم ما يناسب فريقك.
الخطوة 3: تعريف الأخطاء المُنمَّطة
تبدأ القوة الحقيقية لـ Effect مع الأخطاء المُنمَّطة. بدلاً من رمي كائنات Error عامة، تُعرّف فئات أخطاء محددة يتتبعها نظام الأنواع.
أنشئ ملف src/errors/user-errors.ts:
import { Data } from "effect";
// Data.TaggedError ينشئ عضو اتحاد مُوسَم بحقل _tag
// هذا يُمكّن المطابقة الشاملة للأنماط
export class UserNotFoundError extends Data.TaggedError(
"UserNotFoundError"
)<{
readonly userId: string;
}> {}
export class UserAlreadyExistsError extends Data.TaggedError(
"UserAlreadyExistsError"
)<{
readonly email: string;
}> {}
export class ValidationError extends Data.TaggedError(
"ValidationError"
)<{
readonly field: string;
readonly message: string;
}> {}
export class DatabaseError extends Data.TaggedError(
"DatabaseError"
)<{
readonly operation: string;
readonly cause: unknown;
}> {}
export class NetworkError extends Data.TaggedError(
"NetworkError"
)<{
readonly url: string;
readonly statusCode: number;
}> {}الآن استخدمها في دالة:
import { Effect } from "effect";
import { UserNotFoundError, ValidationError } from "./errors/user-errors.js";
// نوع الإرجاع يعلن صراحةً: يمكن أن يفشل بـ
// UserNotFoundError أو ValidationError
function getUser(
id: string
): Effect.Effect<User, UserNotFoundError | ValidationError> {
return Effect.gen(function* () {
if (!id || id.length === 0) {
return yield* new ValidationError({
field: "id",
message: "User ID cannot be empty",
});
}
const user = yield* lookupUser(id);
if (!user) {
return yield* new UserNotFoundError({ userId: id });
}
return user;
});
}المترجم الآن يعرف بالضبط ما الأخطاء التي يمكن أن تُنتجها getUser. إذا استدعيت getUser ونسيت معالجة UserNotFoundError، سيخبرك نظام الأنواع.
الخطوة 4: بناء نموذج المستخدم
أنشئ ملف src/models/user.ts باستخدام @effect/schema للتحقق في وقت التشغيل:
import { Schema } from "@effect/schema";
// تعريف مخطط المستخدم — يتحقق في وقت التشغيل، يستنتج الأنواع في وقت التجميع
export const UserSchema = Schema.Struct({
id: Schema.String.pipe(Schema.nonEmptyString()),
email: Schema.String.pipe(Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)),
name: Schema.String.pipe(Schema.nonEmptyString(), Schema.maxLength(100)),
role: Schema.Literal("admin", "user", "moderator"),
createdAt: Schema.Date,
updatedAt: Schema.Date,
});
// استنتاج نوع TypeScript من المخطط
export type User = typeof UserSchema.Type;
// مخطط لإنشاء مستخدم جديد (بدون id، التواريخ تُولَّد تلقائياً)
export const CreateUserSchema = Schema.Struct({
email: Schema.String.pipe(Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)),
name: Schema.String.pipe(Schema.nonEmptyString(), Schema.maxLength(100)),
role: Schema.optional(Schema.Literal("admin", "user", "moderator"), {
default: () => "user" as const,
}),
});
export type CreateUser = typeof CreateUserSchema.Type;
// مخطط لتحديث مستخدم موجود
export const UpdateUserSchema = Schema.Struct({
email: Schema.optional(
Schema.String.pipe(Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/))
),
name: Schema.optional(
Schema.String.pipe(Schema.nonEmptyString(), Schema.maxLength(100))
),
role: Schema.optional(Schema.Literal("admin", "user", "moderator")),
});
export type UpdateUser = typeof UpdateUserSchema.Type;الخطوة 5: إنشاء الخدمات مع حقن التبعيات
تملك Effect-TS نظام حقن تبعيات مدمجاً باستخدام الخدمات والطبقات. الخدمات تُعرّف القدرات المطلوبة؛ الطبقات توفر التنفيذ.
أنشئ ملف src/services/user-repository.ts:
import { Effect, Context, Layer } from "effect";
import type { User, CreateUser, UpdateUser } from "../models/user.js";
import {
UserNotFoundError,
UserAlreadyExistsError,
DatabaseError,
} from "../errors/user-errors.js";
// 1. تعريف واجهة الخدمة باستخدام Context.Tag
export class UserRepository extends Context.Tag("UserRepository")<
UserRepository,
{
readonly findById: (
id: string
) => Effect.Effect<User, UserNotFoundError | DatabaseError>;
readonly findByEmail: (
email: string
) => Effect.Effect<User | null, DatabaseError>;
readonly create: (
data: CreateUser
) => Effect.Effect<User, UserAlreadyExistsError | DatabaseError>;
readonly update: (
id: string,
data: UpdateUser
) => Effect.Effect<User, UserNotFoundError | DatabaseError>;
readonly delete: (
id: string
) => Effect.Effect<void, UserNotFoundError | DatabaseError>;
readonly list: () => Effect.Effect<ReadonlyArray<User>, DatabaseError>;
}
>() {}ثم أنشئ تنفيذاً في الذاكرة في src/layers/in-memory-user-repository.ts:
import { Effect, Layer, Ref } from "effect";
import { UserRepository } from "../services/user-repository.js";
import type { User, CreateUser, UpdateUser } from "../models/user.js";
import {
UserNotFoundError,
UserAlreadyExistsError,
DatabaseError,
} from "../errors/user-errors.js";
export const InMemoryUserRepository = Layer.effect(
UserRepository,
Effect.gen(function* () {
const store = yield* Ref.make<Map<string, User>>(new Map());
let counter = 0;
return {
findById: (id: string) =>
Effect.gen(function* () {
const users = yield* Ref.get(store);
const user = users.get(id);
if (!user) {
return yield* new UserNotFoundError({ userId: id });
}
return user;
}),
findByEmail: (email: string) =>
Effect.gen(function* () {
const users = yield* Ref.get(store);
for (const user of users.values()) {
if (user.email === email) return user;
}
return null;
}),
create: (data: CreateUser) =>
Effect.gen(function* () {
const existing = yield* Effect.flatMap(
Ref.get(store),
(users) => {
for (const user of users.values()) {
if (user.email === data.email) {
return Effect.succeed(user);
}
}
return Effect.succeed(null);
}
);
if (existing) {
return yield* new UserAlreadyExistsError({
email: data.email,
});
}
counter++;
const now = new Date();
const user: User = {
id: `user_${counter}`,
email: data.email,
name: data.name,
role: data.role ?? "user",
createdAt: now,
updatedAt: now,
};
yield* Ref.update(store, (map) => {
const next = new Map(map);
next.set(user.id, user);
return next;
});
return user;
}),
update: (id: string, data: UpdateUser) =>
Effect.gen(function* () {
const users = yield* Ref.get(store);
const existing = users.get(id);
if (!existing) {
return yield* new UserNotFoundError({ userId: id });
}
const updated: User = {
...existing,
...Object.fromEntries(
Object.entries(data).filter(([, v]) => v !== undefined)
),
updatedAt: new Date(),
};
yield* Ref.update(store, (map) => {
const next = new Map(map);
next.set(id, updated);
return next;
});
return updated;
}),
delete: (id: string) =>
Effect.gen(function* () {
const users = yield* Ref.get(store);
if (!users.has(id)) {
return yield* new UserNotFoundError({ userId: id });
}
yield* Ref.update(store, (map) => {
const next = new Map(map);
next.delete(id);
return next;
});
}),
list: () =>
Effect.gen(function* () {
const users = yield* Ref.get(store);
return Array.from(users.values());
}),
};
})
);الخطوة 6: بناء خدمة المستخدم
الآن أنشئ خدمة عالية المستوى تضيف منطق الأعمال فوق المستودع. أنشئ src/services/user-service.ts:
import { Effect, Context, Layer } from "effect";
import { Schema } from "@effect/schema";
import { UserRepository } from "./user-repository.js";
import {
CreateUserSchema,
UpdateUserSchema,
type User,
} from "../models/user.js";
import {
UserNotFoundError,
UserAlreadyExistsError,
ValidationError,
DatabaseError,
} from "../errors/user-errors.js";
export class UserService extends Context.Tag("UserService")<
UserService,
{
readonly getUser: (
id: string
) => Effect.Effect<User, UserNotFoundError | DatabaseError>;
readonly createUser: (
input: unknown
) => Effect.Effect<
User,
ValidationError | UserAlreadyExistsError | DatabaseError
>;
readonly updateUser: (
id: string,
input: unknown
) => Effect.Effect<
User,
ValidationError | UserNotFoundError | DatabaseError
>;
readonly deleteUser: (
id: string
) => Effect.Effect<void, UserNotFoundError | DatabaseError>;
readonly listUsers: () => Effect.Effect<
ReadonlyArray<User>,
DatabaseError
>;
}
>() {}
export const UserServiceLive = Layer.effect(
UserService,
Effect.gen(function* () {
const repo = yield* UserRepository;
return {
getUser: (id: string) => repo.findById(id),
createUser: (input: unknown) =>
Effect.gen(function* () {
const parsed = yield* Schema.decodeUnknown(
CreateUserSchema
)(input).pipe(
Effect.mapError(
(error) =>
new ValidationError({
field: "input",
message: `Invalid input: ${error.message}`,
})
)
);
const existing = yield* repo.findByEmail(parsed.email);
if (existing) {
return yield* new UserAlreadyExistsError({
email: parsed.email,
});
}
return yield* repo.create(parsed);
}),
updateUser: (id: string, input: unknown) =>
Effect.gen(function* () {
const parsed = yield* Schema.decodeUnknown(
UpdateUserSchema
)(input).pipe(
Effect.mapError(
(error) =>
new ValidationError({
field: "input",
message: `Invalid input: ${error.message}`,
})
)
);
yield* repo.findById(id);
return yield* repo.update(id, parsed);
}),
deleteUser: (id: string) => repo.delete(id),
listUsers: () => repo.list(),
};
})
);الخطوة 7: أنماط معالجة الأخطاء
توفر Effect عدة طرق قوية لمعالجة الأخطاء:
import { Effect, Match } from "effect";
import { UserService } from "./services/user-service.js";
// النمط 1: التقاط أخطاء محددة
const getUserSafe = (id: string) =>
Effect.gen(function* () {
const service = yield* UserService;
return yield* service.getUser(id).pipe(
Effect.catchTag("UserNotFoundError", () =>
Effect.succeed(null)
)
);
});
// النمط 2: مطابقة شاملة للأخطاء
const createUserWithHandling = (input: unknown) =>
Effect.gen(function* () {
const service = yield* UserService;
return yield* service.createUser(input).pipe(
Effect.catchAll((error) =>
Match.value(error).pipe(
Match.tag("ValidationError", (e) =>
Effect.succeed({ status: 400 as const, message: e.message })
),
Match.tag("UserAlreadyExistsError", (e) =>
Effect.succeed({ status: 409 as const, message: `${e.email} موجود` })
),
Match.tag("DatabaseError", (e) =>
Effect.succeed({ status: 500 as const, message: `خطأ قاعدة بيانات: ${e.operation}` })
),
Match.exhaustive
)
)
);
});
// النمط 3: إعادة المحاولة مع تراجع
const getUserWithRetry = (id: string) =>
Effect.gen(function* () {
const service = yield* UserService;
return yield* service.getUser(id).pipe(
Effect.retry({
times: 3,
schedule: "exponential",
while: (error) => error._tag === "DatabaseError",
})
);
});ملاحظة مهمة: على عكس try-catch، أخطاء Effect قابلة للتركيب. عندما تستدعي دالة يمكن أن تفشل بـ A | B، ثم تستدعي أخرى يمكن أن تفشل بـ C، يصبح نوع الخطأ الناتج تلقائياً A | B | C. لا تُبتلع أي أخطاء بصمت.
الخطوة 8: الأنابيب القابلة للتركيب
import { Effect, pipe, Array as Arr } from "effect";
import { UserService } from "./services/user-service.js";
const processUsers = Effect.gen(function* () {
const service = yield* UserService;
const users = yield* service.listUsers();
const processed = pipe(
users,
Arr.filter((user) => user.role === "admin"),
Arr.map((user) => ({
id: user.id,
displayName: user.name.toUpperCase(),
email: user.email,
})),
Arr.sort((a, b) => a.displayName.localeCompare(b.displayName))
);
return processed;
});
// تشغيل عدة effects بالتوازي
const fetchMultipleUsers = (ids: string[]) =>
Effect.gen(function* () {
const service = yield* UserService;
const users = yield* Effect.all(
ids.map((id) => service.getUser(id)),
{ concurrency: 5 }
);
return users;
});الخطوة 9: تجميع كل شيء معاً
أنشئ نقطة الدخول الرئيسية في src/main.ts:
import { Effect, Console, Layer } from "effect";
import { UserService, UserServiceLive } from "./services/user-service.js";
import { InMemoryUserRepository } from "./layers/in-memory-user-repository.js";
const program = Effect.gen(function* () {
const service = yield* UserService;
yield* Console.log("=== Effect-TS User Management ===\n");
const alice = yield* service.createUser({
name: "Alice Johnson",
email: "alice@example.com",
role: "admin",
});
yield* Console.log(`Created: ${alice.name} (${alice.id})`);
const bob = yield* service.createUser({
name: "Bob Smith",
email: "bob@example.com",
});
yield* Console.log(`Created: ${bob.name} (${bob.id})`);
// محاولة إنشاء مكرر
yield* service.createUser({
name: "Alice Clone",
email: "alice@example.com",
}).pipe(
Effect.catchTag("UserAlreadyExistsError", (e) =>
Console.log(`تم منع التكرار: ${e.email} موجود بالفعل`)
)
);
const allUsers = yield* service.listUsers();
yield* Console.log(`\nإجمالي المستخدمين: ${allUsers.length}`);
});
// ربط الطبقات — هنا يحدث حقن التبعيات
const MainLayer = UserServiceLive.pipe(
Layer.provide(InMemoryUserRepository)
);
const runnable = program.pipe(Effect.provide(MainLayer));
Effect.runPromise(runnable).then(() => {
console.log("\n✓ اكتمل البرنامج بنجاح");
});شغّله:
npm startالخطوة 10: الاختبار بتبديل الطبقات
أحد أكبر مزايا نمط الخدمات في Effect هو سهولة الاختبار. يمكنك تبديل أي طبقة في الاختبارات بدون مكتبات محاكاة.
import { Effect, Layer } from "effect";
import { UserService, UserServiceLive } from "./services/user-service.js";
import { UserRepository } from "./services/user-repository.js";
import { DatabaseError } from "./errors/user-errors.js";
// إنشاء مستودع اختبار يحاكي الفشل
const FailingRepository = Layer.succeed(UserRepository, {
findById: (id: string) =>
new DatabaseError({ operation: "findById", cause: "Connection refused" }),
findByEmail: () => Effect.succeed(null),
create: () =>
new DatabaseError({ operation: "create", cause: "Disk full" }),
update: () =>
new DatabaseError({ operation: "update", cause: "Timeout" }),
delete: () =>
new DatabaseError({ operation: "delete", cause: "Permission denied" }),
list: () => Effect.succeed([]),
});
const testDatabaseFailure = Effect.gen(function* () {
const service = yield* UserService;
const result = yield* service.getUser("user_1").pipe(
Effect.matchEffect({
onSuccess: () => Effect.succeed("FAIL: should have errored"),
onFailure: (error) => {
if (error._tag === "DatabaseError") {
return Effect.succeed(`PASS: Got DatabaseError for ${error.operation}`);
}
return Effect.succeed(`FAIL: Wrong error type: ${error._tag}`);
},
})
);
console.log(result);
});
const TestLayer = UserServiceLive.pipe(Layer.provide(FailingRepository));
Effect.runPromise(
testDatabaseFailure.pipe(Effect.provide(TestLayer))
);لا حاجة لمكتبة محاكاة. لأن التبعيات مُعرَّفة كواجهات ومُقدَّمة عبر الطبقات، يمكنك إنشاء أي تنفيذ اختبار — فاشل أو بطيء أو مُسجِّل أو مثالي — وتبديله. هذا حقن تبعيات حقيقي، وليس ترقيعاً.
الخطوة 11: أنماط متقدمة
المُهَل الزمنية والمقاطعة
import { Effect, Duration } from "effect";
const getUserWithTimeout = (id: string) =>
Effect.gen(function* () {
const service = yield* UserService;
return yield* service.getUser(id);
}).pipe(
Effect.timeout(Duration.seconds(5))
);إدارة الموارد
import { Effect } from "effect";
const withDatabaseConnection = Effect.acquireRelease(
Effect.sync(() => {
console.log("Opening database connection");
return { connectionId: Math.random().toString(36) };
}),
(connection) =>
Effect.sync(() => {
console.log(`Closing connection ${connection.connectionId}`);
})
);
const queryWithConnection = Effect.scoped(
Effect.gen(function* () {
const conn = yield* withDatabaseConnection;
console.log(`Using connection: ${conn.connectionId}`);
return "query result";
})
);استكشاف الأخطاء وإصلاحها
مشاكل شائعة
"Cannot find module 'effect'"
تأكد من تثبيت الحزمة: npm install effect.
"Type 'X' is not assignable to type 'Effect'"
على الأرجح نسيت yield* لـ effect داخل المولّد:
// خطأ
const user = service.getUser(id);
// صحيح
const user = yield* service.getUser(id);"Service not found: UserRepository"
نسيت تقديم الطبقة. تأكد من أن سلسلة Layer.provide() تتضمن جميع الخدمات المطلوبة.
الخطوات التالية
- @effect/platform — خادم HTTP ونظام الملفات وخدمات الطرفية مع Effect
- @effect/sql — استعلامات SQL مُنمَّطة باستخدام نمط خدمات Effect
- Effect Schema — الغوص العميق في تركيب المخططات والتحويلات
- Streams — معالجة البيانات اللانهائية مع نوع
Streamفي Effect
الخلاصة
تغيّر Effect-TS بشكل جذري طريقة كتابة TypeScript. بدلاً من الأمل في معالجة الأخطاء بشكل صحيح في وقت التشغيل، تحصل على ضمانات وقت التجميع بأن كل وضع فشل محسوب.
النقاط الرئيسية:
- الأخطاء المُنمَّطة تعني عدم التقاط
unknown— المترجم يتتبع كل فشل - الخدمات والطبقات تمنحك حقن تبعيات حقيقي بدون تكلفة وقت تشغيل
- المولّدات تجعل كود Effect يبدو ويعمل مثل TypeScript العادي
- الأنابيب القابلة للتركيب تستبدل سلاسل try-catch الهشة بتدفقات أنيقة
- الاختبار سهل — فقط بدّل الطبقات، بدون مكتبات محاكاة
ابدأ صغيراً: اختر خدمة واحدة في قاعدة الكود وحوّلها إلى Effect. بمجرد أن ترى أمان الأنواع في العمل، لن ترغب في العودة إلى try-catch.
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

بناء وكيل ذكاء اصطناعي مستقل باستخدام Agentic RAG و Next.js
تعلم كيف تبني وكيل ذكاء اصطناعي يقرر بشكل مستقل متى وكيف يسترجع المعلومات من قواعد البيانات المتجهية. دليل عملي شامل باستخدام Vercel AI SDK و Next.js مع أمثلة قابلة للتنفيذ.

بناء وكلاء الذكاء الاصطناعي من الصفر باستخدام TypeScript: إتقان نمط ReAct مع Vercel AI SDK
تعلّم كيفية بناء وكلاء الذكاء الاصطناعي من الأساس باستخدام TypeScript. يغطي هذا الدليل التعليمي نمط ReAct، واستدعاء الأدوات، والاستدلال متعدد الخطوات، وحلقات الوكلاء الجاهزة للإنتاج مع Vercel AI SDK.

Better Auth مع Next.js 15: الدليل الشامل للمصادقة في 2026
تعلم كيفية تنفيذ نظام مصادقة متكامل في Next.js 15 باستخدام Better Auth. يغطي هذا الدليل تسجيل الدخول بالبريد الإلكتروني وOAuth والجلسات وحماية المسارات والتحكم بالأدوار.