fetchwire 2.0.0 → 2.1.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 +56 -54
- package/dist/index.d.mts +19 -5
- package/dist/index.d.ts +19 -5
- package/dist/index.js +12 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +12 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
A lightweight, focused API fetching library for **React and React Native** applications.
|
|
4
4
|
|
|
5
5
|
**fetchwire** wraps the native `fetch` API in a global configuration layer. It is designed to make it easy to:
|
|
6
|
-
|
|
7
6
|
- Centralize your API base URL, auth token, and common headers.
|
|
8
7
|
- Handle errors consistently.
|
|
9
8
|
|
|
10
9
|
### When to use fetchwire
|
|
11
10
|
|
|
12
11
|
- **React / React Native apps** that:
|
|
13
|
-
- Want a **simple**, centralized way
|
|
12
|
+
- Want a **simple**, centralized way for API fetching setup.
|
|
14
13
|
- Prefer plain hooks over a heavier state management or query library.
|
|
15
14
|
- Need basic tag-based invalidation without a full cache layer.
|
|
16
15
|
|
|
@@ -28,8 +27,6 @@ If you find **fetchwire** helpful and want to support its development, you can b
|
|
|
28
27
|
[](https://ko-fi.com/doanvinhphu)
|
|
29
28
|
[](https://paypal.me/doanvinhphu)
|
|
30
29
|
|
|
31
|
-
Your support helps maintain the library and keep it up to date!
|
|
32
|
-
|
|
33
30
|
## Features
|
|
34
31
|
|
|
35
32
|
- **Global API fetching configuration `initWire`**
|
|
@@ -80,6 +77,7 @@ export function setupWire() {
|
|
|
80
77
|
'x-client': 'web',
|
|
81
78
|
},
|
|
82
79
|
getToken: async () => {
|
|
80
|
+
// Called on each request — return the current access token or null.
|
|
83
81
|
// Read token from localStorage (or any storage you prefer)
|
|
84
82
|
return localStorage.getItem('access_token');
|
|
85
83
|
},
|
|
@@ -172,7 +170,6 @@ You can organize similar helpers for users, invoices, organizations, uploads, et
|
|
|
172
170
|
**Key ideas:**
|
|
173
171
|
|
|
174
172
|
- You pass a **pre-typed API helper** (e.g. `getTodosApi`) into the hook once.
|
|
175
|
-
- The hook infers the data type `T` from that helper, so you rarely need to write `<T>` in components.
|
|
176
173
|
- The hook tracks:
|
|
177
174
|
- `data: T | null`
|
|
178
175
|
- `isLoading: boolean`
|
|
@@ -247,7 +244,8 @@ const {
|
|
|
247
244
|
} = useMutationFn(mutationFn, { invalidatesTags?: string[] });
|
|
248
245
|
```
|
|
249
246
|
|
|
250
|
-
|
|
247
|
+
- If `mutationFn` has **no parameters**, call `executeMutationFn({ onSuccess, onError })`.
|
|
248
|
+
- If `mutationFn` has **one parameter** (e.g. update payload), call `executeMutationFn(variables, { onSuccess, onError })`.
|
|
251
249
|
|
|
252
250
|
Example: creating and toggling todos with `useMutationFn`:
|
|
253
251
|
|
|
@@ -277,9 +275,7 @@ export function TodoActions() {
|
|
|
277
275
|
executeMutationFn: toggleTodo,
|
|
278
276
|
} = useMutationFn(
|
|
279
277
|
(id: string) => toggleTodoApi(id),
|
|
280
|
-
{
|
|
281
|
-
invalidatesTags: ['todos'],
|
|
282
|
-
}
|
|
278
|
+
{ invalidatesTags: ['todos'] }
|
|
283
279
|
);
|
|
284
280
|
|
|
285
281
|
const {
|
|
@@ -287,9 +283,7 @@ export function TodoActions() {
|
|
|
287
283
|
executeMutationFn: deleteTodo,
|
|
288
284
|
} = useMutationFn(
|
|
289
285
|
(id: string) => deleteTodoApi(id),
|
|
290
|
-
{
|
|
291
|
-
invalidatesTags: ['todos'],
|
|
292
|
-
}
|
|
286
|
+
{ invalidatesTags: ['todos'] }
|
|
293
287
|
);
|
|
294
288
|
|
|
295
289
|
const handleCreate = (e: FormEvent) => {
|
|
@@ -301,9 +295,9 @@ export function TodoActions() {
|
|
|
301
295
|
});
|
|
302
296
|
};
|
|
303
297
|
|
|
304
|
-
//
|
|
305
|
-
// toggleTodo({ onSuccess: () => ..., onError: (error) => ... });
|
|
306
|
-
// deleteTodo({ onSuccess: () => ..., onError: (error) => ... });
|
|
298
|
+
// With variables, pass payload first then options:
|
|
299
|
+
// toggleTodo(todoId, { onSuccess: () => ..., onError: (error) => ... });
|
|
300
|
+
// deleteTodo(todoId, { onSuccess: () => ..., onError: (error) => ... });
|
|
307
301
|
|
|
308
302
|
return (
|
|
309
303
|
<form onSubmit={handleCreate}>
|
|
@@ -391,14 +385,19 @@ With `useMutationFn`, you commonly handle errors with `onError`:
|
|
|
391
385
|
```tsx
|
|
392
386
|
import { ApiError } from 'fetchwire';
|
|
393
387
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
},
|
|
388
|
+
// No variables: pass only options
|
|
389
|
+
executeMutationFn({
|
|
390
|
+
onSuccess: () => { /* success logic */ },
|
|
398
391
|
onError: (error: ApiError) => {
|
|
399
392
|
Alert.alert('Login failed', error.message || 'Unexpected error');
|
|
400
393
|
},
|
|
401
394
|
});
|
|
395
|
+
|
|
396
|
+
// With variables: pass variables first, then options
|
|
397
|
+
executeMutationFn(payload, {
|
|
398
|
+
onSuccess: (data) => { /* ... */ },
|
|
399
|
+
onError: (error: ApiError) => { /* ... */ },
|
|
400
|
+
});
|
|
402
401
|
```
|
|
403
402
|
|
|
404
403
|
You can also read `error` directly from `useFetchFn` state if you want to render error messages in your UI.
|
|
@@ -430,7 +429,7 @@ function initWire(config: WireConfig): void;
|
|
|
430
429
|
|
|
431
430
|
- **`baseUrl`**: Base API URL (e.g. `'https://api.example.com'`).
|
|
432
431
|
- **`headers`**: Global headers to apply to every request.
|
|
433
|
-
- **`getToken`**: Async function
|
|
432
|
+
- **`getToken`**: Async function called on each request; return the current access token or `null`. If a non-empty string is returned, fetchwire sends it as `Authorization: Bearer <token>`.
|
|
434
433
|
- **`interceptors`** (optional):
|
|
435
434
|
- `onUnauthorized(error)`: Called when a 401 is returned.
|
|
436
435
|
- `onForbidden(error)`: Called when a 403 is returned.
|
|
@@ -486,73 +485,76 @@ const result = await wireApi<UserResponse>('/user/me', { method: 'GET' });
|
|
|
486
485
|
|
|
487
486
|
---
|
|
488
487
|
|
|
489
|
-
### `useFetchFn<T>(options?)`
|
|
488
|
+
### `useFetchFn<T>(fetchFn, options?)`
|
|
490
489
|
|
|
491
490
|
```ts
|
|
492
491
|
type FetchOptions = {
|
|
493
492
|
tags?: string[];
|
|
494
493
|
};
|
|
495
494
|
|
|
496
|
-
function useFetchFn<T>(
|
|
495
|
+
function useFetchFn<T>(
|
|
496
|
+
fetchFn: () => Promise<HttpResponse<T>>,
|
|
497
|
+
options?: FetchOptions
|
|
498
|
+
): {
|
|
497
499
|
data: T | null;
|
|
498
500
|
isLoading: boolean;
|
|
499
501
|
isRefreshing: boolean;
|
|
500
502
|
error: ApiError | null;
|
|
501
|
-
executeFetchFn: (
|
|
502
|
-
|
|
503
|
-
) => Promise<{ data: T } | null>;
|
|
504
|
-
refreshFetchFn: () => Promise<{ data: T } | null> | null;
|
|
503
|
+
executeFetchFn: () => Promise<HttpResponse<T> | null>;
|
|
504
|
+
refreshFetchFn: () => Promise<HttpResponse<T> | null> | null;
|
|
505
505
|
};
|
|
506
506
|
```
|
|
507
507
|
|
|
508
|
+
- **`fetchFn`**: Async function (e.g. an API helper using `wireApi<T>`). Type `T` is inferred from its return type.
|
|
508
509
|
- **`options.tags`**: Optional array of tag strings to subscribe to. When a mutation invalidates these tags, `refreshFetchFn` is called automatically.
|
|
509
|
-
- **`executeFetchFn`**:
|
|
510
|
-
|
|
511
|
-
- Updates `data`, `isLoading`, `error`.
|
|
512
|
-
- Stores the last function so it can be used by `refreshFetchFn`.
|
|
513
|
-
- **`refreshFetchFn`**:
|
|
514
|
-
- Re-runs the last `executeFetchFn` call, setting `isRefreshing` during the call.
|
|
510
|
+
- **`executeFetchFn()`**: Runs `fetchFn` (no arguments). Updates `data`, `isLoading`, `error`.
|
|
511
|
+
- **`refreshFetchFn()`**: Re-runs the same `fetchFn`, setting `isRefreshing` during the call.
|
|
515
512
|
|
|
516
513
|
---
|
|
517
514
|
|
|
518
|
-
### `useMutationFn<T>(options?)`
|
|
515
|
+
### `useMutationFn<T>(mutationFn, options?)` (no variables)
|
|
516
|
+
### `useMutationFn<T, TVariables>(mutationFn, options?)` (with variables)
|
|
519
517
|
|
|
520
518
|
```ts
|
|
521
519
|
type MutationOptions = {
|
|
522
520
|
invalidatesTags?: string[];
|
|
523
521
|
};
|
|
524
522
|
|
|
525
|
-
type
|
|
526
|
-
onSuccess?: (data: T
|
|
523
|
+
type ExecuteMutationOptions<T> = {
|
|
524
|
+
onSuccess?: (data: T) => void;
|
|
527
525
|
onError?: (error: ApiError) => void;
|
|
528
526
|
};
|
|
529
527
|
|
|
530
|
-
|
|
528
|
+
// No variables: mutationFn has no parameters
|
|
529
|
+
function useMutationFn<T>(
|
|
530
|
+
mutationFn: () => Promise<HttpResponse<T>>,
|
|
531
|
+
options?: MutationOptions
|
|
532
|
+
): {
|
|
533
|
+
data: T | null;
|
|
534
|
+
isMutating: boolean;
|
|
535
|
+
executeMutationFn: (executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
|
|
536
|
+
reset: () => void;
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// With variables: mutationFn accepts one argument (e.g. update payload)
|
|
540
|
+
function useMutationFn<T, TVariables>(
|
|
541
|
+
mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,
|
|
542
|
+
options?: MutationOptions
|
|
543
|
+
): {
|
|
531
544
|
data: T | null;
|
|
532
545
|
isMutating: boolean;
|
|
533
|
-
executeMutationFn: (
|
|
534
|
-
mutationFn: () => Promise<{ data: T }>,
|
|
535
|
-
executeOptions?: ExecuteOptions<T>
|
|
536
|
-
) => Promise<{ data: T } | null>;
|
|
546
|
+
executeMutationFn: (variables: TVariables, executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
|
|
537
547
|
reset: () => void;
|
|
538
548
|
};
|
|
539
549
|
```
|
|
540
550
|
|
|
541
|
-
- **`
|
|
542
|
-
|
|
543
|
-
- All `useFetchFn` hooks that subscribed to any of these tags will be refreshed.
|
|
551
|
+
- **`mutationFn`**: Async function that returns `Promise<HttpResponse<T>>`. If it takes one parameter, `executeMutationFn` will require that variable as the first argument.
|
|
552
|
+
- **`options.invalidatesTags`**: Tags to emit after a **successful** mutation; subscribed `useFetchFn` hooks refresh.
|
|
544
553
|
- **`executeMutationFn`**:
|
|
545
|
-
-
|
|
546
|
-
-
|
|
547
|
-
-
|
|
548
|
-
|
|
549
|
-
- Emits all `invalidatesTags`.
|
|
550
|
-
- Calls `onSuccess` with `response.data` (or `null`).
|
|
551
|
-
- On error:
|
|
552
|
-
- Resets `isMutating`.
|
|
553
|
-
- Calls `onError` with an `ApiError` instance.
|
|
554
|
-
- **`reset`**:
|
|
555
|
-
- Resets `data` and `isMutating` to initial values.
|
|
554
|
+
- **No variables:** `executeMutationFn({ onSuccess, onError })`.
|
|
555
|
+
- **With variables:** `executeMutationFn(variables, { onSuccess, onError })`.
|
|
556
|
+
- Sets `isMutating` while running; on success updates `data`, emits tags, calls `onSuccess`; on error calls `onError`.
|
|
557
|
+
- **`reset()`**: Resets `data` and `isMutating` to initial values.
|
|
556
558
|
|
|
557
559
|
---
|
|
558
560
|
|
package/dist/index.d.mts
CHANGED
|
@@ -158,16 +158,30 @@ declare function useFetchFn<T>(fetchFn: () => Promise<HttpResponse<T>>, options?
|
|
|
158
158
|
};
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* @param
|
|
164
|
-
* @
|
|
161
|
+
* Mutation without variables. Use when the payload is fixed (e.g. from closure/state).
|
|
162
|
+
*
|
|
163
|
+
* @param mutationFn - Function that returns a promise (e.g. `() => createApi()`).
|
|
164
|
+
* @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.
|
|
165
|
+
* @returns executeMutationFn(options?) — call with no args or only options: `executeMutationFn()` or `executeMutationFn({ onSuccess, onError })`.
|
|
165
166
|
*/
|
|
166
|
-
declare function useMutationFn<T>(mutationFn: () => Promise<HttpResponse<T>>, options?: MutationOptions): {
|
|
167
|
+
declare function useMutationFn<T>(mutationFn: (variables: void) => Promise<HttpResponse<T>>, options?: MutationOptions): {
|
|
168
|
+
data: T | null;
|
|
169
|
+
isMutating: boolean;
|
|
167
170
|
executeMutationFn: (executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
|
|
168
171
|
reset: () => void;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Mutation with variables. Use when the payload is passed at call time (e.g. update forms, PATCH body).
|
|
175
|
+
*
|
|
176
|
+
* @param mutationFn - Function that receives variables and returns a promise (e.g. `(data) => updateApi(id, data)`).
|
|
177
|
+
* @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.
|
|
178
|
+
* @returns executeMutationFn(variables, options?) — you must pass variables first, then optional callbacks.
|
|
179
|
+
*/
|
|
180
|
+
declare function useMutationFn<T, TVariables>(mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>, options?: MutationOptions): {
|
|
169
181
|
data: T | null;
|
|
170
182
|
isMutating: boolean;
|
|
183
|
+
executeMutationFn: (variables: TVariables, executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
|
|
184
|
+
reset: () => void;
|
|
171
185
|
};
|
|
172
186
|
|
|
173
187
|
export { ApiError, type ExecuteMutationOptions, type FetchOptions, type HttpResponse, type MutationOptions, type WireConfig, type WireInterceptors, getWireConfig, initWire, updateWireConfig, useFetchFn, useMutationFn, wireApi };
|
package/dist/index.d.ts
CHANGED
|
@@ -158,16 +158,30 @@ declare function useFetchFn<T>(fetchFn: () => Promise<HttpResponse<T>>, options?
|
|
|
158
158
|
};
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* @param
|
|
164
|
-
* @
|
|
161
|
+
* Mutation without variables. Use when the payload is fixed (e.g. from closure/state).
|
|
162
|
+
*
|
|
163
|
+
* @param mutationFn - Function that returns a promise (e.g. `() => createApi()`).
|
|
164
|
+
* @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.
|
|
165
|
+
* @returns executeMutationFn(options?) — call with no args or only options: `executeMutationFn()` or `executeMutationFn({ onSuccess, onError })`.
|
|
165
166
|
*/
|
|
166
|
-
declare function useMutationFn<T>(mutationFn: () => Promise<HttpResponse<T>>, options?: MutationOptions): {
|
|
167
|
+
declare function useMutationFn<T>(mutationFn: (variables: void) => Promise<HttpResponse<T>>, options?: MutationOptions): {
|
|
168
|
+
data: T | null;
|
|
169
|
+
isMutating: boolean;
|
|
167
170
|
executeMutationFn: (executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
|
|
168
171
|
reset: () => void;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Mutation with variables. Use when the payload is passed at call time (e.g. update forms, PATCH body).
|
|
175
|
+
*
|
|
176
|
+
* @param mutationFn - Function that receives variables and returns a promise (e.g. `(data) => updateApi(id, data)`).
|
|
177
|
+
* @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.
|
|
178
|
+
* @returns executeMutationFn(variables, options?) — you must pass variables first, then optional callbacks.
|
|
179
|
+
*/
|
|
180
|
+
declare function useMutationFn<T, TVariables>(mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>, options?: MutationOptions): {
|
|
169
181
|
data: T | null;
|
|
170
182
|
isMutating: boolean;
|
|
183
|
+
executeMutationFn: (variables: TVariables, executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
|
|
184
|
+
reset: () => void;
|
|
171
185
|
};
|
|
172
186
|
|
|
173
187
|
export { ApiError, type ExecuteMutationOptions, type FetchOptions, type HttpResponse, type MutationOptions, type WireConfig, type WireInterceptors, getWireConfig, initWire, updateWireConfig, useFetchFn, useMutationFn, wireApi };
|
package/dist/index.js
CHANGED
|
@@ -150,7 +150,8 @@ function useFetchFn(fetchFn, options) {
|
|
|
150
150
|
error: null
|
|
151
151
|
});
|
|
152
152
|
const isMounted = (0, import_react.useRef)(true);
|
|
153
|
-
const
|
|
153
|
+
const fetchFnRef = (0, import_react.useRef)(fetchFn);
|
|
154
|
+
fetchFnRef.current = fetchFn;
|
|
154
155
|
(0, import_react.useEffect)(() => {
|
|
155
156
|
isMounted.current = true;
|
|
156
157
|
return () => {
|
|
@@ -159,7 +160,7 @@ function useFetchFn(fetchFn, options) {
|
|
|
159
160
|
}, []);
|
|
160
161
|
const execute = (0, import_react.useCallback)(
|
|
161
162
|
async (execOptions) => {
|
|
162
|
-
|
|
163
|
+
const fn = fetchFnRef.current;
|
|
163
164
|
setState((prev) => ({
|
|
164
165
|
...prev,
|
|
165
166
|
isLoading: !execOptions.isRefresh,
|
|
@@ -167,7 +168,7 @@ function useFetchFn(fetchFn, options) {
|
|
|
167
168
|
error: null
|
|
168
169
|
}));
|
|
169
170
|
try {
|
|
170
|
-
const response = await
|
|
171
|
+
const response = await fn();
|
|
171
172
|
if (isMounted.current) {
|
|
172
173
|
setState({
|
|
173
174
|
data: response.data || null,
|
|
@@ -224,19 +225,22 @@ function useMutationFn(mutationFn, options) {
|
|
|
224
225
|
};
|
|
225
226
|
}, []);
|
|
226
227
|
const executeMutationFn = (0, import_react2.useCallback)(
|
|
227
|
-
async (
|
|
228
|
+
async (firstArg, secondArg) => {
|
|
229
|
+
const hasTwoArgs = secondArg !== void 0;
|
|
230
|
+
const variables = hasTwoArgs ? firstArg : void 0;
|
|
231
|
+
const executeOptions = hasTwoArgs ? secondArg : firstArg;
|
|
228
232
|
setState((prev) => ({ ...prev, isMutating: true }));
|
|
229
233
|
try {
|
|
230
|
-
const response = await mutationFn();
|
|
234
|
+
const response = await mutationFn(variables);
|
|
231
235
|
if (isMounted.current) {
|
|
232
236
|
setState({
|
|
233
|
-
data: response.data
|
|
237
|
+
data: response.data ?? null,
|
|
234
238
|
isMutating: false
|
|
235
239
|
});
|
|
236
240
|
options?.invalidatesTags?.forEach((tag) => {
|
|
237
241
|
eventEmitter.emit(tag);
|
|
238
242
|
});
|
|
239
|
-
if (response.data) executeOptions?.onSuccess?.(response.data);
|
|
243
|
+
if (response.data != null) executeOptions?.onSuccess?.(response.data);
|
|
240
244
|
else executeOptions?.onSuccess?.(null);
|
|
241
245
|
}
|
|
242
246
|
return response;
|
|
@@ -247,7 +251,7 @@ function useMutationFn(mutationFn, options) {
|
|
|
247
251
|
data: null,
|
|
248
252
|
isMutating: false
|
|
249
253
|
});
|
|
250
|
-
|
|
254
|
+
executeOptions?.onError?.(apiError);
|
|
251
255
|
}
|
|
252
256
|
return null;
|
|
253
257
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/util/api-error.ts","../src/core/config.ts","../src/core/wire.ts","../src/hook/use-fetch-fn.ts","../src/core/event-emitter.ts","../src/hook/use-mutation-fn.ts"],"sourcesContent":["export * from './interface';\nexport * from './util/api-error';\nexport * from './core/config';\nexport * from './core/wire';\nexport * from './hook/use-fetch-fn';\nexport * from './hook/use-mutation-fn';\n","export class ApiError extends Error {\n public errorCode?: string;\n public statusCode?: number;\n\n constructor(message: string, errorCode?: string, statusCode?: number) {\n super(message);\n this.name = 'ApiError';\n this.errorCode = errorCode;\n this.statusCode = statusCode;\n }\n}","import { WireConfig } from '../interface';\n\nlet globalWireConfig: WireConfig | null = null;\n\n/**\n * Initializes the library with mandatory configurations.\n * Must be executed at the application entry point before any API calls.\n * @param config - The required configuration object including baseUrl and getToken.\n */\nexport const initWire = (config: WireConfig): void => {\n globalWireConfig = {\n ...config,\n headers: config.headers || {},\n };\n};\n\n/**\n * Updates the existing configuration.\n * Merges new headers with existing ones and overrides other provided fields.\n * @param config - A partial configuration object to update.\n */\nexport const updateWireConfig = (config: Partial<WireConfig>): void => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n\n globalWireConfig = {\n ...globalWireConfig,\n ...config,\n headers: {\n ...globalWireConfig.headers,\n ...config.headers,\n },\n };\n};\n\n/**\n * Retrieves the current global configuration state.\n * @throws Error if the configuration state is null.\n * @returns The validated WireConfig object.\n */\nexport const getWireConfig = (): WireConfig => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n return globalWireConfig;\n};","import { HttpResponse } from '../interface';\nimport { ApiError } from '../util/api-error';\nimport { getWireConfig } from './config';\n\n/**\n * Sends an API request and returns the response.\n * @param endpoint - The API endpoint to call. Example: '/api/v1/users'.\n * @param options - The request options is a RequestInit object.\n */\nexport async function wireApi<T>(\n endpoint: string,\n options: RequestInit = {}\n): Promise<HttpResponse<T>> {\n const config = getWireConfig();\n const url = `${config.baseUrl}${endpoint}`;\n const accessToken = await config.getToken();\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),\n ...config.headers,\n ...options.headers,\n };\n\n try {\n const response = await fetch(url, { ...options, headers });\n\n if (!response.ok) {\n let errorData;\n try {\n errorData = await response.json();\n } catch {\n errorData = { message: 'Unknown server error', error: 'UNKNOWN' };\n }\n\n const apiError = new ApiError(\n errorData.message,\n errorData.error,\n response.status\n );\n\n // Resolve effective status-code mappings with default values\n const unauthorizedStatusCodes =\n config.unauthorizedStatusCodes && config.unauthorizedStatusCodes.length > 0\n ? config.unauthorizedStatusCodes\n : [401];\n\n const forbiddenStatusCodes =\n config.forbiddenStatusCodes && config.forbiddenStatusCodes.length > 0\n ? config.forbiddenStatusCodes\n : [403];\n\n // Trigger interceptors based on configured status codes\n if (\n config.interceptors?.onUnauthorized &&\n unauthorizedStatusCodes.includes(response.status)\n ) {\n config.interceptors.onUnauthorized(apiError);\n } else if (\n config.interceptors?.onForbidden &&\n forbiddenStatusCodes.includes(response.status)\n ) {\n config.interceptors.onForbidden(apiError);\n } else if (config.interceptors?.onError) {\n config.interceptors.onError(apiError);\n }\n\n throw apiError;\n }\n\n return await response.json();\n } catch (error) {\n if (error instanceof ApiError) throw error;\n throw new ApiError(\n error instanceof Error ? error.message : 'Network error',\n 'NETWORK_ERROR',\n 520\n );\n }\n}\n","import { useState, useRef, useEffect, useCallback } from 'react';\nimport { ApiError } from '../util/api-error';\nimport { HttpResponse, FetchOptions } from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface FetchState<T> {\n data: T | null;\n isLoading: boolean;\n isRefreshing: boolean;\n error: ApiError | null;\n}\n\n/**\n * A hook for executing a fetch function and managing the state of the fetch.\n * @param options - Has tags property that will trigger refetching of the useFetchFn with the given tags.\n * @returns The state of the fetch and the fetch function.\n */\nexport function useFetchFn<T>(\n fetchFn: () => Promise<HttpResponse<T>>,\n options?: FetchOptions\n) {\n const [state, setState] = useState<FetchState<T>>({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n\n const isMounted = useRef<boolean>(true);\n const lastFetchFn = useRef<(() => Promise<HttpResponse<T>>) | null>(null);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const execute = useCallback(\n async (execOptions: {\n isRefresh: boolean;\n }): Promise<HttpResponse<T> | null> => {\n lastFetchFn.current = fetchFn;\n\n setState((prev) => ({\n ...prev,\n isLoading: !execOptions.isRefresh,\n isRefreshing: !!execOptions.isRefresh,\n error: null,\n }));\n\n try {\n const response = await fetchFn();\n\n if (isMounted.current) {\n setState({\n data: response.data || null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: apiError,\n });\n }\n return null;\n }\n },\n []\n );\n\n const executeFetchFn = useCallback(\n () => execute({ isRefresh: false }),\n [execute]\n );\n const refreshFetchFn = useCallback(() => execute({ isRefresh: true }), [execute]);\n\n useEffect(() => {\n if (!options?.tags || options.tags.length === 0) return;\n\n const subscriptions = options.tags.map((tag) =>\n eventEmitter.addListener(tag, () => {\n refreshFetchFn();\n })\n );\n return () => subscriptions.forEach((sub) => sub.remove());\n }, [options?.tags, refreshFetchFn]);\n\n return { ...state, executeFetchFn, refreshFetchFn };\n}\n","type Listener = () => void;\n\nclass EventEmitter {\n private events: Record<string, Listener[]> = {};\n\n emit(event: string) {\n if (!this.events[event]) return;\n this.events[event].forEach((listener) => listener());\n }\n\n addListener(event: string, listener: Listener) {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n this.events[event].push(listener);\n \n return {\n remove: () => {\n this.events[event] = this.events[event].filter((l) => l !== listener);\n },\n };\n }\n}\n\nexport const eventEmitter = new EventEmitter();","import { useState, useCallback, useRef, useEffect } from 'react';\nimport { ApiError } from '../util/api-error';\nimport { HttpResponse, MutationOptions, ExecuteMutationOptions } from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface MutationState<T> {\n data: T | null;\n isMutating: boolean;\n}\n\n/**\n * A hook for executing a mutation function and managing the state of the mutation.\n * @param mutationFn - The mutation function to execute.\n * @param options - Has invalidatesTags property that will trigger refetching of the useFetchFn with the given tags.\n * @returns The state of the mutation and the mutation function.\n */\nexport function useMutationFn<T>(\n mutationFn: () => Promise<HttpResponse<T>>,\n options?: MutationOptions\n) {\n const [state, setState] = useState<MutationState<T>>({\n data: null,\n isMutating: false,\n });\n const isMounted = useRef<boolean>(true);\n \n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const executeMutationFn = useCallback(\n async (\n executeOptions?: ExecuteMutationOptions<T>\n ): Promise<HttpResponse<T> | null> => {\n setState((prev) => ({ ...prev, isMutating: true }));\n\n try {\n const response = await mutationFn();\n \n if (isMounted.current) {\n setState({\n data: response.data || null,\n isMutating: false,\n });\n \n options?.invalidatesTags?.forEach((tag) => {\n eventEmitter.emit(tag);\n });\n \n if (response.data) executeOptions?.onSuccess?.(response.data);\n else executeOptions?.onSuccess?.(null as unknown as T);\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isMutating: false,\n });\n if (apiError) executeOptions?.onError?.(apiError);\n }\n return null;\n }\n },\n [mutationFn, options?.invalidatesTags]\n );\n\n const reset = useCallback(() => {\n setState({ data: null, isMutating: false });\n }, []);\n\n return { ...state, executeMutationFn, reset };\n}"],"mappings":"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","names":["import_react"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/util/api-error.ts","../src/core/config.ts","../src/core/wire.ts","../src/hook/use-fetch-fn.ts","../src/core/event-emitter.ts","../src/hook/use-mutation-fn.ts"],"sourcesContent":["export * from './interface';\nexport * from './util/api-error';\nexport * from './core/config';\nexport * from './core/wire';\nexport * from './hook/use-fetch-fn';\nexport * from './hook/use-mutation-fn';\n","export class ApiError extends Error {\n public errorCode?: string;\n public statusCode?: number;\n\n constructor(message: string, errorCode?: string, statusCode?: number) {\n super(message);\n this.name = 'ApiError';\n this.errorCode = errorCode;\n this.statusCode = statusCode;\n }\n}","import { WireConfig } from '../interface';\n\nlet globalWireConfig: WireConfig | null = null;\n\n/**\n * Initializes the library with mandatory configurations.\n * Must be executed at the application entry point before any API calls.\n * @param config - The required configuration object including baseUrl and getToken.\n */\nexport const initWire = (config: WireConfig): void => {\n globalWireConfig = {\n ...config,\n headers: config.headers || {},\n };\n};\n\n/**\n * Updates the existing configuration.\n * Merges new headers with existing ones and overrides other provided fields.\n * @param config - A partial configuration object to update.\n */\nexport const updateWireConfig = (config: Partial<WireConfig>): void => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n\n globalWireConfig = {\n ...globalWireConfig,\n ...config,\n headers: {\n ...globalWireConfig.headers,\n ...config.headers,\n },\n };\n};\n\n/**\n * Retrieves the current global configuration state.\n * @throws Error if the configuration state is null.\n * @returns The validated WireConfig object.\n */\nexport const getWireConfig = (): WireConfig => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n return globalWireConfig;\n};","import { HttpResponse } from '../interface';\nimport { ApiError } from '../util/api-error';\nimport { getWireConfig } from './config';\n\n/**\n * Sends an API request and returns the response.\n * @param endpoint - The API endpoint to call. Example: '/api/v1/users'.\n * @param options - The request options is a RequestInit object.\n */\nexport async function wireApi<T>(\n endpoint: string,\n options: RequestInit = {}\n): Promise<HttpResponse<T>> {\n const config = getWireConfig();\n const url = `${config.baseUrl}${endpoint}`;\n const accessToken = await config.getToken();\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),\n ...config.headers,\n ...options.headers,\n };\n\n try {\n const response = await fetch(url, { ...options, headers });\n\n if (!response.ok) {\n let errorData;\n try {\n errorData = await response.json();\n } catch {\n errorData = { message: 'Unknown server error', error: 'UNKNOWN' };\n }\n\n const apiError = new ApiError(\n errorData.message,\n errorData.error,\n response.status\n );\n\n // Resolve effective status-code mappings with default values\n const unauthorizedStatusCodes =\n config.unauthorizedStatusCodes && config.unauthorizedStatusCodes.length > 0\n ? config.unauthorizedStatusCodes\n : [401];\n\n const forbiddenStatusCodes =\n config.forbiddenStatusCodes && config.forbiddenStatusCodes.length > 0\n ? config.forbiddenStatusCodes\n : [403];\n\n // Trigger interceptors based on configured status codes\n if (\n config.interceptors?.onUnauthorized &&\n unauthorizedStatusCodes.includes(response.status)\n ) {\n config.interceptors.onUnauthorized(apiError);\n } else if (\n config.interceptors?.onForbidden &&\n forbiddenStatusCodes.includes(response.status)\n ) {\n config.interceptors.onForbidden(apiError);\n } else if (config.interceptors?.onError) {\n config.interceptors.onError(apiError);\n }\n\n throw apiError;\n }\n\n return await response.json();\n } catch (error) {\n if (error instanceof ApiError) throw error;\n throw new ApiError(\n error instanceof Error ? error.message : 'Network error',\n 'NETWORK_ERROR',\n 520\n );\n }\n}\n","import { useState, useRef, useEffect, useCallback } from 'react';\nimport { ApiError } from '../util/api-error';\nimport { HttpResponse, FetchOptions } from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface FetchState<T> {\n data: T | null;\n isLoading: boolean;\n isRefreshing: boolean;\n error: ApiError | null;\n}\n\n/**\n * A hook for executing a fetch function and managing the state of the fetch.\n * @param options - Has tags property that will trigger refetching of the useFetchFn with the given tags.\n * @returns The state of the fetch and the fetch function.\n */\nexport function useFetchFn<T>(\n fetchFn: () => Promise<HttpResponse<T>>,\n options?: FetchOptions\n) {\n const [state, setState] = useState<FetchState<T>>({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n\n const isMounted = useRef<boolean>(true);\n const fetchFnRef = useRef(fetchFn);\n fetchFnRef.current = fetchFn;\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const execute = useCallback(\n async (execOptions: {\n isRefresh: boolean;\n }): Promise<HttpResponse<T> | null> => {\n const fn = fetchFnRef.current;\n\n setState((prev) => ({\n ...prev,\n isLoading: !execOptions.isRefresh,\n isRefreshing: !!execOptions.isRefresh,\n error: null,\n }));\n\n try {\n const response = await fn();\n\n if (isMounted.current) {\n setState({\n data: response.data || null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: apiError,\n });\n }\n return null;\n }\n },\n []\n );\n\n const executeFetchFn = useCallback(\n () => execute({ isRefresh: false }),\n [execute]\n );\n const refreshFetchFn = useCallback(() => execute({ isRefresh: true }), [execute]);\n\n useEffect(() => {\n if (!options?.tags || options.tags.length === 0) return;\n\n const subscriptions = options.tags.map((tag) =>\n eventEmitter.addListener(tag, () => {\n refreshFetchFn();\n })\n );\n return () => subscriptions.forEach((sub) => sub.remove());\n }, [options?.tags, refreshFetchFn]);\n\n return { ...state, executeFetchFn, refreshFetchFn };\n}\n","type Listener = () => void;\n\nclass EventEmitter {\n private events: Record<string, Listener[]> = {};\n\n emit(event: string) {\n if (!this.events[event]) return;\n this.events[event].forEach((listener) => listener());\n }\n\n addListener(event: string, listener: Listener) {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n this.events[event].push(listener);\n \n return {\n remove: () => {\n this.events[event] = this.events[event].filter((l) => l !== listener);\n },\n };\n }\n}\n\nexport const eventEmitter = new EventEmitter();","import { useState, useCallback, useRef, useEffect } from 'react';\nimport { ApiError } from '../util/api-error';\nimport {\n HttpResponse,\n MutationOptions,\n ExecuteMutationOptions,\n} from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface MutationState<T> {\n data: T | null;\n isMutating: boolean;\n}\n\n/**\n * Mutation without variables. Use when the payload is fixed (e.g. from closure/state).\n *\n * @param mutationFn - Function that returns a promise (e.g. `() => createApi()`).\n * @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.\n * @returns executeMutationFn(options?) — call with no args or only options: `executeMutationFn()` or `executeMutationFn({ onSuccess, onError })`.\n */\nexport function useMutationFn<T>(\n mutationFn: (variables: void) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n): {\n data: T | null;\n isMutating: boolean;\n executeMutationFn: (\n executeOptions?: ExecuteMutationOptions<T>\n ) => Promise<HttpResponse<T> | null>;\n reset: () => void;\n};\n\n/**\n * Mutation with variables. Use when the payload is passed at call time (e.g. update forms, PATCH body).\n *\n * @param mutationFn - Function that receives variables and returns a promise (e.g. `(data) => updateApi(id, data)`).\n * @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.\n * @returns executeMutationFn(variables, options?) — you must pass variables first, then optional callbacks.\n */\nexport function useMutationFn<T, TVariables>(\n mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n): {\n data: T | null;\n isMutating: boolean;\n executeMutationFn: (\n variables: TVariables,\n executeOptions?: ExecuteMutationOptions<T>\n ) => Promise<HttpResponse<T> | null>;\n reset: () => void;\n};\n\nexport function useMutationFn<T, TVariables = void>(\n mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n) {\n const [state, setState] = useState<MutationState<T>>({\n data: null,\n isMutating: false,\n });\n const isMounted = useRef<boolean>(true);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const executeMutationFn = useCallback(\n async (\n firstArg?: TVariables | ExecuteMutationOptions<T>,\n secondArg?: ExecuteMutationOptions<T>\n ): Promise<HttpResponse<T> | null> => {\n const hasTwoArgs = secondArg !== undefined;\n const variables = (hasTwoArgs ? firstArg : undefined) as TVariables;\n const executeOptions = hasTwoArgs ? secondArg : firstArg as ExecuteMutationOptions<T>;\n\n setState((prev) => ({ ...prev, isMutating: true }));\n\n try {\n const response = await mutationFn(variables);\n\n if (isMounted.current) {\n setState({\n data: response.data ?? null,\n isMutating: false,\n });\n\n options?.invalidatesTags?.forEach((tag) => {\n eventEmitter.emit(tag);\n });\n\n if (response.data != null) executeOptions?.onSuccess?.(response.data);\n else executeOptions?.onSuccess?.(null as unknown as T);\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isMutating: false,\n });\n executeOptions?.onError?.(apiError);\n }\n return null;\n }\n },\n [mutationFn, options?.invalidatesTags]\n );\n\n const reset = useCallback(() => {\n setState({ data: null, isMutating: false });\n }, []);\n\n return { ...state, executeMutationFn, reset };\n}\n"],"mappings":"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","names":["import_react"]}
|
package/dist/index.mjs
CHANGED
|
@@ -118,7 +118,8 @@ function useFetchFn(fetchFn, options) {
|
|
|
118
118
|
error: null
|
|
119
119
|
});
|
|
120
120
|
const isMounted = useRef(true);
|
|
121
|
-
const
|
|
121
|
+
const fetchFnRef = useRef(fetchFn);
|
|
122
|
+
fetchFnRef.current = fetchFn;
|
|
122
123
|
useEffect(() => {
|
|
123
124
|
isMounted.current = true;
|
|
124
125
|
return () => {
|
|
@@ -127,7 +128,7 @@ function useFetchFn(fetchFn, options) {
|
|
|
127
128
|
}, []);
|
|
128
129
|
const execute = useCallback(
|
|
129
130
|
async (execOptions) => {
|
|
130
|
-
|
|
131
|
+
const fn = fetchFnRef.current;
|
|
131
132
|
setState((prev) => ({
|
|
132
133
|
...prev,
|
|
133
134
|
isLoading: !execOptions.isRefresh,
|
|
@@ -135,7 +136,7 @@ function useFetchFn(fetchFn, options) {
|
|
|
135
136
|
error: null
|
|
136
137
|
}));
|
|
137
138
|
try {
|
|
138
|
-
const response = await
|
|
139
|
+
const response = await fn();
|
|
139
140
|
if (isMounted.current) {
|
|
140
141
|
setState({
|
|
141
142
|
data: response.data || null,
|
|
@@ -192,19 +193,22 @@ function useMutationFn(mutationFn, options) {
|
|
|
192
193
|
};
|
|
193
194
|
}, []);
|
|
194
195
|
const executeMutationFn = useCallback2(
|
|
195
|
-
async (
|
|
196
|
+
async (firstArg, secondArg) => {
|
|
197
|
+
const hasTwoArgs = secondArg !== void 0;
|
|
198
|
+
const variables = hasTwoArgs ? firstArg : void 0;
|
|
199
|
+
const executeOptions = hasTwoArgs ? secondArg : firstArg;
|
|
196
200
|
setState((prev) => ({ ...prev, isMutating: true }));
|
|
197
201
|
try {
|
|
198
|
-
const response = await mutationFn();
|
|
202
|
+
const response = await mutationFn(variables);
|
|
199
203
|
if (isMounted.current) {
|
|
200
204
|
setState({
|
|
201
|
-
data: response.data
|
|
205
|
+
data: response.data ?? null,
|
|
202
206
|
isMutating: false
|
|
203
207
|
});
|
|
204
208
|
options?.invalidatesTags?.forEach((tag) => {
|
|
205
209
|
eventEmitter.emit(tag);
|
|
206
210
|
});
|
|
207
|
-
if (response.data) executeOptions?.onSuccess?.(response.data);
|
|
211
|
+
if (response.data != null) executeOptions?.onSuccess?.(response.data);
|
|
208
212
|
else executeOptions?.onSuccess?.(null);
|
|
209
213
|
}
|
|
210
214
|
return response;
|
|
@@ -215,7 +219,7 @@ function useMutationFn(mutationFn, options) {
|
|
|
215
219
|
data: null,
|
|
216
220
|
isMutating: false
|
|
217
221
|
});
|
|
218
|
-
|
|
222
|
+
executeOptions?.onError?.(apiError);
|
|
219
223
|
}
|
|
220
224
|
return null;
|
|
221
225
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/util/api-error.ts","../src/core/config.ts","../src/core/wire.ts","../src/hook/use-fetch-fn.ts","../src/core/event-emitter.ts","../src/hook/use-mutation-fn.ts"],"sourcesContent":["export class ApiError extends Error {\n public errorCode?: string;\n public statusCode?: number;\n\n constructor(message: string, errorCode?: string, statusCode?: number) {\n super(message);\n this.name = 'ApiError';\n this.errorCode = errorCode;\n this.statusCode = statusCode;\n }\n}","import { WireConfig } from '../interface';\n\nlet globalWireConfig: WireConfig | null = null;\n\n/**\n * Initializes the library with mandatory configurations.\n * Must be executed at the application entry point before any API calls.\n * @param config - The required configuration object including baseUrl and getToken.\n */\nexport const initWire = (config: WireConfig): void => {\n globalWireConfig = {\n ...config,\n headers: config.headers || {},\n };\n};\n\n/**\n * Updates the existing configuration.\n * Merges new headers with existing ones and overrides other provided fields.\n * @param config - A partial configuration object to update.\n */\nexport const updateWireConfig = (config: Partial<WireConfig>): void => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n\n globalWireConfig = {\n ...globalWireConfig,\n ...config,\n headers: {\n ...globalWireConfig.headers,\n ...config.headers,\n },\n };\n};\n\n/**\n * Retrieves the current global configuration state.\n * @throws Error if the configuration state is null.\n * @returns The validated WireConfig object.\n */\nexport const getWireConfig = (): WireConfig => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n return globalWireConfig;\n};","import { HttpResponse } from '../interface';\nimport { ApiError } from '../util/api-error';\nimport { getWireConfig } from './config';\n\n/**\n * Sends an API request and returns the response.\n * @param endpoint - The API endpoint to call. Example: '/api/v1/users'.\n * @param options - The request options is a RequestInit object.\n */\nexport async function wireApi<T>(\n endpoint: string,\n options: RequestInit = {}\n): Promise<HttpResponse<T>> {\n const config = getWireConfig();\n const url = `${config.baseUrl}${endpoint}`;\n const accessToken = await config.getToken();\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),\n ...config.headers,\n ...options.headers,\n };\n\n try {\n const response = await fetch(url, { ...options, headers });\n\n if (!response.ok) {\n let errorData;\n try {\n errorData = await response.json();\n } catch {\n errorData = { message: 'Unknown server error', error: 'UNKNOWN' };\n }\n\n const apiError = new ApiError(\n errorData.message,\n errorData.error,\n response.status\n );\n\n // Resolve effective status-code mappings with default values\n const unauthorizedStatusCodes =\n config.unauthorizedStatusCodes && config.unauthorizedStatusCodes.length > 0\n ? config.unauthorizedStatusCodes\n : [401];\n\n const forbiddenStatusCodes =\n config.forbiddenStatusCodes && config.forbiddenStatusCodes.length > 0\n ? config.forbiddenStatusCodes\n : [403];\n\n // Trigger interceptors based on configured status codes\n if (\n config.interceptors?.onUnauthorized &&\n unauthorizedStatusCodes.includes(response.status)\n ) {\n config.interceptors.onUnauthorized(apiError);\n } else if (\n config.interceptors?.onForbidden &&\n forbiddenStatusCodes.includes(response.status)\n ) {\n config.interceptors.onForbidden(apiError);\n } else if (config.interceptors?.onError) {\n config.interceptors.onError(apiError);\n }\n\n throw apiError;\n }\n\n return await response.json();\n } catch (error) {\n if (error instanceof ApiError) throw error;\n throw new ApiError(\n error instanceof Error ? error.message : 'Network error',\n 'NETWORK_ERROR',\n 520\n );\n }\n}\n","import { useState, useRef, useEffect, useCallback } from 'react';\nimport { ApiError } from '../util/api-error';\nimport { HttpResponse, FetchOptions } from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface FetchState<T> {\n data: T | null;\n isLoading: boolean;\n isRefreshing: boolean;\n error: ApiError | null;\n}\n\n/**\n * A hook for executing a fetch function and managing the state of the fetch.\n * @param options - Has tags property that will trigger refetching of the useFetchFn with the given tags.\n * @returns The state of the fetch and the fetch function.\n */\nexport function useFetchFn<T>(\n fetchFn: () => Promise<HttpResponse<T>>,\n options?: FetchOptions\n) {\n const [state, setState] = useState<FetchState<T>>({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n\n const isMounted = useRef<boolean>(true);\n const lastFetchFn = useRef<(() => Promise<HttpResponse<T>>) | null>(null);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const execute = useCallback(\n async (execOptions: {\n isRefresh: boolean;\n }): Promise<HttpResponse<T> | null> => {\n lastFetchFn.current = fetchFn;\n\n setState((prev) => ({\n ...prev,\n isLoading: !execOptions.isRefresh,\n isRefreshing: !!execOptions.isRefresh,\n error: null,\n }));\n\n try {\n const response = await fetchFn();\n\n if (isMounted.current) {\n setState({\n data: response.data || null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: apiError,\n });\n }\n return null;\n }\n },\n []\n );\n\n const executeFetchFn = useCallback(\n () => execute({ isRefresh: false }),\n [execute]\n );\n const refreshFetchFn = useCallback(() => execute({ isRefresh: true }), [execute]);\n\n useEffect(() => {\n if (!options?.tags || options.tags.length === 0) return;\n\n const subscriptions = options.tags.map((tag) =>\n eventEmitter.addListener(tag, () => {\n refreshFetchFn();\n })\n );\n return () => subscriptions.forEach((sub) => sub.remove());\n }, [options?.tags, refreshFetchFn]);\n\n return { ...state, executeFetchFn, refreshFetchFn };\n}\n","type Listener = () => void;\n\nclass EventEmitter {\n private events: Record<string, Listener[]> = {};\n\n emit(event: string) {\n if (!this.events[event]) return;\n this.events[event].forEach((listener) => listener());\n }\n\n addListener(event: string, listener: Listener) {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n this.events[event].push(listener);\n \n return {\n remove: () => {\n this.events[event] = this.events[event].filter((l) => l !== listener);\n },\n };\n }\n}\n\nexport const eventEmitter = new EventEmitter();","import { useState, useCallback, useRef, useEffect } from 'react';\nimport { ApiError } from '../util/api-error';\nimport { HttpResponse, MutationOptions, ExecuteMutationOptions } from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface MutationState<T> {\n data: T | null;\n isMutating: boolean;\n}\n\n/**\n * A hook for executing a mutation function and managing the state of the mutation.\n * @param mutationFn - The mutation function to execute.\n * @param options - Has invalidatesTags property that will trigger refetching of the useFetchFn with the given tags.\n * @returns The state of the mutation and the mutation function.\n */\nexport function useMutationFn<T>(\n mutationFn: () => Promise<HttpResponse<T>>,\n options?: MutationOptions\n) {\n const [state, setState] = useState<MutationState<T>>({\n data: null,\n isMutating: false,\n });\n const isMounted = useRef<boolean>(true);\n \n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const executeMutationFn = useCallback(\n async (\n executeOptions?: ExecuteMutationOptions<T>\n ): Promise<HttpResponse<T> | null> => {\n setState((prev) => ({ ...prev, isMutating: true }));\n\n try {\n const response = await mutationFn();\n \n if (isMounted.current) {\n setState({\n data: response.data || null,\n isMutating: false,\n });\n \n options?.invalidatesTags?.forEach((tag) => {\n eventEmitter.emit(tag);\n });\n \n if (response.data) executeOptions?.onSuccess?.(response.data);\n else executeOptions?.onSuccess?.(null as unknown as T);\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isMutating: false,\n });\n if (apiError) executeOptions?.onError?.(apiError);\n }\n return null;\n }\n },\n [mutationFn, options?.invalidatesTags]\n );\n\n const reset = useCallback(() => {\n setState({ data: null, isMutating: false });\n }, []);\n\n return { ...state, executeMutationFn, reset };\n}"],"mappings":"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","names":["useState","useCallback","useRef","useEffect","useState","useRef","useEffect","useCallback"]}
|
|
1
|
+
{"version":3,"sources":["../src/util/api-error.ts","../src/core/config.ts","../src/core/wire.ts","../src/hook/use-fetch-fn.ts","../src/core/event-emitter.ts","../src/hook/use-mutation-fn.ts"],"sourcesContent":["export class ApiError extends Error {\n public errorCode?: string;\n public statusCode?: number;\n\n constructor(message: string, errorCode?: string, statusCode?: number) {\n super(message);\n this.name = 'ApiError';\n this.errorCode = errorCode;\n this.statusCode = statusCode;\n }\n}","import { WireConfig } from '../interface';\n\nlet globalWireConfig: WireConfig | null = null;\n\n/**\n * Initializes the library with mandatory configurations.\n * Must be executed at the application entry point before any API calls.\n * @param config - The required configuration object including baseUrl and getToken.\n */\nexport const initWire = (config: WireConfig): void => {\n globalWireConfig = {\n ...config,\n headers: config.headers || {},\n };\n};\n\n/**\n * Updates the existing configuration.\n * Merges new headers with existing ones and overrides other provided fields.\n * @param config - A partial configuration object to update.\n */\nexport const updateWireConfig = (config: Partial<WireConfig>): void => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n\n globalWireConfig = {\n ...globalWireConfig,\n ...config,\n headers: {\n ...globalWireConfig.headers,\n ...config.headers,\n },\n };\n};\n\n/**\n * Retrieves the current global configuration state.\n * @throws Error if the configuration state is null.\n * @returns The validated WireConfig object.\n */\nexport const getWireConfig = (): WireConfig => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n return globalWireConfig;\n};","import { HttpResponse } from '../interface';\nimport { ApiError } from '../util/api-error';\nimport { getWireConfig } from './config';\n\n/**\n * Sends an API request and returns the response.\n * @param endpoint - The API endpoint to call. Example: '/api/v1/users'.\n * @param options - The request options is a RequestInit object.\n */\nexport async function wireApi<T>(\n endpoint: string,\n options: RequestInit = {}\n): Promise<HttpResponse<T>> {\n const config = getWireConfig();\n const url = `${config.baseUrl}${endpoint}`;\n const accessToken = await config.getToken();\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),\n ...config.headers,\n ...options.headers,\n };\n\n try {\n const response = await fetch(url, { ...options, headers });\n\n if (!response.ok) {\n let errorData;\n try {\n errorData = await response.json();\n } catch {\n errorData = { message: 'Unknown server error', error: 'UNKNOWN' };\n }\n\n const apiError = new ApiError(\n errorData.message,\n errorData.error,\n response.status\n );\n\n // Resolve effective status-code mappings with default values\n const unauthorizedStatusCodes =\n config.unauthorizedStatusCodes && config.unauthorizedStatusCodes.length > 0\n ? config.unauthorizedStatusCodes\n : [401];\n\n const forbiddenStatusCodes =\n config.forbiddenStatusCodes && config.forbiddenStatusCodes.length > 0\n ? config.forbiddenStatusCodes\n : [403];\n\n // Trigger interceptors based on configured status codes\n if (\n config.interceptors?.onUnauthorized &&\n unauthorizedStatusCodes.includes(response.status)\n ) {\n config.interceptors.onUnauthorized(apiError);\n } else if (\n config.interceptors?.onForbidden &&\n forbiddenStatusCodes.includes(response.status)\n ) {\n config.interceptors.onForbidden(apiError);\n } else if (config.interceptors?.onError) {\n config.interceptors.onError(apiError);\n }\n\n throw apiError;\n }\n\n return await response.json();\n } catch (error) {\n if (error instanceof ApiError) throw error;\n throw new ApiError(\n error instanceof Error ? error.message : 'Network error',\n 'NETWORK_ERROR',\n 520\n );\n }\n}\n","import { useState, useRef, useEffect, useCallback } from 'react';\nimport { ApiError } from '../util/api-error';\nimport { HttpResponse, FetchOptions } from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface FetchState<T> {\n data: T | null;\n isLoading: boolean;\n isRefreshing: boolean;\n error: ApiError | null;\n}\n\n/**\n * A hook for executing a fetch function and managing the state of the fetch.\n * @param options - Has tags property that will trigger refetching of the useFetchFn with the given tags.\n * @returns The state of the fetch and the fetch function.\n */\nexport function useFetchFn<T>(\n fetchFn: () => Promise<HttpResponse<T>>,\n options?: FetchOptions\n) {\n const [state, setState] = useState<FetchState<T>>({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n\n const isMounted = useRef<boolean>(true);\n const fetchFnRef = useRef(fetchFn);\n fetchFnRef.current = fetchFn;\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const execute = useCallback(\n async (execOptions: {\n isRefresh: boolean;\n }): Promise<HttpResponse<T> | null> => {\n const fn = fetchFnRef.current;\n\n setState((prev) => ({\n ...prev,\n isLoading: !execOptions.isRefresh,\n isRefreshing: !!execOptions.isRefresh,\n error: null,\n }));\n\n try {\n const response = await fn();\n\n if (isMounted.current) {\n setState({\n data: response.data || null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: apiError,\n });\n }\n return null;\n }\n },\n []\n );\n\n const executeFetchFn = useCallback(\n () => execute({ isRefresh: false }),\n [execute]\n );\n const refreshFetchFn = useCallback(() => execute({ isRefresh: true }), [execute]);\n\n useEffect(() => {\n if (!options?.tags || options.tags.length === 0) return;\n\n const subscriptions = options.tags.map((tag) =>\n eventEmitter.addListener(tag, () => {\n refreshFetchFn();\n })\n );\n return () => subscriptions.forEach((sub) => sub.remove());\n }, [options?.tags, refreshFetchFn]);\n\n return { ...state, executeFetchFn, refreshFetchFn };\n}\n","type Listener = () => void;\n\nclass EventEmitter {\n private events: Record<string, Listener[]> = {};\n\n emit(event: string) {\n if (!this.events[event]) return;\n this.events[event].forEach((listener) => listener());\n }\n\n addListener(event: string, listener: Listener) {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n this.events[event].push(listener);\n \n return {\n remove: () => {\n this.events[event] = this.events[event].filter((l) => l !== listener);\n },\n };\n }\n}\n\nexport const eventEmitter = new EventEmitter();","import { useState, useCallback, useRef, useEffect } from 'react';\nimport { ApiError } from '../util/api-error';\nimport {\n HttpResponse,\n MutationOptions,\n ExecuteMutationOptions,\n} from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface MutationState<T> {\n data: T | null;\n isMutating: boolean;\n}\n\n/**\n * Mutation without variables. Use when the payload is fixed (e.g. from closure/state).\n *\n * @param mutationFn - Function that returns a promise (e.g. `() => createApi()`).\n * @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.\n * @returns executeMutationFn(options?) — call with no args or only options: `executeMutationFn()` or `executeMutationFn({ onSuccess, onError })`.\n */\nexport function useMutationFn<T>(\n mutationFn: (variables: void) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n): {\n data: T | null;\n isMutating: boolean;\n executeMutationFn: (\n executeOptions?: ExecuteMutationOptions<T>\n ) => Promise<HttpResponse<T> | null>;\n reset: () => void;\n};\n\n/**\n * Mutation with variables. Use when the payload is passed at call time (e.g. update forms, PATCH body).\n *\n * @param mutationFn - Function that receives variables and returns a promise (e.g. `(data) => updateApi(id, data)`).\n * @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.\n * @returns executeMutationFn(variables, options?) — you must pass variables first, then optional callbacks.\n */\nexport function useMutationFn<T, TVariables>(\n mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n): {\n data: T | null;\n isMutating: boolean;\n executeMutationFn: (\n variables: TVariables,\n executeOptions?: ExecuteMutationOptions<T>\n ) => Promise<HttpResponse<T> | null>;\n reset: () => void;\n};\n\nexport function useMutationFn<T, TVariables = void>(\n mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n) {\n const [state, setState] = useState<MutationState<T>>({\n data: null,\n isMutating: false,\n });\n const isMounted = useRef<boolean>(true);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const executeMutationFn = useCallback(\n async (\n firstArg?: TVariables | ExecuteMutationOptions<T>,\n secondArg?: ExecuteMutationOptions<T>\n ): Promise<HttpResponse<T> | null> => {\n const hasTwoArgs = secondArg !== undefined;\n const variables = (hasTwoArgs ? firstArg : undefined) as TVariables;\n const executeOptions = hasTwoArgs ? secondArg : firstArg as ExecuteMutationOptions<T>;\n\n setState((prev) => ({ ...prev, isMutating: true }));\n\n try {\n const response = await mutationFn(variables);\n\n if (isMounted.current) {\n setState({\n data: response.data ?? null,\n isMutating: false,\n });\n\n options?.invalidatesTags?.forEach((tag) => {\n eventEmitter.emit(tag);\n });\n\n if (response.data != null) executeOptions?.onSuccess?.(response.data);\n else executeOptions?.onSuccess?.(null as unknown as T);\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isMutating: false,\n });\n executeOptions?.onError?.(apiError);\n }\n return null;\n }\n },\n [mutationFn, options?.invalidatesTags]\n );\n\n const reset = useCallback(() => {\n setState({ data: null, isMutating: false });\n }, []);\n\n return { ...state, executeMutationFn, reset };\n}\n"],"mappings":"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","names":["useState","useCallback","useRef","useEffect","useState","useRef","useEffect","useCallback"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fetchwire",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "A lightweight, focused API fetching library for React and React Native applications.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsup",
|
|
17
|
-
"dev": "tsup --watch"
|
|
17
|
+
"dev": "tsup --watch",
|
|
18
|
+
"lint": "eslint src/"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"dist"
|
|
@@ -27,9 +28,13 @@
|
|
|
27
28
|
"author": "Doanvinhphu",
|
|
28
29
|
"license": "MIT",
|
|
29
30
|
"devDependencies": {
|
|
31
|
+
"@eslint/js": "^9.15.0",
|
|
30
32
|
"@types/react": "^19.2.14",
|
|
33
|
+
"eslint": "^9.15.0",
|
|
34
|
+
"eslint-plugin-react-hooks": "^5.0.0",
|
|
31
35
|
"tsup": "^8.5.1",
|
|
32
|
-
"typescript": "^5.9.3"
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"typescript-eslint": "^8.15.0"
|
|
33
38
|
},
|
|
34
39
|
"funding": [
|
|
35
40
|
{
|