@x-app/nuxt-api
v0.1.1
Published
Configurable API composable for Nuxt 3 and Nuxt 4
Downloads
392
Maintainers
Readme
@x-app/nuxt-api
وحدة Nuxt توفر composable موحدًا للتعامل مع API عبر $fetch. تدعم GET وPOST وPUT وPATCH وDELETE، ورؤوس الطلبات والمصادقة، ومعالجة النجاح والفشل، ومنع تكرار الطلبات وتنظيمها في طابور.
التثبيت والإعداد
ثبّت الحزمة:
npm install @x-app/nuxt-apiأضفها إلى modules في nuxt.config.ts واضبط رابط API في runtimeConfig.public.apiBaseUrl:
export default defineNuxtConfig({
modules: ["@x-app/nuxt-api"],
runtimeConfig: {
public: {
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL,
},
},
});مثلًا، عيّن متغير البيئة:
NUXT_PUBLIC_API_BASE_URL=https://api.example.comإذا لم يُضبط apiBaseUrl، يرمي الطلب خطأً برسالة لم يتم ضبط رابط خدمة API.
يمكن تحديد عنوان افتراضي لمجموعة طلبات عند إنشاء useApi عبر baseUrl، أو تحديد عنوان مختلف لطلب بعينه عبر opts.baseUrl. يفيد ذلك عند الاتصال بخدمات مستقلة:
const api = useApi();
await api.post("/auth/login", { username, password }, {
baseUrl: useRuntimeConfig().public.xAppAuthApiBaseUrl,
});تكون الأولوية لـopts.baseUrl على العنوان الافتراضي في useApi({ baseUrl })، ثم يُستخدم runtimeConfig.public.apiBaseUrl عند عدم تحديد أي منهما. ويظل الاسم القديم AuthBaseUrl مقبولًا للتوافق.
يسجل الموديول useApi() للاستيراد التلقائي. يجب استدعاؤه داخل سياق Nuxt مثل <script setup> أو composable أو plugin. ويمكن استيراده يدويًا:
import { useApi } from "@x-app/nuxt-api/composables/useApi";وضع الإعدادات الافتراضية مركزيًا
ضع عنوان الخدمة مرة واحدة في nuxt.config.ts كما في الإعداد أعلاه. ولتوحيد خيارات الطلب والـ hooks في التطبيق، أنشئ wrapper محليًا في مجلد composables في Nuxt 3 أو app/composables في Nuxt 4:
export function useAppApi() {
return useApi({
dedupe: true,
queue: false,
headers: {
"X-Application": "web",
"Accept-Language": "ar",
},
onFailure: ({ error, status, url, method }) => {
console.error("API request failed", { method, url, status, error });
},
});
}استخدم wrapper التطبيق بدل useApi() مباشرةً، فتُطبق هذه الإعدادات افتراضيًا على دواله:
const api = useAppApi();
const users = await api.get<User[]>("/users");يمكن تجاوز dedupe أو queue في طلب بعينه عبر opts؛ مثلًا لتعطيل منع التكرار لهذا الاستدعاء فقط:
await useAppApi().get<User[]>("/users/latest", {}, { dedupe: false });الرؤوس الممررة إلى useApi({ headers }) تُضاف افتراضيًا لكل طلب. إذا مررت الرؤوس نفسها في opts.headers لطلب محدد، تتقدم قيم الطلب الفردي، بينما تبقى بقية الرؤوس الافتراضية. ترويسة Authorization الصريحة من الرؤوس الافتراضية أو الفردية تتقدم على الرمز التلقائي من cookie.
بهذا الأسلوب تبقى قيمة عنوان الخدمة في runtimeConfig، بينما تحفظ الرؤوس والـ callbacks داخل composable التطبيق لأن hooks دوال JavaScript وليست قيم إعدادات قابلة للتسلسل.
أول طلب
كل دالة طلب تقبل النوع المتوقع للبيانات كـ generic، وتعيد Promise يحتوي بيانات الاستجابة:
<script setup lang="ts">
interface User {
id: number;
name: string;
email: string;
}
const api = useApi();
const users = await api.get<User[]>("/users");
</script>يُضاف apiBaseUrl تلقائيًا إلى بداية المسار. يمكن كتابة المسار بشرطة مائلة في أوله أو دونها:
await api.get<User[]>("users");
await api.get<User[]>("/users");كلا المثالين يطلبان https://api.example.com/users إذا كان هذا هو apiBaseUrl.
دوال HTTP والمعاملات
GET
المعامل الثاني في get يتحول إلى query string. القيم null وundefined تُهمل، والقيمة المصفوفية تتحول إلى عدة قيم بالاسم نفسه:
const result = await api.get<User[]>("/users", {
page: 2,
active: true,
role: ["admin", "editor"],
ignored: null,
});
// GET /users?page=2&active=true&role=admin&role=editorPOST وPUT وPATCH وDELETE
المعامل الثاني هو جسم الطلب. يرسل useApi الجسم كـ JSON ويضيف Content-Type: application/json تلقائيًا:
const created = await api.post<User>("/users", {
name: "Sara",
email: "[email protected]",
});
const replaced = await api.put<User>(`/users/${created.id}`, {
name: "Sara Ali",
email: "[email protected]",
});
const updated = await api.patch<User>(`/users/${created.id}`, {
name: "Sara Ahmed",
});
await api.delete<void>(`/users/${created.id}`);إذا احتاج endpoint إلى بادئة إضافية، مرّر url_layer ضمن خيارات الطلب:
await api.get("/users", {}, { url_layer: "/api/v1/" });
// https://api.example.com/api/v1/usersتُزال الشرطات الزائدة من أطراف url_layer عند بناء الرابط.
خيارات الطلب
المعامل الأخير في كل دالة هو opts. يقبل خيارات $fetch المدعومة من Nuxt/ofetch، بالإضافة إلى خيارات useApi الخاصة:
const users = await api.get<User[]>("/users", { page: 1 }, {
timeout: 5_000,
headers: { "X-Client": "web" },
});
const response = await api.post<User>("/users", { name: "Sara" }, {
retry: 1,
timeout: 10_000,
});من الخيارات الخاصة:
url_layer: بادئة تضاف إلى مسار الطلب.baseUrl: عنوان الأساس لهذا الطلب، وله الأولوية على العنوان الافتراضي لـuseApiوعلىruntimeConfig.public.apiBaseUrl.error_message: افتراضيًاtrue. عندtrueتُرمى رسالة مختارة من استجابة API أوerrorFallback. عندfalseيُعاد رمي خطأ$fetchالأصلي.errorFallback: رسالة بديلة عند تعذّر استخراج رسالة مفيدة من استجابة الخطأ. يحاولuseApiقراءةmessageأوerrorأو أول عنصر منerrors. الافتراضيفشل تنفيذ الطلب.success_message: افتراضيًاfalse. عندtrueتُستخرجmessageأوerrorمن بيانات الاستجابة وتُطبع عبرconsole.infoإذا وُجدت.serviceوendpoint: قيم وصفية تُمرر إلىonFailureلمساعدتك في تصنيف الخطأ.dedupeوqueue: تفعيل منع التكرار أو وضع الطلب في طابور؛ تفاصيلهما في القسم التالي.
المصادقة والرؤوس
يقرأ useApi الرمز من cookie باسم access_token. عند وجود قيمة، يضيف:
Authorization: Bearer <token>تُرسل هذه الترويسة تلقائيًا في الطلبات. إذا مررت Authorization بنفسك، تتقدم القيمة اليدوية على الرمز الموجود في cookie. أسماء الرؤوس غير حساسة لحالة الأحرف.
await api.get<User>("/profile", {}, {
headers: {
"X-Client": "web",
Authorization: "Bearer custom-token",
},
});يمكن استخدام headers عادية أو Headers أو أي صيغة يقبلها $fetch. وتبقى الرؤوس الافتراضية Accept: application/json ونوع محتوى JSON للطلبات غير GET ما لم تستبدلها بترويسة مخصصة.
يمكن تعيين headers مشتركة لكل الدوال عند إنشاء useApi، كما في مثال useAppApi أعلاه. headers الطلب الفردي تدمج فوقها؛ فإذا أعاد الطلب ترويسة بالاسم نفسه، تُستخدم قيمة الطلب الفردي.
Hooks للنجاح والفشل
يمكن تمرير onSuccess وonFailure عند إنشاء useApi. يعمل كل منهما مع جميع دوال HTTP التابعة لذلك الاستدعاء:
const api = useApi({
onSuccess: ({ data, status, url, method }) => {
console.info(`${method} ${url} completed with ${status}`, data);
},
onFailure: ({ error, status, data, url, method, service, endpoint }) => {
console.error(`${service}/${endpoint}`, method, url, status, data, error);
},
});onSuccess يستقبل كائنًا يحتوي data وurl وmethod وstatus عند توفره. يُستدعى بعد نجاح $fetch ورسالة success_message. إذا رمى خطأً، يصل ذلك الخطأ إلى مستدعي الطلب، ولا يُستدعى onFailure بسببه.
onFailure يستقبل error الأصلي، وurl، وmethod، وstatus وبيانات الاستجابة عند توفرهما، إضافةً إلى service وendpoint. يُستدعى عند أخطاء HTTP أو الشبكة. إذا رمى hook نفسه خطأً، يُتجاهل ذلك الخطأ حتى لا يحجب خطأ الطلب الأصلي.
يمكن أن يكون كلا الـ hookين غير متزامن:
const api = useApi({
onFailure: async ({ status }) => {
if (status === 401) {
await navigateTo("/login");
}
},
});لا يوجد logger خاص بالتطبيق داخل الحزمة؛ استخدم onFailure لتسجيل الخطأ في logger مشروعك أو لتنفيذ معالجة عامة.
منع تكرار الطلبات وتنظيمها في طابور
الخياران dedupe وqueue معطلان افتراضيًا. يمكن تفعيلهما افتراضيًا عند إنشاء useApi، ثم تجاوزهما لكل طلب عبر opts:
const api = useApi({ dedupe: true, queue: true });
// يعيد استخدام نتيجة الطلب المطابق إذا كان ما يزال جاريًا.
const usersRequest = api.get<User[]>("/users", { page: 1 });
// يتجاوز الإعداد العام لهذا الطلب.
const health = await api.get<{ ok: boolean }>("/health", {}, {
dedupe: false,
queue: false,
});dedupe
إذا كان dedupe: true، لا يرسل useApi طلب شبكة آخر عند وجود طلب مطابق ما يزال جاريًا أو منتظرًا؛ بل يشارك مستدعوه النتيجة أو الخطأ نفسه. تُقارن طريقة HTTP والرابط النهائي والجسم والرؤوس وخيارات $fetch. لا تدخل callbacks ولا signal في مفتاح المطابقة. بعد اكتمال الطلب، تُزال حالته؛ فالطلبات اللاحقة لا تشارك نتيجة قديمة.
عند مشاركة طلب مكرر، تُنفذ hooks ورسالة success_message مرة واحدة وفق خيارات الاستدعاء الذي بدأ طلب الشبكة؛ أما الاستدعاءات التي انضمت إليه فتستلم النتيجة أو الخطأ نفسه.
queue
إذا كان queue: true، تُنفذ طلبات endpoint نفسه بالتتابع حسب ترتيب وصولها. يجمع الطابور الطلبات إلى المسار نفسه بعد إضافة url_layer، ويتجاهل query string عند تحديد المسار. يمكن أن تختلف الطرق والمعاملات والأجسام، لكنها تنتظر حتى يكتمل الطلب السابق. يبدأ الطلب التالي سواء نجح السابق أو فشل.
إذا فُعّل الخياران معًا، تُشارك الطلبات المطابقة النتيجة أولًا، ثم تنتظر الطلبات المختلفة للمسار نفسه دورها في الطابور. حالة الطوابير ومنع التكرار مشتركة بين استدعاءات useApi داخل تطبيق Nuxt ومعزولة بين سياقات SSR.
معالجة الأخطاء في الاستدعاء
يمكن أيضًا معالجة الخطأ عند موضع الاستدعاء باستخدام try/catch:
try {
const user = await api.get<User>("/users/42");
console.log(user.name);
} catch (error) {
// افتراضيًا يكون error Error برسالة API أو errorFallback.
console.error(error);
}استخدم error_message: false إذا كنت تحتاج إلى كائن خطأ $fetch الأصلي بدل رسالة Error مبسطة:
try {
await api.get("/users/42", {}, { error_message: false });
} catch (error) {
// افحص تفاصيل الخطأ الأصلي هنا.
}البناء والنشر
npm install
npm run build
npm pack --dry-run
npm publishينفذ prepack البناء تلقائيًا قبل إنشاء حزمة npm.
