api-core-lib 4.3.3 → 4.4.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
@@ -191,6 +191,7 @@ interface ActionOptions {
191
191
  * update('123', { status: 'active' }, { endpoint: '/items/123/activate' })
192
192
  */
193
193
  endpoint?: string;
194
+ refetch?: boolean;
194
195
  }
195
196
  /**
196
197
  * واجهة لتهيئة الهوك `useApi`.
@@ -204,6 +205,11 @@ interface UseApiConfig<T> {
204
205
  refetchAfterChange?: boolean;
205
206
  onSuccess?: (message: string, data?: T) => void;
206
207
  onError?: (message: string, error?: ApiError) => void;
208
+ /**
209
+ * إعدادات Axios/Request افتراضية يتم تطبيقها على جميع طلبات الجلب (GET).
210
+ * مفيدة لتمرير إعدادات مثل isPublic.
211
+ */
212
+ requestConfig?: RequestConfig;
207
213
  }
208
214
 
209
215
  /**
@@ -217,12 +223,19 @@ interface UseApiConfig<T> {
217
223
  declare function createApiClient(config: ApiClientConfig): AxiosInstance;
218
224
 
219
225
  type CrudRequestConfig = RequestConfig & ActionOptions;
226
+ /**
227
+ * دالة مصنع (Factory Function) لإنشاء مجموعة خدمات API قابلة لإعادة الاستخدام لنقطة نهاية (endpoint) محددة.
228
+ * توفر عمليات CRUD كاملة بالإضافة إلى ميزات متقدمة مثل الحذف الجماعي ورفع الملفات.
229
+ */
220
230
  declare function createApiServices<T>(axiosInstance: AxiosInstance, endpoint: string): {
221
231
  get: (id?: string, config?: RequestConfig) => Promise<StandardResponse<T | T[]>>;
222
232
  getWithQuery: (query: string, config?: RequestConfig) => Promise<StandardResponse<T[]>>;
223
233
  post: (data: Partial<T>, config?: CrudRequestConfig) => Promise<StandardResponse<T>>;
234
+ put: (id: string, data: T, config?: CrudRequestConfig) => Promise<StandardResponse<T>>;
224
235
  patch: (id: string, data: Partial<T>, config?: CrudRequestConfig) => Promise<StandardResponse<T>>;
225
236
  remove: (id: string, config?: CrudRequestConfig) => Promise<StandardResponse<any>>;
237
+ bulkDelete: (ids: string[], config?: CrudRequestConfig) => Promise<StandardResponse<any>>;
238
+ upload: (file: File, additionalData?: Record<string, any>, config?: CrudRequestConfig) => Promise<StandardResponse<any>>;
226
239
  };
227
240
 
228
241
  /**
@@ -303,8 +316,11 @@ declare function useApi<T extends {
303
316
  actions: {
304
317
  fetch: (options?: QueryOptions) => Promise<void>;
305
318
  create: (newItem: Partial<T>, options?: ActionOptions) => Promise<StandardResponse<T>>;
319
+ put: (id: string, item: T, options?: ActionOptions) => Promise<StandardResponse<T>>;
306
320
  update: (id: string, updatedItem: Partial<T>, options?: ActionOptions) => Promise<StandardResponse<T>>;
307
321
  remove: (id: string, options?: ActionOptions) => Promise<StandardResponse<any>>;
322
+ bulkRemove: (ids: string[], options?: ActionOptions) => Promise<StandardResponse<any>>;
323
+ upload: (file: File, additionalData?: Record<string, any>, options?: ActionOptions) => Promise<StandardResponse<any>>;
308
324
  };
309
325
  query: {
310
326
  options: QueryOptions;
package/dist/index.d.ts CHANGED
@@ -191,6 +191,7 @@ interface ActionOptions {
191
191
  * update('123', { status: 'active' }, { endpoint: '/items/123/activate' })
192
192
  */
193
193
  endpoint?: string;
194
+ refetch?: boolean;
194
195
  }
