api-core-lib 4.4.3 → 5.5.4

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.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { ResponseType, AxiosRequestConfig, AxiosProgressEvent, AxiosInstance, Method, AxiosResponse } from 'axios';
1
+ import { InternalAxiosRequestConfig, AxiosResponse, AxiosRequestConfig, AxiosProgressEvent, AxiosInstance, Method } from 'axios';
2
2
  import * as react from 'react';
3
3
 
4
4
  /**
@@ -35,28 +35,49 @@ interface ApiError {
35
35
  errors?: ValidationError[];
36
36
  requestId?: string;
37
37
  }
38
- /**
39
- * يمثل مجموعة التوكنات التي يتم إدارتها.
40
- */
41
38
  interface Tokens {
42
39
  accessToken: string | null;
43
40
  refreshToken: string | null;
44
41
  expiresAt?: number;
45
42
  tokenType?: string;
46
43
  }
47
- /**
48
- * واجهة لمدير التوكنات، تسمح بفصل منطق تخزين التوكن.
49
- * يمكن للمستخدم توفير تطبيق لهذه الواجهة (e.g., LocalStorage, Cookies).
50
- */
51
44
  interface TokenManager {
52
45
  getTokens(): Promise<Tokens>;
53
46
  setTokens(tokens: Tokens): Promise<void>;
54
47
  clearTokens(): Promise<void>;
48
+ /**
49
+ * دالة لتحديد سياق التشغيل.
50
+ * true إذا كانت التوكنات في كوكيز httpOnly (الوضع الآمن).
51
+ * false إذا كانت في مكان يمكن للعميل الوصول إليه (للتطبيقات غير Next.js).
52
+ */
55
53
  isHttpOnly(): boolean;
56
54
  }
57
55
  /**
58
- * يوسع إعدادات طلب Axios القياسية بخيارات خاصة بالمكتبة.
56
+ * واجهة لـ Logger مخصص. متوافقة مع `console`.
57
+ */
58
+ interface Logger {
59
+ log(message?: any, ...optionalParams: any[]): void;
60
+ info(message?: any, ...optionalParams: any[]): void;
61
+ warn(message?: any, ...optionalParams: any[]): void;
62
+ error(message?: any, ...optionalParams: any[]): void;
63
+ }
64
+ /**
65
+ * سياق البرمجيات الوسيطة الذي يتم تمريره لكل دالة.
59
66
  */
67
+ interface MiddlewareContext {
68
+ req: InternalAxiosRequestConfig;
69
+ res?: AxiosResponse;
70
+ error?: any;
71
+ logger: Logger;
72
+ custom?: Record<string, any>;
73
+ }
74
+ /**
75
+ * دالة برمجية وسيطة (Middleware Function).
76
+ */
77
+ type Middleware = (context: MiddlewareContext, next: () => Promise<void>) => Promise<void>;
78
+ interface RequestConfig extends AxiosRequestConfig {
79
+ isPublic?: boolean;
80
+ }
60
81
  interface RequestConfig extends AxiosRequestConfig {
61
82
  cancelTokenKey?: string;
62
83
  onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
@@ -113,7 +134,7 @@ interface RefreshTokenConfig {
113
134
  };
114
135
  }
115
136
  /**
116
- * الواجهة الرئيسية لتهيئة عميل الـ API.
137
+ * الواجهة الرئيسية لتهيئة عميل الـ API (محدثة بالكامل).
117
138
  */
118
139
  interface ApiClientConfig {
119
140
  baseURL?: string;
@@ -121,13 +142,16 @@ interface ApiClientConfig {
121
142
  timeout?: number;
122
143
  headers?: Record<string, string>;
123
144
  withCredentials?: boolean;
124
- responseType?: ResponseType;
125
- /**
126
- * إعدادات مخصصة لعملية تجديد التوكن.
127
- * إذا لم يتم توفيرها، ستتوقف محاولات التجديد التلقائي.
128
- */
129
145
  refreshTokenConfig?: RefreshTokenConfig;
130
146
  onRefreshError?: (error: any) => void;
147
+ /**
148
+ * ✨ NEW: Logger مخصص. الافتراضي هو `console`.
149
+ */
150
+ logger?: Logger;
151
+ /**
152
+ * ✨ NEW: مصفوفة من البرمجيات الوسيطة (Middleware) لتشغيلها مع كل طلب.
153
+ */
154
+ middleware?: Middleware[];
131
155
  }
132
156
  /**
133
157
  * يمثل بنية الاستجابة القياسية من الـ API.
@@ -191,6 +215,7 @@ interface ActionOptions {
191
215
  * update('123', { status: 'active' }, { endpoint: '/items/123/activate' })
192
216
  */
193
217
  endpoint?: string;
218
+ refetch?: boolean;
194
219
  }
195
220
  /**
196
221
  * واجهة لتهيئة الهوك `useApi`.
@@ -213,21 +238,41 @@ interface UseApiConfig<T> {
213
238
 
214
239
  /**
215
240
  * @file src/core/client.ts
216
- * @description
217
- * هذا هو القلب النابض للمكتبة. دالة `createApiClient` تنشئ وتهيئ نسخة Axios
218
- * مع معترضات (interceptors) ذكية لإدارة التوكنات، بما في ذلك التجديد التلقائي
219
- * والاستباقي للتوكن، ومعالجة الأخطاء بشكل مركزي.
241
+ * @description This is the heart of the API client library.
242
+ * The `createApiClient` function constructs and configures a sophisticated Axios instance.
243
+ * It features intelligent, security-first token management, a flexible middleware system,
244
+ * and customizable logging, all designed to work seamlessly in modern web frameworks like Next.js.
220
245
  */
221
246
 
247
+ /**
248
+ * Creates and configures a new Axios instance with advanced features.
249
+ *
250
+ * This factory function is the central entry point of the library. It sets up
251
+ * request and response interceptors to handle:
252
+ * - Secure token management (supporting httpOnly and client-side storage).
253
+ * - A flexible middleware pipeline for custom logic.
254
+ * - Centralized logging for observability.
255
+ * - Automatic token refresh for client-side scenarios.
256
+ *
257
+ * @param {ApiClientConfig} config - The configuration object for the API client.
258
+ * @returns {AxiosInstance} A fully configured Axios instance ready for use.
259
+ */
222
260
  declare function createApiClient(config: ApiClientConfig): AxiosInstance;
223
261
 
224
262
  type CrudRequestConfig = RequestConfig & ActionOptions;
263
+ /**
264
+ * دالة مصنع (Factory Function) لإنشاء مجموعة خدمات API قابلة لإعادة الاستخدام لنقطة نهاية (endpoint) محددة.
265
+ * توفر عمليات CRUD كاملة بالإضافة إلى ميزات متقدمة مثل الحذف الجماعي ورفع الملفات.
266
+ */
225
267
  declare function createApiServices<T>(axiosInstance: AxiosInstance, endpoint: string): {
226
268
  get: (id?: string, config?: RequestConfig) => Promise<StandardResponse<T | T[]>>;
227
269
  getWithQuery: (query: string, config?: RequestConfig) => Promise<StandardResponse<T[]>>;
228
270
  post: (data: Partial<T>, config?: CrudRequestConfig) => Promise<StandardResponse<T>>;
271
+ put: (id: string, data: T, config?: CrudRequestConfig) => Promise<StandardResponse<T>>;
229
272
  patch: (id: string, data: Partial<T>, config?: CrudRequestConfig) => Promise<StandardResponse<T>>;
230
273
  remove: (id: string, config?: CrudRequestConfig) => Promise<StandardResponse<any>>;
274
+ bulkDelete: (ids: string[], config?: CrudRequestConfig) => Promise<StandardResponse<any>>;
275
+ upload: (file: File, additionalData?: Record<string, any>, config?: CrudRequestConfig) => Promise<StandardResponse<any>>;
231
276
  };
232
277
 
