@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
@@ -16,7 +16,7 @@ const useGetInfiniteRequest = ({ path, load = false, queryOptions, keyTracker, b
16
16
  const globalHeaders = useHeaderStore((state) => state.headers);
17
17
  const [requestPath, setRequestPath] = useState(path);
18
18
  const [options, setOptions] = useState(queryOptions);
19
- const { middleware } = useStore(bootStore);
19
+ useStore(bootStore);
20
20
  const [requestPayload, setRequestPayload] = useState();
21
21
  const isFutureQueriesPaused = usePauseFutureRequests((state) => state.isFutureQueriesPaused);
22
22
  let queryClient = useQueryClient();
@@ -31,17 +31,22 @@ const useGetInfiniteRequest = ({ path, load = false, queryOptions, keyTracker, b
31
31
  baseURL: baseUrl ?? API_URL,
32
32
  timeout: TIMEOUT,
33
33
  };
34
- let getResponse;
35
- if (middleware) {
36
- // perform global middleware
37
- getResponse = await middleware(async (middlewareOptions) => await makeRequest(middlewareOptions ? { ...requestOptions, ...middlewareOptions } : requestOptions), {
38
- path,
39
- baseUrl: baseUrl ?? API_URL,
40
- });
41
- }
42
- else {
43
- getResponse = await makeRequest(requestOptions);
44
- }
34
+ // let getResponse: IRequestError | IRequestSuccess<TResponse>;
35
+ // if (middleware) {
36
+ // // perform global middleware
37
+ // getResponse = await middleware(
38
+ // async (middlewareOptions) =>
39
+ // await makeRequest<TResponse>(
40
+ // middlewareOptions ? { ...requestOptions, ...middlewareOptions } : requestOptions
41
+ // ),
42
+ // {
43
+ // path,
44
+ // baseUrl: baseUrl ?? API_URL,
45
+ // }
46
+ // );
47
+ // } else {
48
+ const getResponse = await makeRequest(requestOptions);
49
+ // }
45
50
  if (getResponse.status) {
46
51
  res(getResponse);
47
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useGetInfiniteRequest.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"useGetInfiniteRequest.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -8,21 +8,29 @@ import { useHeaderStore } from '../stores/useHeaderStore.js';
8
8
  import { usePauseFutureRequests } from '../stores/usePauseFutureRequests.js';
9
9
  import { useStore } from '../../node_modules/@tanstack/react-store/dist/esm/index.js';
10
10
  import 'axios';
11
- import { makeRequest } from '../request/make-request.js';
11
+ import { executeMiddlewareChain, makeRequest } from '../request/make-request.js';
12
12
  import '../request/request.enum.js';
13
13
 
14
- const useGetRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl, headers, }) => {
14
+ /**
15
+ * Hook for making GET requests with pagination support
16
+ */
17
+ const useGetRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl, headers, paginationConfig, }) => {
15
18
  const [requestPath, setRequestPath] = useState(path);
16
19
  const [options, setOptions] = useState(queryOptions);
17
20
  const [page, setPage] = useState(1);
18
21
  const { API_URL, TIMEOUT } = useEnvironmentVariables();
19
- const { middleware } = useStore(bootStore);
22
+ const { middleware, pagination: globalPaginationConfig } = useStore(bootStore);
20
23
  const globalHeaders = useHeaderStore((state) => state.headers);
21
24
  const [requestPayload, setRequestPayload] = useState();
22
25
  const isFutureQueriesPaused = usePauseFutureRequests((state) => state.isFutureQueriesPaused);
23
26
  let queryClient = useQueryClient();
24
27
  // eslint-disable-next-line react-hooks/exhaustive-deps
25
28
  queryClient = useMemo(() => queryClient, []);
29
+ // Merge global and local pagination config
30
+ const pagination = useMemo(() => ({
31
+ ...globalPaginationConfig,
32
+ ...paginationConfig,
33
+ }), [globalPaginationConfig, paginationConfig]);
26
34
  const sendRequest = async (res, rej, queryKey) => {
27
35
  const [url] = queryKey;
28
36
  const requestUrl = (url ?? requestPath);
@@ -32,15 +40,23 @@ const useGetRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl,
32
40
  baseURL: baseUrl ?? API_URL,
33
41
  timeout: TIMEOUT,
34
42
  };
43
+ // Create the final handler that makes the actual request
44
+ const finalHandler = async (options) => {
45
+ const finalOptions = options ? { ...requestOptions, ...options } : requestOptions;
46
+ return await makeRequest(finalOptions);
47
+ };
35
48
  let getResponse;
36
- if (middleware) {
37
- // perform global middleware
38
- getResponse = await middleware(async (middlewareOptions) => await makeRequest(middlewareOptions ? { ...requestOptions, ...middlewareOptions } : requestOptions), {
39
- path,
49
+ // If middleware is available, execute the middleware chain
50
+ if (middleware && Array.isArray(middleware) && middleware.length > 0) {
51
+ const context = {
40
52
  baseUrl: baseUrl ?? API_URL,
41
- });
53
+ path: requestUrl,
54
+ options: requestOptions,
55
+ };
56
+ getResponse = await executeMiddlewareChain(middleware, context, finalHandler);
42
57
  }
43
58
  else {
59
+ // Otherwise, just make the request directly
44
60
  getResponse = await makeRequest(requestOptions);
45
61
  }
46
62
  if (getResponse.status) {
@@ -70,34 +86,72 @@ const useGetRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl,
70
86
  queryClient.setQueryData([keyTracker], [requestPath, {}]);
71
87
  }
72
88
  }, [keyTracker, requestPath, queryClient, queryOptions?.staleTime]);
89
+ /**
90
+ * Extract pagination data from response using configured extractor
91
+ */
92
+ const getPaginationData = (response) => {
93
+ // Use the configured pagination extractor or fall back to default
94
+ const extractPagination = pagination.extractPagination ||
95
+ ((res) => {
96
+ if ('pagination' in res.data) {
97
+ return res.data.pagination;
98
+ }
99
+ return undefined;
100
+ });
101
+ return extractPagination(response);
102
+ };
103
+ /**
104
+ * Navigate to the next page if available
105
+ */
73
106
  const nextPage = () => {
74
- if (query.data.data.pagination) {
75
- const pagination = query.data.data.pagination;
76
- if (pagination.next_page !== pagination.current_page && pagination.next_page > pagination.current_page) {
77
- setRequestPath(constructPaginationLink(requestPath, pagination.next_page));
78
- }
107
+ // The linter thinks query.data is always falsy, but we know it can be defined after a successful query
108
+ // Let's restructure to avoid the conditional
109
+ const paginationData = query.data && getPaginationData(query.data);
110
+ if (!paginationData)
111
+ return;
112
+ if (paginationData.next_page !== paginationData.current_page &&
113
+ paginationData.next_page > paginationData.current_page) {
114
+ setRequestPath(constructPaginationLink(requestPath, paginationData.next_page));
79
115
  }
80
116
  };
117
+ /**
118
+ * Navigate to the previous page if available
119
+ */
81
120
  const prevPage = () => {
82
- if (query.data.data.pagination) {
83
- const pagination = query.data.data.pagination;
84
- if (pagination.previous_page !== pagination.current_page && pagination.previous_page < pagination.current_page) {
85
- setRequestPath(constructPaginationLink(requestPath, pagination.previous_page));
86
- }
121
+ // The linter thinks query.data is always falsy, but we know it can be defined after a successful query
122
+ // Let's restructure to avoid the conditional
123
+ const paginationData = query.data && getPaginationData(query.data);
124
+ if (!paginationData)
125
+ return;
126
+ if (paginationData.previous_page !== paginationData.current_page &&
127
+ paginationData.previous_page < paginationData.current_page) {
128
+ setRequestPath(constructPaginationLink(requestPath, paginationData.previous_page));
87
129
  }
88
130
  };
131
+ /**
132
+ * Construct a pagination URL using the configured builder
133
+ */
89
134
  const constructPaginationLink = (link, pageNumber) => {
90
- const [pathname, queryString] = link.split('?');
91
- const queryParams = new URLSearchParams(queryString);
92
- const oldPage = Number(queryParams.get('page'));
93
- queryParams.set('page', pageNumber);
94
- link = pathname + '?' + queryParams.toString();
95
- // only update page when pagination number changed
96
- if (oldPage !== pageNumber) {
97
- setPage(pageNumber);
98
- }
99
- return link;
135
+ // Use the configured pagination URL builder or fall back to default
136
+ const buildPaginationUrl = pagination.buildPaginationUrl ||
137
+ ((url, page) => {
138
+ const [pathname, queryString] = url.split('?');
139
+ const queryParams = new URLSearchParams(queryString || '');
140
+ const pageParamName = pagination.pageParamName || 'page';
141
+ const oldPage = Number(queryParams.get(pageParamName));
142
+ queryParams.set(pageParamName, String(page));
143
+ const newUrl = pathname + '?' + queryParams.toString();
144
+ // only update page when pagination number changed
145
+ if (oldPage !== pageNumber) {
146
+ setPage(pageNumber);
147
+ }
148
+ return newUrl;
149
+ });
150
+ return buildPaginationUrl(link, pageNumber);
100
151
  };
152
+ /**
153
+ * Navigate to a specific page
154
+ */
101
155
  const gotoPage = (pageNumber) => {
102
156
  setRequestPath(constructPaginationLink(requestPath, pageNumber));
103
157
  };
@@ -139,6 +193,10 @@ const useGetRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl,
139
193
  gotoPage,
140
194
  page,
141
195
  queryKey: [requestPath, {}],
196
+ // Add pagination data accessor - restructured to avoid linter error
197
+ getPaginationData: function () {
198
+ return query.data ? getPaginationData(query.data) : undefined;
199
+ },
142
200
  };
143
201
  };
144
202
 
@@ -1 +1 @@
1
- {"version":3,"file":"useGetRequest.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"useGetRequest.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -19,7 +19,7 @@ const usePatchRequest = ({ path, baseUrl, headers }) => {
19
19
  const globalHeaders = useHeaderStore((state) => state.headers);
20
20
  const [requestPayload, setRequestPayload] = useState();
21
21
  const isFutureMutationsPaused = usePauseFutureRequests((state) => state.isFutureMutationsPaused);
22
- const { middleware, context } = useStore(bootStore);
22
+ const { context } = useStore(bootStore);
23
23
  const sendRequest = async (res, rej, data) => {
24
24
  // get request headers
25
25
  const requestOptions = {
@@ -31,19 +31,24 @@ const usePatchRequest = ({ path, baseUrl, headers }) => {
31
31
  timeout: TIMEOUT,
32
32
  onUploadProgress,
33
33
  };
34
- let patchResponse;
35
- if (middleware) {
36
- // perform global middleware
37
- const middlewareResponse = await middleware(async (options) => await makeRequest(options ? { ...requestOptions, ...options } : requestOptions), {
38
- path,
39
- baseUrl: baseUrl ?? API_URL,
40
- body: data,
41
- });
42
- patchResponse = middlewareResponse;
43
- }
44
- else {
45
- patchResponse = await makeRequest(requestOptions);
46
- }
34
+ // let patchResponse: IRequestError | IRequestSuccess<TResponse>;
35
+ // if (middleware) {
36
+ // // perform global middleware
37
+ // const middlewareResponse = await middleware(
38
+ // async (options) =>
39
+ // await makeRequest<TResponse>(
40
+ // options ? { ...requestOptions, ...options } : requestOptions
41
+ // ),
42
+ // {
43
+ // path,
44
+ // baseUrl: baseUrl ?? API_URL,
45
+ // body: data,
46
+ // }
47
+ // );
48
+ // patchResponse = middlewareResponse;
49
+ // } else {
50
+ const patchResponse = await makeRequest(requestOptions);
51
+ // }
47
52
  if (patchResponse.status) {
48
53
  // scroll to top after success
49
54
  if (context !== 'app') {
@@ -1 +1 @@
1
- {"version":3,"file":"usePatchRequest.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"usePatchRequest.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -16,7 +16,7 @@ import { HttpMethod } from '../request/request.enum.js';
16
16
 
17
17
  const usePostRequest = ({ path, isFormData = false, baseUrl, headers, fileSelectors, }) => {
18
18
  const { API_URL, TIMEOUT } = useEnvironmentVariables();
19
- const { middleware, context } = useStore(bootStore);
19
+ const { context } = useStore(bootStore);
20
20
  const globalHeaders = useHeaderStore((state) => state.headers);
21
21
  const { isApp } = useReactNativeEnv();
22
22
  const { uploadProgressPercent, onUploadProgress } = useUploadProgress();
@@ -41,18 +41,23 @@ const usePostRequest = ({ path, isFormData = false, baseUrl, headers, fileSelect
41
41
  onUploadProgress,
42
42
  ...requestConfig,
43
43
  };
44
- let postResponse;
45
- if (middleware) {
46
- // perform global middleware
47
- postResponse = await middleware(async (options) => await makeRequest(options ? { ...requestOptions, ...options } : requestOptions), {
48
- path,
49
- baseUrl: baseUrl ?? API_URL,
50
- body: data,
51
- });
52
- }
53
- else {
54
- postResponse = await makeRequest(requestOptions);
55
- }
44
+ // let postResponse: IRequestError | IRequestSuccess<TResponse>;
45
+ // if (middleware) {
46
+ // // perform global middleware
47
+ // postResponse = await middleware(
48
+ // async (options) =>
49
+ // await makeRequest<TResponse>(
50
+ // options ? { ...requestOptions, ...options } : requestOptions
51
+ // ),
52
+ // {
53
+ // path,
54
+ // baseUrl: baseUrl ?? API_URL,
55
+ // body: data,
56
+ // }
57
+ // );
58
+ // } else {
59
+ const postResponse = await makeRequest(requestOptions);
60
+ // }
56
61
  if (postResponse.status) {
57
62
  // scroll to top after success
58
63
  if (context !== 'app') {
@@ -1 +1 @@
1
- {"version":3,"file":"usePostRequest.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"usePostRequest.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -3,23 +3,92 @@ import { axiosInstance } from './axios-instance.js';
3
3
  import { HttpMethod, ContentType } from './request.enum.js';
4
4
  import { errorTransformer, successTransformer } from './transformer.js';
5
5
 
6
- async function makeRequest({ body = {}, method = HttpMethod.GET, path, isFormData, headers = {}, baseURL, timeout, appFileConfig, onUploadProgress, }) {
6
+ /**
7
+ * Execute a chain of middleware functions
8
+ */
9
+ async function executeMiddlewareChain(middlewares, context, finalHandler) {
10
+ // Create a chain of middleware functions
11
+ const chain = middlewares.reduceRight((next, middleware) => {
12
+ return (options) => {
13
+ // Update context with new options if provided
14
+ const updatedContext = options ? { ...context, options: { ...context.options, ...options } } : context;
15
+ return middleware(updatedContext, next);
16
+ };
17
+ }, finalHandler);
18
+ // Execute the middleware chain
19
+ return await chain(undefined);
20
+ }
21
+ /**
22
+ * Make an HTTP request with middleware support
23
+ *
24
+ * @param requestOptions - Request options
25
+ * @param middlewares - Optional array of middleware functions
26
+ */
27
+ async function makeRequest(requestOptions, middlewares) {
28
+ const { body = {}, method = HttpMethod.GET, path, isFormData, headers = {}, baseURL, timeout, appFileConfig, onUploadProgress, } = requestOptions;
7
29
  // check if file is included in mobile app environment and extract all file input to avoid
8
30
  // it being formatted to object using axios formData builder
9
31
  const isApp = appFileConfig?.isApp;
10
32
  const appFiles = isApp ? getAppFiles(body, appFileConfig.fileSelectors) : {};
11
33
  // configure body
12
- body = (isFormData ? axios.toFormData(body) : body);
13
- // configure request header1
14
- configureRequestHeader(isFormData, headers, isApp, appFiles, body);
34
+ const processedBody = (isFormData ? axios.toFormData(body) : body);
35
+ // configure request header
36
+ configureRequestHeader(isFormData, headers, isApp, appFiles, processedBody);
37
+ // Create the final handler that makes the actual request
38
+ const finalHandler = async (options) => {
39
+ const finalRequestOptions = options
40
+ ? {
41
+ ...requestOptions,
42
+ body: processedBody,
43
+ ...options,
44
+ }
45
+ : {
46
+ ...requestOptions,
47
+ body: processedBody,
48
+ };
49
+ return await performRequest(finalRequestOptions);
50
+ };
51
+ // If middleware is available, execute the middleware chain
52
+ if (middlewares && middlewares.length > 0) {
53
+ const context = {
54
+ baseUrl: baseURL,
55
+ path,
56
+ body: body,
57
+ method,
58
+ headers,
59
+ options: {
60
+ baseURL,
61
+ timeout,
62
+ path,
63
+ body: processedBody,
64
+ method,
65
+ isFormData,
66
+ headers,
67
+ appFileConfig,
68
+ onUploadProgress,
69
+ },
70
+ };
71
+ return await executeMiddlewareChain(middlewares, context, finalHandler);
72
+ }
73
+ // Otherwise, just make the request directly
74
+ return await finalHandler(undefined);
75
+ }
76
+ /**
77
+ * Perform the actual HTTP request
78
+ */
79
+ async function performRequest({ body, method, path, isFormData, headers, baseURL, timeout, appFileConfig, onUploadProgress, }) {
15
80
  try {
16
81
  const axiosRequest = axiosInstance({ baseURL, headers, timeout });
82
+ const isApp = appFileConfig?.isApp;
17
83
  const axiosRequestConfig = {
18
84
  url: path,
19
85
  method,
20
86
  onUploadProgress,
21
87
  };
22
- if (Object.keys(body).length > 0 || (isFormData && !isApp && [...body.keys()].length > 0)) {
88
+ // Check if body exists and is not null
89
+ if (body &&
90
+ ((typeof body === 'object' && Object.keys(body).length > 0) ||
91
+ (isFormData && !isApp && body instanceof FormData && Array.from(body.keys()).length > 0))) {
23
92
  axiosRequestConfig.data = body;
24
93
  }
25
94
  // send request
@@ -85,5 +154,5 @@ function getAppFiles(body, fileSelectors = []) {
85
154
  return files;
86
155
  }
87
156
 
88
- export { makeRequest };
157
+ export { executeMiddlewareChain, makeRequest };
89
158
  //# sourceMappingURL=make-request.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"make-request.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"make-request.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,5 +1,22 @@
1
1
  import type { AxiosProgressEvent, RawAxiosRequestHeaders } from 'axios';
2
2
  import type { AppFileConfig, HttpMethod, IMakeRequest, IRequestError, IRequestSuccess } from '../request';
3
+ import type { IPagination } from '../queries';
4
+ export type MiddlewareFunction<T = any> = (context: MiddlewareContext<T>, next: MiddlewareNext<T>) => Promise<IRequestError | IRequestSuccess<T>>;
5
+ export interface MiddlewareContext<T = any> {
6
+ baseUrl: string;
7
+ path: string;
8
+ body?: Record<string, any>;
9
+ method?: HttpMethod;
10
+ headers?: RawAxiosRequestHeaders;
11
+ options?: Partial<NextOptions>;
12
+ response?: IRequestError | IRequestSuccess<T>;
13
+ }
14
+ export type MiddlewareNext<T = any> = (options?: Partial<NextOptions>) => Promise<IRequestError | IRequestSuccess<T>>;
15
+ export type LegacyMiddlewareFunction<T = any> = (next: (options?: Partial<NextOptions>) => Promise<IRequestSuccess<T> | IRequestError>, configs?: {
16
+ baseUrl: string;
17
+ path: string;
18
+ body?: Record<string, any>;
19
+ }) => Promise<IRequestError | IRequestSuccess<T>>;
3
20
  export interface BootstrapConfig {
4
21
  environments?: {
5
22
  appBaseUrl: string;
@@ -7,11 +24,13 @@ export interface BootstrapConfig {
7
24
  };
8
25
  context?: ContextType;
9
26
  modelConfig?: BootstrapModelConfig;
10
- middleware?: <T = any>(next: (options?: Partial<NextOptions>) => Promise<IRequestSuccess<T> | IRequestError>, configs?: {
11
- baseUrl: string;
12
- path: string;
13
- body?: Record<string, any>;
14
- }) => Promise<IRequestError | IRequestSuccess<T>>;
27
+ middleware?: MiddlewareFunction[] | LegacyMiddlewareFunction;
28
+ pagination?: PaginationConfig;
29
+ }
30
+ export interface PaginationConfig {
31
+ extractPagination?: <T>(response: IRequestSuccess<T>) => IPagination | undefined;
32
+ buildPaginationUrl?: (url: string, page: number) => string;
33
+ pageParamName?: string;
15
34
  }
16
35
  export interface NextOptions extends Partial<IMakeRequest> {
17
36
  baseURL: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ventlio/tanstack-query",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "contributors": [
@@ -91,6 +91,6 @@
91
91
  "lodash.result": "^4.5.2",
92
92
  "lodash.set": "^4.3.2",
93
93
  "url-search-params-polyfill": "^8.2.5",
94
- "zustand": "^4.4.6"
94
+ "zustand": "^5.0.4"
95
95
  }
96
96
  }
@@ -1,11 +1,56 @@
1
1
  import type { QueryClient } from '@tanstack/react-query';
2
2
  import 'url-search-params-polyfill';
3
- import type { BootstrapConfig } from '../types';
3
+ import type { BootstrapConfig, LegacyMiddlewareFunction, MiddlewareFunction } from '../types';
4
4
  import { bootStore } from './bootStore';
5
+ import type { IPagination } from '../queries';
5
6
 
6
- export const bootstrapQueryRequest = async (queryClient: QueryClient, options: BootstrapConfig): Promise<void> => {
7
- // set default query config
7
+ /**
8
+ * Bootstrap the query request system with configuration options
9
+ *
10
+ * @param queryClient - TanStack Query client instance
11
+ * @param options - Configuration options
12
+ */
13
+ export const bootstrapQueryRequest = async (queryClient: QueryClient, options: BootstrapConfig = {}): Promise<void> => {
14
+ // Resume any paused mutations
8
15
  await queryClient.resumePausedMutations();
9
16
 
17
+ // Set default pagination configuration if not provided
18
+ if (!options.pagination) {
19
+ options.pagination = {
20
+ pageParamName: 'page',
21
+ buildPaginationUrl: (url: string, page: number) => {
22
+ const [pathname, queryString] = url.split('?');
23
+ const queryParams = new URLSearchParams(queryString);
24
+ queryParams.set('page', String(page));
25
+ return pathname + '?' + queryParams.toString();
26
+ },
27
+ extractPagination: (response: any) => {
28
+ // Default pagination extraction from response
29
+ if (response.data && 'pagination' in response.data) {
30
+ return response.data.pagination as IPagination;
31
+ }
32
+ return undefined;
33
+ },
34
+ };
35
+ }
36
+
37
+ // Convert legacy middleware to new format if needed
38
+ if (options.middleware && !Array.isArray(options.middleware)) {
39
+ const legacyMiddleware = options.middleware as LegacyMiddlewareFunction;
40
+
41
+ // Create a new middleware function that adapts the legacy format
42
+ const adaptedMiddleware: MiddlewareFunction = async (context, next) => {
43
+ return await legacyMiddleware((opts) => next(opts), {
44
+ baseUrl: context.baseUrl,
45
+ path: context.path,
46
+ body: context.body,
47
+ });
48
+ };
49
+
50
+ // Replace with array containing the adapted middleware
51
+ options.middleware = [adaptedMiddleware];
52
+ }
53
+
54
+ // Store the configuration
10
55
  bootStore.setState(() => options);
11
56
  };
@@ -1,15 +1,53 @@
1
+ import { useStore } from '@tanstack/react-store';
1
2
  import { useBaseUrlStore } from '../stores/useBaseUrlStore';
3
+ import { bootStore } from './bootStore';
2
4
  import type { IConfig } from './config.interface';
3
5
  import { useReactNativeEnv } from './useReactNativeEnv';
4
6
 
7
+ /**
8
+ * Hook to access environment variables across different frameworks
9
+ * Supports React (CRA), Next.js, Vite, and React Native
10
+ */
5
11
  export const useEnvironmentVariables = (): IConfig => {
6
12
  const { appTimeout, appUrl } = useReactNativeEnv();
7
13
  const { baseUrl } = useBaseUrlStore();
8
- const url = baseUrl ?? process.env.REACT_APP_API_URL ?? process.env.NEXT_PUBLIC_API_URL ?? appUrl;
9
- const timeout = baseUrl ?? process.env.REACT_APP_API_TIMEOUT ?? process.env.NEXT_PUBLIC_API_TIMEOUT ?? appTimeout;
14
+ const { environments } = useStore(bootStore);
15
+
16
+ // Framework environment variables detection
17
+ // Order of precedence:
18
+ // 1. Runtime baseUrl (set via useBaseUrlStore)
19
+ // 2. Bootstrap config environments
20
+ // 3. Framework-specific environment variables
21
+ // 4. React Native app URL
22
+
23
+ // Get global object to check for various environment variables
24
+ const globalObj = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : {};
25
+
26
+ // Check for Vite environment variables (without using import.meta directly)
27
+ // @ts-ignore - Access potential Vite environment variables
28
+ const viteEnv = globalObj.__VITE_ENV__ || {};
29
+ const viteApiUrl = viteEnv.VITE_API_URL;
30
+ const viteApiTimeout = viteEnv.VITE_API_TIMEOUT;
31
+
32
+ // Get URL with fallbacks
33
+ const url =
34
+ baseUrl ??
35
+ environments?.appBaseUrl ??
36
+ process.env.REACT_APP_API_URL ??
37
+ process.env.NEXT_PUBLIC_API_URL ??
38
+ viteApiUrl ??
39
+ appUrl;
40
+
41
+ // Get timeout with fallbacks
42
+ const timeout =
43
+ environments?.appTimeout ??
44
+ process.env.REACT_APP_API_TIMEOUT ??
45
+ process.env.NEXT_PUBLIC_API_TIMEOUT ??
46
+ viteApiTimeout ??
47
+ appTimeout;
10
48
 
11
49
  return {
12
50
  API_URL: url as string,
13
- TIMEOUT: Number(timeout),
51
+ TIMEOUT: Number(timeout) || 30000, // Default timeout of 30 seconds
14
52
  };
15
53
  };