195
196
  /**
196
197
  * واجهة لتهيئة الهوك `useApi`.
@@ -204,6 +205,11 @@ interface UseApiConfig<T> {
204
205
  refetchAfterChange?: boolean;
205
206
  onSuccess?: (message: string, data?: T) => void;
206
207
  onError?: (message: string, error?: ApiError) => void;
208
+ /**
209
+ * إعدادات Axios/Request افتراضية يتم تطبيقها على جميع طلبات الجلب (GET).
210
+ * مفيدة لتمرير إعدادات مثل isPublic.
211
+ */
212
+ requestConfig?: RequestConfig;
207
213
  }
208
214
 
209
215
  /**
@@ -217,12 +223,19 @@ interface UseApiConfig<T> {
217
223
  declare function createApiClient(config: ApiClientConfig): AxiosInstance;
218
224
 
219
225
  type CrudRequestConfig = RequestConfig & ActionOptions;
226
+ /**
227
+ * دالة مصنع (Factory Function) لإنشاء مجموعة خدمات API قابلة لإعادة الاستخدام لنقطة نهاية (endpoint) محددة.
228
+ * توفر عمليات CRUD كاملة بالإضافة إلى ميزات متقدمة مثل الحذف الجماعي ورفع الملفات.
229
+ */
220
230
  declare function createApiServices<T>(axiosInstance: AxiosInstance, endpoint: string): {
221
231
  get: (id?: string, config?: RequestConfig) => Promise<StandardResponse<T | T[]>>;
222
232
  getWithQuery: (query: string, config?: RequestConfig) => Promise<StandardResponse<T[]>>;
223
233
  post: (data: Partial<T>, config?: CrudRequestConfig) => Promise<StandardResponse<T>>;
234
+ put: (id: string, data: T, config?: CrudRequestConfig) => Promise<StandardResponse<T>>;
224
235
  patch: (id: string, data: Partial<T>, config?: CrudRequestConfig) => Promise<StandardResponse<T>>;
225
236
  remove: (id: string, config?: CrudRequestConfig) => Promise<StandardResponse<any>>;
237
+ bulkDelete: (ids: string[], config?: CrudRequestConfig) => Promise<StandardResponse<any>>;
238
+ upload: (file: File, additionalData?: Record<string, any>, config?: CrudRequestConfig) => Promise<StandardResponse<any>>;
226
239
  };
227
240
 
