api-core-lib 1.0.0 → 1.1.1

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,74 +1,103 @@
1
- import { AxiosRequestConfig, AxiosInstance } from 'axios';
1
+ import { ResponseType, AxiosRequestConfig, AxiosProgressEvent, AxiosInstance } from 'axios';
2
2
  import * as react from 'react';
3
3
 
4
+ /**
5
+ * @file src/types.ts
6
+ * @description
7
+ * هذا الملف هو المصدر المركزي لجميع أنواع البيانات (Types) والواجهات (Interfaces)
8
+ * المستخدمة في المكتبة. تعريف الأنواع هنا يضمن التناسق وسلامة الأنواع
9
+ * في جميع أنحاء المشروع ويوفر واجهة واضحة للمستخدم النهائي.
10
+ */
11
+
12
+ /**
13
+ * يمثل بيانات الترقيم (Pagination) التي تأتي من الـ API.
14
+ */
4
15
  interface PaginationMeta {
5
16
  itemsPerPage: number;
6
17
  totalItems: number;
7
18
  currentPage: number;
8
19
  totalPages: number;
9
- sortBy?: [string, 'asc' | 'desc'][];
10
- }
11
- interface ApiLinks {
12
- current: string;
13
- first?: string;
14
- last?: string;
15
- next?: string;
16
- prev?: string;
17
20
  }
21
+ /**
22
+ * يمثل بنية الاستجابة القياسية من الـ API.
23
+ * @template T نوع البيانات الأساسية في الاستجابة.
24
+ */
18
25
  interface ApiResponse<T = any> {
19
26
  data: T;
20
- meta?: PaginationMeta;
21
- links?: ApiLinks;
22
27
  message?: string;
23
- timestamp?: string;
28
+ meta?: PaginationMeta;
24
29
  }
30
+ /**
31
+ * يمثل خطأ التحقق من صحة حقل معين.
32
+ */
25
33
  interface ValidationError {
26
34
  field: string;
27
35
  message: string;
28
- code: string;
29
36
  }
37
+ /**
38
+ * يمثل كائن الخطأ الموحد الذي تنتجه المكتبة.
39
+ */
30
40
  interface ApiError {
31
41
  message: string;
32
42
  status: number;
33
43
  code?: string;
34
44
  errors?: ValidationError[];
35
- timestamp?: string;
36
- path?: string;
37
45
  requestId?: string;
38
46
  }
47
+ /**
48
+ * يمثل كائن الاستجابة الموحد الذي يرجعه كل طلب.
49
+ * @template T نوع البيانات المتوقعة في حالة النجاح.
50
+ */
39
51
  interface StandardResponse<T> {
40
52
  response?: ApiResponse<T> | T;
41
53
  error: ApiError | null;
42
54
  loading: boolean;
43
55
  success: boolean;
44
56
  message?: string;
45
- cached?: boolean;
46
57
  validationErrors?: ValidationError[];
47
58
  }
