@herowcode/utils 1.1.11 → 1.2.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/README.md CHANGED
@@ -11,6 +11,7 @@ A lightweight collection of utility functions for everyday JavaScript/TypeScript
11
11
  - 🎯 **Tree-shakable** - Only import what you need
12
12
  - 📂 **Scoped exports** - Import from specific modules
13
13
  - 🎥 **YouTube utilities** - Extract video IDs, generate URLs, and get video durations
14
+ - 🌐 **API client** - HTTP client with retry logic, authentication, and standardized error handling
14
15
 
15
16
  ## Installation
16
17
 
@@ -24,7 +25,7 @@ yarn add @herowcode/utils
24
25
 
25
26
  ### Import everything:
26
27
  ```typescript
27
- import { formatDate, capitalize, debounce, extractYouTubeId } from '@herowcode/utils';
28
+ import { formatDate, capitalize, debounce, extractYouTubeId, apiClient } from '@herowcode/utils';
28
29
  ```
29
30
 
30
31
  ### Import by scope:
@@ -34,6 +35,7 @@ import { capitalize, camelCase } from '@herowcode/utils/string';
34
35
  import { randomInt } from '@herowcode/utils/number';
35
36
  import { debounce, throttle } from '@herowcode/utils/function';
36
37
  import { extractYouTubeId, generateYoutubeURL } from '@herowcode/utils/youtube';
38
+ import { apiClient, apiWrapper } from '@herowcode/utils/api';
37
39
  ```
38
40
 
39
41
  ### Examples:
@@ -57,10 +59,135 @@ const embedUrl = generateYoutubeURL({
57
59
  embed: true,
58
60
  autoplay: true
59
61
  }); // "https://www.youtube.com/embed/abc123?autoplay=1"
62
+
63
+ // API utilities
64
+ const client = apiClient({ baseURL: 'https://api.example.com' });
65
+ const result = await client.get('/users', { params: { page: 1 } });
66
+ if (result.error) {
67
+ console.error('API Error:', result.error.message);
68
+ } else {
69
+ console.log('Users:', result.data);
70
+ }
60
71
  ```
61
72
 
62
73
  ## API Reference
63
74
 
75
+ ### API Utilities
76
+
77
+ #### `apiClient(config?: TApiClientProps)`
78
+ Creates a configured HTTP client with standardized error handling, authentication, retry logic, and response processing.
79
+
80
+ **Configuration Options:**
81
+ - `baseURL`: Base URL for all requests
82
+ - `onSignoutUnauthorized`: Callback for 401 responses outside sign-in paths
83
+ - `getAccessToken`: Function to retrieve auth tokens
84
+ - `getUserIP`: Function to get user IP for headers
85
+
86
+ **Methods:**
87
+ - `get<T>(url, options?)`: GET request
88
+ - `post<T>(url, options?)`: POST request
89
+ - `put<T>(url, options?)`: PUT request
90
+ - `delete<T>(url, options?)`: DELETE request
91
+ - `patch<T>(url, options?)`: PATCH request
92
+
93
+ **Request Options (`ICustomRequestInit`):**
94
+ - `json`: Data to be JSON-stringified as request body
95
+ - `params`: Query parameters object
96
+ - `retry`: Retry configuration with limit, methods, status codes, and backoff
97
+ - All standard `RequestInit` options except `body`
98
+
99
+ ```typescript
100
+ import { apiClient } from '@herowcode/utils/api';
101
+
102
+ // Basic usage
103
+ const client = apiClient({
104
+ baseURL: 'https://api.example.com',
105
+ getAccessToken: () => Promise.resolve('token123'),
106
+ onSignoutUnauthorized: (response) => {
107
+ // Handle logout
108
+ console.log('Unauthorized:', response);
109
+ }
110
+ });
111
+
112
+ // GET with query parameters
113
+ const result = await client.get('/users', {
114
+ params: { page: 1, limit: 10 }
115
+ });
116
+
117
+ // POST with JSON body
118
+ const newUser = await client.post('/users', {
119
+ json: { name: 'John', email: 'john@example.com' }
120
+ });
121
+
122
+ // PUT with retry configuration
123
+ const updated = await client.put('/users/123', {
124
+ json: { name: 'Jane' },
125
+ retry: {
126
+ limit: 3,
127
+ methods: ['put'],
128
+ statusCodes: [500, 502, 503],
129
+ backoffLimit: 5000
130
+ }
131
+ });
132
+
133
+ // Handle response
134
+ if (result.error) {
135
+ console.error('API Error:', result.error.message);
136
+ } else {
137
+ console.log('Data:', result.data);
138
+ }
139
+ ```
140
+
141
+ #### `apiWrapper<T, D>(apiCall: () => Promise<T>, defaultData?: D)`
142
+ Wraps API calls with standardized error handling and returns a consistent result object.
143
+
144
+ **Returns:** `{ data: T | D, error: IApiError | null }`
145
+
146
+ **Error Types Handled:**
147
+ - `Response` objects (fetch errors)
148
+ - `AxiosError` objects
149
+ - Standard `Error` objects
150
+ - Unknown error types
151
+
152
+ **IApiError Properties:**
153
+ - `message`: Error description
154
+ - `status`: HTTP status code (if applicable)
155
+ - `code`: Error code (if available)
156
+ - `details`: Additional error details
157
+ - `path`: Request path (if applicable)
158
+ - `timestamp`: Error timestamp (if provided by API)
159
+
160
+ ```typescript
161
+ import { apiWrapper } from '@herowcode/utils/api';
162
+
163
+ // Basic usage
164
+ const fetchUser = async (id: string) => {
165
+ const response = await fetch(`/api/users/${id}`);
166
+ if (!response.ok) throw response;
167
+ return response.json();
168
+ };
169
+
170
+ const result = await apiWrapper(() => fetchUser('123'));
171
+ if (result.error) {
172
+ console.error('Failed to fetch user:', result.error.message);
173
+ console.log('Status:', result.error.status);
174
+ } else {
175
+ console.log('User data:', result.data);
176
+ }
177
+
178
+ // With default data
179
+ const defaultPosts = [];
180
+ const postsResult = await apiWrapper(() => fetchPosts(), defaultPosts);
181
+ // postsResult.data will be [] instead of null on error
182
+ ```
183
+
184
+ **Features:**
185
+ - Automatic error type detection and standardization
186
+ - Support for fetch Response, Axios, and standard Error objects
187
+ - Extraction of error details from response bodies
188
+ - Consistent error format across different error types
189
+ - Optional default data for graceful error handling
190
+
64
191
  ### Array Utilities