233
278
  /**
@@ -275,6 +320,7 @@ declare function createApiActions<TActions extends Record<string, {
275
320
  endpoint: string;
276
321
  requestType: any;
277
322
  responseType: any;
323
+ log?: boolean;
278
324
  }>>(axiosInstance: AxiosInstance, actionsConfig: TActions): {
279
325
  [K in keyof TActions]: ApiAction<TActions[K]['requestType'], TActions[K]['responseType']>;
280
326
  };
@@ -298,7 +344,7 @@ declare const cacheManager: CacheManager;
298
344
  * @param responseOrError The raw Axios response or a pre-processed ApiError.
299
345
  * @returns A standardized `StandardResponse` object with direct access to `.data`.
300
346
  */
301
- declare const processResponse: <T>(responseOrError: AxiosResponse<any> | ApiError) => StandardResponse<T>;
347
+ declare const processResponse: <T>(responseOrError: AxiosResponse<any> | ApiError, log?: boolean) => StandardResponse<T>;
302
348
 
303
349
  declare function useApi<T extends {
304
350
  id?: string | number;
@@ -308,8 +354,11 @@ declare function useApi<T extends {
308
354
  actions: {
309
355
  fetch: (options?: QueryOptions) => Promise<void>;
310
356
  create: (newItem: Partial<T>, options?: ActionOptions) => Promise<StandardResponse<T>>;
357
+ put: (id: string, item: T, options?: ActionOptions) => Promise<StandardResponse<T>>;
311
358
  update: (id: string, updatedItem: Partial<T>, options?: ActionOptions) => Promise<StandardResponse<T>>;
312
359
  remove: (id: string, options?: ActionOptions) => Promise<StandardResponse<any>>;
360
+ bulkRemove: (ids: string[], options?: ActionOptions) => Promise<StandardResponse<any>>;
361
+ upload: (file: File, additionalData?: Record<string, any>, options?: ActionOptions) => Promise<StandardResponse<any>>;
313
362
  };
314
363
  query: {
315
364
  options: QueryOptions;
@@ -327,4 +376,4 @@ declare function useApi<T extends {
327
376
  };
328
377
  };
329
378
 
330
- export { type ActionOptions, type ApiClientConfig, type ApiError, type ApiResponse, type PaginateQueryOptions, type PaginationMeta, type QueryOptions, type RefreshTokenConfig, type RequestConfig, type StandardResponse, type TokenManager, type Tokens, type UseApiConfig, type ValidationError, buildPaginateQuery, cacheManager, createApiActions, createApiClient, createApiServices, processResponse, useApi };
379
+ export { type ActionOptions, type ApiClientConfig, type ApiError, type ApiResponse, type Logger, type Middleware, type MiddlewareContext, type PaginateQueryOptions, type PaginationMeta, type QueryOptions, type RefreshTokenConfig, type RequestConfig, type StandardResponse, type TokenManager, type Tokens, type UseApiConfig, type ValidationError, buildPaginateQuery, cacheManager, createApiActions, createApiClient, createApiServices, processResponse, useApi };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ResponseType, AxiosRequestConfig, AxiosProgressEvent, AxiosInstance, Method, AxiosResponse } from 'axios';
1
+ import { InternalAxiosRequestConfig, AxiosResponse, AxiosRequestConfig, AxiosProgressEvent, AxiosInstance, Method } from 'axios';
2
2
  import * as react from 'react';
3
3
 
4
4
  /**
@@ -35,28 +35,49 @@ interface ApiError {
35
35
  errors?: ValidationError[];
36
36
  requestId?: string;
37
37
  }
38
- /**
39
- * يمثل مجموعة التوكنات التي يتم إدارتها.
40
- */
41
38
  interface Tokens {
42
39
  accessToken: string | null;
43
40
  refreshToken: string | null;
44
41
  expiresAt?: number;
45
42
  tokenType?: string;
46
43
  }
47
- /**
48
- * واجهة لمدير التوكنات، تسمح بفصل منطق تخزين التوكن.
49
- * يمكن للمستخدم توفير تطبيق لهذه الواجهة (e.g., LocalStorage, Cookies).
50
- */
51
44
  interface TokenManager {
52
45
  getTokens(): Promise<Tokens>;
53
46
  setTokens(tokens: Tokens): Promise<void>;
54
47
  clearTokens(): Promise<void>;
48
+ /**
49
+ * دالة لتحديد سياق التشغيل.
50
+ * true إذا كانت التوكنات في كوكيز httpOnly (الوضع الآمن).
51
+ * false إذا كانت في مكان يمكن للعميل الوصول إليه (للتطبيقات غير Next.js).
52
+ */
55
53
  isHttpOnly(): boolean;
56
54
  }
57
55
  /**
58
- * يوسع إعدادات طلب Axios القياسية بخيارات خاصة بالمكتبة.
56
+ * واجهة لـ Logger مخصص. متوافقة مع `console`.
57
+ */
58
+ interface Logger {
59
+ log(message?: any, ...optionalParams: any[]): void;
60
+ info(message?: any, ...optionalParams: any[]): void;
61
+ warn(message?: any, ...optionalParams: any[]): void;
62
+ error(message?: any, ...optionalParams: any[]): void;
63
+ }
64
+ /**
65
+ * سياق البرمجيات الوسيطة الذي يتم تمريره لكل دالة.
59
66
  */
67
+ interface MiddlewareContext {
68
+ req: InternalAxiosRequestConfig;
69
+ res?: AxiosResponse;
70
+ error?: any;
71
+ logger: Logger;
72
+ custom?: Record<string, any>;
73
+ }
74
+ /**
75
+ * دالة برمجية وسيطة (Middleware Function).
76
+ */
77
+ type Middleware = (context: MiddlewareContext, next: () => Promise<void>) => Promise<void>;
78
+ interface RequestConfig extends AxiosRequestConfig {
79
+ isPublic?: boolean;
80
+ }
60
81
  interface RequestConfig extends AxiosRequestConfig {
61
82
  cancelTokenKey?: string;
62
83
  onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
@@ -113,7 +134,7 @@ interface RefreshTokenConfig {
113
134
  };
114
135
  }
115
136
  /**
116
- * الواجهة الرئيسية لتهيئة عميل الـ API.
137
+ * الواجهة الرئيسية لتهيئة عميل الـ API (محدثة بالكامل).
117
138
  */
118
139
  interface ApiClientConfig {
119
140
  baseURL?: string;
@@ -121,13 +142,16 @@ interface ApiClientConfig {
121
142
  timeout?: number;
122
143
  headers?: Record<string, string>;
123
144
  withCredentials?: boolean;
124
- responseType?: ResponseType;
125
- /**
126
- * إعدادات مخصصة لعملية تجديد التوكن.
127
- * إذا لم يتم توفيرها، ستتوقف محاولات التجديد التلقائي.
128
- */
129
145
  refreshTokenConfig?: RefreshTokenConfig;
130
146
  onRefreshError?: (error: any) => void;
147
+ /**
148
+ * ✨ NEW: Logger مخصص. الافتراضي هو `console`.
149
+ */
150
+ logger?: Logger;
151
+ /**
152
+ * ✨ NEW: مصفوفة من البرمجيات الوسيطة (Middleware) لتشغيلها مع كل طلب.
153
+ */
154
+ middleware?: Middleware[];
131
155
  }
132
156
  /**
133
157
  * يمثل بنية الاستجابة القياسية من الـ API.
@@ -191,6 +215,7 @@ interface ActionOptions {
191
215
  * update('123', { status: 'active' }, { endpoint: '/items/123/activate' })
192
216
  */
193
217
  endpoint?: string;
218
+ refetch?: boolean;
194
219
  }
195
220
  /**
196
221
  * واجهة لتهيئة الهوك `useApi`.
@@ -213,21 +238,41 @@ interface UseApiConfig<T> {
213
238
 
214
239
  /**
215
240
  * @file src/core/client.ts
216
- * @description
217
- * هذا هو القلب النابض للمكتبة. دالة `createApiClient` تنشئ وتهيئ نسخة Axios
218
- * مع معترضات (interceptors) ذكية لإدارة التوكنات، بما في ذلك التجديد التلقائي
219
- * والاستباقي للتوكن، ومعالجة الأخطاء بشكل مركزي.
241
+ * @description This is the heart of the API client library.
242
+ * The `createApiClient` function constructs and configures a sophisticated Axios instance.
243
+ * It features intelligent, security-first token management, a flexible middleware system,
244
+ * and customizable logging, all designed to work seamlessly in modern web frameworks like Next.js.
220
245
  */
221
246
 
247
+ /**
248
+ * Creates and configures a new Axios instance with advanced features.
249
+ *
250
+ * This factory function is the central entry point of the library. It sets up
251
+ * request and response interceptors to handle:
252
+ * - Secure token management (supporting httpOnly and client-side storage).
253
+ * - A flexible middleware pipeline for custom logic.
254
+ * - Centralized logging for observability.
255
+ * - Automatic token refresh for client-side scenarios.
256
+ *
257
+ * @param {ApiClientConfig} config - The configuration object for the API client.
258
+ * @returns {AxiosInstance} A fully configured Axios instance ready for use.
259
+ */
222
260
  declare function createApiClient(config: ApiClientConfig): AxiosInstance;
223
261
 
224
262
  type CrudRequestConfig = RequestConfig & ActionOptions;
263
+ /**
264
+ * دالة مصنع (Factory Function) لإنشاء مجموعة خدمات API قابلة لإعادة الاستخدام لنقطة نهاية (endpoint) محددة.
265
+ * توفر عمليات CRUD كاملة بالإضافة إلى ميزات متقدمة مثل الحذف الجماعي ورفع الملفات.
266
+ */
225
267
  declare function createApiServices<T>(axiosInstance: AxiosInstance, endpoint: string): {
226
268
  get: (id?: string, config?: RequestConfig) => Promise<StandardResponse<T | T[]>>;
227
269
  getWithQuery: (query: string, config?: RequestConfig) => Promise<StandardResponse<T[]>>;
228
270
  post: (data: Partial<T>, config?: CrudRequestConfig) => Promise<StandardResponse<T>>;
271
+ put: (id: string, data: T, config?: CrudRequestConfig) => Promise<StandardResponse<T>>;
229
272
  patch: (id: string, data: Partial<T>, config?: CrudRequestConfig) => Promise<StandardResponse<T>>;
230
273
  remove: (id: string, config?: CrudRequestConfig) => Promise<StandardResponse<any>>;
274
+ bulkDelete: (ids: string[], config?: CrudRequestConfig) => Promise<StandardResponse<any>>;
275
+ upload: (file: File, additionalData?: Record<string, any>, config?: CrudRequestConfig) => Promise<StandardResponse<any>>;
231
276
  };
232
277
 
233
278
  /**
@@ -275,6 +320,7 @@ declare function createApiActions<TActions extends Record<string, {
275
320
  endpoint: string;
276
321
  requestType: any;
277
322
  responseType: any;
323
+ log?: boolean;
278
324
  }>>(axiosInstance: AxiosInstance, actionsConfig: TActions): {
279
325
  [K in keyof TActions]: ApiAction<TActions[K]['requestType'], TActions[K]['responseType']>;
280
326
  };
@@ -298,7 +344,7 @@ declare const cacheManager: CacheManager;
298
344
  * @param responseOrError The raw Axios response or a pre-processed ApiError.
299
345
  * @returns A standardized `StandardResponse` object with direct access to `.data`.
300
346
  */
301
- declare const processResponse: <T>(responseOrError: AxiosResponse<any> | ApiError) => StandardResponse<T>;
347
+ declare const processResponse: <T>(responseOrError: AxiosResponse<any> | ApiError, log?: boolean) => StandardResponse<T>;
302
348
 
303
349
  declare function useApi<T extends {
304
350
  id?: string | number;
@@ -308,8 +354,11 @@ declare function useApi<T extends {
308
354
  actions: {
309
355
  fetch: (options?: QueryOptions) => Promise<void>;
310
356
  create: (newItem: Partial<T>, options?: ActionOptions) => Promise<StandardResponse<T>>;
357
+ put: (id: string, item: T, options?: ActionOptions) => Promise<StandardResponse<T>>;
311
358
  update: (id: string, updatedItem: Partial<T>, options?: ActionOptions) => Promise<StandardResponse<T>>;
312
359
  remove: (id: string, options?: ActionOptions) => Promise<StandardResponse<any>>;
360
+ bulkRemove: (ids: string[], options?: ActionOptions) => Promise<StandardResponse<any>>;
361
+ upload: (file: File, additionalData?: Record<string, any>, options?: ActionOptions) => Promise<StandardResponse<any>>;
313
362
  };
314
363
  query: {
315
364
  options: QueryOptions;
@@ -327,4 +376,4 @@ declare function useApi<T extends {
327
376
  };
328
377
  };
329
378
 
330
- export { type ActionOptions, type ApiClientConfig, type ApiError, type ApiResponse, type PaginateQueryOptions, type PaginationMeta, type QueryOptions, type RefreshTokenConfig, type RequestConfig, type StandardResponse, type TokenManager, type Tokens, type UseApiConfig, type ValidationError, buildPaginateQuery, cacheManager, createApiActions, createApiClient, createApiServices, processResponse, useApi };
379
+ export { type ActionOptions, type ApiClientConfig, type ApiError, type ApiResponse, type Logger, type Middleware, type MiddlewareContext, type PaginateQueryOptions, type PaginationMeta, type QueryOptions, type RefreshTokenConfig, type RequestConfig, type StandardResponse, type TokenManager, type Tokens, type UseApiConfig, type ValidationError, buildPaginateQuery, cacheManager, createApiActions, createApiClient, createApiServices, processResponse, useApi };
package/dist/index.js CHANGED
@@ -43,10 +43,20 @@ module.exports = __toCommonJS(index_exports);
43
43
  // src/core/client.ts
44
44
  var import_axios = __toESM(require("axios"));
45
45
  var import_uuid = require("uuid");
46
- async function refreshToken(config, tokenManager) {
46
+ async function runMiddleware(context, middlewares = []) {
47
+ const run = async (index) => {
48
+ if (index >= middlewares.length) {
49
+ return;
50
+ }
51
+ const middleware = middlewares[index];
52
+ await middleware(context, () => run(index + 1));
53
+ };
54
+ await run(0);
55
+ }
56
+ async function refreshToken(config, tokenManager, logger) {
47
57
  const { refreshTokenConfig } = config;
48
58
  if (!refreshTokenConfig) {
49
- console.warn("[API Core] Refresh token process is disabled (refreshTokenConfig is not provided).");
59
+ logger.warn("[API Core] Token refresh is disabled: `refreshTokenConfig` is not provided.");
50
60
  await tokenManager.clearTokens();
51
61
  return null;
52
62
  }
@@ -58,6 +68,7 @@ async function refreshToken(config, tokenManager) {
58
68
  const { path, buildRequestBody, buildRequestHeaders, extractTokens } = refreshTokenConfig;
59
69
  const requestBody = buildRequestBody ? buildRequestBody(currentTokens.refreshToken) : { refresh_token: currentTokens.refreshToken };
60
70
  const requestHeaders = buildRequestHeaders ? buildRequestHeaders(currentTokens) : {};
71
+ logger.info(`[API Core] Attempting to refresh token at path: ${path}`);
61
72
  const response = await import_axios.default.post(
62
73
  `${config.baseURL}${path}`,
63
74
  requestBody,
@@ -73,15 +84,14 @@ async function refreshToken(config, tokenManager) {
73
84
  const newTokens = {
74
85
  accessToken: extracted.accessToken,
75
86
  refreshToken: extracted.refreshToken || currentTokens.refreshToken,
76
- // احتفظ بالقديم إذا لم يأتِ جديد
77
87
  expiresAt: Date.now() + extracted.expiresIn * 1e3,
78
88
  tokenType: extracted.tokenType || "Bearer"
79
89
  };
80
90
  await tokenManager.setTokens(newTokens);
81
- console.log("[API Core] Tokens refreshed successfully.");
91
+ logger.info("[API Core] Tokens refreshed successfully.");
82
92
  return newTokens;
83
93
  } catch (err) {
84
- console.error("[API Core] Failed to refresh token.", err.response?.data || err.message);
94
+ logger.error("[API Core] Failed to refresh token.", err.response?.data || err.message);
85
95
  if (config.onRefreshError) {
86
96
  config.onRefreshError(err);
87
97
  }
@@ -95,10 +105,9 @@ function createApiClient(config) {
95
105
  tokenManager,
96
106
  timeout = 15e3,
97
107
  headers = {},
98
- withCredentials = false
99
- // onRefreshError = () => {},
100
- // responseType = 'json',
101
- // refreshTokenConfig,
108
+ withCredentials = false,
109
+ middleware = [],
110
+ logger = console
102
111
  } = config;
103
112
  const axiosInstance = import_axios.default.create({
104
113
  baseURL,
@@ -106,63 +115,88 @@ function createApiClient(config) {
106
115
  headers: { "Content-Type": "application/json", ...headers },
107
116
  withCredentials
108
117
  });
109
- let tokenRefreshPromise = null;
118
+ let isRefreshing = false;
119
+ let failedQueue = [];
120
+ const processQueue = (error, token = null) => {
121
+ failedQueue.forEach((prom) => error ? prom.reject(error) : prom.resolve(token));
122
+ failedQueue = [];
123
+ };
110
124
  axiosInstance.interceptors.request.use(async (req) => {
111
125
  req.headers["X-Request-ID"] = (0, import_uuid.v4)();
126
+ logger.info(`[Request] > ${req.method?.toUpperCase()} ${req.url}`, { id: req.headers["X-Request-ID"] });
127
+ const context = { req, logger, custom: {} };
128
+ await runMiddleware(context, middleware);
112
129
  if (req.isPublic) {
113
- console.log(`[API Core] Skipping token for public request: ${req.url}`);
114
130
  return req;
115
131
  }
116
- if (tokenRefreshPromise) {
117
- await tokenRefreshPromise;
132
+ if (tokenManager.isHttpOnly()) {
133
+ return req;
118
134
  }
119
135
  let tokens = await tokenManager.getTokens();
120
136
  const now = Date.now();
121
137
  const tokenBuffer = 60 * 1e3;
122
- if (tokens.accessToken && tokens.expiresAt && tokens.expiresAt - now < tokenBuffer) {
123
- if (!tokenRefreshPromise && config.refreshTokenConfig) {
124
- console.log("[API Core] Proactive token refresh initiated.");
125
- tokenRefreshPromise = refreshToken(config, tokenManager);
138
+ if (tokens.accessToken && tokens.expiresAt && tokens.expiresAt - now < tokenBuffer && !isRefreshing) {
139
+ if (config.refreshTokenConfig) {
140
+ logger.info("[API Core] Proactive token refresh initiated.");
141
+ isRefreshing = true;
142
+ try {
143
+ const newTokens = await refreshToken(config, tokenManager, logger);
144
+ if (newTokens) tokens = newTokens;
145
+ } finally {
146
+ isRefreshing = false;
147
+ }
126
148
  }
127
- const newTokens = await tokenRefreshPromise;
128
- tokenRefreshPromise = null;
129
- if (newTokens) tokens = newTokens;
130
149
  }
131
- if (tokens.accessToken && !tokenManager.isHttpOnly()) {
132
- const tokenType = tokens.tokenType || "Bearer";
133
- req.headers.Authorization = `${tokenType} ${tokens.accessToken}`;
134
- console.log(`[API Core] Token attached to request: ${req.url}`);
135
- } else {
136
- console.warn(`[API Core] No token attached for request: ${req.url}`);
150
+ if (tokens.accessToken) {
151
+ req.headers.Authorization = `${tokens.tokenType || "Bearer"} ${tokens.accessToken}`;
137
152
  }
138
153
  return req;
139
- }, (error) => Promise.reject(error));
154
+ });
140
155
  axiosInstance.interceptors.response.use(
141
- (response) => response,
156
+ async (res) => {
157
+ logger.info(`[Response] < ${res.config.method?.toUpperCase()} ${res.config.url}`, { id: res.config.headers["X-Request-ID"], status: res.status });
158
+ const context = { req: res.config, res, logger, custom: {} };
159
+ await runMiddleware(context, middleware);
160
+ return res;
161
+ },
142
162
  async (error) => {
143
163
  const originalRequest = error.config;
144
- if (error.response?.status === 401 && !originalRequest._retry) {
145
- originalRequest._retry = true;
146
- if (!tokenRefreshPromise && config.refreshTokenConfig) {
147
- console.log("[API Core] Reactive token refresh initiated due to 401.");
148
- tokenRefreshPromise = refreshToken(config, tokenManager);
164
+ logger.error(`[Response Error] < ${originalRequest.method?.toUpperCase()} ${originalRequest.url}`, { id: originalRequest.headers?.["X-Request-ID"], status: error.response?.status, error: error.message });
165
+ const context = { req: originalRequest, error, logger, custom: {} };
166
+ await runMiddleware(context, middleware);
167
+ const isHttpOnlyMode = tokenManager.isHttpOnly();
168
+ const canAttemptRefresh = error.response?.status === 401 && !originalRequest._retry && config.refreshTokenConfig;
169
+ if (isHttpOnlyMode && canAttemptRefresh) {
170
+ logger.error("[API Core] Fatal: Received 401 in httpOnly mode. Token refresh must be handled by the server-side proxy. The request will not be retried from the client.");
171
+ return Promise.reject(error);
172
+ }
173
+ if (!isHttpOnlyMode && canAttemptRefresh) {
174
+ if (isRefreshing) {
175
+ return new Promise((resolve, reject) => {
176
+ failedQueue.push({ resolve, reject });
177
+ }).then((token) => {
178
+ originalRequest.headers["Authorization"] = `Bearer ${token}`;
179
+ return axiosInstance(originalRequest);
180
+ });
149
181
  }
150
- const newTokens = await tokenRefreshPromise;
151
- tokenRefreshPromise = null;
152
- if (newTokens) {
153
- if (newTokens.accessToken && !tokenManager.isHttpOnly()) {
154
- originalRequest.headers.Authorization = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
182
+ originalRequest._retry = true;
183
+ isRefreshing = true;
184
+ try {
185
+ const newTokens = await refreshToken(config, tokenManager, logger);
186
+ if (!newTokens || !newTokens.accessToken) {
187
+ throw new Error("Token refresh failed to produce a new access token.");
155
188
  }
189
+ processQueue(null, newTokens.accessToken);
190
+ originalRequest.headers["Authorization"] = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
156
191
  return axiosInstance(originalRequest);
192
+ } catch (refreshError) {
193
+ processQueue(refreshError, null);
194
+ return Promise.reject(refreshError);
195
+ } finally {
196
+ isRefreshing = false;
157
197
  }
158
198
  }
159
- const enhancedError = {
160
- message: error.response?.data?.message || error.message,
161
- status: error.response?.status || 500,
162
- errors: error.response?.data?.errors,
163
- requestId: originalRequest.headers?.["X-Request-ID"]
164
- };
165
- return Promise.reject(enhancedError);
199
+ return Promise.reject(error);
166
200
  }
167
201
  );
168
202
  return axiosInstance;
@@ -204,8 +238,10 @@ function isAxiosResponse(obj) {
204
238
  }
205
239
 
206
240
  // src/core/processor.ts
207
- var processResponse = (responseOrError) => {
241
+ var processResponse = (responseOrError, log) => {
208
242
  if (isApiError(responseOrError)) {
243
+ log && console.log(`****[ API ERROR PROCESS RESPONSE ]: ***
244
+ ${responseOrError} *** `);
209
245
  return {
210
246
  data: null,
211
247
  rawResponse: void 0,
@@ -217,15 +253,17 @@ var processResponse = (responseOrError) => {
217
253
  };
218
254
  }
219
255
  if (isAxiosResponse(responseOrError)) {
256
+ log && console.log(`****[ API IS SCSS PROCESS RESPONSE ]: ***
257
+ ${responseOrError} *** `);
220
258
  const rawData = responseOrError.data;
221
259
  const isWrappedResponse = rawData && typeof rawData.success === "boolean" && rawData.data !== void 0;
260
+ log && console.log(`12345[ API IS WRAPPED DATA PROCESS RESPONSE ]: ***
261
+ ${isWrappedResponse} *** `);
222
262
  const finalData = isWrappedResponse ? rawData.data : rawData;
223
263
  const message = isWrappedResponse ? rawData.message : "Request successful.";
224
264
  return {
225
265
  data: finalData,
226
- // <-- وصول مباشر للبيانات النهائية!
227
266
  rawResponse: rawData,
228
- // احتفظ بالاستجابة الكاملة إذا احتجت إليها
229
267
  loading: false,
230
268
  success: true,
231
269
  error: null,
@@ -233,6 +271,7 @@ var processResponse = (responseOrError) => {
233
271
  validationErrors: []
234
272
  };
235
273
  }
274
+ log && console.log(`****[ API STATUS 500 ]: *** ${responseOrError} *** `);
236
275
  return {
237
276
  data: null,
238
277
  rawResponse: void 0,
@@ -270,6 +309,15 @@ function createApiServices(axiosInstance, endpoint) {
270
309
  return processResponse(error);
271
310
  }
272
311
  };
312
+ const put = async (id, data, config) => {
313
+ const finalUrl = config?.endpoint || `${endpoint}/${id}`;
314
+ try {
315
+ const response = await axiosInstance.put(finalUrl, data, config);
316
+ return processResponse(response);
317
+ } catch (error) {
318
+ return processResponse(error);
319
+ }
320
+ };
273
321
  const patch = async (id, data, config) => {
274
322
  const finalUrl = config?.endpoint || `${endpoint}/${id}`;
275
323
  try {
@@ -288,23 +336,57 @@ function createApiServices(axiosInstance, endpoint) {
288
336
  return processResponse(error);
289
337
  }
290
338
  };
291
- return { get, getWithQuery, post, patch, remove };
339
+ const bulkDelete = async (ids, config) => {
340
+ const finalUrl = config?.endpoint || `${endpoint}`;
341
+ try {
342
+ const response = await axiosInstance.delete(finalUrl, { data: { ids }, ...config });
343
+ return processResponse(response);
344
+ } catch (error) {
345
+ return processResponse(error);
346
+ }
347
+ };
348
+ const upload = async (file, additionalData, config) => {
349
+ const finalUrl = config?.endpoint || `${endpoint}`;
350
+ const formData = new FormData();
351
+ formData.append("file", file);
352
+ if (additionalData) {
353
+ Object.keys(additionalData).forEach((key) => {
354
+ formData.append(key, additionalData[key]);
355
+ });
356
+ }
357
+ try {
358
+ const response = await axiosInstance.post(finalUrl, formData, {
359
+ ...config,
360
+ headers: {
361
+ ...config?.headers,
362
+ "Content-Type": "multipart/form-data"
363
+ }
364
+ });
365
+ return processResponse(response);
366
+ } catch (error) {
367
+ return processResponse(error);
368
+ }
369
+ };
370
+ return { get, getWithQuery, post, put, patch, remove, bulkDelete, upload };
292
371
  }
293
372
 
294
373
  // src/services/actions.ts
295
- function createAction(axiosInstance, method, endpoint) {
374
+ function createAction(axiosInstance, method, endpoint, log) {
296
375
  return async (payload, config) => {
376
+ log && console.log(`*[ ${method.toUpperCase()} ACTION API TO ENDPOINT ]: ${endpoint} **** [${payload}]** `);
297
377
  try {
378
+ log && console.log(`**[ ${method.toUpperCase()} ACTION API TO ENDPOINT ]:${endpoint} **** [action try req]** `);
298
379
  const response = await axiosInstance.request({
299
- // <--- غيرنا النوع إلى any
300
380
  url: endpoint,
301
381
  method,
302
382
  ...method.toUpperCase() === "GET" ? { params: payload } : { data: payload },
303
383
  ...config
304
384
  });
305
- return processResponse(response);
385
+ log && console.log(`***[ ${method.toUpperCase()} ACTION API TO ENDPOINT ]: ${endpoint} ***
386
+ ${response}*** `);
387
+ return processResponse(response, log);
306
388
  } catch (error) {
307
- return processResponse(error);
389
+ return processResponse(error, log);
308
390
  }
309
391
  };
310
392
  }
@@ -312,8 +394,8 @@ function createApiActions(axiosInstance, actionsConfig) {
312
394
  const actions = {};
313
395
  for (const actionName in actionsConfig) {
314
396
  if (Object.prototype.hasOwnProperty.call(actionsConfig, actionName)) {
315
- const { method, endpoint } = actionsConfig[actionName];
316
- actions[actionName] = createAction(axiosInstance, method, endpoint);
397
+ const { method, endpoint, log } = actionsConfig[actionName];
398
+ actions[actionName] = createAction(axiosInstance, method, endpoint, log);
317
399
  }
318
400
  }
319
401
  return actions;
@@ -379,7 +461,6 @@ function useApi(axiosInstance, config) {
379
461
  const result = await apiServices.getWithQuery(queryString, {
380
462
  cancelTokenKey: endpoint,
381
463
  ...requestConfig
382
- // <-- هنا نمرر الإعدادات الافتراضية
383
464
  });
384
465
  setState(result);
385
466
  if (!result.success && onError) {
@@ -404,6 +485,19 @@ function useApi(axiosInstance, config) {
404
485
  }
405
486
  return result;
406
487
  };
488
+ const putItem = async (id, item, options) => {
489
+ setState((prev) => ({ ...prev, loading: true }));
490
+ const result = await apiServices.put(id, item, options);
491
+ if (result.success) {
492
+ if (refetchAfterChange) await fetchData();
493
+ else setState((prev) => ({ ...prev, loading: false }));
494
+ if (onSuccess) onSuccess(result.message || "Item replaced successfully!", result.data);
495
+ } else {
496
+ setState((prev) => ({ ...prev, loading: false, error: result.error }));
497
+ if (onError) onError(result.message || "Replace failed", result.error || void 0);
498
+ }
499
+ return result;
500
+ };
407
501
  const updateItem = async (id, updatedItem, options) => {
408
502
  setState((prev) => ({ ...prev, loading: true }));
409
503
  const result = await apiServices.patch(id, updatedItem, options);
@@ -430,19 +524,39 @@ function useApi(axiosInstance, config) {
430
524
  }
431
525
  return result;
432
526
  };
527
+ const bulkDeleteItem = async (ids, options) => {
528
+ setState((prev) => ({ ...prev, loading: true }));
529
+ const result = await apiServices.bulkDelete(ids, options);
530
+ if (result.success) {
531
+ if (refetchAfterChange) await fetchData();
532
+ else setState((prev) => ({ ...prev, loading: false }));
533
+ if (onSuccess) onSuccess(result.message || "Items deleted successfully!");
534
+ } else {
535
+ setState((prev) => ({ ...prev, loading: false, error: result.error }));
536
+ if (onError) onError(result.message || "Bulk delete failed", result.error || void 0);
537
+ }
538
+ return result;
539
+ };
540
+ const uploadFile = async (file, additionalData, options) => {
541
+ setState((prev) => ({ ...prev, loading: true }));
542
+ const result = await apiServices.upload(file, additionalData, options);
543
+ if (result.success) {
544
+ if (refetchAfterChange && options?.refetch !== false) await fetchData();
545
+ else setState((prev) => ({ ...prev, loading: false }));
546
+ if (onSuccess) onSuccess(result.message || "File uploaded successfully!", result.data);
547
+ } else {
548
+ setState((prev) => ({ ...prev, loading: false, error: result.error }));
549
+ if (onError) onError(result.message || "Upload failed", result.error || void 0);
550
+ }
551
+ return result;
552
+ };
433
553
  const setPage = (page) => setQueryOptions((prev) => ({ ...prev, page }));
434
554
  const setLimit = (limit) => setQueryOptions((prev) => ({ ...prev, limit, page: 1 }));
435
555
  const setSearchTerm = (search) => setQueryOptions((prev) => ({ ...prev, search, page: 1 }));
436
556
  const setSorting = (sortBy) => setQueryOptions((prev) => ({ ...prev, sortBy }));
437
557
  const setFilters = (filter) => setQueryOptions((prev) => ({ ...prev, filter, page: 1 }));
438
558
  const setQueryParam = (key, value) => {
439
- setQueryOptions((prev) => {
440
- const newQuery = { ...prev, [key]: value };
441
- if (key !== "page") {
442
- newQuery.page = 1;
443
- }
444
- return newQuery;
445
- });
559
+ setQueryOptions((prev) => ({ ...prev, [key]: value, page: key !== "page" ? 1 : prev.page }));
446
560
  };
447
561
  const resetQuery = () => setQueryOptions(initialQuery);
448
562
  return {
@@ -451,8 +565,14 @@ function useApi(axiosInstance, config) {
451
565
  actions: {
452
566
  fetch: fetchData,
453
567
  create: createItem,
568
+ put: putItem,
569
+ // <-- NEW
454
570
  update: updateItem,
455
- remove: deleteItem
571
+ remove: deleteItem,
572
+ bulkRemove: bulkDeleteItem,
573
+ // <-- NEW
574
+ upload: uploadFile
575
+ // <-- NEW
456
576
  },
457
577
  query: {
458
578
  options: queryOptions,
@@ -463,7 +583,6 @@ function useApi(axiosInstance, config) {
463
583
  setSorting,
464
584
  setFilters,
465
585
  setQueryParam,
466
- // <-- NEW
467
586
  reset: resetQuery
468
587
  }
469
588
  };
package/dist/index.mjs CHANGED
@@ -1,10 +1,20 @@
1
1
  // src/core/client.ts
2
2
  import axios from "axios";
3
3
  import { v4 as uuidv4 } from "uuid";
4
- async function refreshToken(config, tokenManager) {
4
+ async function runMiddleware(context, middlewares = []) {
5
+ const run = async (index) => {
6
+ if (index >= middlewares.length) {
7
+ return;
8
+ }
9
+ const middleware = middlewares[index];
10
+ await middleware(context, () => run(index + 1));
11
+ };
12
+ await run(0);
13
+ }
14
+ async function refreshToken(config, tokenManager, logger) {
5
15
  const { refreshTokenConfig } = config;
6
16
  if (!refreshTokenConfig) {
7
- console.warn("[API Core] Refresh token process is disabled (refreshTokenConfig is not provided).");
17
+ logger.warn("[API Core] Token refresh is disabled: `refreshTokenConfig` is not provided.");
8
18
  await tokenManager.clearTokens();
9
19
  return null;
10
20
  }
@@ -16,6 +26,7 @@ async function refreshToken(config, tokenManager) {
16
26
  const { path, buildRequestBody, buildRequestHeaders, extractTokens } = refreshTokenConfig;
17
27
  const requestBody = buildRequestBody ? buildRequestBody(currentTokens.refreshToken) : { refresh_token: currentTokens.refreshToken };
18
28
  const requestHeaders = buildRequestHeaders ? buildRequestHeaders(currentTokens) : {};
29
+ logger.info(`[API Core] Attempting to refresh token at path: ${path}`);
19
30
  const response = await axios.post(
20
31
  `${config.baseURL}${path}`,
21
32
  requestBody,
@@ -31,15 +42,14 @@ async function refreshToken(config, tokenManager) {
31
42
  const newTokens = {
32
43
  accessToken: extracted.accessToken,
33
44
  refreshToken: extracted.refreshToken || currentTokens.refreshToken,
34
- // احتفظ بالقديم إذا لم يأتِ جديد
35
45
  expiresAt: Date.now() + extracted.expiresIn * 1e3,
36
46
  tokenType: extracted.tokenType || "Bearer"
37
47
  };
38
48
  await tokenManager.setTokens(newTokens);
39
- console.log("[API Core] Tokens refreshed successfully.");
49
+ logger.info("[API Core] Tokens refreshed successfully.");
40
50
  return newTokens;
41
51
  } catch (err) {
42
- console.error("[API Core] Failed to refresh token.", err.response?.data || err.message);
52
+ logger.error("[API Core] Failed to refresh token.", err.response?.data || err.message);
43
53
  if (config.onRefreshError) {
44
54
  config.onRefreshError(err);
45
55
  }
@@ -53,10 +63,9 @@ function createApiClient(config) {
53
63
  tokenManager,
54
64
  timeout = 15e3,
55
65
  headers = {},
56
- withCredentials = false
57
- // onRefreshError = () => {},
58
- // responseType = 'json',
59
- // refreshTokenConfig,
66
+ withCredentials = false,
67
+ middleware = [],
68
+ logger = console
60
69
  } = config;
61
70
  const axiosInstance = axios.create({
62
71
  baseURL,
@@ -64,63 +73,88 @@ function createApiClient(config) {
64
73
  headers: { "Content-Type": "application/json", ...headers },
65
74
  withCredentials
66
75
  });
67
- let tokenRefreshPromise = null;
76
+ let isRefreshing = false;
77
+ let failedQueue = [];
78
+ const processQueue = (error, token = null) => {
79
+ failedQueue.forEach((prom) => error ? prom.reject(error) : prom.resolve(token));
80
+ failedQueue = [];
81
+ };
68
82
  axiosInstance.interceptors.request.use(async (req) => {
69
83
  req.headers["X-Request-ID"] = uuidv4();
84
+ logger.info(`[Request] > ${req.method?.toUpperCase()} ${req.url}`, { id: req.headers["X-Request-ID"] });
85
+ const context = { req, logger, custom: {} };
86
+ await runMiddleware(context, middleware);
70
87
  if (req.isPublic) {
71
- console.log(`[API Core] Skipping token for public request: ${req.url}`);
72
88
  return req;
73
89
  }
74
- if (tokenRefreshPromise) {
75
- await tokenRefreshPromise;
90
+ if (tokenManager.isHttpOnly()) {
91
+ return req;
76
92
  }
77
93
  let tokens = await tokenManager.getTokens();
78
94
  const now = Date.now();
79
95
  const tokenBuffer = 60 * 1e3;
80
- if (tokens.accessToken && tokens.expiresAt && tokens.expiresAt - now < tokenBuffer) {
81
- if (!tokenRefreshPromise && config.refreshTokenConfig) {
82
- console.log("[API Core] Proactive token refresh initiated.");
83
- tokenRefreshPromise = refreshToken(config, tokenManager);
96
+ if (tokens.accessToken && tokens.expiresAt && tokens.expiresAt - now < tokenBuffer && !isRefreshing) {
97
+ if (config.refreshTokenConfig) {
98
+ logger.info("[API Core] Proactive token refresh initiated.");
99
+ isRefreshing = true;
100
+ try {
101
+ const newTokens = await refreshToken(config, tokenManager, logger);
102
+ if (newTokens) tokens = newTokens;
103
+ } finally {
104
+ isRefreshing = false;
105
+ }
84
106
  }
85
- const newTokens = await tokenRefreshPromise;
86
- tokenRefreshPromise = null;
87
- if (newTokens) tokens = newTokens;
88
107
  }
89
- if (tokens.accessToken && !tokenManager.isHttpOnly()) {
90
- const tokenType = tokens.tokenType || "Bearer";
91
- req.headers.Authorization = `${tokenType} ${tokens.accessToken}`;
92
- console.log(`[API Core] Token attached to request: ${req.url}`);
93
- } else {
94
- console.warn(`[API Core] No token attached for request: ${req.url}`);
108
+ if (tokens.accessToken) {
109
+ req.headers.Authorization = `${tokens.tokenType || "Bearer"} ${tokens.accessToken}`;
95
110
  }
96
111
  return req;
97
- }, (error) => Promise.reject(error));
112
+ });
98
113
  axiosInstance.interceptors.response.use(
99
- (response) => response,
114
+ async (res) => {
115
+ logger.info(`[Response] < ${res.config.method?.toUpperCase()} ${res.config.url}`, { id: res.config.headers["X-Request-ID"], status: res.status });
116
+ const context = { req: res.config, res, logger, custom: {} };
117
+ await runMiddleware(context, middleware);
118
+ return res;
119
+ },
100
120
  async (error) => {
101
121
  const originalRequest = error.config;
102
- if (error.response?.status === 401 && !originalRequest._retry) {
103
- originalRequest._retry = true;
104
- if (!tokenRefreshPromise && config.refreshTokenConfig) {
105
- console.log("[API Core] Reactive token refresh initiated due to 401.");
106
- tokenRefreshPromise = refreshToken(config, tokenManager);
122
+ logger.error(`[Response Error] < ${originalRequest.method?.toUpperCase()} ${originalRequest.url}`, { id: originalRequest.headers?.["X-Request-ID"], status: error.response?.status, error: error.message });
123
+ const context = { req: originalRequest, error, logger, custom: {} };
124
+ await runMiddleware(context, middleware);
125
+ const isHttpOnlyMode = tokenManager.isHttpOnly();
126
+ const canAttemptRefresh = error.response?.status === 401 && !originalRequest._retry && config.refreshTokenConfig;
127
+ if (isHttpOnlyMode && canAttemptRefresh) {
128
+ logger.error("[API Core] Fatal: Received 401 in httpOnly mode. Token refresh must be handled by the server-side proxy. The request will not be retried from the client.");
129
+ return Promise.reject(error);
130
+ }
131
+ if (!isHttpOnlyMode && canAttemptRefresh) {
132
+ if (isRefreshing) {
133
+ return new Promise((resolve, reject) => {
134
+ failedQueue.push({ resolve, reject });
135
+ }).then((token) => {
136
+ originalRequest.headers["Authorization"] = `Bearer ${token}`;
137
+ return axiosInstance(originalRequest);
138
+ });
107
139
  }
108
- const newTokens = await tokenRefreshPromise;
109
- tokenRefreshPromise = null;
110
- if (newTokens) {
111
- if (newTokens.accessToken && !tokenManager.isHttpOnly()) {
112
- originalRequest.headers.Authorization = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
140
+ originalRequest._retry = true;
141
+ isRefreshing = true;
142
+ try {
143
+ const newTokens = await refreshToken(config, tokenManager, logger);
144
+ if (!newTokens || !newTokens.accessToken) {
145
+ throw new Error("Token refresh failed to produce a new access token.");
113
146
  }
147
+ processQueue(null, newTokens.accessToken);
148
+ originalRequest.headers["Authorization"] = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
114
149
  return axiosInstance(originalRequest);
150
+ } catch (refreshError) {
151
+ processQueue(refreshError, null);
152
+ return Promise.reject(refreshError);
153
+ } finally {
154
+ isRefreshing = false;
115
155
  }
116
156
  }
117
- const enhancedError = {
118
- message: error.response?.data?.message || error.message,
119
- status: error.response?.status || 500,
120
- errors: error.response?.data?.errors,
121
- requestId: originalRequest.headers?.["X-Request-ID"]
122
- };
123
- return Promise.reject(enhancedError);
157
+ return Promise.reject(error);
124
158
  }
125
159
  );
126
160
  return axiosInstance;
@@ -162,8 +196,10 @@ function isAxiosResponse(obj) {
162
196
  }
163
197
 
164
198
  // src/core/processor.ts
165
- var processResponse = (responseOrError) => {
199
+ var processResponse = (responseOrError, log) => {
166
200
  if (isApiError(responseOrError)) {
201
+ log && console.log(`****[ API ERROR PROCESS RESPONSE ]: ***
202
+ ${responseOrError} *** `);
167
203
  return {
168
204
  data: null,
169
205
  rawResponse: void 0,
@@ -175,15 +211,17 @@ var processResponse = (responseOrError) => {
175
211
  };
176
212
  }
177
213
  if (isAxiosResponse(responseOrError)) {
214
+ log && console.log(`****[ API IS SCSS PROCESS RESPONSE ]: ***
215
+ ${responseOrError} *** `);
178
216
  const rawData = responseOrError.data;
179
217
  const isWrappedResponse = rawData && typeof rawData.success === "boolean" && rawData.data !== void 0;
218
+ log && console.log(`12345[ API IS WRAPPED DATA PROCESS RESPONSE ]: ***
219
+ ${isWrappedResponse} *** `);
180
220
  const finalData = isWrappedResponse ? rawData.data : rawData;
181
221
  const message = isWrappedResponse ? rawData.message : "Request successful.";
182
222
  return {
183
223
  data: finalData,
184
- // <-- وصول مباشر للبيانات النهائية!
185
224
  rawResponse: rawData,
186
- // احتفظ بالاستجابة الكاملة إذا احتجت إليها
187
225
  loading: false,
188
226
  success: true,
189
227
  error: null,
@@ -191,6 +229,7 @@ var processResponse = (responseOrError) => {
191
229
  validationErrors: []
192
230
  };
193
231
  }
232
+ log && console.log(`****[ API STATUS 500 ]: *** ${responseOrError} *** `);
194
233
  return {
195
234
  data: null,
196
235
  rawResponse: void 0,
@@ -228,6 +267,15 @@ function createApiServices(axiosInstance, endpoint) {
228
267
  return processResponse(error);
229
268
  }
230
269
  };
270
+ const put = async (id, data, config) => {
271
+ const finalUrl = config?.endpoint || `${endpoint}/${id}`;
272
+ try {
273
+ const response = await axiosInstance.put(finalUrl, data, config);
274
+ return processResponse(response);
275
+ } catch (error) {
276
+ return processResponse(error);
277
+ }
278
+ };
231
279
  const patch = async (id, data, config) => {
232
280
  const finalUrl = config?.endpoint || `${endpoint}/${id}`;
233
281
  try {
@@ -246,23 +294,57 @@ function createApiServices(axiosInstance, endpoint) {
246
294
  return processResponse(error);
247
295
  }
248
296
  };
249
- return { get, getWithQuery, post, patch, remove };
297
+ const bulkDelete = async (ids, config) => {
298
+ const finalUrl = config?.endpoint || `${endpoint}`;
299
+ try {
300
+ const response = await axiosInstance.delete(finalUrl, { data: { ids }, ...config });
301
+ return processResponse(response);
302
+ } catch (error) {
303
+ return processResponse(error);
304
+ }
305
+ };
306
+ const upload = async (file, additionalData, config) => {
307
+ const finalUrl = config?.endpoint || `${endpoint}`;
308
+ const formData = new FormData();
309
+ formData.append("file", file);
310
+ if (additionalData) {
311
+ Object.keys(additionalData).forEach((key) => {
312
+ formData.append(key, additionalData[key]);
313
+ });
314
+ }
315
+ try {
316
+ const response = await axiosInstance.post(finalUrl, formData, {
317
+ ...config,
318
+ headers: {
319
+ ...config?.headers,
320
+ "Content-Type": "multipart/form-data"
321
+ }
322
+ });
323
+ return processResponse(response);
324
+ } catch (error) {
325
+ return processResponse(error);
326
+ }
327
+ };
328
+ return { get, getWithQuery, post, put, patch, remove, bulkDelete, upload };
250
329
  }
251
330
 
252
331
  // src/services/actions.ts
253
- function createAction(axiosInstance, method, endpoint) {
332
+ function createAction(axiosInstance, method, endpoint, log) {
254
333
  return async (payload, config) => {
334
+ log && console.log(`*[ ${method.toUpperCase()} ACTION API TO ENDPOINT ]: ${endpoint} **** [${payload}]** `);
255
335
  try {
336
+ log && console.log(`**[ ${method.toUpperCase()} ACTION API TO ENDPOINT ]:${endpoint} **** [action try req]** `);
256
337
  const response = await axiosInstance.request({
257
- // <--- غيرنا النوع إلى any
258
338
  url: endpoint,
259
339
  method,
260
340
  ...method.toUpperCase() === "GET" ? { params: payload } : { data: payload },
261
341
  ...config
262
342
  });
263
- return processResponse(response);
343
+ log && console.log(`***[ ${method.toUpperCase()} ACTION API TO ENDPOINT ]: ${endpoint} ***
344
+ ${response}*** `);
345
+ return processResponse(response, log);
264
346
  } catch (error) {
265
- return processResponse(error);
347
+ return processResponse(error, log);
266
348
  }
267
349
  };
268
350
  }
@@ -270,8 +352,8 @@ function createApiActions(axiosInstance, actionsConfig) {
270
352
  const actions = {};
271
353
  for (const actionName in actionsConfig) {
272
354
  if (Object.prototype.hasOwnProperty.call(actionsConfig, actionName)) {
273
- const { method, endpoint } = actionsConfig[actionName];
274
- actions[actionName] = createAction(axiosInstance, method, endpoint);
355
+ const { method, endpoint, log } = actionsConfig[actionName];
356
+ actions[actionName] = createAction(axiosInstance, method, endpoint, log);
275
357
  }
276
358
  }
277
359
  return actions;
@@ -337,7 +419,6 @@ function useApi(axiosInstance, config) {
337
419
  const result = await apiServices.getWithQuery(queryString, {
338
420
  cancelTokenKey: endpoint,
339
421
  ...requestConfig
340
- // <-- هنا نمرر الإعدادات الافتراضية
341
422
  });
342
423
  setState(result);
343
424
  if (!result.success && onError) {
@@ -362,6 +443,19 @@ function useApi(axiosInstance, config) {
362
443
  }
363
444
  return result;
364
445
  };
446
+ const putItem = async (id, item, options) => {
447
+ setState((prev) => ({ ...prev, loading: true }));
448
+ const result = await apiServices.put(id, item, options);
449
+ if (result.success) {
450
+ if (refetchAfterChange) await fetchData();
451
+ else setState((prev) => ({ ...prev, loading: false }));
452
+ if (onSuccess) onSuccess(result.message || "Item replaced successfully!", result.data);
453
+ } else {
454
+ setState((prev) => ({ ...prev, loading: false, error: result.error }));
455
+ if (onError) onError(result.message || "Replace failed", result.error || void 0);
456
+ }
457
+ return result;
458
+ };
365
459
  const updateItem = async (id, updatedItem, options) => {
366
460
  setState((prev) => ({ ...prev, loading: true }));
367
461
  const result = await apiServices.patch(id, updatedItem, options);
@@ -388,19 +482,39 @@ function useApi(axiosInstance, config) {
388
482
  }
389
483
  return result;
390
484
  };
485
+ const bulkDeleteItem = async (ids, options) => {
486
+ setState((prev) => ({ ...prev, loading: true }));
487
+ const result = await apiServices.bulkDelete(ids, options);
488
+ if (result.success) {
489
+ if (refetchAfterChange) await fetchData();
490
+ else setState((prev) => ({ ...prev, loading: false }));
491
+ if (onSuccess) onSuccess(result.message || "Items deleted successfully!");
492
+ } else {
493
+ setState((prev) => ({ ...prev, loading: false, error: result.error }));
494
+ if (onError) onError(result.message || "Bulk delete failed", result.error || void 0);
495
+ }
496
+ return result;
497
+ };
498
+ const uploadFile = async (file, additionalData, options) => {
499
+ setState((prev) => ({ ...prev, loading: true }));
500
+ const result = await apiServices.upload(file, additionalData, options);
501
+ if (result.success) {
502
+ if (refetchAfterChange && options?.refetch !== false) await fetchData();
503
+ else setState((prev) => ({ ...prev, loading: false }));
504
+ if (onSuccess) onSuccess(result.message || "File uploaded successfully!", result.data);
505
+ } else {
506
+ setState((prev) => ({ ...prev, loading: false, error: result.error }));
507
+ if (onError) onError(result.message || "Upload failed", result.error || void 0);
508
+ }
509
+ return result;
510
+ };
391
511
  const setPage = (page) => setQueryOptions((prev) => ({ ...prev, page }));
392
512
  const setLimit = (limit) => setQueryOptions((prev) => ({ ...prev, limit, page: 1 }));
393
513
  const setSearchTerm = (search) => setQueryOptions((prev) => ({ ...prev, search, page: 1 }));
394
514
  const setSorting = (sortBy) => setQueryOptions((prev) => ({ ...prev, sortBy }));
395
515
  const setFilters = (filter) => setQueryOptions((prev) => ({ ...prev, filter, page: 1 }));
396
516
  const setQueryParam = (key, value) => {
397
- setQueryOptions((prev) => {
398
- const newQuery = { ...prev, [key]: value };
399
- if (key !== "page") {
400
- newQuery.page = 1;
401
- }
402
- return newQuery;
403
- });
517
+ setQueryOptions((prev) => ({ ...prev, [key]: value, page: key !== "page" ? 1 : prev.page }));
404
518
  };
405
519
  const resetQuery = () => setQueryOptions(initialQuery);
406
520
  return {
@@ -409,8 +523,14 @@ function useApi(axiosInstance, config) {
409
523
  actions: {
410
524
  fetch: fetchData,
411
525
  create: createItem,
526
+ put: putItem,
527
+ // <-- NEW
412
528
  update: updateItem,
413
- remove: deleteItem
529
+ remove: deleteItem,
530
+ bulkRemove: bulkDeleteItem,
531
+ // <-- NEW
532
+ upload: uploadFile
533
+ // <-- NEW
414
534
  },
415
535
  query: {
416
536
  options: queryOptions,
@@ -421,7 +541,6 @@ function useApi(axiosInstance, config) {
421
541
  setSorting,
422
542
  setFilters,
423
543
  setQueryParam,
424
- // <-- NEW
425
544
  reset: resetQuery
426
545
  }
427
546
  };
package/package.json CHANGED
@@ -1,40 +1,40 @@
1
- {
2
- "name": "api-core-lib",
3
- "version": "4.4.3",
4
- "description": "A flexible and powerful API client library for modern web applications.",
5
- "main": "dist/index.js",
6
- "module": "dist/index.mjs",
7
- "types": "dist/index.d.ts",
8
- "scripts": {
9
- "build": "tsup src/index.ts --format esm,cjs --dts --clean",
10
- "dev": "tsup src/index.ts --format esm,cjs --dts --watch"
11
- },
12
- "keywords": [
13
- "axios",
14
- "api",
15
- "react",
16
- "nextjs",
17
- "typescript",
18
- "state-management"
19
- ],
20
- "author": "Your Name",
21
- "license": "MIT",
22
- "peerDependencies": {
23
- "react": ">=17.0.0"
24
- },
25
- "dependencies": {
26
- "axios": "^1.6.8",
27
- "axios-retry": "^4.1.0",
28
- "uuid": "^9.0.1"
29
- },
30
- "devDependencies": {
31
- "@types/react": "^18.3.2",
32
- "@types/uuid": "^9.0.8",
33
- "react": "^18.3.1",
34
- "tsup": "^8.0.2",
35
- "typescript": "^5.4.5"
36
- },
37
- "files": [
38
- "dist"
39
- ]
1
+ {
2
+ "name": "api-core-lib",
3
+ "version": "5.5.4",
4
+ "description": "A flexible and powerful API client library for modern web applications.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
10
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch"
11
+ },
12
+ "keywords": [
13
+ "axios",
14
+ "api",
15
+ "react",
16
+ "nextjs",
17
+ "typescript",
18
+ "state-management"
19
+ ],
20
+ "author": "Your Name",
21
+ "license": "MIT",
22
+ "peerDependencies": {
23
+ "react": ">=17.0.0"
24
+ },
25
+ "dependencies": {
26
+ "axios": "^1.6.8",
27
+ "axios-retry": "^4.1.0",
28
+ "uuid": "^9.0.1"
29
+ },
30
+ "devDependencies": {
31
+ "@types/react": "^18.3.2",
32
+ "@types/uuid": "^9.0.8",
33
+ "react": "^18.3.1",
34
+ "tsup": "^8.0.2",
35
+ "typescript": "^5.4.5"
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ]
40
40
  }