48
- interface CacheConfig {
49
- enabled: boolean;
50
- duration?: number;
59
+ /**
60
+ * يمثل مجموعة التوكنات التي يتم إدارتها.
61
+ */
62
+ interface Tokens {
63
+ accessToken: string | null;
64
+ refreshToken: string | null;
65
+ expiresAt?: number;
66
+ tokenType?: string;
67
+ }
68
+ /**
69
+ * واجهة لمدير التوكنات، تسمح بفصل منطق تخزين التوكن.
70
+ * يمكن للمستخدم توفير تطبيق لهذه الواجهة (e.g., LocalStorage, Cookies).
71
+ */
72
+ interface TokenManager {
73
+ getTokens(): Promise<Tokens>;
74
+ setTokens(tokens: Tokens): Promise<void>;
75
+ clearTokens(): Promise<void>;
76
+ isHttpOnly(): boolean;
51
77
  }
78
+ /**
79
+ * يوسع إعدادات طلب Axios القياسية بخيارات خاصة بالمكتبة.
80
+ */
52
81
  interface RequestConfig extends AxiosRequestConfig {
53
- retry?: boolean;
54
- retryCount?: number;
55
- cache?: boolean | CacheConfig;
56
82
  cancelTokenKey?: string;
83
+ onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
84
+ onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void;
57
85
  }
86
+ /**
87
+ * الواجهة الرئيسية لتهيئة عميل الـ API.
88
+ */
58
89
  interface ApiClientConfig {
59
90
  baseURL: string;
91
+ tokenManager: TokenManager;
60
92
  timeout?: number;
61
93
  headers?: Record<string, string>;
62
- getAccessToken?: () => string | null | Promise<string | null>;
63
- getRefreshToken?: () => string | null | Promise<string | null>;
64
- handleTokenRefresh?: (refreshToken: string) => Promise<{
65
- newAccessToken: string;
66
- newRefreshToken: string;
67
- }>;
68
- onTokenRefreshSuccess?: (newAccessToken: string, newRefreshToken: string) => void;
69
- onTokenRefreshError?: (error: any) => void;
94
+ withCredentials?: boolean;
95
+ responseType?: ResponseType;
96
+ onRefreshError?: (error: any) => void;
70
97
  }
71
- type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
98
+ /**
99
+ * يمثل خيارات الاستعلام للترقيم والبحث والفرز.
100
+ */
72
101
  interface PaginateQueryOptions {
73
102
  page?: number;
74
103
  limit?: number;
@@ -78,31 +107,30 @@ interface PaginateQueryOptions {
78
107
  direction: 'asc' | 'desc';
79
108
  }[];
80
109
  filter?: Record<string, any>;
81
- [key: string]: any;
82
110
  }
111
+ /**
112
+ * واجهة لتهيئة الهوك `useApi`.
113
+ * @template T نوع البيانات التي يتعامل معها الهوك.
114
+ */
83
115
  interface UseApiConfig<T> {
84
116
  endpoint: string;
85
- reduxKey?: string;
86
117
  initialData?: T | T[];
87
118
  initialQuery?: PaginateQueryOptions;
88
119
  enabled?: boolean;
89
120
  refetchAfterChange?: boolean;
90
- isToastAfterActions?: boolean;
91
121
  onSuccess?: (message: string, data?: T) => void;
92
122
  onError?: (message: string, error?: ApiError) => void;
93
123
  }
94
124
 
95
- declare function createApiClient(config: ApiClientConfig): AxiosInstance;
125
+ /**
126
+ * @file src/core/client.ts
127
+ * @description
128
+ * هذا هو القلب النابض للمكتبة. دالة `createApiClient` تنشئ وتهيئ نسخة Axios
129
+ * مع معترضات (interceptors) ذكية لإدارة التوكنات، بما في ذلك التجديد التلقائي
130
+ * والاستباقي للتوكن، ومعالجة الأخطاء بشكل مركزي.
131
+ */
96
132
 
97
- declare class CacheManager {
98
- private cache;
99
- private defaultDuration;
100
- set<T>(key: string, data: T, duration?: number): void;
101
- get<T>(key: string): T | null;
102
- delete(key: string): void;
103
- clear(): void;
104
- }
105
- declare const cacheManager: CacheManager;
133
+ declare function createApiClient(config: ApiClientConfig): AxiosInstance;
106
134
 
107
135
  declare function createApiServices<T>(axiosInstance: AxiosInstance, endpoint: string): {
108
136
  get: (id?: string, config?: RequestConfig) => Promise<StandardResponse<T | T[]>>;
@@ -112,8 +140,14 @@ declare function createApiServices<T>(axiosInstance: AxiosInstance, endpoint: st
112
140
  remove: (id: string, config?: RequestConfig) => Promise<StandardResponse<any>>;
113
141
  };
114
142
 
115
- declare const cancelRequest: (key: string) => void;
116
- declare const cancelAllRequests: () => void;
143
+ /**
144
+ * @file src/core/utils.ts
145
+ * @description
146
+ * يحتوي هذا الملف على مجموعة من الدوال المساعدة المستخدمة في جميع أنحاء المكتبة،
147
+ * مثل بناء سلاسل الاستعلام (query strings) وإدارة إلغاء الطلبات،
148
+ * بالإضافة إلى دوال التحقق من الأنواع (type guards).
149
+ */
150
+
117
151
  declare function buildPaginateQuery(query: PaginateQueryOptions): string;
118
152
 
119
153
  declare function useApi<T extends {
@@ -142,4 +176,4 @@ declare function useApi<T extends {
142
176
  };
143
177
  };
144
178
 
145
- export { type ApiClientConfig, type ApiError, type ApiLinks, type ApiResponse, type CacheConfig, type HttpMethod, type PaginateQueryOptions, type PaginationMeta, type RequestConfig, type StandardResponse, type UseApiConfig, type ValidationError, buildPaginateQuery, cacheManager, cancelAllRequests, cancelRequest, createApiClient, createApiServices, useApi };
179
+ export { type ApiClientConfig, type ApiError, type ApiResponse, type PaginateQueryOptions, type PaginationMeta, type RequestConfig, type StandardResponse, type TokenManager, type Tokens, type UseApiConfig, type ValidationError, buildPaginateQuery, createApiClient, createApiServices, useApi };
package/dist/index.d.ts CHANGED
@@ -1,74 +1,103 @@
1
- import { AxiosRequestConfig, AxiosInstance } from 'axios';
1
+ import { ResponseType, AxiosRequestConfig, AxiosProgressEvent, AxiosInstance } from 'axios';
2
2
  import * as react from 'react';
3
3
 
4
+ /**
5
+ * @file src/types.ts
6
+ * @description
7
+ * هذا الملف هو المصدر المركزي لجميع أنواع البيانات (Types) والواجهات (Interfaces)
8
+ * المستخدمة في المكتبة. تعريف الأنواع هنا يضمن التناسق وسلامة الأنواع
9
+ * في جميع أنحاء المشروع ويوفر واجهة واضحة للمستخدم النهائي.
10
+ */
11
+
12
+ /**
13
+ * يمثل بيانات الترقيم (Pagination) التي تأتي من الـ API.
14
+ */
4
15
  interface PaginationMeta {
5
16
  itemsPerPage: number;
6
17
  totalItems: number;
7
18
  currentPage: number;
8
19
  totalPages: number;
9
- sortBy?: [string, 'asc' | 'desc'][];
10
- }
11
- interface ApiLinks {
12
- current: string;
13
- first?: string;
14
- last?: string;
15
- next?: string;
16
- prev?: string;
17
20
  }
21
+ /**
22
+ * يمثل بنية الاستجابة القياسية من الـ API.
23
+ * @template T نوع البيانات الأساسية في الاستجابة.
24
+ */
18
25
  interface ApiResponse<T = any> {
19
26
  data: T;
20
- meta?: PaginationMeta;
21
- links?: ApiLinks;
22
27
  message?: string;
23
- timestamp?: string;
28
+ meta?: PaginationMeta;
24
29
  }
30
+ /**
31
+ * يمثل خطأ التحقق من صحة حقل معين.
32
+ */
25
33
  interface ValidationError {
26
34
  field: string;
27
35
  message: string;
28
- code: string;
29
36
  }
37
+ /**
38
+ * يمثل كائن الخطأ الموحد الذي تنتجه المكتبة.
39
+ */
30
40
  interface ApiError {
31
41
  message: string;
32
42
  status: number;
33
43
  code?: string;
34
44
  errors?: ValidationError[];
35
- timestamp?: string;
36
- path?: string;
37
45
  requestId?: string;
38
46
  }
47
+ /**
48
+ * يمثل كائن الاستجابة الموحد الذي يرجعه كل طلب.
49
+ * @template T نوع البيانات المتوقعة في حالة النجاح.
50
+ */
39
51
  interface StandardResponse<T> {
40
52
  response?: ApiResponse<T> | T;
41
53
  error: ApiError | null;
42
54
  loading: boolean;
43
55
  success: boolean;
44
56
  message?: string;
45
- cached?: boolean;
46
57
  validationErrors?: ValidationError[];
47
58
  }
48
- interface CacheConfig {
49
- enabled: boolean;
50
- duration?: number;
59
+ /**
60
+ * يمثل مجموعة التوكنات التي يتم إدارتها.
61
+ */
62
+ interface Tokens {
63
+ accessToken: string | null;
64
+ refreshToken: string | null;
65
+ expiresAt?: number;
66
+ tokenType?: string;
67
+ }
68
+ /**
69
+ * واجهة لمدير التوكنات، تسمح بفصل منطق تخزين التوكن.
70
+ * يمكن للمستخدم توفير تطبيق لهذه الواجهة (e.g., LocalStorage, Cookies).
71
+ */
72
+ interface TokenManager {
73
+ getTokens(): Promise<Tokens>;
74
+ setTokens(tokens: Tokens): Promise<void>;
75
+ clearTokens(): Promise<void>;
76
+ isHttpOnly(): boolean;
51
77
  }
78
+ /**
79
+ * يوسع إعدادات طلب Axios القياسية بخيارات خاصة بالمكتبة.
80
+ */
52
81
  interface RequestConfig extends AxiosRequestConfig {
53
- retry?: boolean;
54
- retryCount?: number;
55
- cache?: boolean | CacheConfig;
56
82
  cancelTokenKey?: string;
83
+ onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
84
+ onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void;
57
85
  }
86
+ /**
87
+ * الواجهة الرئيسية لتهيئة عميل الـ API.
88
+ */
58
89
  interface ApiClientConfig {
59
90
  baseURL: string;
91
+ tokenManager: TokenManager;
60
92
  timeout?: number;
61
93
  headers?: Record<string, string>;
62
- getAccessToken?: () => string | null | Promise<string | null>;
63
- getRefreshToken?: () => string | null | Promise<string | null>;
64
- handleTokenRefresh?: (refreshToken: string) => Promise<{
65
- newAccessToken: string;
66
- newRefreshToken: string;
67
- }>;
68
- onTokenRefreshSuccess?: (newAccessToken: string, newRefreshToken: string) => void;
69
- onTokenRefreshError?: (error: any) => void;
94
+ withCredentials?: boolean;
95
+ responseType?: ResponseType;
96
+ onRefreshError?: (error: any) => void;
70
97
  }
71
- type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
98
+ /**
99
+ * يمثل خيارات الاستعلام للترقيم والبحث والفرز.
100
+ */
72
101
  interface PaginateQueryOptions {
73
102
  page?: number;
74
103
  limit?: number;
@@ -78,31 +107,30 @@ interface PaginateQueryOptions {
78
107
  direction: 'asc' | 'desc';
79
108
  }[];
80
109
  filter?: Record<string, any>;
81
- [key: string]: any;
82
110
  }
111
+ /**
112
+ * واجهة لتهيئة الهوك `useApi`.
113
+ * @template T نوع البيانات التي يتعامل معها الهوك.
114
+ */
83
115
  interface UseApiConfig<T> {
84
116
  endpoint: string;
85
- reduxKey?: string;
86
117
  initialData?: T | T[];
87
118
  initialQuery?: PaginateQueryOptions;
88
119
  enabled?: boolean;
89
120
  refetchAfterChange?: boolean;
90
- isToastAfterActions?: boolean;
91
121
  onSuccess?: (message: string, data?: T) => void;
92
122
  onError?: (message: string, error?: ApiError) => void;
93
123
  }
94
124
 
95
- declare function createApiClient(config: ApiClientConfig): AxiosInstance;
125
+ /**
126
+ * @file src/core/client.ts
127
+ * @description
128
+ * هذا هو القلب النابض للمكتبة. دالة `createApiClient` تنشئ وتهيئ نسخة Axios
129
+ * مع معترضات (interceptors) ذكية لإدارة التوكنات، بما في ذلك التجديد التلقائي
130
+ * والاستباقي للتوكن، ومعالجة الأخطاء بشكل مركزي.
131
+ */
96
132
 
97
- declare class CacheManager {
98
- private cache;
99
- private defaultDuration;
100
- set<T>(key: string, data: T, duration?: number): void;
101
- get<T>(key: string): T | null;
102
- delete(key: string): void;
103
- clear(): void;
104
- }
105
- declare const cacheManager: CacheManager;
133
+ declare function createApiClient(config: ApiClientConfig): AxiosInstance;
106
134
 
107
135
  declare function createApiServices<T>(axiosInstance: AxiosInstance, endpoint: string): {
108
136
  get: (id?: string, config?: RequestConfig) => Promise<StandardResponse<T | T[]>>;
@@ -112,8 +140,14 @@ declare function createApiServices<T>(axiosInstance: AxiosInstance, endpoint: st
112
140
  remove: (id: string, config?: RequestConfig) => Promise<StandardResponse<any>>;
113
141
  };
114
142
 
115
- declare const cancelRequest: (key: string) => void;
116
- declare const cancelAllRequests: () => void;
143
+ /**
144
+ * @file src/core/utils.ts
145
+ * @description
146
+ * يحتوي هذا الملف على مجموعة من الدوال المساعدة المستخدمة في جميع أنحاء المكتبة،
147
+ * مثل بناء سلاسل الاستعلام (query strings) وإدارة إلغاء الطلبات،
148
+ * بالإضافة إلى دوال التحقق من الأنواع (type guards).
149
+ */
150
+
117
151
  declare function buildPaginateQuery(query: PaginateQueryOptions): string;
118
152
 
119
153
  declare function useApi<T extends {
@@ -142,4 +176,4 @@ declare function useApi<T extends {
142
176
  };
143
177
  };
144
178
 
145
- export { type ApiClientConfig, type ApiError, type ApiLinks, type ApiResponse, type CacheConfig, type HttpMethod, type PaginateQueryOptions, type PaginationMeta, type RequestConfig, type StandardResponse, type UseApiConfig, type ValidationError, buildPaginateQuery, cacheManager, cancelAllRequests, cancelRequest, createApiClient, createApiServices, useApi };
179
+ export { type ApiClientConfig, type ApiError, type ApiResponse, type PaginateQueryOptions, type PaginationMeta, type RequestConfig, type StandardResponse, type TokenManager, type Tokens, type UseApiConfig, type ValidationError, buildPaginateQuery, createApiClient, createApiServices, useApi };
package/dist/index.js CHANGED
@@ -31,9 +31,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  buildPaginateQuery: () => buildPaginateQuery,
34
- cacheManager: () => cacheManager,
35
- cancelAllRequests: () => cancelAllRequests,
36
- cancelRequest: () => cancelRequest,
37
34
  createApiClient: () => createApiClient,
38
35
  createApiServices: () => createApiServices,
39
36
  useApi: () => useApi
@@ -41,200 +38,98 @@ __export(index_exports, {
41
38
  module.exports = __toCommonJS(index_exports);
42
39
 
43
40
  // src/core/client.ts
44
- var import_axios2 = __toESM(require("axios"));
45
- var import_axios_retry = __toESM(require("axios-retry"));
41
+ var import_axios = __toESM(require("axios"));
46
42
  var import_uuid = require("uuid");
47
-
48
- // src/core/cache.ts
49
- var CacheManager = class {
50
- cache = /* @__PURE__ */ new Map();
51
- defaultDuration = 15 * 60 * 1e3;
52
- // 15 minutes
53
- set(key, data, duration) {
54
- this.cache.set(key, {
55
- data,
56
- timestamp: Date.now(),
57
- duration: duration || this.defaultDuration
58
- });
59
- }
60
- get(key) {
61
- const item = this.cache.get(key);
62
- if (!item) return null;
63
- const isExpired = Date.now() - item.timestamp > item.duration;
64
- if (isExpired) {
65
- this.cache.delete(key);
66
- return null;
43
+ async function refreshToken(config, tokenManager) {
44
+ try {
45
+ const currentTokens = await tokenManager.getTokens();
46
+ if (!currentTokens.refreshToken) throw new Error("No refresh token available.");
47
+ const response = await import_axios.default.post(
48
+ `${config.baseURL}/auth/refresh`,
49
+ { refresh_token: currentTokens.refreshToken },
50
+ { withCredentials: config.withCredentials }
51
+ );
52
+ const { access_token, refresh_token, expires_in, token_type } = response.data;
53
+ const newTokens = {
54
+ accessToken: access_token,
55
+ refreshToken: refresh_token || currentTokens.refreshToken,
56
+ expiresAt: Date.now() + expires_in * 1e3,
57
+ tokenType: token_type || "Bearer"
58
+ };
59
+ await tokenManager.setTokens(newTokens);
60
+ console.log("[API Core] Tokens refreshed successfully.");
61
+ return newTokens;
62
+ } catch (err) {
63
+ console.error("[API Core] Failed to refresh token.", err);
64
+ if (config.onRefreshError) {
65
+ config.onRefreshError(err);
67
66
  }
68
- return item.data;
69
- }
70
- delete(key) {
71
- this.cache.delete(key);
72
- }
73
- clear() {
74
- this.cache.clear();
75
- }
76
- };
77
- var cacheManager = new CacheManager();
78
-
79
- // src/core/utils.ts
80
- var import_axios = __toESM(require("axios"));
81
- var cancelTokens = /* @__PURE__ */ new Map();
82
- var createCancelToken = (key) => {
83
- const existingToken = cancelTokens.get(key);
84
- if (existingToken) {
85
- existingToken.cancel("Operation canceled due to new request.");
86
- cancelTokens.delete(key);
87
- }
88
- const source = import_axios.default.CancelToken.source();
89
- cancelTokens.set(key, source);
90
- return source;
91
- };
92
- var cancelRequest = (key) => {
93
- const source = cancelTokens.get(key);
94
- if (source) {
95
- source.cancel("Operation canceled by user.");
96
- cancelTokens.delete(key);
97
- }
98
- };
99
- var cancelAllRequests = () => {
100
- cancelTokens.forEach((source) => source.cancel("All operations canceled."));
101
- cancelTokens.clear();
102
- };
103
- function buildPaginateQuery(query) {
104
- const params = new URLSearchParams();
105
- if (!query) return "";
106
- if (query.page) params.append("page", query.page.toString());
107
- if (query.limit) params.append("limit", query.limit.toString());
108
- if (query.search) params.append("search", query.search);
109
- if (query.sortBy) {
110
- query.sortBy.forEach((sort) => params.append("sortBy", `${sort.key}:${sort.direction}`));
111
- }
112
- if (query.filter) {
113
- Object.entries(query.filter).forEach(([field, value]) => {
114
- params.append(`filter.${field}`, value);
115
- });
67
+ await tokenManager.clearTokens();
68
+ return null;
116
69
  }
117
- const queryString = params.toString();
118
- return queryString ? `?${queryString}` : "";
119
- }
120
- function isApiError(obj) {
121
- return obj && typeof obj.status === "number" && typeof obj.message === "string" && obj.config === void 0;
122
70
  }
123
- function isAxiosResponse(obj) {
124
- return obj && obj.data !== void 0 && obj.status !== void 0 && obj.config !== void 0;
125
- }
126
-
127
- // src/core/client.ts
128
71
  function createApiClient(config) {
129
72
  const {
130
73
  baseURL,
131
- timeout = 1e4,
74
+ tokenManager,
75
+ timeout = 15e3,
132
76
  headers = {},
133
- getAccessToken,
134
- getRefreshToken,
135
- handleTokenRefresh,
136
- onTokenRefreshSuccess,
137
- onTokenRefreshError
77
+ withCredentials = false
138
78
  } = config;
139
- const axiosInstance = import_axios2.default.create({
79
+ const axiosInstance = import_axios.default.create({
140
80
  baseURL,
141
81
  timeout,
142
- headers: {
143
- "Content-Type": "application/json",
144
- Accept: "application/json",
145
- ...headers
146
- },
147
- withCredentials: true
148
- });
149
- (0, import_axios_retry.default)(axiosInstance, {
150
- retries: 3,
151
- retryDelay: import_axios_retry.default.exponentialDelay,
152
- retryCondition: (error) => import_axios_retry.default.isNetworkOrIdempotentRequestError(error) || error.response?.status === 429
82
+ headers: { "Content-Type": "application/json", ...headers },
83
+ withCredentials
153
84
  });
154
- let isRefreshing = false;
155
- let requestQueue = [];
85
+ let tokenRefreshPromise = null;
156
86
  axiosInstance.interceptors.request.use(async (req) => {
157
87
  req.headers["X-Request-ID"] = (0, import_uuid.v4)();
158
- if (getAccessToken) {
159
- const token = await getAccessToken();
160
- if (token) {
161
- req.headers.Authorization = `Bearer ${token}`;
162
- }
88
+ if (req.url?.includes("/auth/")) return req;
89
+ if (tokenRefreshPromise) {
90
+ await tokenRefreshPromise;
163
91
  }
164
- if (req.cancelTokenKey) {
165
- req.cancelToken = createCancelToken(req.cancelTokenKey).token;
166
- }
167
- if (req.method?.toLowerCase() === "get" && req.cache) {
168
- const cacheKey = req.url || "default-cache-key";
169
- const cachedData = cacheManager.get(cacheKey);
170
- if (cachedData) {
171
- console.log(`[Cache] HIT for ${cacheKey}`);
172
- req.adapter = () => Promise.resolve({
173
- data: cachedData,
174
- status: 200,
175
- statusText: "OK (from cache)",
176
- headers: req.headers,
177
- config: req
178
- });
179
- req.cached = true;
92
+ let tokens = await tokenManager.getTokens();
93
+ const now = Date.now();
94
+ const tokenBuffer = 60 * 1e3;
95
+ if (tokens.accessToken && tokens.expiresAt && tokens.expiresAt - now < tokenBuffer) {
96
+ if (!tokenRefreshPromise) {
97
+ console.log("[API Core] Proactive token refresh initiated.");
98
+ tokenRefreshPromise = refreshToken(config, tokenManager);
180
99
  }
100
+ const newTokens = await tokenRefreshPromise;
101
+ tokenRefreshPromise = null;
102
+ if (newTokens) tokens = newTokens;
103
+ }
104
+ if (tokens.accessToken && !tokenManager.isHttpOnly()) {
105
+ const tokenType = tokens.tokenType || "Bearer";
106
+ req.headers.Authorization = `${tokenType} ${tokens.accessToken}`;
181
107
  }
182
108
  return req;
183
109
  }, (error) => Promise.reject(error));
184
110
  axiosInstance.interceptors.response.use(
185
- (response) => {
186
- const config2 = response.config;
187
- if (response.config.method?.toLowerCase() === "get" && config2.cache) {
188
- const cacheKey = response.config.url || "default-cache-key";
189
- const cacheConfig = typeof config2.cache === "object" ? config2.cache : { enabled: true };
190
- if (cacheConfig.enabled) {
191
- console.log(`[Cache] SET for ${cacheKey}`);
192
- cacheManager.set(cacheKey, response.data, cacheConfig.duration);
193
- }
194
- }
195
- return response;
196
- },
111
+ (response) => response,
197
112
  async (error) => {
198
113
  const originalRequest = error.config;
199
- if (error.response?.status === 401 && !originalRequest._retry && getRefreshToken && handleTokenRefresh) {
114
+ if (error.response?.status === 401 && !originalRequest._retry) {
200
115
  originalRequest._retry = true;
201
- if (isRefreshing) {
202
- return new Promise((resolve) => {
203
- requestQueue.push((token) => {
204
- originalRequest.headers.Authorization = `Bearer ${token}`;
205
- resolve(axiosInstance(originalRequest));
206
- });
207
- });
116
+ if (!tokenRefreshPromise) {
117
+ console.log("[API Core] Reactive token refresh initiated due to 401.");
118
+ tokenRefreshPromise = refreshToken(config, tokenManager);
208
119
  }
209
- isRefreshing = true;
210
- try {
211
- const currentRefreshToken = await getRefreshToken();
212
- if (!currentRefreshToken) throw new Error("No refresh token available.");
213
- const { newAccessToken, newRefreshToken } = await handleTokenRefresh(currentRefreshToken);
214
- if (onTokenRefreshSuccess) {
215
- onTokenRefreshSuccess(newAccessToken, newRefreshToken);
120
+ const newTokens = await tokenRefreshPromise;
121
+ tokenRefreshPromise = null;
122
+ if (newTokens) {
123
+ if (newTokens.accessToken && !tokenManager.isHttpOnly()) {
124
+ originalRequest.headers.Authorization = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
216
125
  }
217
- axiosInstance.defaults.headers.common["Authorization"] = `Bearer ${newAccessToken}`;
218
- originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
219
- requestQueue.forEach((cb) => cb(newAccessToken));
220
- requestQueue = [];
221
126
  return axiosInstance(originalRequest);
222
- } catch (refreshError) {
223
- if (onTokenRefreshError) {
224
- onTokenRefreshError(refreshError);
225
- }
226
- requestQueue = [];
227
- return Promise.reject(refreshError);
228
- } finally {
229
- isRefreshing = false;
230
127
  }
231
128
  }
232
129
  const enhancedError = {
233
- message: error.response?.data?.message || error.message || "An unexpected error occurred",
130
+ message: error.response?.data?.message || error.message,
234
131
  status: error.response?.status || 500,
235
- code: error.code,
236
132
  errors: error.response?.data?.errors,
237
- path: originalRequest.url,
238
133
  requestId: originalRequest.headers?.["X-Request-ID"]
239
134
  };
240
135
  return Promise.reject(enhancedError);
@@ -243,6 +138,32 @@ function createApiClient(config) {
243
138
  return axiosInstance;
244
139
  }
245
140
 
141
+ // src/core/utils.ts
142
+ var import_axios2 = __toESM(require("axios"));
143
+ function buildPaginateQuery(query) {
144
+ if (!query) return "";
145
+ const params = new URLSearchParams();
146
+ if (query.page) params.append("page", query.page.toString());
147
+ if (query.limit) params.append("limit", query.limit.toString());
148
+ if (query.search) params.append("search", query.search);
149
+ if (query.sortBy) {
150
+ query.sortBy.forEach((sort) => params.append("sortBy", `${sort.key}:${sort.direction}`));
151
+ }
152
+ if (query.filter) {
153
+ Object.entries(query.filter).forEach(([field, value]) => {
154
+ params.append(`filter.${field}`, String(value));
155
+ });
156
+ }
157
+ const queryString = params.toString();
158
+ return queryString ? `?${queryString}` : "";
159
+ }
160
+ function isApiError(obj) {
161
+ return obj && typeof obj.status === "number" && typeof obj.message === "string" && obj.config === void 0;
162
+ }
163
+ function isAxiosResponse(obj) {
164
+ return obj && obj.data !== void 0 && obj.status !== void 0 && obj.config !== void 0;
165
+ }
166
+
246
167
  // src/core/processor.ts
247
168
  var processResponse = (responseOrError) => {
248
169
  if (isApiError(responseOrError)) {
@@ -250,6 +171,7 @@ var processResponse = (responseOrError) => {
250
171
  response: void 0,
251
172
  error: responseOrError,
252
173
  validationErrors: responseOrError.errors || [],
174
+ // الخطأ الآن صحيح
253
175
  success: false,
254
176
  loading: false,
255
177
  message: responseOrError.message
@@ -257,18 +179,15 @@ var processResponse = (responseOrError) => {
257
179
  }
258
180
  if (isAxiosResponse(responseOrError)) {
259
181
  const response = responseOrError;
260
- const isSuccess = response.status >= 200 && response.status < 300;
261
- if (isSuccess) {
262
- return {
263
- response: response.data,
264
- loading: false,
265
- success: true,
266
- cached: response.config.cached,
267
- error: null,
268
- validationErrors: [],
269
- message: response.data.message || "Request successful."
270
- };
271
- }
182
+ return {
183
+ response: response.data,
184
+ loading: false,
185
+ success: true,
186
+ error: null,
187
+ message: response.data.message || "Request successful.",
188
+ validationErrors: []
189
+ // <-- أضف قيمة افتراضية هنا للاتساق
190
+ };
272
191
  }
273
192
  return {
274
193
  response: void 0,
@@ -277,7 +196,9 @@ var processResponse = (responseOrError) => {
277
196
  status: 500
278
197
  },
279
198
  success: false,
280
- loading: false
199
+ loading: false,
200
+ validationErrors: []
201
+ // <-- أضف قيمة افتراضية هنا أيضًا
281
202
  };
282
203
  };
283
204
 
@@ -431,9 +352,6 @@ function useApi(axiosInstance, config) {
431
352
  // Annotate the CommonJS export names for ESM import in node:
432
353
  0 && (module.exports = {
433
354
  buildPaginateQuery,
434
- cacheManager,
435
- cancelAllRequests,
436
- cancelRequest,
437
355
  createApiClient,
438
356
  createApiServices,
439
357
  useApi
package/dist/index.mjs CHANGED
@@ -1,198 +1,96 @@
1
1
  // src/core/client.ts
2
- import axios2 from "axios";
3
- import axiosRetry from "axios-retry";
2
+ import axios from "axios";
4
3
  import { v4 as uuidv4 } from "uuid";
5
-
6
- // src/core/cache.ts
7
- var CacheManager = class {
8
- cache = /* @__PURE__ */ new Map();
9
- defaultDuration = 15 * 60 * 1e3;
10
- // 15 minutes
11
- set(key, data, duration) {
12
- this.cache.set(key, {
13
- data,
14
- timestamp: Date.now(),
15
- duration: duration || this.defaultDuration
16
- });
17
- }
18
- get(key) {
19
- const item = this.cache.get(key);
20
- if (!item) return null;
21
- const isExpired = Date.now() - item.timestamp > item.duration;
22
- if (isExpired) {
23
- this.cache.delete(key);
24
- return null;
4
+ async function refreshToken(config, tokenManager) {
5
+ try {
6
+ const currentTokens = await tokenManager.getTokens();
7
+ if (!currentTokens.refreshToken) throw new Error("No refresh token available.");
8
+ const response = await axios.post(
9
+ `${config.baseURL}/auth/refresh`,
10
+ { refresh_token: currentTokens.refreshToken },
11
+ { withCredentials: config.withCredentials }
12
+ );
13
+ const { access_token, refresh_token, expires_in, token_type } = response.data;
14
+ const newTokens = {
15
+ accessToken: access_token,
16
+ refreshToken: refresh_token || currentTokens.refreshToken,
17
+ expiresAt: Date.now() + expires_in * 1e3,
18
+ tokenType: token_type || "Bearer"
19
+ };
20
+ await tokenManager.setTokens(newTokens);
21
+ console.log("[API Core] Tokens refreshed successfully.");
22
+ return newTokens;
23
+ } catch (err) {
24
+ console.error("[API Core] Failed to refresh token.", err);
25
+ if (config.onRefreshError) {
26
+ config.onRefreshError(err);
25
27
  }
26
- return item.data;
27
- }
28
- delete(key) {
29
- this.cache.delete(key);
30
- }
31
- clear() {
32
- this.cache.clear();
33
- }
34
- };
35
- var cacheManager = new CacheManager();
36
-
37
- // src/core/utils.ts
38
- import axios from "axios";
39
- var cancelTokens = /* @__PURE__ */ new Map();
40
- var createCancelToken = (key) => {
41
- const existingToken = cancelTokens.get(key);
42
- if (existingToken) {
43
- existingToken.cancel("Operation canceled due to new request.");
44
- cancelTokens.delete(key);
45
- }
46
- const source = axios.CancelToken.source();
47
- cancelTokens.set(key, source);
48
- return source;
49
- };
50
- var cancelRequest = (key) => {
51
- const source = cancelTokens.get(key);
52
- if (source) {
53
- source.cancel("Operation canceled by user.");
54
- cancelTokens.delete(key);
55
- }
56
- };
57
- var cancelAllRequests = () => {
58
- cancelTokens.forEach((source) => source.cancel("All operations canceled."));
59
- cancelTokens.clear();
60
- };
61
- function buildPaginateQuery(query) {
62
- const params = new URLSearchParams();
63
- if (!query) return "";
64
- if (query.page) params.append("page", query.page.toString());
65
- if (query.limit) params.append("limit", query.limit.toString());
66
- if (query.search) params.append("search", query.search);
67
- if (query.sortBy) {
68
- query.sortBy.forEach((sort) => params.append("sortBy", `${sort.key}:${sort.direction}`));
69
- }
70
- if (query.filter) {
71
- Object.entries(query.filter).forEach(([field, value]) => {
72
- params.append(`filter.${field}`, value);
73
- });
28
+ await tokenManager.clearTokens();
29
+ return null;
74
30
  }
75
- const queryString = params.toString();
76
- return queryString ? `?${queryString}` : "";
77
- }
78
- function isApiError(obj) {
79
- return obj && typeof obj.status === "number" && typeof obj.message === "string" && obj.config === void 0;
80
31
  }
81
- function isAxiosResponse(obj) {
82
- return obj && obj.data !== void 0 && obj.status !== void 0 && obj.config !== void 0;
83
- }
84
-
85
- // src/core/client.ts
86
32
  function createApiClient(config) {
87
33
  const {
88
34
  baseURL,
89
- timeout = 1e4,
35
+ tokenManager,
36
+ timeout = 15e3,
90
37
  headers = {},
91
- getAccessToken,
92
- getRefreshToken,
93
- handleTokenRefresh,
94
- onTokenRefreshSuccess,
95
- onTokenRefreshError
38
+ withCredentials = false
96
39
  } = config;
97
- const axiosInstance = axios2.create({
40
+ const axiosInstance = axios.create({
98
41
  baseURL,
99
42
  timeout,
100
- headers: {
101
- "Content-Type": "application/json",
102
- Accept: "application/json",
103
- ...headers
104
- },
105
- withCredentials: true
106
- });
107
- axiosRetry(axiosInstance, {
108
- retries: 3,
109
- retryDelay: axiosRetry.exponentialDelay,
110
- retryCondition: (error) => axiosRetry.isNetworkOrIdempotentRequestError(error) || error.response?.status === 429
43
+ headers: { "Content-Type": "application/json", ...headers },
44
+ withCredentials
111
45
  });
112
- let isRefreshing = false;
113
- let requestQueue = [];
46
+ let tokenRefreshPromise = null;
114
47
  axiosInstance.interceptors.request.use(async (req) => {
115
48
  req.headers["X-Request-ID"] = uuidv4();
116
- if (getAccessToken) {
117
- const token = await getAccessToken();
118
- if (token) {
119
- req.headers.Authorization = `Bearer ${token}`;
120
- }
49
+ if (req.url?.includes("/auth/")) return req;
50
+ if (tokenRefreshPromise) {
51
+ await tokenRefreshPromise;
121
52
  }
122
- if (req.cancelTokenKey) {
123
- req.cancelToken = createCancelToken(req.cancelTokenKey).token;
124
- }
125
- if (req.method?.toLowerCase() === "get" && req.cache) {
126
- const cacheKey = req.url || "default-cache-key";
127
- const cachedData = cacheManager.get(cacheKey);
128
- if (cachedData) {
129
- console.log(`[Cache] HIT for ${cacheKey}`);
130
- req.adapter = () => Promise.resolve({
131
- data: cachedData,
132
- status: 200,
133
- statusText: "OK (from cache)",
134
- headers: req.headers,
135
- config: req
136
- });
137
- req.cached = true;
53
+ let tokens = await tokenManager.getTokens();
54
+ const now = Date.now();
55
+ const tokenBuffer = 60 * 1e3;
56
+ if (tokens.accessToken && tokens.expiresAt && tokens.expiresAt - now < tokenBuffer) {
57
+ if (!tokenRefreshPromise) {
58
+ console.log("[API Core] Proactive token refresh initiated.");
59
+ tokenRefreshPromise = refreshToken(config, tokenManager);
138
60
  }
61
+ const newTokens = await tokenRefreshPromise;
62
+ tokenRefreshPromise = null;
63
+ if (newTokens) tokens = newTokens;
64
+ }
65
+ if (tokens.accessToken && !tokenManager.isHttpOnly()) {
66
+ const tokenType = tokens.tokenType || "Bearer";
67
+ req.headers.Authorization = `${tokenType} ${tokens.accessToken}`;
139
68
  }
140
69
  return req;
141
70
  }, (error) => Promise.reject(error));
142
71
  axiosInstance.interceptors.response.use(
143
- (response) => {
144
- const config2 = response.config;
145
- if (response.config.method?.toLowerCase() === "get" && config2.cache) {
146
- const cacheKey = response.config.url || "default-cache-key";
147
- const cacheConfig = typeof config2.cache === "object" ? config2.cache : { enabled: true };
148
- if (cacheConfig.enabled) {
149
- console.log(`[Cache] SET for ${cacheKey}`);
150
- cacheManager.set(cacheKey, response.data, cacheConfig.duration);
151
- }
152
- }
153
- return response;
154
- },
72
+ (response) => response,
155
73
  async (error) => {
156
74
  const originalRequest = error.config;
157
- if (error.response?.status === 401 && !originalRequest._retry && getRefreshToken && handleTokenRefresh) {
75
+ if (error.response?.status === 401 && !originalRequest._retry) {
158
76
  originalRequest._retry = true;
159
- if (isRefreshing) {
160
- return new Promise((resolve) => {
161
- requestQueue.push((token) => {
162
- originalRequest.headers.Authorization = `Bearer ${token}`;
163
- resolve(axiosInstance(originalRequest));
164
- });
165
- });
77
+ if (!tokenRefreshPromise) {
78
+ console.log("[API Core] Reactive token refresh initiated due to 401.");
79
+ tokenRefreshPromise = refreshToken(config, tokenManager);
166
80
  }
167
- isRefreshing = true;
168
- try {
169
- const currentRefreshToken = await getRefreshToken();
170
- if (!currentRefreshToken) throw new Error("No refresh token available.");
171
- const { newAccessToken, newRefreshToken } = await handleTokenRefresh(currentRefreshToken);
172
- if (onTokenRefreshSuccess) {
173
- onTokenRefreshSuccess(newAccessToken, newRefreshToken);
81
+ const newTokens = await tokenRefreshPromise;
82
+ tokenRefreshPromise = null;
83
+ if (newTokens) {
84
+ if (newTokens.accessToken && !tokenManager.isHttpOnly()) {
85
+ originalRequest.headers.Authorization = `${newTokens.tokenType || "Bearer"} ${newTokens.accessToken}`;
174
86
  }
175
- axiosInstance.defaults.headers.common["Authorization"] = `Bearer ${newAccessToken}`;
176
- originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
177
- requestQueue.forEach((cb) => cb(newAccessToken));
178
- requestQueue = [];
179
87
  return axiosInstance(originalRequest);
180
- } catch (refreshError) {
181
- if (onTokenRefreshError) {
182
- onTokenRefreshError(refreshError);
183
- }
184
- requestQueue = [];
185
- return Promise.reject(refreshError);
186
- } finally {
187
- isRefreshing = false;
188
88
  }
189
89
  }
190
90
  const enhancedError = {
191
- message: error.response?.data?.message || error.message || "An unexpected error occurred",
91
+ message: error.response?.data?.message || error.message,
192
92
  status: error.response?.status || 500,
193
- code: error.code,
194
93
  errors: error.response?.data?.errors,
195
- path: originalRequest.url,
196
94
  requestId: originalRequest.headers?.["X-Request-ID"]
197
95
  };
198
96
  return Promise.reject(enhancedError);
@@ -201,6 +99,32 @@ function createApiClient(config) {
201
99
  return axiosInstance;
202
100
  }
203
101
 
102
+ // src/core/utils.ts
103
+ import axios2 from "axios";
104
+ function buildPaginateQuery(query) {
105
+ if (!query) return "";
106
+ const params = new URLSearchParams();
107
+ if (query.page) params.append("page", query.page.toString());
108
+ if (query.limit) params.append("limit", query.limit.toString());
109
+ if (query.search) params.append("search", query.search);
110
+ if (query.sortBy) {
111
+ query.sortBy.forEach((sort) => params.append("sortBy", `${sort.key}:${sort.direction}`));
112
+ }
113
+ if (query.filter) {
114
+ Object.entries(query.filter).forEach(([field, value]) => {
115
+ params.append(`filter.${field}`, String(value));
116
+ });
117
+ }
118
+ const queryString = params.toString();
119
+ return queryString ? `?${queryString}` : "";
120
+ }
121
+ function isApiError(obj) {
122
+ return obj && typeof obj.status === "number" && typeof obj.message === "string" && obj.config === void 0;
123
+ }
124
+ function isAxiosResponse(obj) {
125
+ return obj && obj.data !== void 0 && obj.status !== void 0 && obj.config !== void 0;
126
+ }
127
+
204
128
  // src/core/processor.ts
205
129
  var processResponse = (responseOrError) => {
206
130
  if (isApiError(responseOrError)) {
@@ -208,6 +132,7 @@ var processResponse = (responseOrError) => {
208
132
  response: void 0,
209
133
  error: responseOrError,
210
134
  validationErrors: responseOrError.errors || [],
135
+ // الخطأ الآن صحيح
211
136
  success: false,
212
137
  loading: false,
213
138
  message: responseOrError.message
@@ -215,18 +140,15 @@ var processResponse = (responseOrError) => {
215
140
  }
216
141
  if (isAxiosResponse(responseOrError)) {
217
142
  const response = responseOrError;
218
- const isSuccess = response.status >= 200 && response.status < 300;
219
- if (isSuccess) {
220
- return {
221
- response: response.data,
222
- loading: false,
223
- success: true,
224
- cached: response.config.cached,
225
- error: null,
226
- validationErrors: [],
227
- message: response.data.message || "Request successful."
228
- };
229
- }
143
+ return {
144
+ response: response.data,
145
+ loading: false,
146
+ success: true,
147
+ error: null,
148
+ message: response.data.message || "Request successful.",
149
+ validationErrors: []
150
+ // <-- أضف قيمة افتراضية هنا للاتساق
151
+ };
230
152
  }
231
153
  return {
232
154
  response: void 0,
@@ -235,7 +157,9 @@ var processResponse = (responseOrError) => {
235
157
  status: 500
236
158
  },
237
159
  success: false,
238
- loading: false
160
+ loading: false,
161
+ validationErrors: []
162
+ // <-- أضف قيمة افتراضية هنا أيضًا
239
163
  };
240
164
  };
241
165
 
@@ -388,9 +312,6 @@ function useApi(axiosInstance, config) {
388
312
  }
389
313
  export {
390
314
  buildPaginateQuery,
391
- cacheManager,
392
- cancelAllRequests,
393
- cancelRequest,
394
315
  createApiClient,
395
316
  createApiServices,
396
317
  useApi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-core-lib",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
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",