@herowcode/utils 1.1.11 → 1.2.2
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 +128 -1
- package/dist/api/client.d.ts +25 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/wrapper.d.ts +27 -0
- package/dist/api/wrapper.d.ts.map +1 -0
- package/dist/cjs/api/client.d.ts +25 -0
- package/dist/cjs/api/client.d.ts.map +1 -0
- package/dist/cjs/api/client.js +128 -0
- package/dist/cjs/api/client.js.map +1 -0
- package/dist/cjs/api/index.d.ts +3 -0
- package/dist/cjs/api/index.d.ts.map +1 -0
- package/dist/cjs/api/index.js +6 -0
- package/dist/cjs/api/index.js.map +1 -0
- package/dist/cjs/api/wrapper.d.ts +27 -0
- package/dist/cjs/api/wrapper.d.ts.map +1 -0
- package/dist/cjs/api/wrapper.js +152 -0
- package/dist/cjs/api/wrapper.js.map +1 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/api/client.js +123 -0
- package/dist/esm/api/index.js +1 -0
- package/dist/esm/api/wrapper.js +148 -0
- package/dist/esm/index.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +8 -2
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;AAiED,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 @@
|
|
|
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;AAiED,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,128 @@
|
|
|
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
|
+
// Keep init headers in sync with the mutated request so fetch keeps auth headers
|
|
98
|
+
init.headers = new Headers(request.headers);
|
|
99
|
+
// Execute request with retry logic
|
|
100
|
+
const response = await fetchWithRetry(request, init);
|
|
101
|
+
// Run post-response hooks
|
|
102
|
+
await afterResponseHook(request, response, config.onSignoutUnauthorized);
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
throw response;
|
|
105
|
+
}
|
|
106
|
+
if (response.status === 204) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const text = await response.text();
|
|
110
|
+
if (!text) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return JSON.parse(text);
|
|
114
|
+
}
|
|
115
|
+
const apiClient = (config) => {
|
|
116
|
+
const apiConfig = {
|
|
117
|
+
...config,
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
get: (url, options) => (0, wrapper_1.apiWrapper)(() => api(apiConfig, url, options)),
|
|
121
|
+
post: (url, options) => (0, wrapper_1.apiWrapper)(() => api(apiConfig, url, { ...options, method: "POST" })),
|
|
122
|
+
put: (url, options) => (0, wrapper_1.apiWrapper)(() => api(apiConfig, url, { ...options, method: "PUT" })),
|
|
123
|
+
delete: (url, options) => (0, wrapper_1.apiWrapper)(() => api(apiConfig, url, { ...options, method: "DELETE" })),
|
|
124
|
+
patch: (url, options) => (0, wrapper_1.apiWrapper)(() => api(apiConfig, url, { ...options, method: "PATCH" })),
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
exports.apiClient = apiClient;
|
|
128
|
+
//# 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 @@
|
|
|
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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"}
|
package/dist/cjs/index.d.ts
CHANGED
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -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
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -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,123 @@
|
|
|
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
|
+
// Keep init headers in sync with the mutated request so fetch keeps auth headers
|
|
95
|
+
init.headers = new Headers(request.headers);
|
|
96
|
+
// Execute request with retry logic
|
|
97
|
+
const response = await fetchWithRetry(request, init);
|
|
98
|
+
// Run post-response hooks
|
|
99
|
+
await afterResponseHook(request, response, config.onSignoutUnauthorized);
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw response;
|
|
102
|
+
}
|
|
103
|
+
if (response.status === 204) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const text = await response.text();
|
|
107
|
+
if (!text) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return JSON.parse(text);
|
|
111
|
+
}
|
|
112
|
+
export const apiClient = (config) => {
|
|
113
|
+
const apiConfig = {
|
|
114
|
+
...config,
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
get: (url, options) => apiWrapper(() => api(apiConfig, url, options)),
|
|
118
|
+
post: (url, options) => apiWrapper(() => api(apiConfig, url, { ...options, method: "POST" })),
|
|
119
|
+
put: (url, options) => apiWrapper(() => api(apiConfig, url, { ...options, method: "PUT" })),
|
|
120
|
+
delete: (url, options) => apiWrapper(() => api(apiConfig, url, { ...options, method: "DELETE" })),
|
|
121
|
+
patch: (url, options) => apiWrapper(() => api(apiConfig, url, { ...options, method: "PATCH" })),
|
|
122
|
+
};
|
|
123
|
+
};
|
|
@@ -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
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "1.2.2",
|
|
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.
|
|
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",
|