api-core-lib 12.0.6 → 12.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +9 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +5 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +0 -10
- package/dist/server.d.ts +0 -10
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/processor.ts","../src/core/utils.ts","../src/core/buildDynamicUrl.ts","../src/services/crud.ts","../src/core/globalStateManager.ts","../src/server.ts"],"sourcesContent":["import axios, { AxiosError, AxiosResponse } from 'axios';\r\nimport { ApiError, StandardResponse, ValidationError } from '../types';\r\n// [مهم] استيراد دوال الحماية الجديدة\r\nimport { isServerError, isNetworkError, isAxiosResponse } from './utils';\r\n\r\n/**\r\n * [النسخة النهائية والمضمونة]\r\n * تستخدم دوال حماية النوع المخصصة للقضاء على أخطاء 'never' بشكل نهائي.\r\n */\r\nexport const processResponse = <T>(\r\n responseOrError: AxiosResponse<any> | AxiosError,\r\n): StandardResponse<T> => {\r\n \r\n // ===================================================================================\r\n // #region المسار 1: معالجة النجاح\r\n // ===================================================================================\r\n if (isAxiosResponse(responseOrError)) {\r\n const response = responseOrError;\r\n const rawData = response.data;\r\n const isStandardApiResponse = rawData && typeof rawData.success === 'boolean' && rawData.data !== undefined;\r\n \r\n return {\r\n data: isStandardApiResponse ? rawData.data : rawData,\r\n links: isStandardApiResponse ? rawData.links : undefined,\r\n meta: isStandardApiResponse ? rawData.meta : undefined,\r\n rawResponse: rawData,\r\n loading: false, success: true, error: null,\r\n message: isStandardApiResponse ? rawData.message : 'Request successful.',\r\n validationErrors: [],\r\n };\r\n }\r\n\r\n // ===================================================================================\r\n // #region المسار 2: معالجة الأخطاء\r\n // ===================================================================================\r\n\r\n // الحالة 2.1: خطأ من الخادم (4xx/5xx)\r\n // داخل هذا البلوك، TypeScript متأكد 100% أن `responseOrError.response` موجود.\r\n if (isServerError(responseOrError)) {\r\n const error = responseOrError; // الآن `error` من النوع الصحيح\r\n type ApiErrorResponse = { message?: string; code?: string; errors?: ValidationError[] };\r\n const responseData = error.response.data as ApiErrorResponse | undefined;\r\n const status = error.response.status;\r\n let defaultMessage = 'An unexpected server error occurred.';\r\n\r\n switch (status) {\r\n case 400: defaultMessage = 'Bad Request'; break;\r\n case 401: defaultMessage = 'Unauthorized'; break;\r\n case 403: defaultMessage = 'Forbidden'; break;\r\n case 404: defaultMessage = 'Not Found'; break;\r\n case 422: defaultMessage = 'Validation Failed'; break;\r\n case 500: defaultMessage = 'Internal Server Error'; break;\r\n case 503: defaultMessage = 'Service Unavailable'; break;\r\n default: defaultMessage = `Server Error (${status})`;\r\n }\r\n\r\n const finalApiError: ApiError = {\r\n message: responseData?.message || defaultMessage,\r\n status: status,\r\n code: responseData?.code || error.code,\r\n errors: responseData?.errors || [],\r\n };\r\n \r\n return {\r\n data: null, rawResponse: responseData,\r\n error: finalApiError,\r\n validationErrors: finalApiError.errors,\r\n success: false, loading: false, message: finalApiError.message,\r\n };\r\n }\r\n\r\n // الحالة 2.2: خطأ في الشبكة\r\n // داخل هذا البلوك، TypeScript متأكد 100% أن `responseOrError.request` موجود.\r\n if (isNetworkError(responseOrError)) {\r\n const error = responseOrError;\r\n return {\r\n data: null, rawResponse: error.request,\r\n error: { message: 'Network Error: Unable to connect.', status: 0, code: error.code },\r\n success: false, loading: false, message: 'Network Error.',\r\n };\r\n }\r\n \r\n // الحالة 2.3: تم إلغاء الطلب\r\n if (axios.isCancel(responseOrError)) {\r\n return {\r\n data: null, rawResponse: null,\r\n error: { message: 'Request Canceled.', status: 499 },\r\n success: false, loading: false, message: 'Request Canceled.',\r\n };\r\n }\r\n\r\n // ===================================================================================\r\n // #region المسار الأخير: الحالة الاحتياطية\r\n // ===================================================================================\r\n \r\n // لأي خطأ آخر لم تتم معالجته (مثل أخطاء الإعداد)\r\n return {\r\n data: null, rawResponse: responseOrError,\r\n error: { message: 'An unknown error occurred.', status: -1 },\r\n success: false, loading: false, message: 'An unknown error occurred.',\r\n };\r\n};\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n// // file: src/core/processor.ts (مُحدَّث)\r\n\r\n// import { AxiosResponse } from 'axios';\r\n// import { ApiError, StandardResponse } from '../types';\r\n// import { isApiError, isAxiosResponse } from './utils';\r\n\r\n// export const processResponse = <T>(\r\n// responseOrError: AxiosResponse<any> | ApiError,\r\n// ): StandardResponse<T> => {\r\n// console.log(\"responseOrError [lib]\" , responseOrError)\r\n// console.log(\"responseOrError.data [lib]\" , responseOrError?.status)\r\n\r\n// if (isApiError(responseOrError)) {\r\n// return {\r\n// data: null,\r\n// rawResponse: undefined,\r\n// error: responseOrError,\r\n// validationErrors: responseOrError.errors || [],\r\n// success: false,\r\n// loading: false,\r\n// message: responseOrError.message,\r\n// };\r\n// }\r\n \r\n// if (isAxiosResponse(responseOrError)) {\r\n// const rawData = responseOrError.data;\r\n// const isWrappedResponse = \r\n// rawData && \r\n// typeof rawData.success === 'boolean' && \r\n// rawData.data !== undefined;\r\n \r\n// const finalData: T = isWrappedResponse ? rawData.data : rawData;\r\n// const message = isWrappedResponse ? rawData.message : 'Request successful.';\r\n\r\n// return {\r\n// data: finalData,\r\n// rawResponse: rawData,\r\n// loading: false,\r\n// success: true,\r\n// error: null,\r\n// message: message,\r\n// validationErrors: [],\r\n// };\r\n// }\r\n\r\n// return {\r\n// data: null,\r\n// rawResponse: undefined,\r\n// error: { message: 'An unknown error occurred during response processing.', status: 500 },\r\n// success: false,\r\n// loading: false,\r\n// message: 'An unknown error occurred.',\r\n// validationErrors: []\r\n// };\r\n// };","import axios, { AxiosError, AxiosResponse, CancelTokenSource } from 'axios';\r\nimport { ApiError, QueryOptions, StandardResponse } from '../types';\r\n\r\n// ===================================================================================\r\n// #region Type Guards (التحقق من الأنواع)\r\n// ===================================================================================\r\n\r\n/**\r\n * يتحقق مما إذا كان الكائن هو خطأ مخصص من نوع ApiError.\r\n */\r\nexport function isApiError(obj: any): obj is ApiError {\r\n return obj && typeof obj.status === 'number' && typeof obj.message === 'string' && obj.config === undefined;\r\n}\r\n\r\n\r\n// الدوال القديمة يمكن الإبقاء عليها للتوافق أو استبدالها بالكامل\r\nexport function isAxiosError(obj: any): obj is AxiosError {\r\n return obj && obj.isAxiosError === true;\r\n}\r\n\r\nexport function isAxiosResponse(obj: any): obj is AxiosResponse {\r\n return obj && obj.data !== undefined && obj.status !== undefined && obj.config !== undefined;\r\n}\r\n\r\n\r\n\r\n\r\n// ===================================================================================\r\n// #region URL & Query Handling (معالجة روابط URL والاستعلامات)\r\n// ===================================================================================\r\n\r\n/**\r\n * يبني رابطًا ديناميكيًا عن طريق استبدال المتغيرات المؤقتة مثل {key} بالقيم من كائن.\r\n * @param template - قالب الرابط (مثال: '/users/{userId}/posts').\r\n * @param params - كائن يحتوي على قيم المتغيرات (مثال: { userId: 123 }).\r\n * @returns الرابط النهائي بعد المعالجة.\r\n */\r\nexport function buildDynamicUrl(template: string, params?: Record<string, any>): string {\r\n if (!params) {\r\n return template;\r\n }\r\n return template.replace(/\\{(\\w+)\\}/g, (placeholder, key) => {\r\n return params.hasOwnProperty(key) ? String(params[key]) : placeholder;\r\n });\r\n}\r\n\r\n/**\r\n * [نسخة مطورة] يبني سلسلة استعلام (query string) من كائن الخيارات.\r\n * يدعم الآن الفلاتر المتداخلة (filter[key]=value) والترتيب المتعدد.\r\n * @param options - كائن خيارات الاستعلام (فلترة, ترتيب, ...).\r\n * @returns سلسلة استعلام جاهزة للإضافة للرابط.\r\n */\r\nexport function buildPaginateQuery(options: QueryOptions): string {\r\n const params = new URLSearchParams();\r\n\r\n for (const key in options) {\r\n const value = options[key];\r\n\r\n if (value === null || value === undefined) {\r\n continue; // تجاهل القيم الفارغة\r\n }\r\n\r\n if (key === 'filter' && typeof value === 'object' && !Array.isArray(value)) {\r\n // التعامل مع الفلاتر المتداخلة\r\n for (const filterKey in value) {\r\n const filterValue = value[filterKey];\r\n if (filterValue !== null && filterValue !== undefined) {\r\n params.append(`filter[${filterKey}]`, String(filterValue));\r\n }\r\n }\r\n } else if (key === 'sortBy' && Array.isArray(value)) {\r\n // التعامل مع الترتيب المتعدد\r\n value.forEach(sortItem => {\r\n if (sortItem && sortItem.key && sortItem.direction) {\r\n params.append('sortBy[]', `${sortItem.key}:${sortItem.direction}`);\r\n }\r\n });\r\n } else {\r\n // التعامل مع باقي المعاملات\r\n params.append(key, String(value));\r\n }\r\n }\r\n\r\n return params.toString();\r\n}\r\n\r\n// ===================================================================================\r\n// #region Object & Data Helpers (أدوات مساعدة للكائنات والبيانات)\r\n// ===================================================================================\r\n\r\n/**\r\n * يتحقق مما إذا كانت الاستجابة تحتوي على بيانات قابلة للعرض.\r\n * @param response - كائن StandardResponse.\r\n * @returns `true` إذا كانت البيانات موجودة وليست مصفوفة فارغة.\r\n */\r\nexport function hasData<T>(response: StandardResponse<T> | null | undefined): boolean {\r\n if (!response || !response.success || response.data === null || response.data === undefined) {\r\n return false;\r\n }\r\n if (Array.isArray(response.data) && response.data.length === 0) {\r\n return false;\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * ينشئ نسخة جديدة من كائن مع إزالة كل الخصائص التي قيمتها `null` أو `undefined`.\r\n * @param obj - الكائن المراد تنظيفه.\r\n * @returns نسخة نظيفة من الكائن.\r\n */\r\nexport function cleanObject<T extends Record<string, any>>(obj: T): Partial<T> {\r\n const newObj: Partial<T> = {};\r\n for (const key in obj) {\r\n if (obj[key] !== null && obj[key] !== undefined) {\r\n newObj[key] = obj[key];\r\n }\r\n }\r\n return newObj;\r\n}\r\n\r\n// ===================================================================================\r\n// #region Async Helpers (أدوات مساعدة غير متزامنة)\r\n// ===================================================================================\r\n\r\n/**\r\n * ينشئ دالة تقوم بتأخير تنفيذ الدالة الأصلية حتى انقضاء فترة زمنية معينة\r\n * بعد آخر مرة تم استدعاؤها. مفيدة جدًا لتقليل الطلبات في حقول البحث.\r\n * @param func - الدالة التي سيتم تأخير تنفيذها.\r\n * @param delay - مدة التأخير بالمللي ثانية.\r\n * @returns دالة جديدة قابلة للاستدعاء.\r\n */\r\nexport function debounce<T extends (...args: any[]) => any>(func: T, delay: number): (...args: Parameters<T>) => void {\r\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\r\n return function(...args: Parameters<T>) {\r\n if (timeoutId) {\r\n clearTimeout(timeoutId);\r\n }\r\n timeoutId = setTimeout(() => {\r\n func(...args);\r\n }, delay);\r\n };\r\n}\r\n\r\n/**\r\n * دالة بسيطة لإيقاف التنفيذ لمدة زمنية محددة.\r\n * @param ms - مدة التأخير بالمللي ثانية.\r\n * @returns Promise يكتمل بعد انقضاء المدة.\r\n */\r\nexport function delay(ms: number): Promise<void> {\r\n return new Promise(resolve => setTimeout(resolve, ms));\r\n}\r\n\r\n// ===================================================================================\r\n// #region Request Cancellation (إدارة إلغاء الطلبات)\r\n// ===================================================================================\r\n\r\nconst cancelTokens = new Map<string, CancelTokenSource>();\r\n\r\n/**\r\n * ينشئ cancel token جديد لطلب معين، ويلغي أي طلب قديم بنفس المفتاح.\r\n * يمنع حالات الـ race conditions في المكونات التي ترسل طلبات متتالية.\r\n * @param key - مفتاح فريد لتعريف الطلب (مثال: 'user-search').\r\n * @returns كائن CancelTokenSource من Axios.\r\n */\r\nexport const createCancelToken = (key: string): CancelTokenSource => {\r\n const existingToken = cancelTokens.get(key);\r\n if (existingToken) {\r\n existingToken.cancel('Operation canceled due to a new request.');\r\n }\r\n const source = axios.CancelToken.source();\r\n cancelTokens.set(key, source);\r\n return source;\r\n};\r\n\r\n\r\n\r\n\r\n\r\n// ===================================================================================\r\n// #region Advanced Type Guards (أدوات تحقق متقدمة من الأنواع)\r\n// ===================================================================================\r\n\r\n/**\r\n * [جديد وحاسم] يتحقق مما إذا كان الخطأ هو خطأ من الخادم (يحتوي على `response`).\r\n * الأهم من ذلك، أنه يضيق نوع الخطأ ليضمن وجود `response`.\r\n */\r\nexport function isServerError(error: any): error is AxiosError & { response: AxiosResponse } {\r\n return axios.isAxiosError(error) && error.response !== undefined;\r\n}\r\n\r\n/**\r\n * [جديد وحاسم] يتحقق مما إذا كان الخطأ هو خطأ في الشبكة (لا يحتوي على `response` ولكن يحتوي على `request`).\r\n * يضيق نوع الخطأ ليضمن عدم وجود `response`.\r\n */\r\nexport function isNetworkError(error: any): error is AxiosError & { request: any } {\r\n return axios.isAxiosError(error) && error.response === undefined && error.request !== undefined;\r\n}\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n// /**\r\n// * @file src/core/utils.ts\r\n// * @description\r\n// * يحتوي هذا الملف على مجموعة من الدوال المساعدة المستخدمة في جميع أنحاء المكتبة،\r\n// * مثل بناء سلاسل الاستعلام (query strings) وإدارة إلغاء الطلبات،\r\n// * بالإضافة إلى دوال التحقق من الأنواع (type guards).\r\n// */\r\n\r\n// import axios, { CancelTokenSource } from 'axios';\r\n// import { ApiError, QueryOptions } from '../types';\r\n// import { AxiosResponse } from 'axios';\r\n\r\n// // ===================================\r\n// // إدارة إلغاء الطلبات\r\n// // ===================================\r\n// const cancelTokens = new Map<string, CancelTokenSource>();\r\n\r\n// export const createCancelToken = (key: string): CancelTokenSource => {\r\n// const existingToken = cancelTokens.get(key);\r\n// if (existingToken) {\r\n// existingToken.cancel('Operation canceled due to a new request.');\r\n// cancelTokens.delete(key);\r\n// }\r\n// const source = axios.CancelToken.source();\r\n// cancelTokens.set(key, source);\r\n// return source;\r\n// };\r\n\r\n\r\n\r\n// interface BuildQueryOptions {\r\n// encode?: boolean;\r\n// }\r\n\r\n// /**\r\n// * يبني سلسلة استعلام (query string) من كائن الخيارات.\r\n// * @param options - كائن خيارات الاستعلام (فلترة, ترتيب, ...).\r\n// * @param config - إعدادات إضافية مثل التحكم في التشفير.\r\n// * @returns سلسلة استعلام جاهزة للإضافة للرابط.\r\n// */\r\n// export function buildPaginateQuery(options: QueryOptions, config: BuildQueryOptions = { encode: true }): string {\r\n// // استخدام URLSearchParams يقوم بالتشفير تلقائيًا وهو الأسلوب القياسي والآمن\r\n// if (config.encode) {\r\n// const params = new URLSearchParams();\r\n// Object.entries(options).forEach(([key, value]) => {\r\n// if (value !== undefined && value !== null) {\r\n// // يمكنك إضافة منطق أكثر تعقيدًا هنا للتعامل مع المصفوفات أو الكائنات\r\n// params.append(key, String(value));\r\n// }\r\n// });\r\n// return params.toString();\r\n// }\r\n\r\n// // منطق بديل إذا كان encode = false (لـ APIs التي تتطلب تنسيقًا خاصًا)\r\n// const parts = Object.entries(options)\r\n// .filter(([, value]) => value !== undefined && value !== null)\r\n// .map(([key, value]) => `${key}=${String(value)}`);\r\n \r\n// return parts.join('&');\r\n// }\r\n\r\n// // ===================================\r\n// // التحقق من الأنواع (Type Guards)\r\n// // ===================================\r\n// export function isApiError(obj: any): obj is ApiError {\r\n// console.log(\"obj in IsApiError\" , obj)\r\n// return obj && typeof obj.status === 'number' && typeof obj.message === 'string' && obj.config === undefined;\r\n// }\r\n\r\n// export function isAxiosResponse(obj: any): obj is AxiosResponse {\r\n// return obj && obj.data !== undefined && obj.status !== undefined && obj.config !== undefined;\r\n// }\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n// // import axios, { CancelTokenSource } from 'axios';\r\n// // import { PaginateQueryOptions, ApiError } from '../types';\r\n\r\n// // // === Cancel Token Management ===\r\n// // const cancelTokens = new Map<string, CancelTokenSource>();\r\n\r\n// // export const createCancelToken = (key: string): CancelTokenSource => {\r\n// // const existingToken = cancelTokens.get(key);\r\n// // if (existingToken) {\r\n// // existingToken.cancel('Operation canceled due to new request.');\r\n// // cancelTokens.delete(key);\r\n// // }\r\n// // const source = axios.CancelToken.source();\r\n// // cancelTokens.set(key, source);\r\n// // return source;\r\n// // };\r\n\r\n// // export const cancelRequest = (key: string): void => {\r\n// // const source = cancelTokens.get(key);\r\n// // if (source) {\r\n// // source.cancel('Operation canceled by user.');\r\n// // cancelTokens.delete(key);\r\n// // }\r\n// // };\r\n\r\n// // export const cancelAllRequests = (): void => {\r\n// // cancelTokens.forEach(source => source.cancel('All operations canceled.'));\r\n// // cancelTokens.clear();\r\n// // };\r\n\r\n// // // === Query Builder ===\r\n// // export function buildPaginateQuery(query: PaginateQueryOptions): string {\r\n// // const params = new URLSearchParams();\r\n// // if (!query) return '';\r\n\r\n// // if (query.page) params.append('page', query.page.toString());\r\n// // if (query.limit) params.append('limit', query.limit.toString());\r\n// // if (query.search) params.append('search', query.search);\r\n// // if (query.sortBy) {\r\n// // query.sortBy.forEach(sort => params.append('sortBy', `${sort.key}:${sort.direction}`));\r\n// // }\r\n// // if (query.filter) {\r\n// // Object.entries(query.filter).forEach(([field, value]) => {\r\n// // params.append(`filter.${field}`, value);\r\n// // });\r\n// // }\r\n// // const queryString = params.toString();\r\n// // return queryString ? `?${queryString}` : '';\r\n// // }\r\n\r\n// // // === Type Guards ===\r\n// // export function isApiError(obj: any): obj is ApiError {\r\n// // return obj && typeof obj.status === 'number' && typeof obj.message === 'string' && obj.config === undefined;\r\n// // }\r\n\r\n// // export function isAxiosResponse(obj: any): obj is any {\r\n// // return obj && obj.data !== undefined && obj.status !== undefined && obj.config !== undefined;\r\n// // }","/**\r\n * Replaces path placeholders like {key} with values from a params object.\r\n * @param template - The URL template (e.g., '/users/{userId}/posts').\r\n * @param params - An object with keys matching the placeholders (e.g., { userId: 123 }).\r\n * @returns The final, resolved URL string.\r\n */\r\nexport function buildDynamicUrl(template: string, params?: Record<string, any>): string {\r\n if (!params) {\r\n return template;\r\n }\r\n return template.replace(/\\{(\\w+)\\}/g, (placeholder, key) => {\r\n return params.hasOwnProperty(key) ? String(params[key]) : placeholder;\r\n });\r\n}","// file: src/services/crud.ts\r\n\r\nimport { AxiosInstance, AxiosRequestConfig } from 'axios';\r\nimport { ActionOptions, RequestConfig, StandardResponse } from '../types';\r\nimport { processResponse } from '../core/processor';\r\nimport { buildDynamicUrl } from '@/core/buildDynamicUrl';\r\nimport { ActionConfigModule } from '@/types/apiModule.types';\r\n\r\n/**\r\n * A factory function to create a reusable set of API services for a specific endpoint.\r\n * It provides full CRUD operations plus advanced features like bulk deletion and file uploads,\r\n * with intelligent, dynamic URL building.\r\n */\r\nexport function createApiServices<T>(axiosInstance: AxiosInstance, baseEndpoint: string) {\r\n \r\n /**\r\n * Intelligently resolves the final URL for a request.\r\n * Priority:\r\n * 1. A full endpoint override from `config.endpoint`.\r\n * 2. The base endpoint appended with an ID, if provided.\r\n * 3. The base endpoint alone.\r\n * It also replaces dynamic segments like {id} or {tenantId} in the URL.\r\n */\r\n const resolveUrl = (config: ActionOptions = {}, id?: string | number): string => {\r\n const endpointTemplate = config.endpoint || (id != null ? `${baseEndpoint}/{id}` : baseEndpoint);\r\n \r\n // Create a params object for URL building. We include common ID variations.\r\n const params = id != null ? { id, tenant: id, tenantId: id, recordId: id } : {};\r\n \r\n return buildDynamicUrl(endpointTemplate, params);\r\n };\r\n\r\n /**\r\n * Fetches data. Can fetch a list or a single record based on whether an ID is provided.\r\n * This is the primary flexible read operation.\r\n */\r\n const get = async (id?: string | number, config?: ActionOptions): Promise<StandardResponse<T>> => {\r\n const url = resolveUrl(config, id);\r\n try {\r\n const response = await axiosInstance.get(url, config);\r\n return processResponse<T>(response);\r\n } catch (error) {\r\n return processResponse<T>(error as any);\r\n }\r\n };\r\n \r\n /**\r\n * Fetches a list of records using a query string.\r\n * This is specifically for fetching paginated/filtered lists.\r\n */\r\n const getWithQuery = async (query: string, config?: RequestConfig): Promise<StandardResponse<T>> => {\r\n const url = `${baseEndpoint}?${query}`;\r\n try {\r\n const response = await axiosInstance.get(url, config);\r\n return processResponse<T>(response);\r\n } catch (error) {\r\n return processResponse<T>(error as any);\r\n }\r\n };\r\n\r\n /**\r\n * Creates a new resource.\r\n */\r\n const post = async (data: Partial<T>, config?: ActionOptions): Promise<StandardResponse<T>> => {\r\n const url = resolveUrl(config);\r\n try {\r\n const response = await axiosInstance.post(url, data, config);\r\n console.log(\"[lib] response POST: \" , response)\r\n console.log(\"[lib] response processResponse POST: \" , processResponse<T>(response))\r\n return processResponse<T>(response);\r\n } catch (error) {\r\n console.log(\"[lib] response error POST: \" , processResponse<T>(error as any))\r\n return processResponse<T>(error as any);\r\n }\r\n };\r\n \r\n /**\r\n * Replaces an entire resource.\r\n */\r\n const put = async (id: string | number, data: T, config?: ActionOptions): Promise<StandardResponse<T>> => {\r\n const url = resolveUrl(config, id);\r\n try {\r\n const response = await axiosInstance.put(url, data, config);\r\n return processResponse<T>(response);\r\n } catch (error) {\r\n return processResponse<T>(error as any);\r\n }\r\n };\r\n\r\n /**\r\n * Partially updates a resource.\r\n */\r\n const patch = async (id: string | number, data: Partial<T>, config?: ActionOptions): Promise<StandardResponse<T>> => {\r\n const url = resolveUrl(config, id);\r\n try {\r\n const response = await axiosInstance.patch(url, data, config);\r\n return processResponse<T>(response);\r\n } catch (error) {\r\n return processResponse<T>(error as any);\r\n }\r\n };\r\n\r\n /**\r\n * Deletes a resource.\r\n */\r\n const remove = async (id: string | number, config?: ActionOptions): Promise<StandardResponse<any>> => {\r\n const url = resolveUrl(config, id);\r\n try {\r\n const response = await axiosInstance.delete(url, config);\r\n return processResponse<any>(response);\r\n } catch (error) {\r\n return processResponse<any>(error as any);\r\n }\r\n };\r\n \r\n /**\r\n * Deletes multiple resources in a single request.\r\n */\r\n const bulkDelete = async (ids: Array<string | number>, config?: ActionOptions): Promise<StandardResponse<any>> => {\r\n const url = resolveUrl(config); // Bulk actions typically don't use an ID in the path.\r\n try {\r\n const response = await axiosInstance.delete(url, { data: { ids }, ...config });\r\n return processResponse<any>(response);\r\n } catch (error) {\r\n return processResponse<any>(error as any);\r\n }\r\n };\r\n\r\n /**\r\n * Uploads a file, with optional additional data.\r\n */\r\n const upload = async (file: File, additionalData?: Record<string, any>, config?: ActionOptions): Promise<StandardResponse<any>> => {\r\n const url = resolveUrl(config);\r\n const formData = new FormData();\r\n formData.append('file', file);\r\n if (additionalData) {\r\n Object.keys(additionalData).forEach(key => formData.append(key, String(additionalData[key])));\r\n }\r\n try {\r\n const response = await axiosInstance.post(url, formData, {\r\n ...config, headers: { ...config?.headers, 'Content-Type': 'multipart/form-data' },\r\n });\r\n return processResponse<any>(response);\r\n } catch (error) {\r\n return processResponse<any>(error as any);\r\n }\r\n };\r\n\r\n return { get, getWithQuery, post, put, patch, remove, bulkDelete, upload };\r\n}\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n// نفترض أن هذه الدوال موجودة في ملف مساعد\r\n// import { buildDynamicUrl } from '@/core/buildDynamicUrl';\r\n// import { processResponse } from '../core/processor';\r\n\r\n/**\r\n * [نسخة محدثة ومحسّنة]\r\n * دالة عامة وصريحة لتنفيذ أي طلب API.\r\n * تستقبل وسائط منظمة بدلاً من محاولة تخمينها.\r\n * \r\n * @param axiosInstance - نسخة Axios.\r\n * @param baseEndpoint - نقطة النهاية الأساسية للموديول.\r\n * @param actionConfig - إعدادات الإجراء المحدد.\r\n * @param params - كائن يحتوي على جميع البارامترات اللازمة للطلب.\r\n * @returns Promise<StandardResponse<any>>\r\n */\r\nexport async function callDynamicApi(\r\n axiosInstance: AxiosInstance,\r\n baseEndpoint: string,\r\n actionConfig: ActionConfigModule<any, any>,\r\n params: {\r\n pathParams?: Record<string, string | number>;\r\n body?: any;\r\n config?: AxiosRequestConfig;\r\n }\r\n): Promise<StandardResponse<any>> {\r\n \r\n // 1. استخراج البارامترات بشكل صريح وواضح\r\n const { pathParams, body, config } = params;\r\n\r\n // 2. بناء الـ URL الديناميكي\r\n const urlTemplate = `${baseEndpoint}${actionConfig.path}`;\r\n // افترض وجود هذه الدوال أو قم بإنشائها\r\n const finalUrl = buildDynamicUrl(urlTemplate, pathParams || {}); \r\n\r\n try {\r\n const { method } = actionConfig;\r\n let response;\r\n\r\n // 3. تنفيذ الطلب بناءً على نوع الـ Method\r\n // أصبح المنطق الآن أكثر نظافة ووضوحًا\r\n if (method === 'POST' || method === 'PUT' || method === 'PATCH') {\r\n response = await axiosInstance[method.toLowerCase() as 'post'](finalUrl, body, config);\r\n } else if (method === 'DELETE') {\r\n // DELETE قد يحتوي على body في بعض الحالات (مثل bulk)\r\n response = await axiosInstance.delete(finalUrl, { data: body, ...config });\r\n } else { // GET\r\n // في GET، الجسم (body) يمثل معاملات الاستعلام (query params)\r\n response = await axiosInstance.get(finalUrl, { params: body, ...config });\r\n }\r\n \r\n return processResponse(response);\r\n } catch (error) {\r\n // معالجة الأخطاء تبقى كما هي\r\n return processResponse(error as any);\r\n }\r\n}\r\n\r\n\r\n\r\n\r\n// import { AxiosInstance } from 'axios';\r\n// import { ActionOptions, RequestConfig, StandardResponse } from '../types';\r\n// import { processResponse } from '../core/processor';\r\n\r\n// type CrudRequestConfig = RequestConfig & ActionOptions;\r\n\r\n// /**\r\n// * دالة مصنع (Factory Function) لإنشاء مجموعة خدمات API قابلة لإعادة الاستخدام لنقطة نهاية (endpoint) محددة.\r\n// * توفر عمليات CRUD كاملة بالإضافة إلى ميزات متقدمة مثل الحذف الجماعي ورفع الملفات.\r\n// */\r\n// export function createApiServices<T>(axiosInstance: AxiosInstance, endpoint: string) {\r\n \r\n// // --- Read Operations ---\r\n// const get = async (id?: string, config?: RequestConfig): Promise<StandardResponse<T | T[]>> => {\r\n// const url = id ? `${endpoint}/${id}` : endpoint;\r\n// try {\r\n// const response = await axiosInstance.get(url, config);\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n \r\n// const getWithQuery = async (query: string, config?: RequestConfig): Promise<StandardResponse<T[]>> => {\r\n// try {\r\n// const response = await axiosInstance.get(`${endpoint}${query}`, config);\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n\r\n// // --- Write Operations ---\r\n// const post = async (data: Partial<T>, config?: CrudRequestConfig): Promise<StandardResponse<T>> => {\r\n// const finalUrl = config?.endpoint || endpoint;\r\n// try {\r\n// const response = await axiosInstance.post(finalUrl, data, config);\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n \r\n// /**\r\n// * ✅ NEW: إضافة دالة PUT\r\n// * تستخدم لاستبدال مورد بالكامل.\r\n// * لاحظ أن `data` من النوع `T` وليس `Partial<T>` لأن PUT يتوقع المورد كاملاً.\r\n// */\r\n// const put = async (id: string, data: T, config?: CrudRequestConfig): Promise<StandardResponse<T>> => {\r\n// const finalUrl = config?.endpoint || `${endpoint}/${id}`;\r\n// try {\r\n// const response = await axiosInstance.put(finalUrl, data, config);\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n\r\n// const patch = async (id: string, data: Partial<T>, config?: CrudRequestConfig): Promise<StandardResponse<T>> => {\r\n// const finalUrl = config?.endpoint || `${endpoint}/${id}`;\r\n// try {\r\n// const response = await axiosInstance.patch(finalUrl, data, config);\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n\r\n// const remove = async (id: string, config?: CrudRequestConfig): Promise<StandardResponse<any>> => {\r\n// const finalUrl = config?.endpoint || `${endpoint}/${id}`;\r\n// try {\r\n// const response = await axiosInstance.delete(finalUrl, config);\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n \r\n// // --- Advanced Operations ---\r\n\r\n// /**\r\n// * ✅ NEW: إضافة دالة للحذف الجماعي (Bulk Delete)\r\n// * مفيدة جدًا لحذف عدة عناصر في طلب واحد لتحسين الأداء.\r\n// * ترسل مصفوفة من الـ IDs في جسم الطلب (request body).\r\n// */\r\n// const bulkDelete = async (ids: string[], config?: CrudRequestConfig): Promise<StandardResponse<any>> => {\r\n// const finalUrl = config?.endpoint || `${endpoint}`; // يمكن تخصيص المسار\r\n// try {\r\n// // Axios's delete method can send a body via the config object\r\n// const response = await axiosInstance.delete(finalUrl, { data: { ids }, ...config });\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n\r\n// /**\r\n// * ✅ NEW: إضافة دالة لرفع الملفات (File Upload)\r\n// * تتعامل مع `FormData` لرفع الملفات، وهي ميزة أساسية في معظم التطبيقات.\r\n// * يمكن إرسال بيانات إضافية مع الملف.\r\n// */\r\n// const upload = async (file: File, additionalData?: Record<string, any>, config?: CrudRequestConfig): Promise<StandardResponse<any>> => {\r\n// const finalUrl = config?.endpoint || `${endpoint}`;\r\n \r\n// const formData = new FormData();\r\n// formData.append('file', file); // 'file' هو اسم الحقل الشائع، يمكن تغييره\r\n \r\n// if (additionalData) {\r\n// Object.keys(additionalData).forEach(key => {\r\n// formData.append(key, additionalData[key]);\r\n// });\r\n// }\r\n\r\n// try {\r\n// const response = await axiosInstance.post(finalUrl, formData, {\r\n// ...config,\r\n// headers: {\r\n// ...config?.headers,\r\n// 'Content-Type': 'multipart/form-data',\r\n// },\r\n// });\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n\r\n// return { get, getWithQuery, post, put, patch, remove, bulkDelete, upload };\r\n// }\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n","// file: src/core/globalStateManager.ts\r\n\r\nimport { ActionStateModule } from \"@/types\";\r\n\r\n\r\ntype CacheItem = {\r\n state: ActionStateModule<any>;\r\n listeners: Set<() => void>;\r\n};\r\n\r\nconst createInitialState = (): ActionStateModule<any> => ({\r\n data: null,\r\n links: undefined,\r\n meta: undefined,\r\n error: null,\r\n loading: false,\r\n success: false,\r\n called: false,\r\n isStale: false,\r\n message: undefined,\r\n validationErrors: [],\r\n rawResponse: null,\r\n});\r\n\r\nclass GlobalStateManager {\r\n private store = new Map<string, CacheItem>();\r\n\r\n /**\r\n * يحصل على لقطة (snapshot) للحالة الحالية لمفتاح معين.\r\n */\r\n public getSnapshot<T>(key: string): ActionStateModule<T> {\r\n return this.store.get(key)?.state as ActionStateModule<T> ?? createInitialState();\r\n }\r\n\r\n /**\r\n * يسجل دالة callback للاستماع إلى التغييرات على مفتاح معين.\r\n * @returns دالة لإلغاء الاشتراك.\r\n */\r\n public subscribe(key: string, callback: () => void): () => void {\r\n if (!this.store.has(key)) {\r\n this.store.set(key, { state: createInitialState(), listeners: new Set() });\r\n }\r\n const item = this.store.get(key)!;\r\n item.listeners.add(callback);\r\n\r\n return () => {\r\n item.listeners.delete(callback);\r\n };\r\n }\r\n\r\n /**\r\n * يحدّث الحالة لمفتاح معين ويقوم بإعلام جميع المشتركين.\r\n */\r\n public setState<T>(key: string, updater: (prevState: ActionStateModule<T>) => ActionStateModule<T>): void {\r\n const currentState = this.getSnapshot<T>(key);\r\n const newState = updater(currentState);\r\n\r\n // نضمن وجود العنصر قبل التحديث\r\n if (!this.store.has(key)) {\r\n this.store.set(key, { state: newState, listeners: new Set() });\r\n } else {\r\n this.store.get(key)!.state = newState;\r\n }\r\n\r\n // إعلام المشتركين\r\n this.store.get(key)!.listeners.forEach(listener => listener());\r\n }\r\n\r\n /**\r\n * يجعل البيانات المرتبطة بمفتاح معين \"قديمة\" (stale).\r\n */\r\n public invalidate(key: string): void {\r\n const state = this.getSnapshot(key);\r\n // يبطل فقط إذا كانت هناك بيانات بالفعل\r\n if (state.called) {\r\n this.setState(key, (prev) => ({ ...prev, isStale: true }));\r\n }\r\n }\r\n\r\n\r\n /**\r\n * [نسخة محدثة وأكثر قوة]\r\n * يجعل كل البيانات التي تبدأ بمفتاح معين \"قديمة\" (stale).\r\n * @example invalidateByPrefix('myModule/list::') سيبطل كل صفحات القائمة.\r\n */\r\n public invalidateByPrefix(prefix: string): void {\r\n this.store.forEach((value, key) => {\r\n if (key.startsWith(prefix)) {\r\n const state = this.getSnapshot(key);\r\n // يبطل فقط إذا كانت هناك بيانات بالفعل\r\n if (state.called) {\r\n this.setState(key, (prev) => ({ ...prev, isStale: true }));\r\n }\r\n }\r\n });\r\n }\r\n\r\n\r\n /**\r\n * Serializes the current state of the query store into a JSON string.\r\n * This is used on the server to pass the initial state to the client.\r\n * @returns A JSON string representing the dehydrated state.\r\n */\r\n public dehydrate(): string {\r\n const stateToPersist: Record<string, any> = {};\r\n this.store.forEach((value, key) => {\r\n // Dehydrate only if the state has been populated.\r\n if (value.state.called) {\r\n stateToPersist[key] = value.state;\r\n }\r\n });\r\n return JSON.stringify(stateToPersist);\r\n }\r\n\r\n /**\r\n * Merges a dehydrated state object into the current store.\r\n * This is used on the client to hydrate the state received from the server.\r\n * @param hydratedState - A JSON string from the `dehydrate` method.\r\n */\r\n public rehydrate(hydratedState: string): void {\r\n try {\r\n const parsedState = JSON.parse(hydratedState);\r\n for (const key in parsedState) {\r\n // We call setState to ensure any listening components are notified.\r\n // This prevents race conditions where a component might subscribe before rehydration is complete.\r\n this.setState(key, () => parsedState[key]);\r\n }\r\n } catch (e) {\r\n console.error(\"[api-core-lib] Failed to rehydrate state:\", e);\r\n }\r\n }\r\n\r\n \r\n}\r\n\r\nexport const globalStateManager = new GlobalStateManager();","// file: api-core-lib/src/server.ts\r\n\r\n/**\r\n * ===================================================================================\r\n * SERVER-SIDE ENTRY POINT\r\n * ===================================================================================\r\n * This file exports utilities that are safe to use in server-only environments\r\n * like React Server Components, Next.js API Routes, or other backend logic.\r\n * It MUST NOT import any file that has a dependency on client-only React APIs\r\n * like 'useState', 'useEffect', or 'createContext'.\r\n */\r\n\r\n\r\nimport type { AxiosInstance } from 'axios';\r\nimport { ActionConfigModule, ApiModuleConfig, InputOf } from './types/apiModule.types';\r\nimport { callDynamicApi } from './services/crud';\r\nimport { globalStateManager } from './core/globalStateManager';\r\n\r\n// This helper is duplicated here to keep this file self-contained and server-safe.\r\nconst generateCacheKey = (\r\n moduleName: string,\r\n actionName: string,\r\n input?: unknown,\r\n callOptions: { pathParams?: Record<string, any> } = {}\r\n): string => {\r\n const params = { path: callOptions.pathParams, body: input };\r\n try {\r\n return `${moduleName}/${actionName}::${JSON.stringify(params)}`;\r\n } catch (error) {\r\n return `${moduleName}/${actionName}::${Date.now()}`;\r\n }\r\n};\r\n\r\n/**\r\n * Creates a server-safe interface for pre-fetching data and dehydrating state.\r\n * Use this in your React Server Components to prepare data for the client.\r\n * @param apiClient A configured Axios instance.\r\n * @param moduleConfig The configuration for the module you want to use.\r\n * @returns An object with `prefetch` and `dehydrate` methods.\r\n */\r\nexport function createServerApi<\r\n TActions extends Record<string, ActionConfigModule<any, any>>\r\n>(\r\n apiClient: AxiosInstance,\r\n moduleConfig: ApiModuleConfig<TActions>\r\n) {\r\n return {\r\n /**\r\n * Prefetches data for a specific action on the server and populates the cache.\r\n */\r\n prefetch: async <TActionName extends keyof TActions>(\r\n actionName: TActionName,\r\n input?: InputOf<TActions[TActionName]>,\r\n options: { pathParams?: Record<string, any> } = {}\r\n ): Promise<any> => {\r\n const actionConfig = moduleConfig.actions[actionName];\r\n if (!actionConfig) {\r\n throw new Error(`Action \"${actionName as string}\" not found in module.`);\r\n }\r\n\r\n const result = await callDynamicApi(apiClient, moduleConfig.baseEndpoint, actionConfig, {\r\n pathParams: options.pathParams,\r\n body: input,\r\n });\r\n\r\n const cacheKey = generateCacheKey(moduleConfig.baseEndpoint, actionName as string, input, { pathParams: options.pathParams });\r\n \r\n if (result.success) {\r\n globalStateManager.setState(cacheKey, () => ({\r\n data: result.data, error: null, loading: false, success: true, called: true, isStale: false, rawResponse: result.rawResponse,\r\n }));\r\n } else {\r\n globalStateManager.setState(cacheKey, () => ({\r\n data: null, error: result.error, loading: false, success: false, called: true, isStale: true, rawResponse: result.rawResponse,\r\n }));\r\n }\r\n\r\n return result.data;\r\n },\r\n\r\n /**\r\n * Dehydrates the current state in the global manager into a JSON string.\r\n */\r\n dehydrate: (): string => {\r\n return globalStateManager.dehydrate();\r\n },\r\n };\r\n}"],"mappings":"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","names":["axios","axios"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/processor.ts","../src/core/utils.ts","../src/core/buildDynamicUrl.ts","../src/services/crud.ts","../src/core/globalStateManager.ts","../src/server.ts"],"sourcesContent":["import axios, { AxiosError, AxiosResponse } from 'axios';\r\nimport { ApiError, StandardResponse, ValidationError } from '../types';\r\n// [مهم] استيراد دوال الحماية الجديدة\r\nimport { isServerError, isNetworkError, isAxiosResponse } from './utils';\r\n\r\n/**\r\n * [النسخة النهائية والمضمونة]\r\n * تستخدم دوال حماية النوع المخصصة للقضاء على أخطاء 'never' بشكل نهائي.\r\n */\r\nexport const processResponse = <T>(\r\n responseOrError: AxiosResponse<any> | AxiosError,\r\n): StandardResponse<T> => {\r\n \r\n // ===================================================================================\r\n // #region المسار 1: معالجة النجاح\r\n // ===================================================================================\r\n if (isAxiosResponse(responseOrError)) {\r\n const response = responseOrError;\r\n const rawData = response.data;\r\n const isStandardApiResponse = rawData && typeof rawData.success === 'boolean' && rawData.data !== undefined;\r\n \r\n return {\r\n data: isStandardApiResponse ? rawData.data : rawData,\r\n links: isStandardApiResponse ? rawData.links : undefined,\r\n meta: isStandardApiResponse ? rawData.meta : undefined,\r\n rawResponse: rawData,\r\n loading: false, success: true, error: null,\r\n message: isStandardApiResponse ? rawData.message : 'Request successful.',\r\n validationErrors: [],\r\n };\r\n }\r\n\r\n // ===================================================================================\r\n // #region المسار 2: معالجة الأخطاء\r\n // ===================================================================================\r\n\r\n // الحالة 2.1: خطأ من الخادم (4xx/5xx)\r\n // داخل هذا البلوك، TypeScript متأكد 100% أن `responseOrError.response` موجود.\r\n if (isServerError(responseOrError)) {\r\n const error = responseOrError; // الآن `error` من النوع الصحيح\r\n type ApiErrorResponse = { message?: string; code?: string; errors?: ValidationError[] };\r\n const responseData = error.response.data as ApiErrorResponse | undefined;\r\n const status = error.response.status;\r\n let defaultMessage = 'An unexpected server error occurred.';\r\n\r\n switch (status) {\r\n case 400: defaultMessage = 'Bad Request'; break;\r\n case 401: defaultMessage = 'Unauthorized'; break;\r\n case 403: defaultMessage = 'Forbidden'; break;\r\n case 404: defaultMessage = 'Not Found'; break;\r\n case 422: defaultMessage = 'Validation Failed'; break;\r\n case 500: defaultMessage = 'Internal Server Error'; break;\r\n case 503: defaultMessage = 'Service Unavailable'; break;\r\n default: defaultMessage = `Server Error (${status})`;\r\n }\r\n\r\n const finalApiError: ApiError = {\r\n message: responseData?.message || defaultMessage,\r\n status: status,\r\n code: responseData?.code || error.code,\r\n errors: responseData?.errors || [],\r\n };\r\n \r\n return {\r\n data: null, rawResponse: responseData,\r\n error: finalApiError,\r\n validationErrors: finalApiError.errors,\r\n success: false, loading: false, message: finalApiError.message,\r\n };\r\n }\r\n\r\n // الحالة 2.2: خطأ في الشبكة\r\n // داخل هذا البلوك، TypeScript متأكد 100% أن `responseOrError.request` موجود.\r\n if (isNetworkError(responseOrError)) {\r\n const error = responseOrError;\r\n return {\r\n data: null, rawResponse: error.request,\r\n error: { message: 'Network Error: Unable to connect.', status: 0, code: error.code },\r\n success: false, loading: false, message: 'Network Error.',\r\n };\r\n }\r\n \r\n // الحالة 2.3: تم إلغاء الطلب\r\n if (axios.isCancel(responseOrError)) {\r\n return {\r\n data: null, rawResponse: null,\r\n error: { message: 'Request Canceled.', status: 499 },\r\n success: false, loading: false, message: 'Request Canceled.',\r\n };\r\n }\r\n\r\n // ===================================================================================\r\n // #region المسار الأخير: الحالة الاحتياطية\r\n // ===================================================================================\r\n \r\n // لأي خطأ آخر لم تتم معالجته (مثل أخطاء الإعداد)\r\n return {\r\n data: null, rawResponse: responseOrError,\r\n error: { message: 'An unknown error occurred.', status: -1 },\r\n success: false, loading: false, message: 'An unknown error occurred.',\r\n };\r\n};\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n// // file: src/core/processor.ts (مُحدَّث)\r\n\r\n// import { AxiosResponse } from 'axios';\r\n// import { ApiError, StandardResponse } from '../types';\r\n// import { isApiError, isAxiosResponse } from './utils';\r\n\r\n// export const processResponse = <T>(\r\n// responseOrError: AxiosResponse<any> | ApiError,\r\n// ): StandardResponse<T> => {\r\n// console.log(\"responseOrError [lib]\" , responseOrError)\r\n// console.log(\"responseOrError.data [lib]\" , responseOrError?.status)\r\n\r\n// if (isApiError(responseOrError)) {\r\n// return {\r\n// data: null,\r\n// rawResponse: undefined,\r\n// error: responseOrError,\r\n// validationErrors: responseOrError.errors || [],\r\n// success: false,\r\n// loading: false,\r\n// message: responseOrError.message,\r\n// };\r\n// }\r\n \r\n// if (isAxiosResponse(responseOrError)) {\r\n// const rawData = responseOrError.data;\r\n// const isWrappedResponse = \r\n// rawData && \r\n// typeof rawData.success === 'boolean' && \r\n// rawData.data !== undefined;\r\n \r\n// const finalData: T = isWrappedResponse ? rawData.data : rawData;\r\n// const message = isWrappedResponse ? rawData.message : 'Request successful.';\r\n\r\n// return {\r\n// data: finalData,\r\n// rawResponse: rawData,\r\n// loading: false,\r\n// success: true,\r\n// error: null,\r\n// message: message,\r\n// validationErrors: [],\r\n// };\r\n// }\r\n\r\n// return {\r\n// data: null,\r\n// rawResponse: undefined,\r\n// error: { message: 'An unknown error occurred during response processing.', status: 500 },\r\n// success: false,\r\n// loading: false,\r\n// message: 'An unknown error occurred.',\r\n// validationErrors: []\r\n// };\r\n// };","import axios, { AxiosError, AxiosResponse, CancelTokenSource } from 'axios';\r\nimport { ApiError, QueryOptions, StandardResponse } from '../types';\r\n\r\n// ===================================================================================\r\n// #region Type Guards (التحقق من الأنواع)\r\n// ===================================================================================\r\n\r\n/**\r\n * يتحقق مما إذا كان الكائن هو خطأ مخصص من نوع ApiError.\r\n */\r\nexport function isApiError(obj: any): obj is ApiError {\r\n return obj && typeof obj.status === 'number' && typeof obj.message === 'string' && obj.config === undefined;\r\n}\r\n\r\n\r\n// الدوال القديمة يمكن الإبقاء عليها للتوافق أو استبدالها بالكامل\r\nexport function isAxiosError(obj: any): obj is AxiosError {\r\n return obj && obj.isAxiosError === true;\r\n}\r\n\r\nexport function isAxiosResponse(obj: any): obj is AxiosResponse {\r\n return obj && obj.data !== undefined && obj.status !== undefined && obj.config !== undefined;\r\n}\r\n\r\n\r\n\r\n\r\n// ===================================================================================\r\n// #region URL & Query Handling (معالجة روابط URL والاستعلامات)\r\n// ===================================================================================\r\n\r\n/**\r\n * يبني رابطًا ديناميكيًا عن طريق استبدال المتغيرات المؤقتة مثل {key} بالقيم من كائن.\r\n * @param template - قالب الرابط (مثال: '/users/{userId}/posts').\r\n * @param params - كائن يحتوي على قيم المتغيرات (مثال: { userId: 123 }).\r\n * @returns الرابط النهائي بعد المعالجة.\r\n */\r\nexport function buildDynamicUrl(template: string, params?: Record<string, any>): string {\r\n if (!params) {\r\n return template;\r\n }\r\n return template.replace(/\\{(\\w+)\\}/g, (placeholder, key) => {\r\n return params.hasOwnProperty(key) ? String(params[key]) : placeholder;\r\n });\r\n}\r\n\r\n/**\r\n * [نسخة مطورة] يبني سلسلة استعلام (query string) من كائن الخيارات.\r\n * يدعم الآن الفلاتر المتداخلة (filter[key]=value) والترتيب المتعدد.\r\n * @param options - كائن خيارات الاستعلام (فلترة, ترتيب, ...).\r\n * @returns سلسلة استعلام جاهزة للإضافة للرابط.\r\n */\r\nexport function buildPaginateQuery(options: QueryOptions): string {\r\n const params = new URLSearchParams();\r\n\r\n for (const key in options) {\r\n const value = options[key];\r\n\r\n if (value === null || value === undefined) {\r\n continue; // تجاهل القيم الفارغة\r\n }\r\n\r\n if (key === 'filter' && typeof value === 'object' && !Array.isArray(value)) {\r\n // التعامل مع الفلاتر المتداخلة\r\n for (const filterKey in value) {\r\n const filterValue = value[filterKey];\r\n if (filterValue !== null && filterValue !== undefined) {\r\n params.append(`filter[${filterKey}]`, String(filterValue));\r\n }\r\n }\r\n } else if (key === 'sortBy' && Array.isArray(value)) {\r\n // التعامل مع الترتيب المتعدد\r\n value.forEach(sortItem => {\r\n if (sortItem && sortItem.key && sortItem.direction) {\r\n params.append('sortBy[]', `${sortItem.key}:${sortItem.direction}`);\r\n }\r\n });\r\n } else {\r\n // التعامل مع باقي المعاملات\r\n params.append(key, String(value));\r\n }\r\n }\r\n\r\n return params.toString();\r\n}\r\n\r\n// ===================================================================================\r\n// #region Object & Data Helpers (أدوات مساعدة للكائنات والبيانات)\r\n// ===================================================================================\r\n\r\n/**\r\n * يتحقق مما إذا كانت الاستجابة تحتوي على بيانات قابلة للعرض.\r\n * @param response - كائن StandardResponse.\r\n * @returns `true` إذا كانت البيانات موجودة وليست مصفوفة فارغة.\r\n */\r\nexport function hasData<T>(response: StandardResponse<T> | null | undefined): boolean {\r\n if (!response || !response.success || response.data === null || response.data === undefined) {\r\n return false;\r\n }\r\n if (Array.isArray(response.data) && response.data.length === 0) {\r\n return false;\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * ينشئ نسخة جديدة من كائن مع إزالة كل الخصائص التي قيمتها `null` أو `undefined`.\r\n * @param obj - الكائن المراد تنظيفه.\r\n * @returns نسخة نظيفة من الكائن.\r\n */\r\nexport function cleanObject<T extends Record<string, any>>(obj: T): Partial<T> {\r\n const newObj: Partial<T> = {};\r\n for (const key in obj) {\r\n if (obj[key] !== null && obj[key] !== undefined) {\r\n newObj[key] = obj[key];\r\n }\r\n }\r\n return newObj;\r\n}\r\n\r\n// ===================================================================================\r\n// #region Async Helpers (أدوات مساعدة غير متزامنة)\r\n// ===================================================================================\r\n\r\n/**\r\n * ينشئ دالة تقوم بتأخير تنفيذ الدالة الأصلية حتى انقضاء فترة زمنية معينة\r\n * بعد آخر مرة تم استدعاؤها. مفيدة جدًا لتقليل الطلبات في حقول البحث.\r\n * @param func - الدالة التي سيتم تأخير تنفيذها.\r\n * @param delay - مدة التأخير بالمللي ثانية.\r\n * @returns دالة جديدة قابلة للاستدعاء.\r\n */\r\nexport function debounce<T extends (...args: any[]) => any>(func: T, delay: number): (...args: Parameters<T>) => void {\r\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\r\n return function(...args: Parameters<T>) {\r\n if (timeoutId) {\r\n clearTimeout(timeoutId);\r\n }\r\n timeoutId = setTimeout(() => {\r\n func(...args);\r\n }, delay);\r\n };\r\n}\r\n\r\n/**\r\n * دالة بسيطة لإيقاف التنفيذ لمدة زمنية محددة.\r\n * @param ms - مدة التأخير بالمللي ثانية.\r\n * @returns Promise يكتمل بعد انقضاء المدة.\r\n */\r\nexport function delay(ms: number): Promise<void> {\r\n return new Promise(resolve => setTimeout(resolve, ms));\r\n}\r\n\r\n// ===================================================================================\r\n// #region Request Cancellation (إدارة إلغاء الطلبات)\r\n// ===================================================================================\r\n\r\nconst cancelTokens = new Map<string, CancelTokenSource>();\r\n\r\n/**\r\n * ينشئ cancel token جديد لطلب معين، ويلغي أي طلب قديم بنفس المفتاح.\r\n * يمنع حالات الـ race conditions في المكونات التي ترسل طلبات متتالية.\r\n * @param key - مفتاح فريد لتعريف الطلب (مثال: 'user-search').\r\n * @returns كائن CancelTokenSource من Axios.\r\n */\r\nexport const createCancelToken = (key: string): CancelTokenSource => {\r\n const existingToken = cancelTokens.get(key);\r\n if (existingToken) {\r\n existingToken.cancel('Operation canceled due to a new request.');\r\n }\r\n const source = axios.CancelToken.source();\r\n cancelTokens.set(key, source);\r\n return source;\r\n};\r\n\r\n\r\n\r\n\r\n\r\n// ===================================================================================\r\n// #region Advanced Type Guards (أدوات تحقق متقدمة من الأنواع)\r\n// ===================================================================================\r\n\r\n/**\r\n * [جديد وحاسم] يتحقق مما إذا كان الخطأ هو خطأ من الخادم (يحتوي على `response`).\r\n * الأهم من ذلك، أنه يضيق نوع الخطأ ليضمن وجود `response`.\r\n */\r\nexport function isServerError(error: any): error is AxiosError & { response: AxiosResponse } {\r\n return axios.isAxiosError(error) && error.response !== undefined;\r\n}\r\n\r\n/**\r\n * [جديد وحاسم] يتحقق مما إذا كان الخطأ هو خطأ في الشبكة (لا يحتوي على `response` ولكن يحتوي على `request`).\r\n * يضيق نوع الخطأ ليضمن عدم وجود `response`.\r\n */\r\nexport function isNetworkError(error: any): error is AxiosError & { request: any } {\r\n return axios.isAxiosError(error) && error.response === undefined && error.request !== undefined;\r\n}\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n// /**\r\n// * @file src/core/utils.ts\r\n// * @description\r\n// * يحتوي هذا الملف على مجموعة من الدوال المساعدة المستخدمة في جميع أنحاء المكتبة،\r\n// * مثل بناء سلاسل الاستعلام (query strings) وإدارة إلغاء الطلبات،\r\n// * بالإضافة إلى دوال التحقق من الأنواع (type guards).\r\n// */\r\n\r\n// import axios, { CancelTokenSource } from 'axios';\r\n// import { ApiError, QueryOptions } from '../types';\r\n// import { AxiosResponse } from 'axios';\r\n\r\n// // ===================================\r\n// // إدارة إلغاء الطلبات\r\n// // ===================================\r\n// const cancelTokens = new Map<string, CancelTokenSource>();\r\n\r\n// export const createCancelToken = (key: string): CancelTokenSource => {\r\n// const existingToken = cancelTokens.get(key);\r\n// if (existingToken) {\r\n// existingToken.cancel('Operation canceled due to a new request.');\r\n// cancelTokens.delete(key);\r\n// }\r\n// const source = axios.CancelToken.source();\r\n// cancelTokens.set(key, source);\r\n// return source;\r\n// };\r\n\r\n\r\n\r\n// interface BuildQueryOptions {\r\n// encode?: boolean;\r\n// }\r\n\r\n// /**\r\n// * يبني سلسلة استعلام (query string) من كائن الخيارات.\r\n// * @param options - كائن خيارات الاستعلام (فلترة, ترتيب, ...).\r\n// * @param config - إعدادات إضافية مثل التحكم في التشفير.\r\n// * @returns سلسلة استعلام جاهزة للإضافة للرابط.\r\n// */\r\n// export function buildPaginateQuery(options: QueryOptions, config: BuildQueryOptions = { encode: true }): string {\r\n// // استخدام URLSearchParams يقوم بالتشفير تلقائيًا وهو الأسلوب القياسي والآمن\r\n// if (config.encode) {\r\n// const params = new URLSearchParams();\r\n// Object.entries(options).forEach(([key, value]) => {\r\n// if (value !== undefined && value !== null) {\r\n// // يمكنك إضافة منطق أكثر تعقيدًا هنا للتعامل مع المصفوفات أو الكائنات\r\n// params.append(key, String(value));\r\n// }\r\n// });\r\n// return params.toString();\r\n// }\r\n\r\n// // منطق بديل إذا كان encode = false (لـ APIs التي تتطلب تنسيقًا خاصًا)\r\n// const parts = Object.entries(options)\r\n// .filter(([, value]) => value !== undefined && value !== null)\r\n// .map(([key, value]) => `${key}=${String(value)}`);\r\n \r\n// return parts.join('&');\r\n// }\r\n\r\n// // ===================================\r\n// // التحقق من الأنواع (Type Guards)\r\n// // ===================================\r\n// export function isApiError(obj: any): obj is ApiError {\r\n// console.log(\"obj in IsApiError\" , obj)\r\n// return obj && typeof obj.status === 'number' && typeof obj.message === 'string' && obj.config === undefined;\r\n// }\r\n\r\n// export function isAxiosResponse(obj: any): obj is AxiosResponse {\r\n// return obj && obj.data !== undefined && obj.status !== undefined && obj.config !== undefined;\r\n// }\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n// // import axios, { CancelTokenSource } from 'axios';\r\n// // import { PaginateQueryOptions, ApiError } from '../types';\r\n\r\n// // // === Cancel Token Management ===\r\n// // const cancelTokens = new Map<string, CancelTokenSource>();\r\n\r\n// // export const createCancelToken = (key: string): CancelTokenSource => {\r\n// // const existingToken = cancelTokens.get(key);\r\n// // if (existingToken) {\r\n// // existingToken.cancel('Operation canceled due to new request.');\r\n// // cancelTokens.delete(key);\r\n// // }\r\n// // const source = axios.CancelToken.source();\r\n// // cancelTokens.set(key, source);\r\n// // return source;\r\n// // };\r\n\r\n// // export const cancelRequest = (key: string): void => {\r\n// // const source = cancelTokens.get(key);\r\n// // if (source) {\r\n// // source.cancel('Operation canceled by user.');\r\n// // cancelTokens.delete(key);\r\n// // }\r\n// // };\r\n\r\n// // export const cancelAllRequests = (): void => {\r\n// // cancelTokens.forEach(source => source.cancel('All operations canceled.'));\r\n// // cancelTokens.clear();\r\n// // };\r\n\r\n// // // === Query Builder ===\r\n// // export function buildPaginateQuery(query: PaginateQueryOptions): string {\r\n// // const params = new URLSearchParams();\r\n// // if (!query) return '';\r\n\r\n// // if (query.page) params.append('page', query.page.toString());\r\n// // if (query.limit) params.append('limit', query.limit.toString());\r\n// // if (query.search) params.append('search', query.search);\r\n// // if (query.sortBy) {\r\n// // query.sortBy.forEach(sort => params.append('sortBy', `${sort.key}:${sort.direction}`));\r\n// // }\r\n// // if (query.filter) {\r\n// // Object.entries(query.filter).forEach(([field, value]) => {\r\n// // params.append(`filter.${field}`, value);\r\n// // });\r\n// // }\r\n// // const queryString = params.toString();\r\n// // return queryString ? `?${queryString}` : '';\r\n// // }\r\n\r\n// // // === Type Guards ===\r\n// // export function isApiError(obj: any): obj is ApiError {\r\n// // return obj && typeof obj.status === 'number' && typeof obj.message === 'string' && obj.config === undefined;\r\n// // }\r\n\r\n// // export function isAxiosResponse(obj: any): obj is any {\r\n// // return obj && obj.data !== undefined && obj.status !== undefined && obj.config !== undefined;\r\n// // }","/**\r\n * Replaces path placeholders like {key} with values from a params object.\r\n * @param template - The URL template (e.g., '/users/{userId}/posts').\r\n * @param params - An object with keys matching the placeholders (e.g., { userId: 123 }).\r\n * @returns The final, resolved URL string.\r\n */\r\nexport function buildDynamicUrl(template: string, params?: Record<string, any>): string {\r\n if (!params) {\r\n return template;\r\n }\r\n return template.replace(/\\{(\\w+)\\}/g, (placeholder, key) => {\r\n return params.hasOwnProperty(key) ? String(params[key]) : placeholder;\r\n });\r\n}","// file: src/services/crud.ts\r\n\r\nimport { AxiosInstance, AxiosRequestConfig } from 'axios';\r\nimport { ActionOptions, RequestConfig, StandardResponse } from '../types';\r\nimport { processResponse } from '../core/processor';\r\nimport { buildDynamicUrl } from '@/core/buildDynamicUrl';\r\nimport { ActionConfigModule } from '@/types/apiModule.types';\r\n\r\n/**\r\n * A factory function to create a reusable set of API services for a specific endpoint.\r\n * It provides full CRUD operations plus advanced features like bulk deletion and file uploads,\r\n * with intelligent, dynamic URL building.\r\n */\r\nexport function createApiServices<T>(axiosInstance: AxiosInstance, baseEndpoint: string) {\r\n \r\n /**\r\n * Intelligently resolves the final URL for a request.\r\n * Priority:\r\n * 1. A full endpoint override from `config.endpoint`.\r\n * 2. The base endpoint appended with an ID, if provided.\r\n * 3. The base endpoint alone.\r\n * It also replaces dynamic segments like {id} or {tenantId} in the URL.\r\n */\r\n const resolveUrl = (config: ActionOptions = {}, id?: string | number): string => {\r\n const endpointTemplate = config.endpoint || (id != null ? `${baseEndpoint}/{id}` : baseEndpoint);\r\n \r\n // Create a params object for URL building. We include common ID variations.\r\n const params = id != null ? { id, tenant: id, tenantId: id, recordId: id } : {};\r\n \r\n return buildDynamicUrl(endpointTemplate, params);\r\n };\r\n\r\n /**\r\n * Fetches data. Can fetch a list or a single record based on whether an ID is provided.\r\n * This is the primary flexible read operation.\r\n */\r\n const get = async (id?: string | number, config?: ActionOptions): Promise<StandardResponse<T>> => {\r\n const url = resolveUrl(config, id);\r\n try {\r\n const response = await axiosInstance.get(url, config);\r\n return processResponse<T>(response);\r\n } catch (error) {\r\n return processResponse<T>(error as any);\r\n }\r\n };\r\n \r\n /**\r\n * Fetches a list of records using a query string.\r\n * This is specifically for fetching paginated/filtered lists.\r\n */\r\n const getWithQuery = async (query: string, config?: RequestConfig): Promise<StandardResponse<T>> => {\r\n const url = `${baseEndpoint}?${query}`;\r\n try {\r\n const response = await axiosInstance.get(url, config);\r\n return processResponse<T>(response);\r\n } catch (error) {\r\n return processResponse<T>(error as any);\r\n }\r\n };\r\n\r\n /**\r\n * Creates a new resource.\r\n */\r\n const post = async (data: Partial<T>, config?: ActionOptions): Promise<StandardResponse<T>> => {\r\n const url = resolveUrl(config);\r\n try {\r\n const response = await axiosInstance.post(url, data, config);\r\n console.log(\"[lib] response POST: \" , response)\r\n console.log(\"[lib] response processResponse POST: \" , processResponse<T>(response))\r\n return processResponse<T>(response);\r\n } catch (error) {\r\n console.log(\"[lib] response error POST: \" , processResponse<T>(error as any))\r\n return processResponse<T>(error as any);\r\n }\r\n };\r\n \r\n /**\r\n * Replaces an entire resource.\r\n */\r\n const put = async (id: string | number, data: T, config?: ActionOptions): Promise<StandardResponse<T>> => {\r\n const url = resolveUrl(config, id);\r\n try {\r\n const response = await axiosInstance.put(url, data, config);\r\n return processResponse<T>(response);\r\n } catch (error) {\r\n return processResponse<T>(error as any);\r\n }\r\n };\r\n\r\n /**\r\n * Partially updates a resource.\r\n */\r\n const patch = async (id: string | number, data: Partial<T>, config?: ActionOptions): Promise<StandardResponse<T>> => {\r\n const url = resolveUrl(config, id);\r\n try {\r\n const response = await axiosInstance.patch(url, data, config);\r\n return processResponse<T>(response);\r\n } catch (error) {\r\n return processResponse<T>(error as any);\r\n }\r\n };\r\n\r\n /**\r\n * Deletes a resource.\r\n */\r\n const remove = async (id: string | number, config?: ActionOptions): Promise<StandardResponse<any>> => {\r\n const url = resolveUrl(config, id);\r\n try {\r\n const response = await axiosInstance.delete(url, config);\r\n return processResponse<any>(response);\r\n } catch (error) {\r\n return processResponse<any>(error as any);\r\n }\r\n };\r\n \r\n /**\r\n * Deletes multiple resources in a single request.\r\n */\r\n const bulkDelete = async (ids: Array<string | number>, config?: ActionOptions): Promise<StandardResponse<any>> => {\r\n const url = resolveUrl(config); // Bulk actions typically don't use an ID in the path.\r\n try {\r\n const response = await axiosInstance.delete(url, { data: { ids }, ...config });\r\n return processResponse<any>(response);\r\n } catch (error) {\r\n return processResponse<any>(error as any);\r\n }\r\n };\r\n\r\n /**\r\n * Uploads a file, with optional additional data.\r\n */\r\n const upload = async (file: File, additionalData?: Record<string, any>, config?: ActionOptions): Promise<StandardResponse<any>> => {\r\n const url = resolveUrl(config);\r\n const formData = new FormData();\r\n formData.append('file', file);\r\n if (additionalData) {\r\n Object.keys(additionalData).forEach(key => formData.append(key, String(additionalData[key])));\r\n }\r\n try {\r\n const response = await axiosInstance.post(url, formData, {\r\n ...config, headers: { ...config?.headers, 'Content-Type': 'multipart/form-data' },\r\n });\r\n return processResponse<any>(response);\r\n } catch (error) {\r\n return processResponse<any>(error as any);\r\n }\r\n };\r\n\r\n return { get, getWithQuery, post, put, patch, remove, bulkDelete, upload };\r\n}\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n// نفترض أن هذه الدوال موجودة في ملف مساعد\r\n// import { buildDynamicUrl } from '@/core/buildDynamicUrl';\r\n// import { processResponse } from '../core/processor';\r\n\r\n/**\r\n * [نسخة محدثة ومحسّنة]\r\n * دالة عامة وصريحة لتنفيذ أي طلب API.\r\n * تستقبل وسائط منظمة بدلاً من محاولة تخمينها.\r\n * \r\n * @param axiosInstance - نسخة Axios.\r\n * @param baseEndpoint - نقطة النهاية الأساسية للموديول.\r\n * @param actionConfig - إعدادات الإجراء المحدد.\r\n * @param params - كائن يحتوي على جميع البارامترات اللازمة للطلب.\r\n * @returns Promise<StandardResponse<any>>\r\n */\r\nexport async function callDynamicApi(\r\n axiosInstance: AxiosInstance,\r\n baseEndpoint: string,\r\n actionConfig: ActionConfigModule<any, any>,\r\n params: {\r\n pathParams?: Record<string, string | number>;\r\n body?: any;\r\n config?: AxiosRequestConfig;\r\n }\r\n): Promise<StandardResponse<any>> {\r\n \r\n // 1. استخراج البارامترات بشكل صريح وواضح\r\n const { pathParams, body, config } = params;\r\n\r\n // 2. بناء الـ URL الديناميكي\r\n const urlTemplate = `${baseEndpoint}${actionConfig.path}`;\r\n // افترض وجود هذه الدوال أو قم بإنشائها\r\n const finalUrl = buildDynamicUrl(urlTemplate, pathParams || {}); \r\n\r\n try {\r\n const { method } = actionConfig;\r\n let response;\r\n\r\n // 3. تنفيذ الطلب بناءً على نوع الـ Method\r\n // أصبح المنطق الآن أكثر نظافة ووضوحًا\r\n if (method === 'POST' || method === 'PUT' || method === 'PATCH') {\r\n response = await axiosInstance[method.toLowerCase() as 'post'](finalUrl, body, config);\r\n } else if (method === 'DELETE') {\r\n // DELETE قد يحتوي على body في بعض الحالات (مثل bulk)\r\n response = await axiosInstance.delete(finalUrl, { data: body, ...config });\r\n } else { // GET\r\n // في GET، الجسم (body) يمثل معاملات الاستعلام (query params)\r\n response = await axiosInstance.get(finalUrl, { params: body, ...config });\r\n }\r\n \r\n return processResponse(response);\r\n } catch (error) {\r\n // معالجة الأخطاء تبقى كما هي\r\n return processResponse(error as any);\r\n }\r\n}\r\n\r\n\r\n\r\n\r\n// import { AxiosInstance } from 'axios';\r\n// import { ActionOptions, RequestConfig, StandardResponse } from '../types';\r\n// import { processResponse } from '../core/processor';\r\n\r\n// type CrudRequestConfig = RequestConfig & ActionOptions;\r\n\r\n// /**\r\n// * دالة مصنع (Factory Function) لإنشاء مجموعة خدمات API قابلة لإعادة الاستخدام لنقطة نهاية (endpoint) محددة.\r\n// * توفر عمليات CRUD كاملة بالإضافة إلى ميزات متقدمة مثل الحذف الجماعي ورفع الملفات.\r\n// */\r\n// export function createApiServices<T>(axiosInstance: AxiosInstance, endpoint: string) {\r\n \r\n// // --- Read Operations ---\r\n// const get = async (id?: string, config?: RequestConfig): Promise<StandardResponse<T | T[]>> => {\r\n// const url = id ? `${endpoint}/${id}` : endpoint;\r\n// try {\r\n// const response = await axiosInstance.get(url, config);\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n \r\n// const getWithQuery = async (query: string, config?: RequestConfig): Promise<StandardResponse<T[]>> => {\r\n// try {\r\n// const response = await axiosInstance.get(`${endpoint}${query}`, config);\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n\r\n// // --- Write Operations ---\r\n// const post = async (data: Partial<T>, config?: CrudRequestConfig): Promise<StandardResponse<T>> => {\r\n// const finalUrl = config?.endpoint || endpoint;\r\n// try {\r\n// const response = await axiosInstance.post(finalUrl, data, config);\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n \r\n// /**\r\n// * ✅ NEW: إضافة دالة PUT\r\n// * تستخدم لاستبدال مورد بالكامل.\r\n// * لاحظ أن `data` من النوع `T` وليس `Partial<T>` لأن PUT يتوقع المورد كاملاً.\r\n// */\r\n// const put = async (id: string, data: T, config?: CrudRequestConfig): Promise<StandardResponse<T>> => {\r\n// const finalUrl = config?.endpoint || `${endpoint}/${id}`;\r\n// try {\r\n// const response = await axiosInstance.put(finalUrl, data, config);\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n\r\n// const patch = async (id: string, data: Partial<T>, config?: CrudRequestConfig): Promise<StandardResponse<T>> => {\r\n// const finalUrl = config?.endpoint || `${endpoint}/${id}`;\r\n// try {\r\n// const response = await axiosInstance.patch(finalUrl, data, config);\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n\r\n// const remove = async (id: string, config?: CrudRequestConfig): Promise<StandardResponse<any>> => {\r\n// const finalUrl = config?.endpoint || `${endpoint}/${id}`;\r\n// try {\r\n// const response = await axiosInstance.delete(finalUrl, config);\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n \r\n// // --- Advanced Operations ---\r\n\r\n// /**\r\n// * ✅ NEW: إضافة دالة للحذف الجماعي (Bulk Delete)\r\n// * مفيدة جدًا لحذف عدة عناصر في طلب واحد لتحسين الأداء.\r\n// * ترسل مصفوفة من الـ IDs في جسم الطلب (request body).\r\n// */\r\n// const bulkDelete = async (ids: string[], config?: CrudRequestConfig): Promise<StandardResponse<any>> => {\r\n// const finalUrl = config?.endpoint || `${endpoint}`; // يمكن تخصيص المسار\r\n// try {\r\n// // Axios's delete method can send a body via the config object\r\n// const response = await axiosInstance.delete(finalUrl, { data: { ids }, ...config });\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n\r\n// /**\r\n// * ✅ NEW: إضافة دالة لرفع الملفات (File Upload)\r\n// * تتعامل مع `FormData` لرفع الملفات، وهي ميزة أساسية في معظم التطبيقات.\r\n// * يمكن إرسال بيانات إضافية مع الملف.\r\n// */\r\n// const upload = async (file: File, additionalData?: Record<string, any>, config?: CrudRequestConfig): Promise<StandardResponse<any>> => {\r\n// const finalUrl = config?.endpoint || `${endpoint}`;\r\n \r\n// const formData = new FormData();\r\n// formData.append('file', file); // 'file' هو اسم الحقل الشائع، يمكن تغييره\r\n \r\n// if (additionalData) {\r\n// Object.keys(additionalData).forEach(key => {\r\n// formData.append(key, additionalData[key]);\r\n// });\r\n// }\r\n\r\n// try {\r\n// const response = await axiosInstance.post(finalUrl, formData, {\r\n// ...config,\r\n// headers: {\r\n// ...config?.headers,\r\n// 'Content-Type': 'multipart/form-data',\r\n// },\r\n// });\r\n// return processResponse(response);\r\n// } catch (error) {\r\n// return processResponse(error as any);\r\n// }\r\n// };\r\n\r\n// return { get, getWithQuery, post, put, patch, remove, bulkDelete, upload };\r\n// }\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n","// file: src/core/globalStateManager.ts\r\n\r\nimport { ActionStateModule } from \"@/types\";\r\n\r\n\r\ntype CacheItem = {\r\n state: ActionStateModule<any>;\r\n listeners: Set<() => void>;\r\n};\r\n\r\nconst createInitialState = (): ActionStateModule<any> => ({\r\n data: null,\r\n links: undefined,\r\n meta: undefined,\r\n error: null,\r\n loading: false,\r\n success: false,\r\n called: false,\r\n isStale: false,\r\n message: undefined,\r\n validationErrors: [],\r\n rawResponse: null,\r\n});\r\n\r\nclass GlobalStateManager {\r\n private store = new Map<string, CacheItem>();\r\n\r\n /**\r\n * يحصل على لقطة (snapshot) للحالة الحالية لمفتاح معين.\r\n */\r\n public getSnapshot<T>(key: string): ActionStateModule<T> {\r\n return this.store.get(key)?.state as ActionStateModule<T> ?? createInitialState();\r\n }\r\n\r\n /**\r\n * يسجل دالة callback للاستماع إلى التغييرات على مفتاح معين.\r\n * @returns دالة لإلغاء الاشتراك.\r\n */\r\n public subscribe(key: string, callback: () => void): () => void {\r\n if (!this.store.has(key)) {\r\n this.store.set(key, { state: createInitialState(), listeners: new Set() });\r\n }\r\n const item = this.store.get(key)!;\r\n item.listeners.add(callback);\r\n\r\n return () => {\r\n item.listeners.delete(callback);\r\n };\r\n }\r\n\r\n /**\r\n * يحدّث الحالة لمفتاح معين ويقوم بإعلام جميع المشتركين.\r\n */\r\n public setState<T>(key: string, updater: (prevState: ActionStateModule<T>) => ActionStateModule<T>): void {\r\n const currentState = this.getSnapshot<T>(key);\r\n const newState = updater(currentState);\r\n\r\n // نضمن وجود العنصر قبل التحديث\r\n if (!this.store.has(key)) {\r\n this.store.set(key, { state: newState, listeners: new Set() });\r\n } else {\r\n this.store.get(key)!.state = newState;\r\n }\r\n\r\n // إعلام المشتركين\r\n this.store.get(key)!.listeners.forEach(listener => listener());\r\n }\r\n\r\n /**\r\n * يجعل البيانات المرتبطة بمفتاح معين \"قديمة\" (stale).\r\n */\r\n public invalidate(key: string): void {\r\n const state = this.getSnapshot(key);\r\n // يبطل فقط إذا كانت هناك بيانات بالفعل\r\n if (state.called) {\r\n this.setState(key, (prev) => ({ ...prev, isStale: true }));\r\n }\r\n }\r\n\r\n\r\n /**\r\n * [نسخة محدثة وأكثر قوة]\r\n * يجعل كل البيانات التي تبدأ بمفتاح معين \"قديمة\" (stale).\r\n * @example invalidateByPrefix('myModule/list::') سيبطل كل صفحات القائمة.\r\n */\r\n public invalidateByPrefix(prefix: string): void {\r\n this.store.forEach((value, key) => {\r\n if (key.startsWith(prefix)) {\r\n const state = this.getSnapshot(key);\r\n // يبطل فقط إذا كانت هناك بيانات بالفعل\r\n if (state.called) {\r\n this.setState(key, (prev) => ({ ...prev, isStale: true }));\r\n }\r\n }\r\n });\r\n }\r\n\r\n\r\n /**\r\n * Serializes the current state of the query store into a JSON string.\r\n * This is used on the server to pass the initial state to the client.\r\n * @returns A JSON string representing the dehydrated state.\r\n */\r\n public dehydrate(): string {\r\n const stateToPersist: Record<string, any> = {};\r\n this.store.forEach((value, key) => {\r\n // Dehydrate only if the state has been populated.\r\n if (value.state.called) {\r\n stateToPersist[key] = value.state;\r\n }\r\n });\r\n return JSON.stringify(stateToPersist);\r\n }\r\n\r\n /**\r\n * Merges a dehydrated state object into the current store.\r\n * This is used on the client to hydrate the state received from the server.\r\n * @param hydratedState - A JSON string from the `dehydrate` method.\r\n */\r\n public rehydrate(hydratedState: string): void {\r\n try {\r\n const parsedState = JSON.parse(hydratedState);\r\n for (const key in parsedState) {\r\n // We call setState to ensure any listening components are notified.\r\n // This prevents race conditions where a component might subscribe before rehydration is complete.\r\n this.setState(key, () => parsedState[key]);\r\n }\r\n } catch (e) {\r\n console.error(\"[api-core-lib] Failed to rehydrate state:\", e);\r\n }\r\n }\r\n\r\n \r\n}\r\n\r\nexport const globalStateManager = new GlobalStateManager();","// file: api-core-lib/src/server.ts\r\n\r\n\r\n\r\n\r\nimport type { AxiosInstance } from 'axios';\r\nimport { ActionConfigModule, ApiModuleConfig, InputOf } from './types/apiModule.types';\r\nimport { callDynamicApi } from './services/crud';\r\nimport { globalStateManager } from './core/globalStateManager';\r\n\r\n// This helper is duplicated here to keep this file self-contained and server-safe.\r\nconst generateCacheKey = (\r\n moduleName: string,\r\n actionName: string,\r\n input?: unknown,\r\n callOptions: { pathParams?: Record<string, any> } = {}\r\n): string => {\r\n const params = { path: callOptions.pathParams, body: input };\r\n try {\r\n return `${moduleName}/${actionName}::${JSON.stringify(params)}`;\r\n } catch (error) {\r\n return `${moduleName}/${actionName}::${Date.now()}`;\r\n }\r\n};\r\n\r\n/**\r\n * Creates a server-safe interface for pre-fetching data and dehydrating state.\r\n * Use this in your React Server Components to prepare data for the client.\r\n * @param apiClient A configured Axios instance.\r\n * @param moduleConfig The configuration for the module you want to use.\r\n * @returns An object with `prefetch` and `dehydrate` methods.\r\n */\r\nexport function createServerApi<\r\n TActions extends Record<string, ActionConfigModule<any, any>>\r\n>(\r\n apiClient: AxiosInstance,\r\n moduleConfig: ApiModuleConfig<TActions>\r\n) {\r\n return {\r\n /**\r\n * Prefetches data for a specific action on the server and populates the cache.\r\n */\r\n prefetch: async <TActionName extends keyof TActions>(\r\n actionName: TActionName,\r\n input?: InputOf<TActions[TActionName]>,\r\n options: { pathParams?: Record<string, any> } = {}\r\n ): Promise<any> => {\r\n const actionConfig = moduleConfig.actions[actionName];\r\n if (!actionConfig) {\r\n throw new Error(`Action \"${actionName as string}\" not found in module.`);\r\n }\r\n\r\n const result = await callDynamicApi(apiClient, moduleConfig.baseEndpoint, actionConfig, {\r\n pathParams: options.pathParams,\r\n body: input,\r\n });\r\n\r\n const cacheKey = generateCacheKey(moduleConfig.baseEndpoint, actionName as string, input, { pathParams: options.pathParams });\r\n \r\n if (result.success) {\r\n globalStateManager.setState(cacheKey, () => ({\r\n data: result.data, error: null, loading: false, success: true, called: true, isStale: false, rawResponse: result.rawResponse,\r\n }));\r\n } else {\r\n globalStateManager.setState(cacheKey, () => ({\r\n data: null, error: result.error, loading: false, success: false, called: true, isStale: true, rawResponse: result.rawResponse,\r\n }));\r\n }\r\n\r\n return result.data;\r\n },\r\n\r\n /**\r\n * Dehydrates the current state in the global manager into a JSON string.\r\n */\r\n dehydrate: (): string => {\r\n return globalStateManager.dehydrate();\r\n },\r\n };\r\n}"],"mappings":"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","names":["axios","axios"]}
|