65
192
 
66
193
  #### `shuffle<T>(array: T[]): T[]`
@@ -0,0 +1,25 @@
1
+ interface ICustomRequestInit extends Omit<RequestInit, "body"> {
2
+ json?: unknown;
3
+ params?: Record<string, string | number | boolean | null | undefined>;
4
+ retry?: {
5
+ limit?: number;
6
+ methods?: string[];
7
+ statusCodes?: number[];
8
+ backoffLimit?: number;
9
+ };
10
+ }
11
+ type TApiClientProps = {
12
+ baseURL?: string;
13
+ onSignoutUnauthorized?: (response?: unknown) => void;
14
+ getAccessToken?: () => Promise<string | null>;
15
+ getUserIP?: () => Promise<string | undefined>;
16
+ };
17
+ export declare const apiClient: (config?: TApiClientProps) => {
18
+ get: <T = unknown>(url: string, options?: ICustomRequestInit) => Promise<import("..").TTryCatchResult<T, import("./wrapper").IApiError, null>>;
19
+ post: <T = unknown>(url: string, options?: ICustomRequestInit) => Promise<import("..").TTryCatchResult<T, import("./wrapper").IApiError, null>>;
20
+ put: <T = unknown>(url: string, options?: ICustomRequestInit) => Promise<import("..").TTryCatchResult<T, import("./wrapper").IApiError, null>>;
21
+ delete: <T = unknown>(url: string, options?: ICustomRequestInit) => Promise<import("..").TTryCatchResult<T, import("./wrapper").IApiError, null>>;
22
+ patch: <T = unknown>(url: string, options?: ICustomRequestInit) => Promise<import("..").TTryCatchResult<T, import("./wrapper").IApiError, null>>;
23
+ };
24
+ export {};
25
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAEA,UAAU,kBAAmB,SAAQ,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC;IAC5D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;IACrE,KAAK,CAAC,EAAE;QACN,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;QAClB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;QACtB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;CACF;AAoGD,KAAK,eAAe,GAAG;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,qBAAqB,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACpD,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAC7C,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CAC9C,CAAA;AA8DD,eAAO,MAAM,SAAS,GAAI,SAAS,eAAe;UAMxC,CAAC,iBAAiB,MAAM,YAAY,kBAAkB;WAGrD,CAAC,iBAAiB,MAAM,YAAY,kBAAkB;UAGvD,CAAC,iBAAiB,MAAM,YAAY,kBAAkB;aAGnD,CAAC,iBAAiB,MAAM,YAAY,kBAAkB;YAKvD,CAAC,iBAAiB,MAAM,YAAY,kBAAkB;CAGjE,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { apiClient } from "./client";
2
+ export type { IApiError } from "./wrapper";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA"}
@@ -0,0 +1,27 @@
1
+ import type { TTryCatchResult } from "../function";
2
+ export interface IApiError {
3
+ message: string;
4
+ status?: number;
5
+ code?: string;
6
+ details?: unknown;
7
+ path?: string;
8
+ timestamp?: string;
9
+ }
10
+ /**
11
+ * Wraps API calls with standardized error handling
12
+ *
13
+ * @param apiCall - The API call function to execute
14
+ * @param defaultData - Optional default value to return when an error occurs
15
+ * @returns A standardized result object with data or formatted error message
16
+ *
17
+ * @example
18
+ * // Basic usage
19
+ * const result = await apiWrapper(() => fetchUserData(userId));
20
+ *
21
+ * @example
22
+ * // With default data value
23
+ * const result = await apiWrapper(() => fetchPosts(), []);
24
+ * // result.data will be [] instead of null on error
25
+ */
26
+ export declare function apiWrapper<T, D = null>(apiCall: () => Promise<T>, defaultData?: D): Promise<TTryCatchResult<T, IApiError, D extends null ? null : D>>;
27
+ //# sourceMappingURL=wrapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../src/api/wrapper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAGlD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAwJD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,EAC1C,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,WAAW,CAAC,EAAE,CAAC,GACd,OAAO,CAAC,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,SAAS,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAcnE"}
@@ -0,0 +1,25 @@
1
+ interface ICustomRequestInit extends Omit<RequestInit, "body"> {
2
+ json?: unknown;
3
+ params?: Record<string, string | number | boolean | null | undefined>;
4
+ retry?: {
5
+ limit?: number;
6
+ methods?: string[];
7
+ statusCodes?: number[];
8
+ backoffLimit?: number;
9
+ };
10
+ }
11
+ type TApiClientProps = {
12
+ baseURL?: string;
13
+ onSignoutUnauthorized?: (response?: unknown) => void;
14
+ getAccessToken?: () => Promise<string | null>;
15
+ getUserIP?: () => Promise<string | undefined>;
16
+ };
17
+ export declare const apiClient: (config?: TApiClientProps) => {
18
+ get: <T = unknown>(url: string, options?: ICustomRequestInit) => Promise<import("..").TTryCatchResult<T, import("./wrapper").IApiError, null>>;
19
+ post: <T = unknown>(url: string, options?: ICustomRequestInit) => Promise<import("..").TTryCatchResult<T, import("./wrapper").IApiError, null>>;
20
+ put: <T = unknown>(url: string, options?: ICustomRequestInit) => Promise<import("..").TTryCatchResult<T, import("./wrapper").IApiError, null>>;
21
+ delete: <T = unknown>(url: string, options?: ICustomRequestInit) => Promise<import("..").TTryCatchResult<T, import("./wrapper").IApiError, null>>;
22
+ patch: <T = unknown>(url: string, options?: ICustomRequestInit) => Promise<import("..").TTryCatchResult<T, import("./wrapper").IApiError, null>>;
23
+ };
24
+ export {};
25
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/api/client.ts"],"names":[],"mappings":"AAEA,UAAU,kBAAmB,SAAQ,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC;IAC5D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;IACrE,KAAK,CAAC,EAAE;QACN,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;QAClB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;QACtB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;CACF;AAoGD,KAAK,eAAe,GAAG;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,qBAAqB,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACpD,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAC7C,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CAC9C,CAAA;AA8DD,eAAO,MAAM,SAAS,GAAI,SAAS,eAAe;UAMxC,CAAC,iBAAiB,MAAM,YAAY,kBAAkB;WAGrD,CAAC,iBAAiB,MAAM,YAAY,kBAAkB;UAGvD,CAAC,iBAAiB,MAAM,YAAY,kBAAkB;aAGnD,CAAC,iBAAiB,MAAM,YAAY,kBAAkB;YAKvD,CAAC,iBAAiB,MAAM,YAAY,kBAAkB;CAGjE,CAAA"}
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apiClient = void 0;
4
+ const wrapper_1 = require("./wrapper");
5
+ const defaultRetryConfig = {
6
+ limit: 5,
7
+ methods: ["get"],
8
+ statusCodes: [413],
9
+ backoffLimit: 3000,
10
+ };
11
+ function buildQueryString(params) {
12
+ const searchParams = new URLSearchParams();
13
+ Object.entries(params).forEach(([key, value]) => {
14
+ if (value !== null && value !== undefined) {
15
+ searchParams.append(key, String(value));
16
+ }
17
+ });
18
+ const queryString = searchParams.toString();
19
+ return queryString ? `?${queryString}` : "";
20
+ }
21
+ async function beforeRequestHook(request, getAccessToken, getUserIP) {
22
+ const token = await (getAccessToken === null || getAccessToken === void 0 ? void 0 : getAccessToken());
23
+ const headers = new Headers(request.headers);
24
+ if (token) {
25
+ headers.set("Authorization", `Bearer ${token}`);
26
+ }
27
+ const userIP = await (getUserIP === null || getUserIP === void 0 ? void 0 : getUserIP());
28
+ if (userIP) {
29
+ headers.set("X-User-IP", userIP);
30
+ }
31
+ return new Request(request, { headers });
32
+ }
33
+ async function afterResponseHook(request, response, onSignoutUnauthorized) {
34
+ const possibleSignInPaths = ["signin", "sign-in", "login"];
35
+ const isSignInPath = possibleSignInPaths.some((path) => request.url.includes(path));
36
+ const needsBody = response.status === 401 && !isSignInPath;
37
+ let responseBody = null;
38
+ let responseBodyFor401 = null;
39
+ if (needsBody) {
40
+ const clone = response.clone();
41
+ try {
42
+ responseBody = await clone.json();
43
+ responseBodyFor401 = responseBody;
44
+ }
45
+ catch (_a) {
46
+ responseBody = await clone.text();
47
+ }
48
+ }
49
+ if (response.status === 401 && !isSignInPath) {
50
+ return onSignoutUnauthorized === null || onSignoutUnauthorized === void 0 ? void 0 : onSignoutUnauthorized(responseBodyFor401);
51
+ }
52
+ }
53
+ async function fetchWithRetry(input, init) {
54
+ var _a, _b, _c, _d;
55
+ const retryConfig = (init === null || init === void 0 ? void 0 : init.retry) || defaultRetryConfig;
56
+ const method = ((init === null || init === void 0 ? void 0 : init.method) || "GET").toLowerCase();
57
+ let attempt = 0;
58
+ while (true) {
59
+ attempt++;
60
+ const response = await fetch(input, init);
61
+ const shouldRetry = ((_a = retryConfig.statusCodes) === null || _a === void 0 ? void 0 : _a.includes(response.status)) &&
62
+ ((_b = retryConfig.methods) === null || _b === void 0 ? void 0 : _b.includes(method)) &&
63
+ attempt < ((_c = retryConfig.limit) !== null && _c !== void 0 ? _c : 5);
64
+ if (!shouldRetry)
65
+ return response;
66
+ const backoff = Math.min(2 ** attempt * 100, (_d = retryConfig.backoffLimit) !== null && _d !== void 0 ? _d : 3000);
67
+ await new Promise((resolve) => setTimeout(resolve, backoff));
68
+ }
69
+ }
70
+ async function api(config, endpoint, options = {}) {
71
+ var _a;
72
+ let url = endpoint.startsWith("http")
73
+ ? endpoint
74
+ : `${(_a = config.baseURL) === null || _a === void 0 ? void 0 : _a.replace(/\/$/, "")}/${endpoint.replace(/^\//, "")}`;
75
+ // Add query parameters if provided
76
+ if (options.params) {
77
+ const queryString = buildQueryString(options.params);
78
+ url += queryString;
79
+ }
80
+ // Apply default options
81
+ const init = {
82
+ credentials: "include",
83
+ ...options,
84
+ };
85
+ let body;
86
+ if (options.json) {
87
+ body = JSON.stringify(options.json);
88
+ init.headers = {
89
+ ...init.headers,
90
+ "Content-Type": "application/json",
91
+ };
92
+ }
93
+ // Create initial request
94
+ let request = new Request(url, { ...init, body });
95
+ // Run pre-request hooks
96
+ request = await beforeRequestHook(request, config.getAccessToken, config.getUserIP);
97
+ // Execute request with retry logic
98
+ const response = await fetchWithRetry(request, init);
99
+ // Run post-response hooks
100
+ await afterResponseHook(request, response, config.onSignoutUnauthorized);
101
+ if (!response.ok) {
102
+ throw response;
103
+ }
104
+ if (response.status === 204) {
105
+ return null;
106
+ }
107
+ const text = await response.text();
108
+ if (!text) {
109
+ return null;
110
+ }
111
+ return JSON.parse(text);
112
+ }
113
+ const apiClient = (config) => {
114
+ const apiConfig = {
115
+ ...config,
116
+ };
117
+ return {
118
+ get: (url, options) => (0, wrapper_1.apiWrapper)(() => api(apiConfig, url, options)),
119
+ post: (url, options) => (0, wrapper_1.apiWrapper)(() => api(apiConfig, url, { ...options, method: "POST" })),
120
+ put: (url, options) => (0, wrapper_1.apiWrapper)(() => api(apiConfig, url, { ...options, method: "PUT" })),
121
+ delete: (url, options) => (0, wrapper_1.apiWrapper)(() => api(apiConfig, url, { ...options, method: "DELETE" })),
122
+ patch: (url, options) => (0, wrapper_1.apiWrapper)(() => api(apiConfig, url, { ...options, method: "PATCH" })),
123
+ };
124
+ };
125
+ exports.apiClient = apiClient;
126
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/api/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { apiClient } from "./client";
2
+ export type { IApiError } from "./wrapper";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apiClient = void 0;
4
+ var client_1 = require("./client");
5
+ Object.defineProperty(exports, "apiClient", { enumerable: true, get: function () { return client_1.apiClient; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":";;;AAAA,mCAAoC;AAA3B,mGAAA,SAAS,OAAA"}
@@ -0,0 +1,27 @@
1
+ import type { TTryCatchResult } from "../function";
2
+ export interface IApiError {
3
+ message: string;
4
+ status?: number;
5
+ code?: string;
6
+ details?: unknown;
7
+ path?: string;
8
+ timestamp?: string;
9
+ }
10
+ /**
11
+ * Wraps API calls with standardized error handling
12
+ *
13
+ * @param apiCall - The API call function to execute
14
+ * @param defaultData - Optional default value to return when an error occurs
15
+ * @returns A standardized result object with data or formatted error message
16
+ *
17
+ * @example
18
+ * // Basic usage
19
+ * const result = await apiWrapper(() => fetchUserData(userId));
20
+ *
21
+ * @example
22
+ * // With default data value
23
+ * const result = await apiWrapper(() => fetchPosts(), []);
24
+ * // result.data will be [] instead of null on error
25
+ */
26
+ export declare function apiWrapper<T, D = null>(apiCall: () => Promise<T>, defaultData?: D): Promise<TTryCatchResult<T, IApiError, D extends null ? null : D>>;
27
+ //# sourceMappingURL=wrapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../../src/api/wrapper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAGlD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAwJD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,EAC1C,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,WAAW,CAAC,EAAE,CAAC,GACd,OAAO,CAAC,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,SAAS,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAcnE"}
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apiWrapper = apiWrapper;
4
+ const axios_1 = require("axios");
5
+ /**
6
+ * Extracts error properties from Axios errors
7
+ */
8
+ async function extractAxiosErrorDetails(error) {
9
+ var _a, _b, _c;
10
+ const errorObj = {
11
+ message: error.message || "Axios Error",
12
+ status: (_a = error.response) === null || _a === void 0 ? void 0 : _a.status,
13
+ path: (_b = error.config) === null || _b === void 0 ? void 0 : _b.url,
14
+ };
15
+ if (((_c = error.response) === null || _c === void 0 ? void 0 : _c.data) && typeof error.response.data === "object") {
16
+ return await enrichErrorWithData(errorObj, Promise.resolve(error.response.data));
17
+ }
18
+ return errorObj;
19
+ }
20
+ /**
21
+ * Extracts error properties from standard Error objects
22
+ */
23
+ function extractStandardErrorDetails(error) {
24
+ const errorObj = {
25
+ message: error.message,
26
+ };
27
+ // Capture any custom properties the error might have
28
+ const extendedError = error;
29
+ if (extendedError.code)
30
+ errorObj.code = String(extendedError.code);
31
+ if (extendedError.status)
32
+ errorObj.status = Number(extendedError.status);
33
+ if (extendedError.details)
34
+ errorObj.details = extendedError.details;
35
+ return errorObj;
36
+ }
37
+ /**
38
+ * Extracts error properties from fetch Response objects
39
+ */
40
+ async function extractResponseErrorDetails(response) {
41
+ const errorObj = {
42
+ message: `HTTP Error ${response.status}: ${response.statusText}`,
43
+ status: response.status,
44
+ path: response.url,
45
+ };
46
+ try {
47
+ // Clone the response to avoid consuming the body stream
48
+ const clonedResponse = response.clone();
49
+ const errorData = await clonedResponse.json();
50
+ // Directly enrich the error object with the parsed data
51
+ if (errorData && typeof errorData === "object") {
52
+ if ("message" in errorData && errorData.message) {
53
+ errorObj.message = String(errorData.message);
54
+ }
55
+ if ("code" in errorData) {
56
+ errorObj.code = String(errorData.code);
57
+ }
58
+ if ("details" in errorData) {
59
+ errorObj.details = errorData.details;
60
+ }
61
+ else if ("issues" in errorData) {
62
+ errorObj.details = errorData.issues;
63
+ }
64
+ if ("timestamp" in errorData) {
65
+ errorObj.timestamp = String(errorData.timestamp);
66
+ }
67
+ }
68
+ return errorObj;
69
+ }
70
+ catch (_a) {
71
+ // If JSON parsing fails, return the basic error object
72
+ return errorObj;
73
+ }
74
+ }
75
+ /**
76
+ * Enriches an error object with additional data from response
77
+ */
78
+ async function enrichErrorWithData(errorObj, errorData, path) {
79
+ const data = await errorData;
80
+ if (data) {
81
+ if ("message" in data) {
82
+ errorObj.message = String(data.message);
83
+ }
84
+ if ("code" in data) {
85
+ errorObj.code = String(data.code);
86
+ }
87
+ if ("details" in data) {
88
+ errorObj.details = data.details;
89
+ }
90
+ else if ("issues" in data) {
91
+ errorObj.details = data.issues;
92
+ }
93
+ if ("timestamp" in data) {
94
+ errorObj.timestamp = String(data.timestamp);
95
+ }
96
+ if (path) {
97
+ errorObj.path = path;
98
+ }
99
+ }
100
+ return errorObj;
101
+ }
102
+ /**
103
+ * Determines the type of error and routes to appropriate handler
104
+ */
105
+ async function processError(error) {
106
+ if (error instanceof Response) {
107
+ return extractResponseErrorDetails(error);
108
+ }
109
+ if ((0, axios_1.isAxiosError)(error)) {
110
+ return extractAxiosErrorDetails(error);
111
+ }
112
+ if (error instanceof Error) {
113
+ return extractStandardErrorDetails(error);
114
+ }
115
+ // Handle unknown error types
116
+ return {
117
+ message: error ? JSON.stringify(error) : "Unknown error",
118
+ };
119
+ }
120
+ /**
121
+ * Wraps API calls with standardized error handling
122
+ *
123
+ * @param apiCall - The API call function to execute
124
+ * @param defaultData - Optional default value to return when an error occurs
125
+ * @returns A standardized result object with data or formatted error message
126
+ *
127
+ * @example
128
+ * // Basic usage
129
+ * const result = await apiWrapper(() => fetchUserData(userId));
130
+ *
131
+ * @example
132
+ * // With default data value
133
+ * const result = await apiWrapper(() => fetchPosts(), []);
134
+ * // result.data will be [] instead of null on error
135
+ */
136
+ async function apiWrapper(apiCall, defaultData) {
137
+ try {
138
+ const data = await apiCall();
139
+ return {
140
+ data,
141
+ error: null,
142
+ };
143
+ }
144
+ catch (error) {
145
+ const errorObj = await processError(error);
146
+ return {
147
+ data: (defaultData !== null && defaultData !== void 0 ? defaultData : null),
148
+ error: errorObj,
149
+ };
150
+ }
151
+ }
152
+ //# sourceMappingURL=wrapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper.js","sourceRoot":"","sources":["../../../src/api/wrapper.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ export * from "./api/index.js";
1
2
  export * from "./array/index.js";
2
3
  export * from "./date/index.js";
3
4
  export * from "./files/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA"}
package/dist/cjs/index.js CHANGED
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./api/index.js"), exports); // API utilities
17
18
  __exportStar(require("./array/index.js"), exports); // Array utilities
18
19
  __exportStar(require("./date/index.js"), exports); // Date utilities
19
20
  __exportStar(require("./files/index.js"), exports); // Files utilities
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,mDAAgC,CAAC,kBAAkB;AACnD,kDAA+B,CAAC,iBAAiB;AACjD,mDAAgC,CAAC,kBAAkB;AACnD,sDAAmC,CAAC,qBAAqB;AACzD,oDAAiC,CAAC,mBAAmB;AACrD,qDAAkC,CAAC,oBAAoB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA8B,CAAC,gBAAgB;AAC/C,mDAAgC,CAAC,kBAAkB;AACnD,kDAA+B,CAAC,iBAAiB;AACjD,mDAAgC,CAAC,kBAAkB;AACnD,sDAAmC,CAAC,qBAAqB;AACzD,oDAAiC,CAAC,mBAAmB;AACrD,qDAAkC,CAAC,oBAAoB"}
@@ -0,0 +1,121 @@
1
+ import { apiWrapper } from "./wrapper.js";
2
+ const defaultRetryConfig = {
3
+ limit: 5,
4
+ methods: ["get"],
5
+ statusCodes: [413],
6
+ backoffLimit: 3000,
7
+ };
8
+ function buildQueryString(params) {
9
+ const searchParams = new URLSearchParams();
10
+ Object.entries(params).forEach(([key, value]) => {
11
+ if (value !== null && value !== undefined) {
12
+ searchParams.append(key, String(value));
13
+ }
14
+ });
15
+ const queryString = searchParams.toString();
16
+ return queryString ? `?${queryString}` : "";
17
+ }
18
+ async function beforeRequestHook(request, getAccessToken, getUserIP) {
19
+ const token = await (getAccessToken === null || getAccessToken === void 0 ? void 0 : getAccessToken());
20
+ const headers = new Headers(request.headers);
21
+ if (token) {
22
+ headers.set("Authorization", `Bearer ${token}`);
23
+ }
24
+ const userIP = await (getUserIP === null || getUserIP === void 0 ? void 0 : getUserIP());
25
+ if (userIP) {
26
+ headers.set("X-User-IP", userIP);
27
+ }
28
+ return new Request(request, { headers });
29
+ }
30
+ async function afterResponseHook(request, response, onSignoutUnauthorized) {
31
+ const possibleSignInPaths = ["signin", "sign-in", "login"];
32
+ const isSignInPath = possibleSignInPaths.some((path) => request.url.includes(path));
33
+ const needsBody = response.status === 401 && !isSignInPath;
34
+ let responseBody = null;
35
+ let responseBodyFor401 = null;
36
+ if (needsBody) {
37
+ const clone = response.clone();
38
+ try {
39
+ responseBody = await clone.json();
40
+ responseBodyFor401 = responseBody;
41
+ }
42
+ catch (_a) {
43
+ responseBody = await clone.text();
44
+ }
45
+ }
46
+ if (response.status === 401 && !isSignInPath) {
47
+ return onSignoutUnauthorized === null || onSignoutUnauthorized === void 0 ? void 0 : onSignoutUnauthorized(responseBodyFor401);
48
+ }
49
+ }
50
+ async function fetchWithRetry(input, init) {
51
+ var _a, _b, _c, _d;
52
+ const retryConfig = (init === null || init === void 0 ? void 0 : init.retry) || defaultRetryConfig;
53
+ const method = ((init === null || init === void 0 ? void 0 : init.method) || "GET").toLowerCase();
54
+ let attempt = 0;
55
+ while (true) {
56
+ attempt++;
57
+ const response = await fetch(input, init);
58
+ const shouldRetry = ((_a = retryConfig.statusCodes) === null || _a === void 0 ? void 0 : _a.includes(response.status)) &&
59
+ ((_b = retryConfig.methods) === null || _b === void 0 ? void 0 : _b.includes(method)) &&
60
+ attempt < ((_c = retryConfig.limit) !== null && _c !== void 0 ? _c : 5);
61
+ if (!shouldRetry)
62
+ return response;
63
+ const backoff = Math.min(2 ** attempt * 100, (_d = retryConfig.backoffLimit) !== null && _d !== void 0 ? _d : 3000);
64
+ await new Promise((resolve) => setTimeout(resolve, backoff));
65
+ }
66
+ }
67
+ async function api(config, endpoint, options = {}) {
68
+ var _a;
69
+ let url = endpoint.startsWith("http")
70
+ ? endpoint
71
+ : `${(_a = config.baseURL) === null || _a === void 0 ? void 0 : _a.replace(/\/$/, "")}/${endpoint.replace(/^\//, "")}`;
72
+ // Add query parameters if provided
73
+ if (options.params) {
74
+ const queryString = buildQueryString(options.params);
75
+ url += queryString;
76
+ }
77
+ // Apply default options
78
+ const init = {
79
+ credentials: "include",
80
+ ...options,
81
+ };
82
+ let body;
83
+ if (options.json) {
84
+ body = JSON.stringify(options.json);
85
+ init.headers = {
86
+ ...init.headers,
87
+ "Content-Type": "application/json",
88
+ };
89
+ }
90
+ // Create initial request
91
+ let request = new Request(url, { ...init, body });
92
+ // Run pre-request hooks
93
+ request = await beforeRequestHook(request, config.getAccessToken, config.getUserIP);
94
+ // Execute request with retry logic
95
+ const response = await fetchWithRetry(request, init);
96
+ // Run post-response hooks
97
+ await afterResponseHook(request, response, config.onSignoutUnauthorized);
98
+ if (!response.ok) {
99
+ throw response;
100
+ }
101
+ if (response.status === 204) {
102
+ return null;
103
+ }
104
+ const text = await response.text();
105
+ if (!text) {
106
+ return null;
107
+ }
108
+ return JSON.parse(text);
109
+ }
110
+ export const apiClient = (config) => {
111
+ const apiConfig = {
112
+ ...config,
113
+ };
114
+ return {
115
+ get: (url, options) => apiWrapper(() => api(apiConfig, url, options)),
116
+ post: (url, options) => apiWrapper(() => api(apiConfig, url, { ...options, method: "POST" })),
117
+ put: (url, options) => apiWrapper(() => api(apiConfig, url, { ...options, method: "PUT" })),
118
+ delete: (url, options) => apiWrapper(() => api(apiConfig, url, { ...options, method: "DELETE" })),
119
+ patch: (url, options) => apiWrapper(() => api(apiConfig, url, { ...options, method: "PATCH" })),
120
+ };
121
+ };
@@ -0,0 +1 @@
1
+ export { apiClient } from "./client.js";
@@ -0,0 +1,148 @@
1
+ import { isAxiosError } from "axios";
2
+ /**
3
+ * Extracts error properties from Axios errors
4
+ */
5
+ async function extractAxiosErrorDetails(error) {
6
+ var _a, _b, _c;
7
+ const errorObj = {
8
+ message: error.message || "Axios Error",
9
+ status: (_a = error.response) === null || _a === void 0 ? void 0 : _a.status,
10
+ path: (_b = error.config) === null || _b === void 0 ? void 0 : _b.url,
11
+ };
12
+ if (((_c = error.response) === null || _c === void 0 ? void 0 : _c.data) && typeof error.response.data === "object") {
13
+ return await enrichErrorWithData(errorObj, Promise.resolve(error.response.data));
14
+ }
15
+ return errorObj;
16
+ }
17
+ /**
18
+ * Extracts error properties from standard Error objects
19
+ */
20
+ function extractStandardErrorDetails(error) {
21
+ const errorObj = {
22
+ message: error.message,
23
+ };
24
+ // Capture any custom properties the error might have
25
+ const extendedError = error;
26
+ if (extendedError.code)
27
+ errorObj.code = String(extendedError.code);
28
+ if (extendedError.status)
29
+ errorObj.status = Number(extendedError.status);
30
+ if (extendedError.details)
31
+ errorObj.details = extendedError.details;
32
+ return errorObj;
33
+ }
34
+ /**
35
+ * Extracts error properties from fetch Response objects
36
+ */
37
+ async function extractResponseErrorDetails(response) {
38
+ const errorObj = {
39
+ message: `HTTP Error ${response.status}: ${response.statusText}`,
40
+ status: response.status,
41
+ path: response.url,
42
+ };
43
+ try {
44
+ // Clone the response to avoid consuming the body stream
45
+ const clonedResponse = response.clone();
46
+ const errorData = await clonedResponse.json();
47
+ // Directly enrich the error object with the parsed data
48
+ if (errorData && typeof errorData === "object") {
49
+ if ("message" in errorData && errorData.message) {
50
+ errorObj.message = String(errorData.message);
51
+ }
52
+ if ("code" in errorData) {
53
+ errorObj.code = String(errorData.code);
54
+ }
55
+ if ("details" in errorData) {
56
+ errorObj.details = errorData.details;
57
+ }
58
+ else if ("issues" in errorData) {
59
+ errorObj.details = errorData.issues;
60
+ }
61
+ if ("timestamp" in errorData) {
62
+ errorObj.timestamp = String(errorData.timestamp);
63
+ }
64
+ }
65
+ return errorObj;
66
+ }
67
+ catch (_a) {
68
+ // If JSON parsing fails, return the basic error object
69
+ return errorObj;
70
+ }
71
+ }
72
+ /**
73
+ * Enriches an error object with additional data from response
74
+ */
75
+ async function enrichErrorWithData(errorObj, errorData, path) {
76
+ const data = await errorData;
77
+ if (data) {
78
+ if ("message" in data) {
79
+ errorObj.message = String(data.message);
80
+ }
81
+ if ("code" in data) {
82
+ errorObj.code = String(data.code);
83
+ }
84
+ if ("details" in data) {
85
+ errorObj.details = data.details;
86
+ }
87
+ else if ("issues" in data) {
88
+ errorObj.details = data.issues;
89
+ }
90
+ if ("timestamp" in data) {
91
+ errorObj.timestamp = String(data.timestamp);
92
+ }
93
+ if (path) {
94
+ errorObj.path = path;
95
+ }
96
+ }
97
+ return errorObj;
98
+ }
99
+ /**
100
+ * Determines the type of error and routes to appropriate handler
101
+ */
102
+ async function processError(error) {
103
+ if (error instanceof Response) {
104
+ return extractResponseErrorDetails(error);
105
+ }
106
+ if (isAxiosError(error)) {
107
+ return extractAxiosErrorDetails(error);
108
+ }
109
+ if (error instanceof Error) {
110
+ return extractStandardErrorDetails(error);
111
+ }
112
+ // Handle unknown error types
113
+ return {
114
+ message: error ? JSON.stringify(error) : "Unknown error",
115
+ };
116
+ }
117
+ /**
118
+ * Wraps API calls with standardized error handling
119
+ *
120
+ * @param apiCall - The API call function to execute
121
+ * @param defaultData - Optional default value to return when an error occurs
122
+ * @returns A standardized result object with data or formatted error message
123
+ *
124
+ * @example
125
+ * // Basic usage
126
+ * const result = await apiWrapper(() => fetchUserData(userId));
127
+ *
128
+ * @example
129
+ * // With default data value
130
+ * const result = await apiWrapper(() => fetchPosts(), []);
131
+ * // result.data will be [] instead of null on error
132
+ */
133
+ export async function apiWrapper(apiCall, defaultData) {
134
+ try {
135
+ const data = await apiCall();
136
+ return {
137
+ data,
138
+ error: null,
139
+ };
140
+ }
141
+ catch (error) {
142
+ const errorObj = await processError(error);
143
+ return {
144
+ data: (defaultData !== null && defaultData !== void 0 ? defaultData : null),
145
+ error: errorObj,
146
+ };
147
+ }
148
+ }
package/dist/esm/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./api/index.js"; // API utilities
1
2
  export * from "./array/index.js"; // Array utilities
2
3
  export * from "./date/index.js"; // Date utilities
3
4
  export * from "./files/index.js"; // Files utilities
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./api/index.js";
1
2
  export * from "./array/index.js";
2
3
  export * from "./date/index.js";
3
4
  export * from "./files/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herowcode/utils",
3
- "version": "1.1.11",
3
+ "version": "1.2.1",
4
4
  "description": "A lightweight collection of utility functions for everyday JavaScript/TypeScript development",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -40,6 +40,11 @@
40
40
  "types": "./dist/youtube/index.d.ts",
41
41
  "import": "./dist/esm/youtube/index.js",
42
42
  "require": "./dist/cjs/youtube/index.js"
43
+ },
44
+ "./api": {
45
+ "types": "./dist/api/index.d.ts",
46
+ "import": "./dist/esm/api/index.js",
47
+ "require": "./dist/cjs/api/index.js"
43
48
  }
44
49
  },
45
50
  "files": [
@@ -78,11 +83,12 @@
78
83
  },
79
84
  "license": "MIT",
80
85
  "dependencies": {
86
+ "axios": "1.12.2",
81
87
  "dayjs": "^1.11.10",
82
88
  "react": "19.1.1"
83
89
  },
84
90
  "devDependencies": {
85
- "@biomejs/biome": "2.2.3",
91
+ "@biomejs/biome": "2.1.4",
86
92
  "@testing-library/dom": "10.4.1",
87
93
  "@testing-library/jest-dom": "6.8.0",
88
94
  "@testing-library/react": "16.3.0",