@softimist/api 1.0.0

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.cjs ADDED
@@ -0,0 +1,665 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ BaseApiClient: () => BaseApiClient,
34
+ QueryProvider: () => QueryProvider,
35
+ getErrorMessage: () => getErrorMessage,
36
+ getQueryClient: () => getQueryClient,
37
+ getSuccessMessage: () => getSuccessMessage,
38
+ makeQueryClient: () => makeQueryClient,
39
+ useApi: () => useApi,
40
+ useApiAction: () => useApiAction,
41
+ useInfiniteQuery: () => import_react_query3.useInfiniteQuery,
42
+ useMockPaginatedApi: () => useMockPaginatedApi,
43
+ usePaginatedApi: () => usePaginatedApi,
44
+ useQuery: () => import_react_query3.useQuery
45
+ });
46
+ module.exports = __toCommonJS(index_exports);
47
+
48
+ // src/client.ts
49
+ var import_axios = __toESM(require("axios"), 1);
50
+ var BaseApiClient = class {
51
+ axiosInstance;
52
+ getAuthToken;
53
+ getRefreshToken;
54
+ setAuthToken;
55
+ setRefreshToken;
56
+ clearAuthTokens;
57
+ refreshTokenUrl;
58
+ isRefreshing = false;
59
+ failedQueue = [];
60
+ constructor(config = {}) {
61
+ const defaultHeaders = {
62
+ "Content-Type": "application/json",
63
+ Accept: "application/json",
64
+ ...config.headers
65
+ };
66
+ this.axiosInstance = import_axios.default.create({
67
+ baseURL: config.baseURL || "",
68
+ timeout: config.timeout || 3e4,
69
+ headers: defaultHeaders
70
+ });
71
+ this.getAuthToken = config.getAuthToken;
72
+ this.getRefreshToken = config.getRefreshToken;
73
+ this.setAuthToken = config.setAuthToken;
74
+ this.setRefreshToken = config.setRefreshToken;
75
+ this.clearAuthTokens = config.clearAuthTokens;
76
+ this.refreshTokenUrl = config.refreshTokenUrl || `${config.baseURL || ""}/v1/auth/refresh`;
77
+ this.axiosInstance.interceptors.request.use(
78
+ (requestConfig) => {
79
+ if (this.getAuthToken) {
80
+ const token = this.getAuthToken();
81
+ if (token) {
82
+ requestConfig.headers.Authorization = `Bearer ${token}`;
83
+ }
84
+ }
85
+ return requestConfig;
86
+ },
87
+ (error) => {
88
+ return Promise.reject(error);
89
+ }
90
+ );
91
+ this.axiosInstance.interceptors.response.use(
92
+ (response) => response,
93
+ async (error) => {
94
+ const originalRequest = error.config;
95
+ if (error.response?.status === 401 && !originalRequest._retry && this.getRefreshToken) {
96
+ if (this.isRefreshing) {
97
+ return new Promise((resolve, reject) => {
98
+ this.failedQueue.push({ resolve, reject });
99
+ }).then((token) => {
100
+ originalRequest.headers.Authorization = `Bearer ${token}`;
101
+ return this.axiosInstance(originalRequest);
102
+ }).catch((err) => {
103
+ return Promise.reject(err);
104
+ });
105
+ }
106
+ originalRequest._retry = true;
107
+ this.isRefreshing = true;
108
+ try {
109
+ const accessToken = await this.refreshAuthToken();
110
+ originalRequest.headers.Authorization = `Bearer ${accessToken}`;
111
+ this.processQueue(null, accessToken);
112
+ this.isRefreshing = false;
113
+ return this.axiosInstance(originalRequest);
114
+ } catch (refreshError) {
115
+ this.processQueue(refreshError, null);
116
+ this.isRefreshing = false;
117
+ return Promise.reject(this.handleError(refreshError));
118
+ }
119
+ }
120
+ return Promise.reject(this.handleError(error));
121
+ }
122
+ );
123
+ }
124
+ /**
125
+ * Process queued requests after token refresh
126
+ */
127
+ processQueue(error, token = null) {
128
+ this.failedQueue.forEach(({ resolve, reject }) => {
129
+ if (error) {
130
+ reject(error);
131
+ } else {
132
+ resolve(token);
133
+ }
134
+ });
135
+ this.failedQueue = [];
136
+ }
137
+ /**
138
+ * Refresh authentication token
139
+ */
140
+ async refreshAuthToken() {
141
+ if (!this.getRefreshToken) {
142
+ throw new Error("No refresh token function provided");
143
+ }
144
+ const refreshToken = this.getRefreshToken();
145
+ if (!refreshToken) {
146
+ if (this.clearAuthTokens) {
147
+ this.clearAuthTokens();
148
+ }
149
+ throw new Error("No refresh token available");
150
+ }
151
+ try {
152
+ const response = await import_axios.default.post(this.refreshTokenUrl, {
153
+ refresh_token: refreshToken
154
+ }, {
155
+ baseURL: ""
156
+ // Use absolute URL or empty to avoid baseURL duplication
157
+ });
158
+ const { access_token, refresh_token } = response.data;
159
+ if (this.setAuthToken) {
160
+ this.setAuthToken(access_token);
161
+ }
162
+ if (refresh_token && this.setRefreshToken) {
163
+ this.setRefreshToken(refresh_token);
164
+ }
165
+ return access_token;
166
+ } catch (error) {
167
+ if (this.clearAuthTokens) {
168
+ this.clearAuthTokens();
169
+ }
170
+ throw error;
171
+ }
172
+ }
173
+ /**
174
+ * Handle request errors
175
+ */
176
+ handleError(error) {
177
+ if (error.response) {
178
+ const message = error.response.data?.message || error.response.data?.error || error.message || "An unknown error occurred";
179
+ return new Error(message);
180
+ } else if (error.request) {
181
+ return new Error("Network error: No response from server");
182
+ } else {
183
+ return error instanceof Error ? error : new Error(String(error));
184
+ }
185
+ }
186
+ /**
187
+ * Make HTTP GET request
188
+ * Returns unwrapped server response data
189
+ */
190
+ async get(url, config) {
191
+ const axiosConfig = {
192
+ params: config?.params,
193
+ headers: config?.headers,
194
+ ...config
195
+ };
196
+ delete axiosConfig.params;
197
+ delete axiosConfig.headers;
198
+ const response = await this.axiosInstance.get(url, {
199
+ ...axiosConfig,
200
+ params: config?.params,
201
+ headers: config?.headers
202
+ });
203
+ return response.data;
204
+ }
205
+ /**
206
+ * Make HTTP POST request
207
+ * Returns unwrapped server response data
208
+ */
209
+ async post(url, data, config) {
210
+ const axiosConfig = {
211
+ headers: config?.headers,
212
+ ...config
213
+ };
214
+ delete axiosConfig.headers;
215
+ const response = await this.axiosInstance.post(url, data, {
216
+ ...axiosConfig,
217
+ headers: config?.headers
218
+ });
219
+ return response.data;
220
+ }
221
+ /**
222
+ * Make HTTP PATCH request
223
+ * Returns unwrapped server response data
224
+ */
225
+ async patch(url, data, config) {
226
+ const axiosConfig = {
227
+ headers: config?.headers,
228
+ ...config
229
+ };
230
+ delete axiosConfig.headers;
231
+ const response = await this.axiosInstance.patch(url, data, {
232
+ ...axiosConfig,
233
+ headers: config?.headers
234
+ });
235
+ return response.data;
236
+ }
237
+ /**
238
+ * Make HTTP PUT request
239
+ * Returns unwrapped server response data
240
+ */
241
+ async put(url, data, config) {
242
+ const axiosConfig = {
243
+ headers: config?.headers,
244
+ ...config
245
+ };
246
+ delete axiosConfig.headers;
247
+ const response = await this.axiosInstance.put(url, data, {
248
+ ...axiosConfig,
249
+ headers: config?.headers
250
+ });
251
+ return response.data;
252
+ }
253
+ /**
254
+ * Make HTTP DELETE request
255
+ * Returns unwrapped server response data
256
+ */
257
+ async delete(url, config) {
258
+ const axiosConfig = {
259
+ headers: config?.headers,
260
+ ...config
261
+ };
262
+ delete axiosConfig.headers;
263
+ const response = await this.axiosInstance.delete(url, {
264
+ ...axiosConfig,
265
+ headers: config?.headers
266
+ });
267
+ return response.data;
268
+ }
269
+ /**
270
+ * Get the underlying axios instance for advanced usage
271
+ */
272
+ get axios() {
273
+ return this.axiosInstance;
274
+ }
275
+ /**
276
+ * Create a new client instance with a different baseURL
277
+ * Useful for server-side requests or different API endpoints
278
+ */
279
+ withBaseURL(baseURL) {
280
+ const ConfigClass = this.constructor;
281
+ const headers = { ...this.axiosInstance.defaults.headers };
282
+ return new ConfigClass({
283
+ baseURL,
284
+ getAuthToken: this.getAuthToken,
285
+ getRefreshToken: this.getRefreshToken,
286
+ setAuthToken: this.setAuthToken,
287
+ setRefreshToken: this.setRefreshToken,
288
+ clearAuthTokens: this.clearAuthTokens,
289
+ refreshTokenUrl: this.refreshTokenUrl,
290
+ timeout: this.axiosInstance.defaults.timeout,
291
+ headers
292
+ });
293
+ }
294
+ };
295
+
296
+ // src/hooks.ts
297
+ var import_react_query2 = require("@tanstack/react-query");
298
+ var import_nuqs = require("nuqs");
299
+ var import_react = require("react");
300
+ var import_sonner = require("sonner");
301
+
302
+ // src/tanstack-query.tsx
303
+ var import_react_query = require("@tanstack/react-query");
304
+ var import_jsx_runtime = require("react/jsx-runtime");
305
+ function makeQueryClient() {
306
+ return new import_react_query.QueryClient({
307
+ defaultOptions: {
308
+ queries: {
309
+ refetchOnWindowFocus: false,
310
+ refetchOnReconnect: false
311
+ }
312
+ }
313
+ });
314
+ }
315
+ var browserQueryClient = void 0;
316
+ function getQueryClient() {
317
+ if (typeof window === "undefined") {
318
+ return makeQueryClient();
319
+ } else {
320
+ if (!browserQueryClient) browserQueryClient = makeQueryClient();
321
+ return browserQueryClient;
322
+ }
323
+ }
324
+ function QueryProvider({ children }) {
325
+ const queryClient = getQueryClient();
326
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_query.QueryClientProvider, { client: queryClient, children });
327
+ }
328
+
329
+ // src/hooks.ts
330
+ function useApi(httpClient, url, options = {}) {
331
+ const { queryKey, enabled = true, ...restOptions } = options;
332
+ return (0, import_react_query2.useQuery)({
333
+ queryKey: queryKey || [url],
334
+ queryFn: async () => {
335
+ if (!url) throw new Error("URL is required");
336
+ return httpClient.get(url);
337
+ },
338
+ enabled: url !== null && enabled,
339
+ ...restOptions
340
+ });
341
+ }
342
+ function useApiAction(httpClient, options) {
343
+ const { action, showToast = true, invalidateQueries, onSuccess, onError } = options;
344
+ const [isExecuted, setIsExecuted] = (0, import_react.useState)(false);
345
+ const mutation = (0, import_react_query2.useMutation)({
346
+ mutationFn: action,
347
+ onSuccess: (res) => {
348
+ onSuccess?.(res.data);
349
+ setIsExecuted(true);
350
+ if (showToast) {
351
+ import_sonner.toast.success(res.message || "Success");
352
+ }
353
+ if (invalidateQueries && invalidateQueries.length > 0) {
354
+ const queryClient = getQueryClient();
355
+ invalidateQueries.forEach((queryKey) => {
356
+ queryClient.invalidateQueries({ queryKey });
357
+ });
358
+ }
359
+ },
360
+ onError: (err) => {
361
+ onError?.(err);
362
+ if (showToast) {
363
+ import_sonner.toast.error(err.message || "An error occurred");
364
+ }
365
+ }
366
+ });
367
+ const execute = () => {
368
+ mutation.mutate();
369
+ };
370
+ return {
371
+ isLoading: mutation.isPending,
372
+ error: mutation.error ?? void 0,
373
+ data: mutation.data?.data,
374
+ execute,
375
+ isExecuted
376
+ };
377
+ }
378
+ var parseAsFilterArray = (0, import_nuqs.createParser)({
379
+ parse: (value) => {
380
+ return JSON.parse(value);
381
+ },
382
+ serialize: (value) => {
383
+ return JSON.stringify(value);
384
+ }
385
+ });
386
+ function usePaginationState(options) {
387
+ const { persist = true, params: defaultParams } = options;
388
+ const defaultPerPage = defaultParams?.per_page || 20;
389
+ const defaultPage = defaultParams?.page || 1;
390
+ const [perPageQuery, setPerPageQuery] = (0, import_nuqs.useQueryState)(
391
+ "per_page",
392
+ import_nuqs.parseAsInteger.withDefault(defaultPerPage)
393
+ );
394
+ const [pageQuery, setPageQuery] = (0, import_nuqs.useQueryState)("page", import_nuqs.parseAsInteger.withDefault(defaultPage));
395
+ const [searchQuery, setSearchQuery] = (0, import_nuqs.useQueryState)("search", import_nuqs.parseAsString.withDefault(""));
396
+ const [filtersQuery, setFiltersQuery] = (0, import_nuqs.useQueryState)(
397
+ "filters",
398
+ parseAsFilterArray.withDefault([])
399
+ );
400
+ const [sortQuery, setSortQuery] = (0, import_nuqs.useQueryState)("sort", import_nuqs.parseAsString.withDefault(""));
401
+ const [perPageState, setPerPageState] = (0, import_react.useState)(defaultPerPage);
402
+ const [pageState, setPageState] = (0, import_react.useState)(defaultPage);
403
+ const [searchState, setSearchState] = (0, import_react.useState)("");
404
+ const [filtersState, setFiltersState] = (0, import_react.useState)([]);
405
+ const [sortState, setSortState] = (0, import_react.useState)();
406
+ const perPage = persist ? perPageQuery : perPageState;
407
+ const page = persist ? pageQuery : pageState;
408
+ const search = persist ? searchQuery : searchState;
409
+ const filters = persist ? filtersQuery : filtersState;
410
+ const sort = persist ? sortQuery : sortState;
411
+ const setPerPage = (value) => {
412
+ if (persist) {
413
+ setPerPageQuery(value);
414
+ } else {
415
+ setPerPageState(value);
416
+ }
417
+ };
418
+ const setPage = (value) => {
419
+ if (persist) {
420
+ setPageQuery(value);
421
+ } else {
422
+ setPageState(value);
423
+ }
424
+ };
425
+ const setSearch = (value) => {
426
+ if (persist) {
427
+ setSearchQuery(value);
428
+ } else {
429
+ setSearchState(value);
430
+ }
431
+ };
432
+ const setFilters = (value) => {
433
+ if (persist) {
434
+ setFiltersQuery(value);
435
+ } else {
436
+ setFiltersState(value);
437
+ }
438
+ };
439
+ const setSort = (value) => {
440
+ if (persist) {
441
+ setSortQuery(value);
442
+ } else {
443
+ setSortState(typeof value === "function" ? value(sortState || "") : value);
444
+ }
445
+ };
446
+ const getParams = () => {
447
+ const params = new URLSearchParams();
448
+ if (defaultParams) {
449
+ Object.entries(defaultParams).forEach(([key, value]) => {
450
+ if (value !== void 0) {
451
+ params.set(key, value.toString());
452
+ }
453
+ });
454
+ }
455
+ if (perPage) params.set("per_page", perPage.toString());
456
+ if (page) params.set("page", page.toString());
457
+ if (search) params.set("search", search);
458
+ if (sort) {
459
+ const [sortBy, sortDirection] = sort.split(":");
460
+ if (sortBy) params.set("sort_by", sortBy);
461
+ if (sortDirection) params.set("sort_dir", sortDirection);
462
+ }
463
+ filters?.forEach((filter) => {
464
+ if (Array.isArray(filter.value)) {
465
+ filter.value.forEach((v) => {
466
+ params.append(filter.key, v.toString());
467
+ });
468
+ } else {
469
+ params.set(filter.key, filter.value.toString());
470
+ }
471
+ });
472
+ return params;
473
+ };
474
+ return {
475
+ perPage,
476
+ page,
477
+ search,
478
+ filters,
479
+ sort,
480
+ setPerPage,
481
+ setPage,
482
+ setSearch,
483
+ setFilters,
484
+ setSort,
485
+ getParams
486
+ };
487
+ }
488
+ function usePaginatedApi(httpClient, path, options = {}) {
489
+ const {
490
+ perPage,
491
+ page,
492
+ search,
493
+ filters,
494
+ sort,
495
+ setPerPage,
496
+ setPage,
497
+ setSearch,
498
+ setFilters,
499
+ setSort,
500
+ getParams
501
+ } = usePaginationState(options);
502
+ const getUrl = () => {
503
+ if (!path) return null;
504
+ const params = getParams();
505
+ return params.size === 0 ? path : `${path}?${params.toString()}`;
506
+ };
507
+ const url = getUrl();
508
+ const { data, isLoading, error, refetch, isSuccess, isFetching } = (0, import_react_query2.useQuery)({
509
+ queryKey: [path, options, page, perPage, search, filters, sort],
510
+ queryFn: async () => {
511
+ if (!url) throw new Error("URL is required");
512
+ return httpClient.get(url);
513
+ },
514
+ enabled: !!url
515
+ });
516
+ function getBulkActionUrl(action, additionalFilters) {
517
+ const params = getParams();
518
+ params.delete("page");
519
+ params.delete("per_page");
520
+ Object.entries(additionalFilters || {}).forEach(([key, value]) => {
521
+ if (value === void 0) {
522
+ params.delete(key);
523
+ } else if (Array.isArray(value)) {
524
+ value.forEach((v) => {
525
+ params.append(key, v);
526
+ });
527
+ } else {
528
+ params.set(key, value);
529
+ }
530
+ });
531
+ if (params.size === 0) return `${path}/${action}`;
532
+ return `${path}/${action}?${params.toString()}`;
533
+ }
534
+ function setFilter(key, value) {
535
+ if (value === void 0) {
536
+ removeFilter(key);
537
+ return;
538
+ }
539
+ const index = filters.findIndex((filter) => filter.key === key);
540
+ if (index === -1) {
541
+ setFilters([...filters, { key, value }]);
542
+ } else {
543
+ setFilters([...filters.slice(0, index), { key, value }, ...filters.slice(index + 1)]);
544
+ }
545
+ }
546
+ function removeFilter(key) {
547
+ setFilters(filters.filter((filter) => filter.key !== key));
548
+ }
549
+ function getFilterValue(key) {
550
+ return filters.find((filter) => filter.key === key)?.value;
551
+ }
552
+ function resetFilters() {
553
+ setFilters([]);
554
+ }
555
+ return {
556
+ message: data?.message || "",
557
+ data: data?.data || [],
558
+ pagination: data?.pagination,
559
+ isLoading,
560
+ isSuccess,
561
+ isFetching,
562
+ error,
563
+ mutate: refetch,
564
+ page,
565
+ setPage,
566
+ perPage,
567
+ setPerPage,
568
+ search,
569
+ setSearch,
570
+ filters,
571
+ setFilters,
572
+ setFilter,
573
+ removeFilter,
574
+ getFilterValue,
575
+ resetFilters,
576
+ getBulkActionUrl,
577
+ sort,
578
+ setSort
579
+ };
580
+ }
581
+ function useMockPaginatedApi(data) {
582
+ return {
583
+ data,
584
+ pagination: {
585
+ total: data.length,
586
+ per_page: 10,
587
+ current_page: 1,
588
+ total_pages: Math.ceil(data.length / 10),
589
+ from: 1,
590
+ to: data.length
591
+ },
592
+ message: "Data fetched successfully",
593
+ isLoading: false,
594
+ isSuccess: true,
595
+ isFetching: false,
596
+ mutate: () => {
597
+ },
598
+ page: 1,
599
+ setPage: () => {
600
+ },
601
+ perPage: 10,
602
+ setPerPage: () => {
603
+ },
604
+ search: "",
605
+ setSearch: () => {
606
+ },
607
+ filters: [],
608
+ setFilters: () => {
609
+ },
610
+ getFilterValue: () => void 0,
611
+ resetFilters: () => {
612
+ },
613
+ setFilter: () => {
614
+ },
615
+ removeFilter: () => {
616
+ },
617
+ sort: "",
618
+ setSort: () => {
619
+ },
620
+ getBulkActionUrl: () => ""
621
+ };
622
+ }
623
+
624
+ // src/utils.ts
625
+ function getErrorMessage(error) {
626
+ const err = error;
627
+ const apiMessage = err?.response?.data?.message;
628
+ if (typeof apiMessage === "string" && apiMessage.trim() !== "") {
629
+ return apiMessage;
630
+ }
631
+ const apiError = err?.response?.data?.error;
632
+ if (typeof apiError === "string" && apiError.trim() !== "") {
633
+ return apiError;
634
+ }
635
+ const message = err?.message;
636
+ if (typeof message === "string" && message.trim() !== "") {
637
+ return message;
638
+ }
639
+ if (error instanceof Error && error.message.trim() !== "") {
640
+ return error.message;
641
+ }
642
+ return "Something went wrong";
643
+ }
644
+ function getSuccessMessage(response, defaultMessage) {
645
+ return response?.data?.message || defaultMessage || "Success";
646
+ }
647
+
648
+ // src/index.ts
649
+ var import_react_query3 = require("@tanstack/react-query");
650
+ // Annotate the CommonJS export names for ESM import in node:
651
+ 0 && (module.exports = {
652
+ BaseApiClient,
653
+ QueryProvider,
654
+ getErrorMessage,
655
+ getQueryClient,
656
+ getSuccessMessage,
657
+ makeQueryClient,
658
+ useApi,
659
+ useApiAction,
660
+ useInfiniteQuery,
661
+ useMockPaginatedApi,
662
+ usePaginatedApi,
663
+ useQuery
664
+ });
665
+ //# sourceMappingURL=index.cjs.map