api-core-lib 4.4.4 → 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`.
59
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
+ * سياق البرمجيات الوسيطة الذي يتم تمريره لكل دالة.
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.
@@ -214,12 +238,25 @@ interface UseApiConfig<T> {
214
238
 
215
239
  /**
216
240
  * @file src/core/client.ts
217
- * @description
218
- * هذا هو القلب النابض للمكتبة. دالة `createApiClient` تنشئ وتهيئ نسخة Axios
219
- * مع معترضات (interceptors) ذكية لإدارة التوكنات، بما في ذلك التجديد التلقائي
220
- * والاستباقي للتوكن، ومعالجة الأخطاء بشكل مركزي.
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.
221
245
  */
222
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
+ */
223
260
  declare function createApiClient(config: ApiClientConfig): AxiosInstance;
224
261
 
225
262
  type CrudRequestConfig = RequestConfig & ActionOptions;
@@ -283,6 +320,7 @@ declare function createApiActions<TActions extends Record<string, {
283
320
  endpoint: string;
284
321
  requestType: any;
285
322
  responseType: any;
323
+ log?: boolean;
286
324
  }>>(axiosInstance: AxiosInstance, actionsConfig: TActions): {
287
325
  [K in keyof TActions]: ApiAction<TActions[K]['requestType'], TActions[K]['responseType']>;
288
326
  };
@@ -306,7 +344,7 @@ declare const cacheManager: CacheManager;
306
344
  * @param responseOrError The raw Axios response or a pre-processed ApiError.
307
345
  * @returns A standardized `StandardResponse` object with direct access to `.data`.
308
346
  */
309
- declare const processResponse: <T>(responseOrError: AxiosResponse<any> | ApiError) => StandardResponse<T>;
347
+ declare const processResponse: <T>(responseOrError: AxiosResponse<any> | ApiError, log?: boolean) => StandardResponse<T>;
310
348
 
311
349
  declare function useApi<T extends {
312
350
  id?: string | number;
@@ -338,4 +376,4 @@ declare function useApi<T extends {
338
376
  };
339
377
  };
340
378
 
341
- 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`.
59
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
+ * سياق البرمجيات الوسيطة الذي يتم تمريره لكل دالة.
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.
@@ -214,12 +238,25 @@ interface UseApiConfig<T> {
214
238
 
215
239
  /**
216
240
  * @file src/core/client.ts
217
- * @description
218
- * هذا هو القلب النابض للمكتبة. دالة `createApiClient` تنشئ وتهيئ نسخة Axios
219
- * مع معترضات (interceptors) ذكية لإدارة التوكنات، بما في ذلك التجديد التلقائي
220
- * والاستباقي للتوكن، ومعالجة الأخطاء بشكل مركزي.
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.
221
245
  */
222
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
+ */
223
260
  declare function createApiClient(config: ApiClientConfig): AxiosInstance;
224
261
 
225
262
  type CrudRequestConfig = RequestConfig & ActionOptions;
@@ -283,6 +320,7 @@ declare function createApiActions<TActions extends Record<string, {
283
320
  endpoint: string;
284
321
  requestType: any;
285
322
  responseType: any;
323
+ log?: boolean;
286
324
  }>>(axiosInstance: AxiosInstance, actionsConfig: TActions): {
287
325
  [K in keyof TActions]: ApiAction<TActions[K]['requestType'], TActions[K]['responseType']>;
288
326
  };
@@ -306,7 +344,7 @@ declare const cacheManager: CacheManager;
306
344
  * @param responseOrError The raw Axios response or a pre-processed ApiError.
307
345
  * @returns A standardized `StandardResponse` object with direct access to `.data`.
308
346
  */
309
- declare const processResponse: <T>(responseOrError: AxiosResponse<any> | ApiError) => StandardResponse<T>;
347
+ declare const processResponse: <T>(responseOrError: AxiosResponse<any> | ApiError, log?: boolean) => StandardResponse<T>;
310
348
 
311
349
  declare function useApi<T extends {
312
350
  id?: string | number;
@@ -338,4 +376,4 @@ declare function useApi<T extends {
338
376
  };
339
377
  };
340
378
 
341
- 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,7 +105,9 @@ function createApiClient(config) {
95
105
  tokenManager,
96
106
  timeout = 15e3,
97
107
  headers = {},
98
- withCredentials = false
108
+ withCredentials = false,
109
+ middleware = [],
110
+ logger = console
99
111
  } = config;
100
112
  const axiosInstance = import_axios.default.create({
101
113
  baseURL,
@@ -106,66 +118,76 @@ function createApiClient(config) {
106
118
  let isRefreshing = false;
107
119
  let failedQueue = [];
108
120
  const processQueue = (error, token = null) => {
109
- failedQueue.forEach((prom) => {
110
- if (error) {
111
- prom.reject(error);
112
- } else {
113
- prom.resolve(token);
114
- }
115
- });
121
+ failedQueue.forEach((prom) => error ? prom.reject(error) : prom.resolve(token));
116
122
  failedQueue = [];
117
123
  };
118
124
  axiosInstance.interceptors.request.use(async (req) => {
119
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);
120
129
  if (req.isPublic) {
121
130
  return req;
122
131
  }
132
+ if (tokenManager.isHttpOnly()) {
133
+ return req;
134
+ }
123
135
  let tokens = await tokenManager.getTokens();
124
136
  const now = Date.now();
125
137
  const tokenBuffer = 60 * 1e3;
126
138
  if (tokens.accessToken && tokens.expiresAt && tokens.expiresAt - now < tokenBuffer && !isRefreshing) {
127
139
  if (config.refreshTokenConfig) {
128
- console.log("[API Core] Proactive token refresh initiated.");
140
+ logger.info("[API Core] Proactive token refresh initiated.");
129
141
  isRefreshing = true;
130
142
  try {
131
- const newTokens = await refreshToken(config, tokenManager);
143
+ const newTokens = await refreshToken(config, tokenManager, logger);
132
144
  if (newTokens) tokens = newTokens;
133
145
  } finally {
134
146
  isRefreshing = false;
135
147
  }
136
148
  }
137
149
  }
138
- if (tokens.accessToken && !tokenManager.isHttpOnly()) {
150
+ if (tokens.accessToken) {
139
151
  req.headers.Authorization = `${tokens.tokenType || "Bearer"} ${tokens.accessToken}`;
140
152
  }
141
153
  return req;
142
- }, (error) => Promise.reject(error));
154
+ });
143
155
  axiosInstance.interceptors.response.use(
144
- (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
+ },
145
162
  async (error) => {
146
163
  const originalRequest = error.config;
147
- if (error.response?.status === 401 && !originalRequest._retry && config.refreshTokenConfig) {
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) {
148
174
  if (isRefreshing) {
149
175
  return new Promise((resolve, reject) => {
150
176
  failedQueue.push({ resolve, reject });
151
177
  }).then((token) => {
152
- if (token && !tokenManager.isHttpOnly()) {
153
- originalRequest.headers["Authorization"] = `Bearer ${token}`;
154
- }
178
+ originalRequest.headers["Authorization"] = `Bearer ${token}`;
155
179
  return axiosInstance(originalRequest);
156
180
  });
157
181
  }
158
182
  originalRequest._retry = true;
159
183
  isRefreshing = true;
160
184
  try {
161
- const newTokens = await refreshToken(config, tokenManager);
185
+ const newTokens = await refreshToken(config, tokenManager, logger);
162
186
  if (!newTokens || !newTokens.accessToken) {
163
187
  throw new Error("Token refresh failed to produce a new access token.");
164
188
  }
165
189
  processQueue(null, newTokens.accessToken);
166
- if (!tokenManager.isHttpOnly()) {
167
- originalRequest.headers["Authorization"] = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
168
- }
190
+ originalRequest.headers["Authorization"] = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
169
191
  return axiosInstance(originalRequest);
170
192
  } catch (refreshError) {
171
193
  processQueue(refreshError, null);
@@ -174,13 +196,7 @@ function createApiClient(config) {
174
196
  isRefreshing = false;
175
197
  }
176
198
  }
177
- const enhancedError = {
178
- message: error.response?.data?.message || error.message,
179
- status: error.response?.status || 500,
180
- errors: error.response?.data?.errors,
181
- requestId: originalRequest.headers?.["X-Request-ID"]
182
- };
183
- return Promise.reject(enhancedError);
199
+ return Promise.reject(error);
184
200
  }
185
201
  );
186
202
  return axiosInstance;
@@ -222,8 +238,10 @@ function isAxiosResponse(obj) {
222
238
  }
223
239
 
224
240
  // src/core/processor.ts
225
- var processResponse = (responseOrError) => {
241
+ var processResponse = (responseOrError, log) => {
226
242
  if (isApiError(responseOrError)) {
243
+ log && console.log(`****[ API ERROR PROCESS RESPONSE ]: ***
244
+ ${responseOrError} *** `);
227
245
  return {
228
246
  data: null,
229
247
  rawResponse: void 0,
@@ -235,15 +253,17 @@ var processResponse = (responseOrError) => {
235
253
  };
236
254
  }
237
255
  if (isAxiosResponse(responseOrError)) {
256
+ log && console.log(`****[ API IS SCSS PROCESS RESPONSE ]: ***
257
+ ${responseOrError} *** `);
238
258
  const rawData = responseOrError.data;
239
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} *** `);
240
262
  const finalData = isWrappedResponse ? rawData.data : rawData;
241
263
  const message = isWrappedResponse ? rawData.message : "Request successful.";
242
264
  return {
243
265
  data: finalData,
244
- // <-- وصول مباشر للبيانات النهائية!
245
266
  rawResponse: rawData,
246
- // احتفظ بالاستجابة الكاملة إذا احتجت إليها
247
267
  loading: false,
248
268
  success: true,
249
269
  error: null,
@@ -251,6 +271,7 @@ var processResponse = (responseOrError) => {
251
271
  validationErrors: []
252
272
  };
253
273
  }
274
+ log && console.log(`****[ API STATUS 500 ]: *** ${responseOrError} *** `);
254
275
  return {
255
276
  data: null,
256
277
  rawResponse: void 0,
@@ -350,19 +371,22 @@ function createApiServices(axiosInstance, endpoint) {
350
371
  }
351
372
 
352
373
  // src/services/actions.ts
353
- function createAction(axiosInstance, method, endpoint) {
374
+ function createAction(axiosInstance, method, endpoint, log) {
354
375
  return async (payload, config) => {
376
+ log && console.log(`*[ ${method.toUpperCase()} ACTION API TO ENDPOINT ]: ${endpoint} **** [${payload}]** `);
355
377
  try {
378
+ log && console.log(`**[ ${method.toUpperCase()} ACTION API TO ENDPOINT ]:${endpoint} **** [action try req]** `);
356
379
  const response = await axiosInstance.request({
357
- // <--- غيرنا النوع إلى any
358
380
  url: endpoint,
359
381
  method,
360
382
  ...method.toUpperCase() === "GET" ? { params: payload } : { data: payload },
361
383
  ...config
362
384
  });
363
- return processResponse(response);
385
+ log && console.log(`***[ ${method.toUpperCase()} ACTION API TO ENDPOINT ]: ${endpoint} ***
386
+ ${response}*** `);
387
+ return processResponse(response, log);
364
388
  } catch (error) {
365
- return processResponse(error);
389
+ return processResponse(error, log);
366
390
  }
367
391
  };
368
392
  }
@@ -370,8 +394,8 @@ function createApiActions(axiosInstance, actionsConfig) {
370
394
  const actions = {};
371
395
  for (const actionName in actionsConfig) {
372
396
  if (Object.prototype.hasOwnProperty.call(actionsConfig, actionName)) {
373
- const { method, endpoint } = actionsConfig[actionName];
374
- actions[actionName] = createAction(axiosInstance, method, endpoint);
397
+ const { method, endpoint, log } = actionsConfig[actionName];
398
+ actions[actionName] = createAction(axiosInstance, method, endpoint, log);
375
399
  }
376
400
  }
377
401
  return actions;
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,7 +63,9 @@ function createApiClient(config) {
53
63
  tokenManager,
54
64
  timeout = 15e3,
55
65
  headers = {},
56
- withCredentials = false
66
+ withCredentials = false,
67
+ middleware = [],
68
+ logger = console
57
69
  } = config;
58
70
  const axiosInstance = axios.create({
59
71
  baseURL,
@@ -64,66 +76,76 @@ function createApiClient(config) {
64
76
  let isRefreshing = false;
65
77
  let failedQueue = [];
66
78
  const processQueue = (error, token = null) => {
67
- failedQueue.forEach((prom) => {
68
- if (error) {
69
- prom.reject(error);
70
- } else {
71
- prom.resolve(token);
72
- }
73
- });
79
+ failedQueue.forEach((prom) => error ? prom.reject(error) : prom.resolve(token));
74
80
  failedQueue = [];
75
81
  };
76
82
  axiosInstance.interceptors.request.use(async (req) => {
77
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);
78
87
  if (req.isPublic) {
79
88
  return req;
80
89
  }
90
+ if (tokenManager.isHttpOnly()) {
91
+ return req;
92
+ }
81
93
  let tokens = await tokenManager.getTokens();
82
94
  const now = Date.now();
83
95
  const tokenBuffer = 60 * 1e3;
84
96
  if (tokens.accessToken && tokens.expiresAt && tokens.expiresAt - now < tokenBuffer && !isRefreshing) {
85
97
  if (config.refreshTokenConfig) {
86
- console.log("[API Core] Proactive token refresh initiated.");
98
+ logger.info("[API Core] Proactive token refresh initiated.");
87
99
  isRefreshing = true;
88
100
  try {
89
- const newTokens = await refreshToken(config, tokenManager);
101
+ const newTokens = await refreshToken(config, tokenManager, logger);
90
102
  if (newTokens) tokens = newTokens;
91
103
  } finally {
92
104
  isRefreshing = false;
93
105
  }
94
106
  }
95
107
  }
96
- if (tokens.accessToken && !tokenManager.isHttpOnly()) {
108
+ if (tokens.accessToken) {
97
109
  req.headers.Authorization = `${tokens.tokenType || "Bearer"} ${tokens.accessToken}`;
98
110
  }
99
111
  return req;
100
- }, (error) => Promise.reject(error));
112
+ });
101
113
  axiosInstance.interceptors.response.use(
102
- (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
+ },
103
120
  async (error) => {
104
121
  const originalRequest = error.config;
105
- if (error.response?.status === 401 && !originalRequest._retry && config.refreshTokenConfig) {
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) {
106
132
  if (isRefreshing) {
107
133
  return new Promise((resolve, reject) => {
108
134
  failedQueue.push({ resolve, reject });
109
135
  }).then((token) => {
110
- if (token && !tokenManager.isHttpOnly()) {
111
- originalRequest.headers["Authorization"] = `Bearer ${token}`;
112
- }
136
+ originalRequest.headers["Authorization"] = `Bearer ${token}`;
113
137
  return axiosInstance(originalRequest);
114
138
  });
115
139
  }
116
140
  originalRequest._retry = true;
117
141
  isRefreshing = true;
118
142
  try {
119
- const newTokens = await refreshToken(config, tokenManager);
143
+ const newTokens = await refreshToken(config, tokenManager, logger);
120
144
  if (!newTokens || !newTokens.accessToken) {
121
145
  throw new Error("Token refresh failed to produce a new access token.");
122
146
  }
123
147
  processQueue(null, newTokens.accessToken);
124
- if (!tokenManager.isHttpOnly()) {
125
- originalRequest.headers["Authorization"] = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
126
- }
148
+ originalRequest.headers["Authorization"] = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
127
149
  return axiosInstance(originalRequest);
128
150
  } catch (refreshError) {
129
151
  processQueue(refreshError, null);
@@ -132,13 +154,7 @@ function createApiClient(config) {
132
154
  isRefreshing = false;
133
155
  }
134
156
  }
135
- const enhancedError = {
136
- message: error.response?.data?.message || error.message,
137
- status: error.response?.status || 500,
138
- errors: error.response?.data?.errors,
139
- requestId: originalRequest.headers?.["X-Request-ID"]
140
- };
141
- return Promise.reject(enhancedError);
157
+ return Promise.reject(error);
142
158
  }
143
159
  );
144
160
  return axiosInstance;
@@ -180,8 +196,10 @@ function isAxiosResponse(obj) {
180
196
  }
181
197
 
182
198
  // src/core/processor.ts
183
- var processResponse = (responseOrError) => {
199
+ var processResponse = (responseOrError, log) => {
184
200
  if (isApiError(responseOrError)) {
201
+ log && console.log(`****[ API ERROR PROCESS RESPONSE ]: ***
202
+ ${responseOrError} *** `);
185
203
  return {
186
204
  data: null,
187
205
  rawResponse: void 0,
@@ -193,15 +211,17 @@ var processResponse = (responseOrError) => {
193
211
  };
194
212
  }
195
213
  if (isAxiosResponse(responseOrError)) {
214
+ log && console.log(`****[ API IS SCSS PROCESS RESPONSE ]: ***
215
+ ${responseOrError} *** `);
196
216
  const rawData = responseOrError.data;
197
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} *** `);
198
220
  const finalData = isWrappedResponse ? rawData.data : rawData;
199
221
  const message = isWrappedResponse ? rawData.message : "Request successful.";
200
222
  return {
201
223
  data: finalData,
202
- // <-- وصول مباشر للبيانات النهائية!
203
224
  rawResponse: rawData,
204
- // احتفظ بالاستجابة الكاملة إذا احتجت إليها
205
225
  loading: false,
206
226
  success: true,
207
227
  error: null,
@@ -209,6 +229,7 @@ var processResponse = (responseOrError) => {
209
229
  validationErrors: []
210
230
  };
211
231
  }
232
+ log && console.log(`****[ API STATUS 500 ]: *** ${responseOrError} *** `);
212
233
  return {
213
234
  data: null,
214
235
  rawResponse: void 0,
@@ -308,19 +329,22 @@ function createApiServices(axiosInstance, endpoint) {
308
329
  }
309
330
 
310
331
  // src/services/actions.ts
311
- function createAction(axiosInstance, method, endpoint) {
332
+ function createAction(axiosInstance, method, endpoint, log) {
312
333
  return async (payload, config) => {
334
+ log && console.log(`*[ ${method.toUpperCase()} ACTION API TO ENDPOINT ]: ${endpoint} **** [${payload}]** `);
313
335
  try {
336
+ log && console.log(`**[ ${method.toUpperCase()} ACTION API TO ENDPOINT ]:${endpoint} **** [action try req]** `);
314
337
  const response = await axiosInstance.request({
315
- // <--- غيرنا النوع إلى any
316
338
  url: endpoint,
317
339
  method,
318
340
  ...method.toUpperCase() === "GET" ? { params: payload } : { data: payload },
319
341
  ...config
320
342
  });
321
- return processResponse(response);
343
+ log && console.log(`***[ ${method.toUpperCase()} ACTION API TO ENDPOINT ]: ${endpoint} ***
344
+ ${response}*** `);
345
+ return processResponse(response, log);
322
346
  } catch (error) {
323
- return processResponse(error);
347
+ return processResponse(error, log);
324
348
  }
325
349
  };
326
350
  }
@@ -328,8 +352,8 @@ function createApiActions(axiosInstance, actionsConfig) {
328
352
  const actions = {};
329
353
  for (const actionName in actionsConfig) {
330
354
  if (Object.prototype.hasOwnProperty.call(actionsConfig, actionName)) {
331
- const { method, endpoint } = actionsConfig[actionName];
332
- actions[actionName] = createAction(axiosInstance, method, endpoint);
355
+ const { method, endpoint, log } = actionsConfig[actionName];
356
+ actions[actionName] = createAction(axiosInstance, method, endpoint, log);
333
357
  }
334
358
  }
335
359
  return actions;
package/package.json CHANGED
@@ -1,40 +1,40 @@
1
- {
2
- "name": "api-core-lib",
3
- "version": "4.4.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
- ]
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
  }