@ventlio/tanstack-query 0.5.11 → 0.5.13

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.
Files changed (55) hide show
  1. package/README.md +178 -150
  2. package/dist/config/bootstrapQueryRequest.d.ts +7 -1
  3. package/dist/config/useEnvironmentVariables.d.ts +4 -0
  4. package/dist/index.mjs +307 -90
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/node_modules/@tanstack/react-store/dist/esm/index.js +1 -1
  7. package/dist/node_modules/{@tanstack/react-store/node_modules/use-sync-external-store → use-sync-external-store}/cjs/use-sync-external-store-shim/with-selector.development.js +1 -1
  8. package/dist/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.development.js.map +1 -0
  9. package/dist/node_modules/{@tanstack/react-store/node_modules/use-sync-external-store → use-sync-external-store}/cjs/use-sync-external-store-shim/with-selector.production.js +1 -1
  10. package/dist/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.production.js.map +1 -0
  11. package/dist/node_modules/{@tanstack/react-store/node_modules/use-sync-external-store → use-sync-external-store}/cjs/use-sync-external-store-shim.development.js +1 -1
  12. package/dist/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.development.js.map +1 -0
  13. package/dist/node_modules/{@tanstack/react-store/node_modules/use-sync-external-store → use-sync-external-store}/cjs/use-sync-external-store-shim.production.js +1 -1
  14. package/dist/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.production.js.map +1 -0
  15. package/dist/node_modules/{@tanstack/react-store/node_modules/use-sync-external-store → use-sync-external-store}/shim/index.js +1 -1
  16. package/dist/node_modules/use-sync-external-store/shim/index.js.map +1 -0
  17. package/dist/node_modules/{@tanstack/react-store/node_modules/use-sync-external-store → use-sync-external-store}/shim/with-selector.js +1 -1
  18. package/dist/node_modules/use-sync-external-store/shim/with-selector.js.map +1 -0
  19. package/dist/queries/useGetRequest.d.ts +12 -2
  20. package/dist/request/make-request.d.ts +12 -1
  21. package/dist/src/config/bootstrapQueryRequest.js +42 -2
  22. package/dist/src/config/bootstrapQueryRequest.js.map +1 -1
  23. package/dist/src/config/useEnvironmentVariables.js +34 -3
  24. package/dist/src/config/useEnvironmentVariables.js.map +1 -1
  25. package/dist/src/index.js +1 -1
  26. package/dist/src/queries/useDeleteRequest.js +19 -14
  27. package/dist/src/queries/useDeleteRequest.js.map +1 -1
  28. package/dist/src/queries/useGetInfiniteRequest.js +17 -12
  29. package/dist/src/queries/useGetInfiniteRequest.js.map +1 -1
  30. package/dist/src/queries/useGetRequest.js +86 -28
  31. package/dist/src/queries/useGetRequest.js.map +1 -1
  32. package/dist/src/queries/usePatchRequest.js +19 -14
  33. package/dist/src/queries/usePatchRequest.js.map +1 -1
  34. package/dist/src/queries/usePostRequest.js +18 -13
  35. package/dist/src/queries/usePostRequest.js.map +1 -1
  36. package/dist/src/request/make-request.js +75 -6
  37. package/dist/src/request/make-request.js.map +1 -1
  38. package/dist/types/index.d.ts +24 -5
  39. package/package.json +2 -2
  40. package/src/config/bootstrapQueryRequest.ts +48 -3
  41. package/src/config/useEnvironmentVariables.ts +41 -3
  42. package/src/queries/useDeleteRequest.ts +18 -20
  43. package/src/queries/useGetInfiniteRequest.ts +17 -17
  44. package/src/queries/useGetRequest.ts +109 -33
  45. package/src/queries/usePatchRequest.ts +19 -16
  46. package/src/queries/usePostRequest.ts +18 -15
  47. package/src/queries/usePutRequest.ts +16 -15
  48. package/src/request/make-request.ts +112 -15
  49. package/src/types/index.ts +38 -4
  50. package/dist/node_modules/@tanstack/react-store/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.development.js.map +0 -1
  51. package/dist/node_modules/@tanstack/react-store/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.production.js.map +0 -1
  52. package/dist/node_modules/@tanstack/react-store/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.development.js.map +0 -1
  53. package/dist/node_modules/@tanstack/react-store/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.production.js.map +0 -1
  54. package/dist/node_modules/@tanstack/react-store/node_modules/use-sync-external-store/shim/index.js.map +0 -1
  55. package/dist/node_modules/@tanstack/react-store/node_modules/use-sync-external-store/shim/with-selector.js.map +0 -1