228
241
  /**
@@ -303,8 +316,11 @@ declare function useApi<T extends {
303
316
  actions: {
304
317
  fetch: (options?: QueryOptions) => Promise<void>;
305
318
  create: (newItem: Partial<T>, options?: ActionOptions) => Promise<StandardResponse<T>>;
319
+ put: (id: string, item: T, options?: ActionOptions) => Promise<StandardResponse<T>>;
306
320
  update: (id: string, updatedItem: Partial<T>, options?: ActionOptions) => Promise<StandardResponse<T>>;
307
321
  remove: (id: string, options?: ActionOptions) => Promise<StandardResponse<any>>;
322
+ bulkRemove: (ids: string[], options?: ActionOptions) => Promise<StandardResponse<any>>;
323
+ upload: (file: File, additionalData?: Record<string, any>, options?: ActionOptions) => Promise<StandardResponse<any>>;
308
324
  };
309
325
  query: {
310
326
  options: QueryOptions;
package/dist/index.js CHANGED
@@ -103,34 +103,40 @@ function createApiClient(config) {
103
103
  headers: { "Content-Type": "application/json", ...headers },
104
104
  withCredentials
105
105
  });
106
- let tokenRefreshPromise = null;
106
+ let isRefreshing = false;
107
+ let failedQueue = [];
108
+ const processQueue = (error, token = null) => {
109
+ failedQueue.forEach((prom) => {
110
+ if (error) {
111
+ prom.reject(error);
112
+ } else {
113
+ prom.resolve(token);
114
+ }
115
+ });
116
+ failedQueue = [];
117
+ };
107
118
  axiosInstance.interceptors.request.use(async (req) => {
108
119
  req.headers["X-Request-ID"] = (0, import_uuid.v4)();
109
120
  if (req.isPublic) {
110
- console.log(`[API Core] Skipping token for public request: ${req.url}`);
111
121
  return req;
112
122
  }
113
- if (tokenRefreshPromise) {
114
- await tokenRefreshPromise;
115
- }
116
123
  let tokens = await tokenManager.getTokens();
117
124
  const now = Date.now();
118
125
  const tokenBuffer = 60 * 1e3;
119
- if (tokens.accessToken && tokens.expiresAt && tokens.expiresAt - now < tokenBuffer) {
120
- if (!tokenRefreshPromise && config.refreshTokenConfig) {
126
+ if (tokens.accessToken && tokens.expiresAt && tokens.expiresAt - now < tokenBuffer && !isRefreshing) {
127
+ if (config.refreshTokenConfig) {
121
128
  console.log("[API Core] Proactive token refresh initiated.");
122
- tokenRefreshPromise = refreshToken(config, tokenManager);
129
+ isRefreshing = true;
130
+ try {
131
+ const newTokens = await refreshToken(config, tokenManager);
132
+ if (newTokens) tokens = newTokens;
133
+ } finally {
134
+ isRefreshing = false;
135
+ }
123
136
  }
124
- const newTokens = await tokenRefreshPromise;
125
- tokenRefreshPromise = null;
126
- if (newTokens) tokens = newTokens;
127
137
  }
128
138
  if (tokens.accessToken && !tokenManager.isHttpOnly()) {
129
- const tokenType = tokens.tokenType || "Bearer";
130
- req.headers.Authorization = `${tokenType} ${tokens.accessToken}`;
131
- console.log(`[API Core] Token attached to request: ${req.url}`);
132
- } else {
133
- console.warn(`[API Core] No token attached for request: ${req.url}`);
139
+ req.headers.Authorization = `${tokens.tokenType || "Bearer"} ${tokens.accessToken}`;
134
140
  }
135
141
  return req;
136
142
  }, (error) => Promise.reject(error));
@@ -138,19 +144,34 @@ function createApiClient(config) {
138
144
  (response) => response,
139
145
  async (error) => {
140
146
  const originalRequest = error.config;
141
- if (error.response?.status === 401 && !originalRequest._retry) {
142
- originalRequest._retry = true;
143
- if (!tokenRefreshPromise && config.refreshTokenConfig) {
144
- console.log("[API Core] Reactive token refresh initiated due to 401.");
145
- tokenRefreshPromise = refreshToken(config, tokenManager);
147
+ if (error.response?.status === 401 && !originalRequest._retry && config.refreshTokenConfig) {
148
+ if (isRefreshing) {
149
+ return new Promise((resolve, reject) => {
150
+ failedQueue.push({ resolve, reject });
151
+ }).then((token) => {
152
+ if (token && !tokenManager.isHttpOnly()) {
153
+ originalRequest.headers["Authorization"] = `Bearer ${token}`;
154
+ }
155
+ return axiosInstance(originalRequest);
156
+ });
146
157
  }
147
- const newTokens = await tokenRefreshPromise;
148
- tokenRefreshPromise = null;
149
- if (newTokens) {
150
- if (newTokens.accessToken && !tokenManager.isHttpOnly()) {
151
- originalRequest.headers.Authorization = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
158
+ originalRequest._retry = true;
159
+ isRefreshing = true;
160
+ try {
161
+ const newTokens = await refreshToken(config, tokenManager);
162
+ if (!newTokens || !newTokens.accessToken) {
163
+ throw new Error("Token refresh failed to produce a new access token.");
164
+ }
165
+ processQueue(null, newTokens.accessToken);
166
+ if (!tokenManager.isHttpOnly()) {
167
+ originalRequest.headers["Authorization"] = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
152
168
  }
153
169
  return axiosInstance(originalRequest);
170
+ } catch (refreshError) {
171
+ processQueue(refreshError, null);
172
+ return Promise.reject(refreshError);
173
+ } finally {
174
+ isRefreshing = false;
154
175
  }
155
176
  }
156
177
  const enhancedError = {
@@ -267,6 +288,15 @@ function createApiServices(axiosInstance, endpoint) {
267
288
  return processResponse(error);
268
289
  }
269
290
  };
291
+ const put = async (id, data, config) => {
292
+ const finalUrl = config?.endpoint || `${endpoint}/${id}`;
293
+ try {
294
+ const response = await axiosInstance.put(finalUrl, data, config);
295
+ return processResponse(response);
296
+ } catch (error) {
297
+ return processResponse(error);
298
+ }
299
+ };
270
300
  const patch = async (id, data, config) => {
271
301
  const finalUrl = config?.endpoint || `${endpoint}/${id}`;
272
302
  try {
@@ -285,7 +315,38 @@ function createApiServices(axiosInstance, endpoint) {
285
315
  return processResponse(error);
286
316
  }
287
317
  };
288
- return { get, getWithQuery, post, patch, remove };
318
+ const bulkDelete = async (ids, config) => {
319
+ const finalUrl = config?.endpoint || `${endpoint}`;
320
+ try {
321
+ const response = await axiosInstance.delete(finalUrl, { data: { ids }, ...config });
322
+ return processResponse(response);
323
+ } catch (error) {
324
+ return processResponse(error);
325
+ }
326
+ };
327
+ const upload = async (file, additionalData, config) => {
328
+ const finalUrl = config?.endpoint || `${endpoint}`;
329
+ const formData = new FormData();
330
+ formData.append("file", file);
331
+ if (additionalData) {
332
+ Object.keys(additionalData).forEach((key) => {
333
+ formData.append(key, additionalData[key]);
334
+ });
335
+ }
336
+ try {
337
+ const response = await axiosInstance.post(finalUrl, formData, {
338
+ ...config,
339
+ headers: {
340
+ ...config?.headers,
341
+ "Content-Type": "multipart/form-data"
342
+ }
343
+ });
344
+ return processResponse(response);
345
+ } catch (error) {
346
+ return processResponse(error);
347
+ }
348
+ };
349
+ return { get, getWithQuery, post, put, patch, remove, bulkDelete, upload };
289
350
  }
290
351
 
291
352
  // src/services/actions.ts
@@ -356,7 +417,7 @@ function useApi(axiosInstance, config) {
356
417
  initialQuery = { limit: 10 },
357
418
  enabled = true,
358
419
  refetchAfterChange = true,
359
- // تم تعديل القيمة الافتراضية لتكون أكثر شيوعًا
420
+ requestConfig,
360
421
  onSuccess,
361
422
  onError
362
423
  } = config;
@@ -373,12 +434,15 @@ function useApi(axiosInstance, config) {
373
434
  const currentQuery = options || queryOptions;
374
435
  setState((prev) => ({ ...prev, data: null, loading: true, error: null }));
375
436
  const queryString = buildPaginateQuery(currentQuery);
376
- const result = await apiServices.getWithQuery(queryString, { cancelTokenKey: endpoint });
437
+ const result = await apiServices.getWithQuery(queryString, {
438
+ cancelTokenKey: endpoint,
439
+ ...requestConfig
440
+ });
377
441
  setState(result);
378
442
  if (!result.success && onError) {
379
443
  onError(result.message || "Fetch failed", result.error || void 0);
380
444
  }
381
- }, [apiServices, queryOptions, endpoint, onError]);
445
+ }, [apiServices, queryOptions, endpoint, onError, requestConfig]);
382
446
  (0, import_react.useEffect)(() => {
383
447
  if (enabled) {
384
448
  fetchData();
@@ -397,6 +461,19 @@ function useApi(axiosInstance, config) {
397
461
  }
398
462
  return result;
399
463
  };
464
+ const putItem = async (id, item, options) => {
465
+ setState((prev) => ({ ...prev, loading: true }));
466
+ const result = await apiServices.put(id, item, options);
467
+ if (result.success) {
468
+ if (refetchAfterChange) await fetchData();
469
+ else setState((prev) => ({ ...prev, loading: false }));
470
+ if (onSuccess) onSuccess(result.message || "Item replaced successfully!", result.data);
471
+ } else {
472
+ setState((prev) => ({ ...prev, loading: false, error: result.error }));
473
+ if (onError) onError(result.message || "Replace failed", result.error || void 0);
474
+ }
475
+ return result;
476
+ };
400
477
  const updateItem = async (id, updatedItem, options) => {
401
478
  setState((prev) => ({ ...prev, loading: true }));
402
479
  const result = await apiServices.patch(id, updatedItem, options);
@@ -423,19 +500,39 @@ function useApi(axiosInstance, config) {
423
500
  }
424
501
  return result;
425
502
  };
503
+ const bulkDeleteItem = async (ids, options) => {
504
+ setState((prev) => ({ ...prev, loading: true }));
505
+ const result = await apiServices.bulkDelete(ids, options);
506
+ if (result.success) {
507
+ if (refetchAfterChange) await fetchData();
508
+ else setState((prev) => ({ ...prev, loading: false }));
509
+ if (onSuccess) onSuccess(result.message || "Items deleted successfully!");
510
+ } else {
511
+ setState((prev) => ({ ...prev, loading: false, error: result.error }));
512
+ if (onError) onError(result.message || "Bulk delete failed", result.error || void 0);
513
+ }
514
+ return result;
515
+ };
516
+ const uploadFile = async (file, additionalData, options) => {
517
+ setState((prev) => ({ ...prev, loading: true }));
518
+ const result = await apiServices.upload(file, additionalData, options);
519
+ if (result.success) {
520
+ if (refetchAfterChange && options?.refetch !== false) await fetchData();
521
+ else setState((prev) => ({ ...prev, loading: false }));
522
+ if (onSuccess) onSuccess(result.message || "File uploaded successfully!", result.data);
523
+ } else {
524
+ setState((prev) => ({ ...prev, loading: false, error: result.error }));
525
+ if (onError) onError(result.message || "Upload failed", result.error || void 0);
526
+ }
527
+ return result;
528
+ };
426
529
  const setPage = (page) => setQueryOptions((prev) => ({ ...prev, page }));
427
530
  const setLimit = (limit) => setQueryOptions((prev) => ({ ...prev, limit, page: 1 }));
428
531
  const setSearchTerm = (search) => setQueryOptions((prev) => ({ ...prev, search, page: 1 }));
429
532
  const setSorting = (sortBy) => setQueryOptions((prev) => ({ ...prev, sortBy }));
430
533
  const setFilters = (filter) => setQueryOptions((prev) => ({ ...prev, filter, page: 1 }));
431
534
  const setQueryParam = (key, value) => {
432
- setQueryOptions((prev) => {
433
- const newQuery = { ...prev, [key]: value };
434
- if (key !== "page") {
435
- newQuery.page = 1;
436
- }
437
- return newQuery;
438
- });
535
+ setQueryOptions((prev) => ({ ...prev, [key]: value, page: key !== "page" ? 1 : prev.page }));
439
536
  };
440
537
  const resetQuery = () => setQueryOptions(initialQuery);
441
538
  return {
@@ -444,8 +541,14 @@ function useApi(axiosInstance, config) {
444
541
  actions: {
445
542
  fetch: fetchData,
446
543
  create: createItem,
544
+ put: putItem,
545
+ // <-- NEW
447
546
  update: updateItem,
448
- remove: deleteItem
547
+ remove: deleteItem,
548
+ bulkRemove: bulkDeleteItem,
549
+ // <-- NEW
550
+ upload: uploadFile
551
+ // <-- NEW
449
552
  },
450
553
  query: {
451
554
  options: queryOptions,
@@ -456,7 +559,6 @@ function useApi(axiosInstance, config) {
456
559
  setSorting,
457
560
  setFilters,
458
561
  setQueryParam,
459
- // <-- NEW
460
562
  reset: resetQuery
461
563
  }
462
564
  };
package/dist/index.mjs CHANGED
@@ -61,34 +61,40 @@ function createApiClient(config) {
61
61
  headers: { "Content-Type": "application/json", ...headers },
62
62
  withCredentials
63
63
  });
64
- let tokenRefreshPromise = null;
64
+ let isRefreshing = false;
65
+ let failedQueue = [];
66
+ const processQueue = (error, token = null) => {
67
+ failedQueue.forEach((prom) => {
68
+ if (error) {
69
+ prom.reject(error);
70
+ } else {
71
+ prom.resolve(token);
72
+ }
73
+ });
74
+ failedQueue = [];
75
+ };
65
76
  axiosInstance.interceptors.request.use(async (req) => {
66
77
  req.headers["X-Request-ID"] = uuidv4();
67
78
  if (req.isPublic) {
68
- console.log(`[API Core] Skipping token for public request: ${req.url}`);
69
79
  return req;
70
80
  }
71
- if (tokenRefreshPromise) {
72
- await tokenRefreshPromise;
73
- }
74
81
  let tokens = await tokenManager.getTokens();
75
82
  const now = Date.now();
76
83
  const tokenBuffer = 60 * 1e3;
77
- if (tokens.accessToken && tokens.expiresAt && tokens.expiresAt - now < tokenBuffer) {
78
- if (!tokenRefreshPromise && config.refreshTokenConfig) {
84
+ if (tokens.accessToken && tokens.expiresAt && tokens.expiresAt - now < tokenBuffer && !isRefreshing) {
85
+ if (config.refreshTokenConfig) {
79
86
  console.log("[API Core] Proactive token refresh initiated.");
80
- tokenRefreshPromise = refreshToken(config, tokenManager);
87
+ isRefreshing = true;
88
+ try {
89
+ const newTokens = await refreshToken(config, tokenManager);
90
+ if (newTokens) tokens = newTokens;
91
+ } finally {
92
+ isRefreshing = false;
93
+ }
81
94
  }
82
- const newTokens = await tokenRefreshPromise;
83
- tokenRefreshPromise = null;
84
- if (newTokens) tokens = newTokens;
85
95
  }
86
96
  if (tokens.accessToken && !tokenManager.isHttpOnly()) {
87
- const tokenType = tokens.tokenType || "Bearer";
88
- req.headers.Authorization = `${tokenType} ${tokens.accessToken}`;
89
- console.log(`[API Core] Token attached to request: ${req.url}`);
90
- } else {
91
- console.warn(`[API Core] No token attached for request: ${req.url}`);
97
+ req.headers.Authorization = `${tokens.tokenType || "Bearer"} ${tokens.accessToken}`;
92
98
  }
93
99
  return req;
94
100
  }, (error) => Promise.reject(error));
@@ -96,19 +102,34 @@ function createApiClient(config) {
96
102
  (response) => response,
97
103
  async (error) => {
98
104
  const originalRequest = error.config;
99
- if (error.response?.status === 401 && !originalRequest._retry) {
100
- originalRequest._retry = true;
101
- if (!tokenRefreshPromise && config.refreshTokenConfig) {
102
- console.log("[API Core] Reactive token refresh initiated due to 401.");
103
- tokenRefreshPromise = refreshToken(config, tokenManager);
105
+ if (error.response?.status === 401 && !originalRequest._retry && config.refreshTokenConfig) {
106
+ if (isRefreshing) {
107
+ return new Promise((resolve, reject) => {
108
+ failedQueue.push({ resolve, reject });
109
+ }).then((token) => {
110
+ if (token && !tokenManager.isHttpOnly()) {
111
+ originalRequest.headers["Authorization"] = `Bearer ${token}`;
112
+ }
113
+ return axiosInstance(originalRequest);
114
+ });
104
115
  }
105
- const newTokens = await tokenRefreshPromise;
106
- tokenRefreshPromise = null;
107
- if (newTokens) {
108
- if (newTokens.accessToken && !tokenManager.isHttpOnly()) {
109
- originalRequest.headers.Authorization = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
116
+ originalRequest._retry = true;
117
+ isRefreshing = true;
118
+ try {
119
+ const newTokens = await refreshToken(config, tokenManager);
120
+ if (!newTokens || !newTokens.accessToken) {
121
+ throw new Error("Token refresh failed to produce a new access token.");
122
+ }
123
+ processQueue(null, newTokens.accessToken);
124
+ if (!tokenManager.isHttpOnly()) {
125
+ originalRequest.headers["Authorization"] = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
110
126
  }
111
127
  return axiosInstance(originalRequest);
128
+ } catch (refreshError) {
129
+ processQueue(refreshError, null);
130
+ return Promise.reject(refreshError);
131
+ } finally {
132
+ isRefreshing = false;
112
133
  }
113
134
  }
114
135
  const enhancedError = {
@@ -225,6 +246,15 @@ function createApiServices(axiosInstance, endpoint) {
225
246
  return processResponse(error);
226
247
  }
227
248
  };
249
+ const put = async (id, data, config) => {
250
+ const finalUrl = config?.endpoint || `${endpoint}/${id}`;
251
+ try {
252
+ const response = await axiosInstance.put(finalUrl, data, config);
253
+ return processResponse(response);
254
+ } catch (error) {
255
+ return processResponse(error);
256
+ }
257
+ };
228
258
  const patch = async (id, data, config) => {
229
259
  const finalUrl = config?.endpoint || `${endpoint}/${id}`;
230
260
  try {
@@ -243,7 +273,38 @@ function createApiServices(axiosInstance, endpoint) {
243
273
  return processResponse(error);
244
274
  }
245
275
  };
246
- return { get, getWithQuery, post, patch, remove };
276
+ const bulkDelete = async (ids, config) => {
277
+ const finalUrl = config?.endpoint || `${endpoint}`;
278
+ try {
279
+ const response = await axiosInstance.delete(finalUrl, { data: { ids }, ...config });
280
+ return processResponse(response);
281
+ } catch (error) {
282
+ return processResponse(error);
283
+ }
284
+ };
285
+ const upload = async (file, additionalData, config) => {
286
+ const finalUrl = config?.endpoint || `${endpoint}`;
287
+ const formData = new FormData();
288
+ formData.append("file", file);
289
+ if (additionalData) {
290
+ Object.keys(additionalData).forEach((key) => {
291
+ formData.append(key, additionalData[key]);
292
+ });
293
+ }
294
+ try {
295
+ const response = await axiosInstance.post(finalUrl, formData, {
296
+ ...config,
297
+ headers: {
298
+ ...config?.headers,
299
+ "Content-Type": "multipart/form-data"
300
+ }
301
+ });
302
+ return processResponse(response);
303
+ } catch (error) {
304
+ return processResponse(error);
305
+ }
306
+ };
307
+ return { get, getWithQuery, post, put, patch, remove, bulkDelete, upload };
247
308
  }
248
309
 
249
310
  // src/services/actions.ts
@@ -314,7 +375,7 @@ function useApi(axiosInstance, config) {
314
375
  initialQuery = { limit: 10 },
315
376
  enabled = true,
316
377
  refetchAfterChange = true,
317
- // تم تعديل القيمة الافتراضية لتكون أكثر شيوعًا
378
+ requestConfig,
318
379
  onSuccess,
319
380
  onError
320
381
  } = config;
@@ -331,12 +392,15 @@ function useApi(axiosInstance, config) {
331
392
  const currentQuery = options || queryOptions;
332
393
  setState((prev) => ({ ...prev, data: null, loading: true, error: null }));
333
394
  const queryString = buildPaginateQuery(currentQuery);
334
- const result = await apiServices.getWithQuery(queryString, { cancelTokenKey: endpoint });
395
+ const result = await apiServices.getWithQuery(queryString, {
396
+ cancelTokenKey: endpoint,
397
+ ...requestConfig
398
+ });
335
399
  setState(result);
336
400
  if (!result.success && onError) {
337
401
  onError(result.message || "Fetch failed", result.error || void 0);
338
402
  }
339
- }, [apiServices, queryOptions, endpoint, onError]);
403
+ }, [apiServices, queryOptions, endpoint, onError, requestConfig]);
340
404
  useEffect(() => {
341
405
  if (enabled) {
342
406
  fetchData();
@@ -355,6 +419,19 @@ function useApi(axiosInstance, config) {
355
419
  }
356
420
  return result;
357
421
  };
422
+ const putItem = async (id, item, options) => {
423
+ setState((prev) => ({ ...prev, loading: true }));
424
+ const result = await apiServices.put(id, item, options);
425
+ if (result.success) {
426
+ if (refetchAfterChange) await fetchData();
427
+ else setState((prev) => ({ ...prev, loading: false }));
428
+ if (onSuccess) onSuccess(result.message || "Item replaced successfully!", result.data);
429
+ } else {
430
+ setState((prev) => ({ ...prev, loading: false, error: result.error }));
431
+ if (onError) onError(result.message || "Replace failed", result.error || void 0);
432
+ }
433
+ return result;
434
+ };
358
435
  const updateItem = async (id, updatedItem, options) => {
359
436
  setState((prev) => ({ ...prev, loading: true }));
360
437
  const result = await apiServices.patch(id, updatedItem, options);
@@ -381,19 +458,39 @@ function useApi(axiosInstance, config) {
381
458
  }
382
459
  return result;
383
460
  };
461
+ const bulkDeleteItem = async (ids, options) => {
462
+ setState((prev) => ({ ...prev, loading: true }));
463
+ const result = await apiServices.bulkDelete(ids, options);
464
+ if (result.success) {
465
+ if (refetchAfterChange) await fetchData();
466
+ else setState((prev) => ({ ...prev, loading: false }));
467
+ if (onSuccess) onSuccess(result.message || "Items deleted successfully!");
468
+ } else {
469
+ setState((prev) => ({ ...prev, loading: false, error: result.error }));
470
+ if (onError) onError(result.message || "Bulk delete failed", result.error || void 0);
471
+ }
472
+ return result;
473
+ };
474
+ const uploadFile = async (file, additionalData, options) => {
475
+ setState((prev) => ({ ...prev, loading: true }));
476
+ const result = await apiServices.upload(file, additionalData, options);
477
+ if (result.success) {
478
+ if (refetchAfterChange && options?.refetch !== false) await fetchData();
479
+ else setState((prev) => ({ ...prev, loading: false }));
480
+ if (onSuccess) onSuccess(result.message || "File uploaded successfully!", result.data);
481
+ } else {
482
+ setState((prev) => ({ ...prev, loading: false, error: result.error }));
483
+ if (onError) onError(result.message || "Upload failed", result.error || void 0);
484
+ }
485
+ return result;
486
+ };
384
487
  const setPage = (page) => setQueryOptions((prev) => ({ ...prev, page }));
385
488
  const setLimit = (limit) => setQueryOptions((prev) => ({ ...prev, limit, page: 1 }));
386
489
  const setSearchTerm = (search) => setQueryOptions((prev) => ({ ...prev, search, page: 1 }));
387
490
  const setSorting = (sortBy) => setQueryOptions((prev) => ({ ...prev, sortBy }));
388
491
  const setFilters = (filter) => setQueryOptions((prev) => ({ ...prev, filter, page: 1 }));
389
492
  const setQueryParam = (key, value) => {
390
- setQueryOptions((prev) => {
391
- const newQuery = { ...prev, [key]: value };
392
- if (key !== "page") {
393
- newQuery.page = 1;
394
- }
395
- return newQuery;
396
- });
493
+ setQueryOptions((prev) => ({ ...prev, [key]: value, page: key !== "page" ? 1 : prev.page }));
397
494
  };
398
495
  const resetQuery = () => setQueryOptions(initialQuery);
399
496
  return {
@@ -402,8 +499,14 @@ function useApi(axiosInstance, config) {
402
499
  actions: {
403
500
  fetch: fetchData,
404
501
  create: createItem,
502
+ put: putItem,
503
+ // <-- NEW
405
504
  update: updateItem,
406
- remove: deleteItem
505
+ remove: deleteItem,
506
+ bulkRemove: bulkDeleteItem,
507
+ // <-- NEW
508
+ upload: uploadFile
509
+ // <-- NEW
407
510
  },
408
511
  query: {
409
512
  options: queryOptions,
@@ -414,7 +517,6 @@ function useApi(axiosInstance, config) {
414
517
  setSorting,
415
518
  setFilters,
416
519
  setQueryParam,
417
- // <-- NEW
418
520
  reset: resetQuery
419
521
  }
420
522
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-core-lib",
3
- "version": "4.3.3",
3
+ "version": "4.4.4",
4
4
  "description": "A flexible and powerful API client library for modern web applications.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",