@@ -1,9 +1,7 @@
1
1
  import type { QueryKey, UseQueryOptions } from '@tanstack/react-query';
2
2
  import { useQuery } from '@tanstack/react-query';
3
- import { useStore } from '@tanstack/react-store';
4
3
  import { useEffect, useState } from 'react';
5
4
  import { useEnvironmentVariables } from '../config';
6
- import { bootStore } from '../config/bootStore';
7
5
  import type { IRequestError, IRequestSuccess } from '../request';
8
6
  import { HttpMethod, makeRequest } from '../request';
9
7
  import { useHeaderStore, usePauseFutureRequests } from '../stores';
@@ -14,8 +12,8 @@ export const useDeleteRequest = <TResponse>(deleteOptions?: DefaultRequestOption
14
12
  const [requestPath, setRequestPath] = useState<string>('');
15
13
  const [options, setOptions] = useState<any>();
16
14
 
17
- const { middleware } = useStore(bootStore);
18
-
15
+ // const { middleware: middlewares } = useStore(bootStore);
16
+ // const [middleware] = middlewares as unknown as MiddlewareFunction[];
19
17
  const [requestPayload, setRequestPayload] = useState<Record<any, any>>();
20
18
 
21
19
  const isFutureQueriesPaused = usePauseFutureRequests((state) => state.isFutureQueriesPaused);
@@ -36,22 +34,22 @@ export const useDeleteRequest = <TResponse>(deleteOptions?: DefaultRequestOption
36
34
  timeout: TIMEOUT,
37
35
  };
38
36
 
39
- let deleteResponse: IRequestError | IRequestSuccess<TResponse>;
40
- if (middleware) {
41
- // perform global middleware
42
- deleteResponse = await middleware(
43
- async (middlewareOptions) =>
44
- await makeRequest<TResponse>(
45
- middlewareOptions ? { ...requestOptions, ...middlewareOptions } : requestOptions
46
- ),
47
- {
48
- path: requestUrl,
49
- baseUrl: baseUrl ?? API_URL,
50
- }
51
- );
52
- } else {
53
- deleteResponse = await makeRequest<TResponse>(requestOptions);
54
- }
37
+ // let deleteResponse: IRequestError | IRequestSuccess<TResponse>;
38
+ // if (middleware) {
39
+ // // perform global middleware
40
+ // deleteResponse = await middleware(
41
+ // async (middlewareOptions) =>
42
+ // await makeRequest<TResponse>(
43
+ // middlewareOptions ? { ...requestOptions, ...middlewareOptions } : requestOptions
44
+ // ),
45
+ // {
46
+ // path: requestUrl,
47
+ // baseUrl: baseUrl ?? API_URL,
48
+ // }
49
+ // );
50
+ // } else {
51
+ const deleteResponse = await makeRequest<TResponse>(requestOptions);
52
+ // }
55
53
 
56
54
  if (deleteResponse.status) {
57
55
  res(deleteResponse as IRequestSuccess<TResponse>);
@@ -37,7 +37,7 @@ export const useGetInfiniteRequest = <TResponse extends Record<string, any>>({
37
37
  const [requestPath, setRequestPath] = useState<string>(path);
38
38
 
39
39
  const [options, setOptions] = useState<any>(queryOptions);
40
- const { middleware } = useStore(bootStore);
40
+ useStore(bootStore);
41
41
 
42
42
  const [requestPayload, setRequestPayload] = useState<Record<any, any>>();
43
43
 
@@ -68,22 +68,22 @@ export const useGetInfiniteRequest = <TResponse extends Record<string, any>>({
68
68
  timeout: TIMEOUT,
69
69
  };
70
70
 
71
- let getResponse: IRequestError | IRequestSuccess<TResponse>;
72
- if (middleware) {
73
- // perform global middleware
74
- getResponse = await middleware(
75
- async (middlewareOptions) =>
76
- await makeRequest<TResponse>(
77
- middlewareOptions ? { ...requestOptions, ...middlewareOptions } : requestOptions
78
- ),
79
- {
80
- path,
81
- baseUrl: baseUrl ?? API_URL,
82
- }
83
- );
84
- } else {
85
- getResponse = await makeRequest<TResponse>(requestOptions);
86
- }
71
+ // let getResponse: IRequestError | IRequestSuccess<TResponse>;
72
+ // if (middleware) {
73
+ // // perform global middleware
74
+ // getResponse = await middleware(
75
+ // async (middlewareOptions) =>
76
+ // await makeRequest<TResponse>(
77
+ // middlewareOptions ? { ...requestOptions, ...middlewareOptions } : requestOptions
78
+ // ),
79
+ // {
80
+ // path,
81
+ // baseUrl: baseUrl ?? API_URL,
82
+ // }
83
+ // );
84
+ // } else {
85
+ const getResponse = await makeRequest<TResponse>(requestOptions);
86
+ // }
87
87
 
88
88
  if (getResponse.status) {
89
89
  res(getResponse as IRequestSuccess<TResponse & { pagination: Pagination }>);
@@ -5,9 +5,14 @@ import { useEnvironmentVariables } from '../config';
5
5
  import { useStore } from '@tanstack/react-store';
6
6
  import { bootStore } from '../config/bootStore';
7
7
  import { IRequestError, IRequestSuccess, makeRequest } from '../request';
8
+ import { executeMiddlewareChain } from '../request/make-request';
8
9
  import { useHeaderStore, usePauseFutureRequests } from '../stores';
10
+ import type { MiddlewareContext, MiddlewareNext } from '../types';
9
11
  import { DefaultRequestOptions, IPagination, TanstackQueryOption } from './queries.interface';
10
12
 
13
+ /**
14
+ * Hook for making GET requests with pagination support
15
+ */
11
16
  export const useGetRequest = <TResponse extends Record<string, any>>({
12
17
  path,
13
18
  load = false,
@@ -15,18 +20,24 @@ export const useGetRequest = <TResponse extends Record<string, any>>({
15
20
  keyTracker,
16
21
  baseUrl,
17
22
  headers,
23
+ paginationConfig,
18
24
  }: {
19
25
  path: string;
20
26
  load?: boolean;
21
27
  queryOptions?: TanstackQueryOption<TResponse>;
22
28
  keyTracker?: string;
29
+ paginationConfig?: {
30
+ extractPagination?: (response: IRequestSuccess<TResponse>) => IPagination | undefined;
31
+ buildPaginationUrl?: (url: string, page: number) => string;
32
+ pageParamName?: string;
33
+ };
23
34
  } & DefaultRequestOptions) => {
24
35
  const [requestPath, setRequestPath] = useState<string>(path);
25
36
  const [options, setOptions] = useState<any>(queryOptions);
26
37
  const [page, setPage] = useState<number>(1);
27
38
 
28
39
  const { API_URL, TIMEOUT } = useEnvironmentVariables();
29
- const { middleware } = useStore(bootStore);
40
+ const { middleware, pagination: globalPaginationConfig } = useStore(bootStore);
30
41
 
31
42
  const globalHeaders = useHeaderStore((state) => state.headers);
32
43
 
@@ -39,6 +50,15 @@ export const useGetRequest = <TResponse extends Record<string, any>>({
39
50
  // eslint-disable-next-line react-hooks/exhaustive-deps
40
51
  queryClient = useMemo(() => queryClient, []);
41
52
 
53
+ // Merge global and local pagination config
54
+ const pagination = useMemo(
55
+ () => ({
56
+ ...globalPaginationConfig,
57
+ ...paginationConfig,
58
+ }),
59
+ [globalPaginationConfig, paginationConfig]
60
+ );
61
+
42
62
  const sendRequest = async (
43
63
  res: (
44
64
  value: IRequestError | IRequestSuccess<TResponse> | PromiseLike<IRequestError | IRequestSuccess<TResponse>>
@@ -56,20 +76,25 @@ export const useGetRequest = <TResponse extends Record<string, any>>({
56
76
  timeout: TIMEOUT,
57
77
  };
58
78
 
79
+ // Create the final handler that makes the actual request
80
+ const finalHandler: MiddlewareNext<TResponse> = async (options) => {
81
+ const finalOptions = options ? { ...requestOptions, ...options } : requestOptions;
82
+ return await makeRequest<TResponse>(finalOptions);
83
+ };
84
+
59
85
  let getResponse: IRequestError | IRequestSuccess<TResponse>;
60
- if (middleware) {
61
- // perform global middleware
62
- getResponse = await middleware(
63
- async (middlewareOptions) =>
64
- await makeRequest<TResponse>(
65
- middlewareOptions ? { ...requestOptions, ...middlewareOptions } : requestOptions
66
- ),
67
- {
68
- path,
69
- baseUrl: baseUrl ?? API_URL,
70
- }
71
- );
86
+
87
+ // If middleware is available, execute the middleware chain
88
+ if (middleware && Array.isArray(middleware) && middleware.length > 0) {
89
+ const context: MiddlewareContext<TResponse> = {
90
+ baseUrl: baseUrl ?? API_URL,
91
+ path: requestUrl,
92
+ options: requestOptions,
93
+ };
94
+
95
+ getResponse = await executeMiddlewareChain<TResponse>(middleware, context, finalHandler);
72
96
  } else {
97
+ // Otherwise, just make the request directly
73
98
  getResponse = await makeRequest<TResponse>(requestOptions);
74
99
  }
75
100
 
@@ -105,41 +130,88 @@ export const useGetRequest = <TResponse extends Record<string, any>>({
105
130
  }
106
131
  }, [keyTracker, requestPath, queryClient, queryOptions?.staleTime]);
107
132
 
133
+ /**
134
+ * Extract pagination data from response using configured extractor
135
+ */
136
+ const getPaginationData = (response: IRequestSuccess<TResponse>): IPagination | undefined => {
137
+ // Use the configured pagination extractor or fall back to default
138
+ const extractPagination =
139
+ pagination.extractPagination ||
140
+ ((res) => {
141
+ if ('pagination' in res.data) {
142
+ return res.data.pagination as IPagination;
143
+ }
144
+ return undefined;
145
+ });
146
+
147
+ return extractPagination(response);
148
+ };
149
+
150
+ /**
151
+ * Navigate to the next page if available
152
+ */
108
153
  const nextPage = () => {
109
- if (query.data.data.pagination) {
110
- const pagination: IPagination = query.data.data.pagination;
111
- if (pagination.next_page !== pagination.current_page && pagination.next_page > pagination.current_page) {
112
- setRequestPath(constructPaginationLink(requestPath, pagination.next_page));
113
- }
154
+ // The linter thinks query.data is always falsy, but we know it can be defined after a successful query
155
+ // Let's restructure to avoid the conditional
156
+ const paginationData = (query as any).data && getPaginationData(query.data);
157
+ if (!paginationData) return;
158
+
159
+ if (
160
+ paginationData.next_page !== paginationData.current_page &&
161
+ paginationData.next_page > paginationData.current_page
162
+ ) {
163
+ setRequestPath(constructPaginationLink(requestPath, paginationData.next_page));
114
164
  }
115
165
  };
116
166
 
167
+ /**
168
+ * Navigate to the previous page if available
169
+ */
117
170
  const prevPage = () => {
118
- if (query.data.data.pagination) {
119
- const pagination: IPagination = query.data.data.pagination;
120
- if (pagination.previous_page !== pagination.current_page && pagination.previous_page < pagination.current_page) {
121
- setRequestPath(constructPaginationLink(requestPath, pagination.previous_page));
122
- }
171
+ // The linter thinks query.data is always falsy, but we know it can be defined after a successful query
172
+ // Let's restructure to avoid the conditional
173
+ const paginationData = (query as any).data && getPaginationData(query.data);
174
+ if (!paginationData) return;
175
+
176
+ if (
177
+ paginationData.previous_page !== paginationData.current_page &&
178
+ paginationData.previous_page < paginationData.current_page
179
+ ) {
180
+ setRequestPath(constructPaginationLink(requestPath, paginationData.previous_page));
123
181
  }
124
182
  };
125
183
 
184
+ /**
185
+ * Construct a pagination URL using the configured builder
186
+ */
126
187
  const constructPaginationLink = (link: string, pageNumber: number) => {
127
- const [pathname, queryString] = link.split('?');
128
- const queryParams = new URLSearchParams(queryString);
188
+ // Use the configured pagination URL builder or fall back to default
189
+ const buildPaginationUrl =
190
+ pagination.buildPaginationUrl ||
191
+ ((url, page) => {
192
+ const [pathname, queryString] = url.split('?');
193
+ const queryParams = new URLSearchParams(queryString || '');
194
+ const pageParamName = pagination.pageParamName || 'page';
129
195
 
130
- const oldPage = Number(queryParams.get('page'));
196
+ const oldPage = Number(queryParams.get(pageParamName));
197
+ queryParams.set(pageParamName, String(page));
131
198
 
132
- queryParams.set('page', pageNumber as any);
199
+ const newUrl = pathname + '?' + queryParams.toString();
133
200
 
134
- link = pathname + '?' + queryParams.toString();
201
+ // only update page when pagination number changed
202
+ if (oldPage !== pageNumber) {
203
+ setPage(pageNumber);
204
+ }
135
205
 
136
- // only update page when pagination number changed
137
- if (oldPage !== pageNumber) {
138
- setPage(pageNumber);
139
- }
140
- return link;
206
+ return newUrl;
207
+ });
208
+
209
+ return buildPaginationUrl(link, pageNumber);
141
210
  };
142
211
 
212
+ /**
213
+ * Navigate to a specific page
214
+ */
143
215
  const gotoPage = (pageNumber: number) => {
144
216
  setRequestPath(constructPaginationLink(requestPath, pageNumber));
145
217
  };
@@ -194,5 +266,9 @@ export const useGetRequest = <TResponse extends Record<string, any>>({
194
266
  gotoPage,
195
267
  page,
196
268
  queryKey: [requestPath, {}],
269
+ // Add pagination data accessor - restructured to avoid linter error
270
+ getPaginationData: function () {
271
+ return (query as any).data ? getPaginationData(query.data) : undefined;
272
+ },
197
273
  };
198
274
  };
@@ -19,7 +19,7 @@ export const usePatchRequest = <TResponse>({ path, baseUrl, headers }: { path: s
19
19
  const [requestPayload, setRequestPayload] = useState<Record<any, any>>();
20
20
 
21
21
  const isFutureMutationsPaused = usePauseFutureRequests((state) => state.isFutureMutationsPaused);
22
- const { middleware, context } = useStore(bootStore);
22
+ const { context } = useStore(bootStore);
23
23
 
24
24
  const sendRequest = async (res: (value: any) => void, rej: (reason?: any) => void, data: any) => {
25
25
  // get request headers
@@ -34,22 +34,25 @@ export const usePatchRequest = <TResponse>({ path, baseUrl, headers }: { path: s
34
34
  onUploadProgress,
35
35
  };
36
36
 
37
- let patchResponse: IRequestError | IRequestSuccess<TResponse>;
38
- if (middleware) {
39
- // perform global middleware
40
- const middlewareResponse = await middleware(
41
- async (options) => await makeRequest<TResponse>(options ? { ...requestOptions, ...options } : requestOptions),
42
- {
43
- path,
44
- baseUrl: baseUrl ?? API_URL,
45
- body: data,
46
- }
47
- );
37
+ // let patchResponse: IRequestError | IRequestSuccess<TResponse>;
38
+ // if (middleware) {
39
+ // // perform global middleware
40
+ // const middlewareResponse = await middleware(
41
+ // async (options) =>
42
+ // await makeRequest<TResponse>(
43
+ // options ? { ...requestOptions, ...options } : requestOptions
44
+ // ),
45
+ // {
46
+ // path,
47
+ // baseUrl: baseUrl ?? API_URL,
48
+ // body: data,
49
+ // }
50
+ // );
48
51
 
49
- patchResponse = middlewareResponse;
50
- } else {
51
- patchResponse = await makeRequest<TResponse>(requestOptions);
52
- }
52
+ // patchResponse = middlewareResponse;
53
+ // } else {
54
+ const patchResponse = await makeRequest<TResponse>(requestOptions);
55
+ // }
53
56
  if (patchResponse.status) {
54
57
  // scroll to top after success
55
58
  if (context !== 'app') {
@@ -25,7 +25,7 @@ export const usePostRequest = <TResponse>({
25
25
  } & DefaultRequestOptions) => {
26
26
  const { API_URL, TIMEOUT } = useEnvironmentVariables();
27
27
 
28
- const { middleware, context } = useStore(bootStore);
28
+ const { context } = useStore(bootStore);
29
29
 
30
30
  const globalHeaders = useHeaderStore((state) => state.headers);
31
31
  const { isApp } = useReactNativeEnv();
@@ -61,20 +61,23 @@ export const usePostRequest = <TResponse>({
61
61
  ...requestConfig,
62
62
  };
63
63
 
64
- let postResponse: IRequestError | IRequestSuccess<TResponse>;
65
- if (middleware) {
66
- // perform global middleware
67
- postResponse = await middleware(
68
- async (options) => await makeRequest<TResponse>(options ? { ...requestOptions, ...options } : requestOptions),
69
- {
70
- path,
71
- baseUrl: baseUrl ?? API_URL,
72
- body: data,
73
- }
74
- );
75
- } else {
76
- postResponse = await makeRequest<TResponse>(requestOptions);
77
- }
64
+ // let postResponse: IRequestError | IRequestSuccess<TResponse>;
65
+ // if (middleware) {
66
+ // // perform global middleware
67
+ // postResponse = await middleware(
68
+ // async (options) =>
69
+ // await makeRequest<TResponse>(
70
+ // options ? { ...requestOptions, ...options } : requestOptions
71
+ // ),
72
+ // {
73
+ // path,
74
+ // baseUrl: baseUrl ?? API_URL,
75
+ // body: data,
76
+ // }
77
+ // );
78
+ // } else {
79
+ const postResponse = await makeRequest<TResponse>(requestOptions);
80
+ // }
78
81
 
79
82
  if (postResponse.status) {
80
83
  // scroll to top after success
@@ -20,7 +20,7 @@ export const usePutRequest = <TResponse>({ path, baseUrl, headers }: { path: str
20
20
 
21
21
  const isFutureMutationsPaused = usePauseFutureRequests((state) => state.isFutureMutationsPaused);
22
22
 
23
- const { middleware, context } = useStore(bootStore);
23
+ const { context } = useStore(bootStore);
24
24
 
25
25
  const sendRequest = async (res: (value: any) => void, rej: (reason?: any) => void, data: any) => {
26
26
  // get request headers
@@ -35,20 +35,21 @@ export const usePutRequest = <TResponse>({ path, baseUrl, headers }: { path: str
35
35
  onUploadProgress,
36
36
  };
37
37
 
38
- let putResponse: IRequestError | IRequestSuccess<TResponse>;
39
- if (middleware) {
40
- // perform global middleware
41
- putResponse = await middleware(
42
- async (options) => await makeRequest<TResponse>(options ? { ...requestOptions, ...options } : requestOptions),
43
- {
44
- path,
45
- baseUrl: baseUrl ?? API_URL,
46
- body: data,
47
- }
48
- );
49
- } else {
50
- putResponse = await makeRequest<TResponse>(requestOptions);
51
- }
38
+ // let putResponse: IRequestError | IRequestSuccess<TResponse>;
39
+ // if (middleware) {
40
+ // // perform global middleware
41
+ // putResponse = await middleware(
42
+ // async (options) =>
43
+ // await makeRequest<TResponse>(options ? { ...requestOptions, ...options } : requestOptions),
44
+ // {
45
+ // path,
46
+ // baseUrl: baseUrl ?? API_URL,
47
+ // body: data,
48
+ // }
49
+ // );
50
+ // } else {
51
+ const putResponse = await makeRequest<TResponse>(requestOptions);
52
+ // }
52
53
  if (putResponse.status) {
53
54
  // scroll to top after success
54
55
  if (context !== 'app') {
@@ -2,34 +2,126 @@ import type { AxiosRequestConfig, RawAxiosRequestHeaders } from 'axios';
2
2
  import axios from 'axios';
3
3
  import { axiosInstance } from './axios-instance';
4
4
 
5
+ import type { MiddlewareContext, MiddlewareFunction, MiddlewareNext } from '../types';
5
6
  import { ContentType, HttpMethod } from './request.enum';
6
7
  import type { IMakeRequest, IRequestError, IRequestSuccess } from './request.interface';
7
8
  import { errorTransformer, successTransformer } from './transformer';
8
9
 
9
- export async function makeRequest<TResponse>({
10
- body = {},
11
- method = HttpMethod.GET,
12
- path,
13
- isFormData,
14
- headers = {},
15
- baseURL,
16
- timeout,
17
- appFileConfig,
18
- onUploadProgress,
19
- }: IMakeRequest): Promise<IRequestSuccess<TResponse> | IRequestError> {
10
+ /**
11
+ * Execute a chain of middleware functions
12
+ */
13
+ export async function executeMiddlewareChain<T>(
14
+ middlewares: MiddlewareFunction[],
15
+ context: MiddlewareContext<T>,
16
+ finalHandler: MiddlewareNext<T>
17
+ ): Promise<IRequestSuccess<T> | IRequestError> {
18
+ // Create a chain of middleware functions
19
+ const chain = middlewares.reduceRight((next: MiddlewareNext<T>, middleware: MiddlewareFunction<T>) => {
20
+ return (options) => {
21
+ // Update context with new options if provided
22
+ const updatedContext = options ? { ...context, options: { ...context.options, ...options } } : context;
23
+ return middleware(updatedContext, next);
24
+ };
25
+ }, finalHandler);
26
+
27
+ // Execute the middleware chain
28
+ return await chain(undefined);
29
+ }
30
+
31
+ /**
32
+ * Make an HTTP request with middleware support
33
+ *
34
+ * @param requestOptions - Request options
35
+ * @param middlewares - Optional array of middleware functions
36
+ */
37
+ export async function makeRequest<TResponse>(
38
+ requestOptions: IMakeRequest,
39
+ middlewares?: MiddlewareFunction[]
40
+ ): Promise<IRequestSuccess<TResponse> | IRequestError> {
41
+ const {
42
+ body = {},
43
+ method = HttpMethod.GET,
44
+ path,
45
+ isFormData,
46
+ headers = {},
47
+ baseURL,
48
+ timeout,
49
+ appFileConfig,
50
+ onUploadProgress,
51
+ } = requestOptions;
52
+
20
53
  // check if file is included in mobile app environment and extract all file input to avoid
21
54
  // it being formatted to object using axios formData builder
22
55
  const isApp = appFileConfig?.isApp;
23
56
  const appFiles: Record<string, string> = isApp ? getAppFiles(body, appFileConfig.fileSelectors) : {};
24
57
 
25
58
  // configure body
26
- body = (isFormData ? axios.toFormData(body as FormData) : body) as FormData;
59
+ const processedBody = (isFormData ? axios.toFormData(body as FormData) : body) as FormData;
60
+
61
+ // configure request header
62
+ configureRequestHeader(isFormData, headers, isApp, appFiles, processedBody);
63
+
64
+ // Create the final handler that makes the actual request
65
+ const finalHandler: MiddlewareNext<TResponse> = async (options) => {
66
+ const finalRequestOptions = options
67
+ ? {
68
+ ...requestOptions,
69
+ body: processedBody,
70
+ ...options,
71
+ }
72
+ : {
73
+ ...requestOptions,
74
+ body: processedBody,
75
+ };
76
+
77
+ return await performRequest<TResponse>(finalRequestOptions);
78
+ };
79
+
80
+ // If middleware is available, execute the middleware chain
81
+ if (middlewares && middlewares.length > 0) {
82
+ const context: MiddlewareContext<TResponse> = {
83
+ baseUrl: baseURL,
84
+ path,
85
+ body: body as Record<string, any>,
86
+ method,
87
+ headers,
88
+ options: {
89
+ baseURL,
90
+ timeout,
91
+ path,
92
+ body: processedBody,
93
+ method,
94
+ isFormData,
95
+ headers,
96
+ appFileConfig,
97
+ onUploadProgress,
98
+ },
99
+ };
100
+
101
+ return await executeMiddlewareChain<TResponse>(middlewares, context, finalHandler);
102
+ }
27
103
 
28
- // configure request header1
29
- configureRequestHeader(isFormData, headers, isApp, appFiles, body);
104
+ // Otherwise, just make the request directly
105
+ return await finalHandler(undefined);
106
+ }
30
107
 
108
+ /**
109
+ * Perform the actual HTTP request
110
+ */
111
+ async function performRequest<TResponse>({
112
+ body,
113
+ method,
114
+ path,
115
+ isFormData,
116
+ headers,
117
+ baseURL,
118
+ timeout,
119
+ appFileConfig,
120
+ onUploadProgress,
121
+ }: IMakeRequest): Promise<IRequestSuccess<TResponse> | IRequestError> {
31
122
  try {
32
123
  const axiosRequest = axiosInstance({ baseURL, headers, timeout });
124
+ const isApp = appFileConfig?.isApp;
33
125
 
34
126
  const axiosRequestConfig: AxiosRequestConfig<Record<string, any>> = {
35
127
  url: path,
@@ -37,7 +129,12 @@ export async function makeRequest<TResponse>({
37
129
  onUploadProgress,
38
130
  };
39
131
 
40
- if (Object.keys(body).length > 0 || (isFormData && !isApp && [...body.keys()].length > 0)) {
132
+ // Check if body exists and is not null
133
+ if (
134
+ body &&
135
+ ((typeof body === 'object' && Object.keys(body).length > 0) ||
136
+ (isFormData && !isApp && body instanceof FormData && Array.from(body.keys()).length > 0))
137
+ ) {
41
138
  axiosRequestConfig.data = body;
42
139
  }
43
140