fetchwire 1.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,35 +7,38 @@ A lightweight, focused API fetching library for **React and React Native** appli
7
7
  - Handle errors consistently.
8
8
 
9
9
  ### When to use fetchwire
10
+
10
11
  - **React / React Native apps** that:
11
- - Want a **simple**, centralized way to call HTTP APIs.
12
+ - Want a **simple**, centralized way for API fetching setup.
12
13
  - Prefer plain hooks over a heavier state management or query library.
13
14
  - Need basic tag-based invalidation without a full cache layer.
14
15
 
15
16
  ### When not to use fetchwire
17
+
16
18
  - Consider a more full-featured solution (e.g. TanStack Query / React Query, SWR, RTK Query) if:
17
19
  - You need advanced, automatic caching strategies.
18
20
  - You need built-in pagination helpers, infinite queries.
19
21
  - You need a more powerful data-fetching library and you want to avoid overlap.
20
22
 
21
23
  ## Support
24
+
22
25
  If you find **fetchwire** helpful and want to support its development, you can buy me a coffee via:
23
26
 
24
27
  [![Ko-fi](https://img.shields.io/badge/Ko--fi-F16061?style=for-the-badge&logo=ko-fi&logoColor=white)](https://ko-fi.com/doanvinhphu)
25
28
  [![PayPal](https://img.shields.io/badge/PayPal-004595?style=for-the-badge&logo=paypal&logoColor=white)](https://paypal.me/doanvinhphu)
26
29
 
27
- Your support helps maintain the library and keep it up to date!
28
-
29
30
  ## Features
31
+
30
32
  - **Global API fetching configuration `initWire`**
31
33
  - Configure `baseUrl`, default headers, and how to read the auth token.
32
34
  - Optionally register global interceptors for 401/403/other errors.
33
35
  - Converts server/network errors into a typed `ApiError`.
34
36
 
35
- - **React hooks for data fetching and mutation with Tag-based invalidation**
36
- - **`useFetchFn<T>`** for data fetching
37
- - **`useMutationFn<T>`** for mutations
37
+ - **React hooks for data fetching and mutation with tag-based invalidation**
38
+ - **`useFetchFn`** for data fetching
39
+ - **`useMutationFn`** for mutations
38
40
  - With a simple, explicit way to refetch related data through tags
41
+
39
42
  ---
40
43
 
41
44
  ## Installation
@@ -49,6 +52,7 @@ pnpm add fetchwire
49
52
  ```
50
53
 
51
54
  ### Peer expectations
55
+
52
56
  - TypeScript is recommended but not required.
53
57
  - For React Native / Expo, make sure the global `fetch` is available (default in modern RN/Expo).
54
58
 
@@ -73,6 +77,7 @@ export function setupWire() {
73
77
  'x-client': 'web',
74
78
  },
75
79
  getToken: async () => {
80
+ // Called on each request — return the current access token or null.
76
81
  // Read token from localStorage (or any storage you prefer)
77
82
  return localStorage.getItem('access_token');
78
83
  },
@@ -106,7 +111,7 @@ setupWire();
106
111
  ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
107
112
  <React.StrictMode>
108
113
  <App />
109
- </React.StrictMode>,
114
+ </React.StrictMode>
110
115
  );
111
116
  ```
112
117
 
@@ -160,18 +165,17 @@ You can organize similar helpers for users, invoices, organizations, uploads, et
160
165
 
161
166
  ### 2. Fetch data with `useFetchFn`
162
167
 
163
- `useFetchFn<T>` is a generic hook that manages state for running an async function returning `{ data: T }`.
168
+ `useFetchFn` is a generic hook that manages state for running an async function returning `HttpResponse<T>`, where `T` is **inferred** from your API helper.
164
169
 
165
170
  **Key ideas:**
166
171
 
167
- - You **do not** pass the async function into the hook directly.
168
- - Instead, the hook returns `executeFetchFn`, which you call with a function that performs your API request.
172
+ - You pass a **pre-typed API helper** (e.g. `getTodosApi`) into the hook once.
169
173
  - The hook tracks:
170
174
  - `data: T | null`
171
175
  - `isLoading: boolean`
172
176
  - `isRefreshing: boolean`
173
177
  - `error: ApiError | null`
174
- - `executeFetchFn(fetchFn)`
178
+ - `executeFetchFn()`
175
179
  - `refreshFetchFn()`
176
180
 
177
181
  Example: loading and refreshing a todo list in a React component:
@@ -190,12 +194,12 @@ export function TodoList() {
190
194
  error,
191
195
  executeFetchFn: fetchTodos,
192
196
  refreshFetchFn: refreshTodos,
193
- } = useFetchFn<Todo[]>({
197
+ } = useFetchFn(getTodosApi, {
194
198
  tags: ['todos'],
195
199
  });
196
200
 
197
201
  useEffect(() => {
198
- fetchTodos(() => getTodosApi());
202
+ fetchTodos();
199
203
  }, [fetchTodos]);
200
204
 
201
205
  if (isLoading) return <div>Loading...</div>;
@@ -223,7 +227,7 @@ export function TodoList() {
223
227
 
224
228
  ### 3. Mutate data with `useMutationFn`
225
229
 
226
- `useMutationFn<T>` is a hook for mutations (create/update/delete). It:
230
+ `useMutationFn` is a hook for mutations (create/update/delete). It:
227
231
 
228
232
  - Tracks `data` and `isMutating`.
229
233
  - Lets you invalidate **tags** after a successful mutation.
@@ -237,9 +241,12 @@ const {
237
241
  isMutating,
238
242
  executeMutationFn,
239
243
  reset,
240
- } = useMutationFn<T>({ invalidatesTags?: string[] });
244
+ } = useMutationFn(mutationFn, { invalidatesTags?: string[] });
241
245
  ```
242
246
 
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 })`.
249
+
243
250
  Example: creating and toggling todos with `useMutationFn`:
244
251
 
245
252
  ```tsx
@@ -259,36 +266,38 @@ export function TodoActions() {
259
266
  const {
260
267
  isMutating: isCreating,
261
268
  executeMutationFn: createTodo,
262
- } = useMutationFn<Todo>({
269
+ } = useMutationFn(() => createTodoApi({ title }), {
263
270
  invalidatesTags: ['todos'],
264
271
  });
265
272
 
266
273
  const {
267
274
  isMutating: isToggling,
268
275
  executeMutationFn: toggleTodo,
269
- } = useMutationFn<Todo>({
270
- invalidatesTags: ['todos'],
271
- });
276
+ } = useMutationFn(
277
+ (id: string) => toggleTodoApi(id),
278
+ { invalidatesTags: ['todos'] }
279
+ );
272
280
 
273
281
  const {
274
282
  isMutating: isDeleting,
275
283
  executeMutationFn: deleteTodo,
276
- } = useMutationFn<null>({
277
- invalidatesTags: ['todos'],
278
- });
284
+ } = useMutationFn(
285
+ (id: string) => deleteTodoApi(id),
286
+ { invalidatesTags: ['todos'] }
287
+ );
279
288
 
280
289
  const handleCreate = (e: FormEvent) => {
281
290
  e.preventDefault();
282
291
  if (!title.trim()) return;
283
292
 
284
- createTodo(() => createTodoApi({ title }), {
293
+ createTodo({
285
294
  onSuccess: () => setTitle(''),
286
295
  });
287
296
  };
288
297
 
289
- // Example usage of toggleTodo and deleteTodo in your UI:
290
- // toggleTodo(() => toggleTodoApi(todoId))
291
- // deleteTodo(() => deleteTodoApi(todoId))
298
+ // With variables, pass payload first then options:
299
+ // toggleTodo(todoId, { onSuccess: () => ..., onError: (error) => ... });
300
+ // deleteTodo(todoId, { onSuccess: () => ..., onError: (error) => ... });
292
301
 
293
302
  return (
294
303
  <form onSubmit={handleCreate}>
@@ -311,8 +320,8 @@ export function TodoActions() {
311
320
 
312
321
  Tags provide a simple way to coordinate refetches across your app:
313
322
 
314
- - `useFetchFn({ tags: [...] })` subscribes the hook to one or more **tags**.
315
- - `useMutationFn({ invalidatesTags: [...] })` emits those tags after a **successful** mutation.
323
+ - `useFetchFn(fetchFn, { tags: [...] })` subscribes the hook to one or more **tags**.
324
+ - `useMutationFn(mutationFn, { invalidatesTags: [...] })` emits those tags after a **successful** mutation.
316
325
  - When a tag is emitted, all subscribed fetch hooks will automatically **call `refreshFetchFn`**.
317
326
 
318
327
  This pattern keeps your code explicit and small, without introducing a full query cache library.
@@ -376,14 +385,19 @@ With `useMutationFn`, you commonly handle errors with `onError`:
376
385
  ```tsx
377
386
  import { ApiError } from 'fetchwire';
378
387
 
379
- executeMutationFn(() => someMutationApi(), {
380
- onSuccess: () => {
381
- // success logic
382
- },
388
+ // No variables: pass only options
389
+ executeMutationFn({
390
+ onSuccess: () => { /* success logic */ },
383
391
  onError: (error: ApiError) => {
384
392
  Alert.alert('Login failed', error.message || 'Unexpected error');
385
393
  },
386
394
  });
395
+
396
+ // With variables: pass variables first, then options
397
+ executeMutationFn(payload, {
398
+ onSuccess: (data) => { /* ... */ },
399
+ onError: (error: ApiError) => { /* ... */ },
400
+ });
387
401
  ```
388
402
 
389
403
  You can also read `error` directly from `useFetchFn` state if you want to render error messages in your UI.
@@ -415,7 +429,7 @@ function initWire(config: WireConfig): void;
415
429
 
416
430
  - **`baseUrl`**: Base API URL (e.g. `'https://api.example.com'`).
417
431
  - **`headers`**: Global headers to apply to every request.
418
- - **`getToken`**: Async function that returns a bearer token or `null`. If present, fetchwire adds `Authorization: Bearer <token>`.
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>`.
419
433
  - **`interceptors`** (optional):
420
434
  - `onUnauthorized(error)`: Called when a 401 is returned.
421
435
  - `onForbidden(error)`: Called when a 403 is returned.
@@ -452,7 +466,7 @@ function getWireConfig(): WireConfig;
452
466
  ```ts
453
467
  async function wireApi<T>(
454
468
  endpoint: string,
455
- options?: RequestInit & { headers?: Record<string, string> },
469
+ options?: RequestInit & { headers?: Record<string, string> }
456
470
  ): Promise<HttpResponse<T>>;
457
471
  ```
458
472
 
@@ -471,71 +485,76 @@ const result = await wireApi<UserResponse>('/user/me', { method: 'GET' });
471
485
 
472
486
  ---
473
487
 
474
- ### `useFetchFn<T>(options?)`
488
+ ### `useFetchFn<T>(fetchFn, options?)`
475
489
 
476
490
  ```ts
477
491
  type FetchOptions = {
478
492
  tags?: string[];
479
493
  };
480
494
 
481
- function useFetchFn<T>(options?: FetchOptions): {
495
+ function useFetchFn<T>(
496
+ fetchFn: () => Promise<HttpResponse<T>>,
497
+ options?: FetchOptions
498
+ ): {
482
499
  data: T | null;
483
500
  isLoading: boolean;
484
501
  isRefreshing: boolean;
485
502
  error: ApiError | null;
486
- executeFetchFn: (fetchFn: () => Promise<{ data: T }>) => Promise<{ data: T } | null>;
487
- refreshFetchFn: () => Promise<{ data: T } | null> | null;
503
+ executeFetchFn: () => Promise<HttpResponse<T> | null>;
504
+ refreshFetchFn: () => Promise<HttpResponse<T> | null> | null;
488
505
  };
489
506
  ```
490
507
 
508
+ - **`fetchFn`**: Async function (e.g. an API helper using `wireApi<T>`). Type `T` is inferred from its return type.
491
509
  - **`options.tags`**: Optional array of tag strings to subscribe to. When a mutation invalidates these tags, `refreshFetchFn` is called automatically.
492
- - **`executeFetchFn`**:
493
- - Executes the provided async function.
494
- - Updates `data`, `isLoading`, `error`.
495
- - Stores the last function so it can be used by `refreshFetchFn`.
496
- - **`refreshFetchFn`**:
497
- - 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.
498
512
 
499
513
  ---
500
514
 
501
- ### `useMutationFn<T>(options?)`
515
+ ### `useMutationFn<T>(mutationFn, options?)` (no variables)
516
+ ### `useMutationFn<T, TVariables>(mutationFn, options?)` (with variables)
502
517
 
503
518
  ```ts
504
519
  type MutationOptions = {
505
520
  invalidatesTags?: string[];
506
521
  };
507
522
 
508
- type ExecuteOptions<T> = {
509
- onSuccess?: (data: T | null) => void;
523
+ type ExecuteMutationOptions<T> = {
524
+ onSuccess?: (data: T) => void;
510
525
  onError?: (error: ApiError) => void;
511
526
  };
512
527
 
513
- function useMutationFn<T>(options?: MutationOptions): {
528
+ // No variables: mutationFn has no parameters
529
+ function useMutationFn<T>(
530
+ mutationFn: () => Promise<HttpResponse<T>>,
531
+ options?: MutationOptions
532
+ ): {
514
533
  data: T | null;
515
534
  isMutating: boolean;
516
- executeMutationFn: (
517
- mutationFn: () => Promise<{ data: T }>,
518
- executeOptions?: ExecuteOptions<T>,
519
- ) => Promise<{ data: T } | null>;
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
+ ): {
544
+ data: T | null;
545
+ isMutating: boolean;
546
+ executeMutationFn: (variables: TVariables, executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
520
547
  reset: () => void;
521
548
  };
522
549
  ```
523
550
 
524
- - **`options.invalidatesTags`**:
525
- - List of tags to emit after a **successful** mutation.
526
- - 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.
527
553
  - **`executeMutationFn`**:
528
- - Executes the provided `mutationFn`.
529
- - Sets `isMutating` while running.
530
- - On success:
531
- - Updates `data`.
532
- - Emits all `invalidatesTags`.
533
- - Calls `onSuccess` with `response.data` (or `null`).
534
- - On error:
535
- - Resets `isMutating`.
536
- - Calls `onError` with an `ApiError` instance.
537
- - **`reset`**:
538
- - 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.
539
558
 
540
559
  ---
541
560
 
@@ -546,4 +565,3 @@ function useMutationFn<T>(options?: MutationOptions): {
546
565
  Copyright (c) Doanvinhphu
547
566
 
548
567
  See the `LICENSE` file for details (or include the standard MIT text directly in your repository).
549
-
package/dist/index.d.mts CHANGED
@@ -148,9 +148,9 @@ declare function wireApi<T>(endpoint: string, options?: RequestInit): Promise<Ht
148
148
  * @param options - Has tags property that will trigger refetching of the useFetchFn with the given tags.
149
149
  * @returns The state of the fetch and the fetch function.
150
150
  */
151
- declare function useFetchFn<T>(options?: FetchOptions): {
152
- executeFetchFn: (fetchFn: () => Promise<HttpResponse<T>>) => Promise<HttpResponse<T> | null>;
153
- refreshFetchFn: () => Promise<HttpResponse<T> | null> | null;
151
+ declare function useFetchFn<T>(fetchFn: () => Promise<HttpResponse<T>>, options?: FetchOptions): {
152
+ executeFetchFn: () => Promise<HttpResponse<T> | null>;
153
+ refreshFetchFn: () => Promise<HttpResponse<T> | null>;
154
154
  data: T | null;
155
155
  isLoading: boolean;
156
156
  isRefreshing: boolean;
@@ -158,15 +158,30 @@ declare function useFetchFn<T>(options?: FetchOptions): {
158
158
  };
159
159
 
160
160
  /**
161
- * A hook for executing a mutation function and managing the state of the mutation.
162
- * @param options - Has invalidatesTags property that will trigger refetching of the useFetchFn with the given tags.
163
- * @returns The state of the mutation and the mutation function.
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 })`.
164
166
  */
165
- declare function useMutationFn<T>(options?: MutationOptions): {
166
- executeMutationFn: (mutationFn: () => Promise<HttpResponse<T>>, executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
167
+ declare function useMutationFn<T>(mutationFn: (variables: void) => Promise<HttpResponse<T>>, options?: MutationOptions): {
168
+ data: T | null;
169
+ isMutating: boolean;
170
+ executeMutationFn: (executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
167
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): {
168
181
  data: T | null;
169
182
  isMutating: boolean;
183
+ executeMutationFn: (variables: TVariables, executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
184
+ reset: () => void;
170
185
  };
171
186
 
172
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
@@ -148,9 +148,9 @@ declare function wireApi<T>(endpoint: string, options?: RequestInit): Promise<Ht
148
148
  * @param options - Has tags property that will trigger refetching of the useFetchFn with the given tags.
149
149
  * @returns The state of the fetch and the fetch function.
150
150
  */
151
- declare function useFetchFn<T>(options?: FetchOptions): {
152
- executeFetchFn: (fetchFn: () => Promise<HttpResponse<T>>) => Promise<HttpResponse<T> | null>;
153
- refreshFetchFn: () => Promise<HttpResponse<T> | null> | null;
151
+ declare function useFetchFn<T>(fetchFn: () => Promise<HttpResponse<T>>, options?: FetchOptions): {
152
+ executeFetchFn: () => Promise<HttpResponse<T> | null>;
153
+ refreshFetchFn: () => Promise<HttpResponse<T> | null>;
154
154
  data: T | null;
155
155
  isLoading: boolean;
156
156
  isRefreshing: boolean;
@@ -158,15 +158,30 @@ declare function useFetchFn<T>(options?: FetchOptions): {
158
158
  };
159
159
 
160
160
  /**
161
- * A hook for executing a mutation function and managing the state of the mutation.
162
- * @param options - Has invalidatesTags property that will trigger refetching of the useFetchFn with the given tags.
163
- * @returns The state of the mutation and the mutation function.
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 })`.
164
166
  */
165
- declare function useMutationFn<T>(options?: MutationOptions): {
166
- executeMutationFn: (mutationFn: () => Promise<HttpResponse<T>>, executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
167
+ declare function useMutationFn<T>(mutationFn: (variables: void) => Promise<HttpResponse<T>>, options?: MutationOptions): {
168
+ data: T | null;
169
+ isMutating: boolean;
170
+ executeMutationFn: (executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
167
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): {
168
181
  data: T | null;
169
182
  isMutating: boolean;
183
+ executeMutationFn: (variables: TVariables, executeOptions?: ExecuteMutationOptions<T>) => Promise<HttpResponse<T> | null>;
184
+ reset: () => void;
170
185
  };
171
186
 
172
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
@@ -142,7 +142,7 @@ var EventEmitter = class {
142
142
  var eventEmitter = new EventEmitter();
143
143
 
144
144
  // src/hook/use-fetch-fn.ts
145
- function useFetchFn(options) {
145
+ function useFetchFn(fetchFn, options) {
146
146
  const [state, setState] = (0, import_react.useState)({
147
147
  data: null,
148
148
  isLoading: false,
@@ -158,7 +158,7 @@ function useFetchFn(options) {
158
158
  };
159
159
  }, []);
160
160
  const execute = (0, import_react.useCallback)(
161
- async (fetchFn, execOptions) => {
161
+ async (execOptions) => {
162
162
  lastFetchFn.current = fetchFn;
163
163
  setState((prev) => ({
164
164
  ...prev,
@@ -193,17 +193,10 @@ function useFetchFn(options) {
193
193
  []
194
194
  );
195
195
  const executeFetchFn = (0, import_react.useCallback)(
196
- (fetchFn) => {
197
- return execute(fetchFn, { isRefresh: false });
198
- },
196
+ () => execute({ isRefresh: false }),
199
197
  [execute]
200
198
  );
201
- const refreshFetchFn = (0, import_react.useCallback)(() => {
202
- if (lastFetchFn.current) {
203
- return execute(lastFetchFn.current, { isRefresh: true });
204
- }
205
- return null;
206
- }, [execute]);
199
+ const refreshFetchFn = (0, import_react.useCallback)(() => execute({ isRefresh: true }), [execute]);
207
200
  (0, import_react.useEffect)(() => {
208
201
  if (!options?.tags || options.tags.length === 0) return;
209
202
  const subscriptions = options.tags.map(
@@ -218,7 +211,7 @@ function useFetchFn(options) {
218
211
 
219
212
  // src/hook/use-mutation-fn.ts
220
213
  var import_react2 = require("react");
221
- function useMutationFn(options) {
214
+ function useMutationFn(mutationFn, options) {
222
215
  const [state, setState] = (0, import_react2.useState)({
223
216
  data: null,
224
217
  isMutating: false
@@ -231,19 +224,22 @@ function useMutationFn(options) {
231
224
  };
232
225
  }, []);
233
226
  const executeMutationFn = (0, import_react2.useCallback)(
234
- async (mutationFn, executeOptions) => {
227
+ async (firstArg, secondArg) => {
228
+ const hasTwoArgs = secondArg !== void 0;
229
+ const variables = hasTwoArgs ? firstArg : void 0;
230
+ const executeOptions = hasTwoArgs ? secondArg : firstArg;
235
231
  setState((prev) => ({ ...prev, isMutating: true }));
236
232
  try {
237
- const response = await mutationFn();
233
+ const response = await mutationFn(variables);
238
234
  if (isMounted.current) {
239
235
  setState({
240
- data: response.data || null,
236
+ data: response.data ?? null,
241
237
  isMutating: false
242
238
  });
243
239
  options?.invalidatesTags?.forEach((tag) => {
244
240
  eventEmitter.emit(tag);
245
241
  });
246
- if (response.data) executeOptions?.onSuccess?.(response.data);
242
+ if (response.data != null) executeOptions?.onSuccess?.(response.data);
247
243
  else executeOptions?.onSuccess?.(null);
248
244
  }
249
245
  return response;
@@ -254,12 +250,12 @@ function useMutationFn(options) {
254
250
  data: null,
255
251
  isMutating: false
256
252
  });
257
- if (apiError) executeOptions?.onError?.(apiError);
253
+ executeOptions?.onError?.(apiError);
258
254
  }
259
255
  return null;
260
256
  }
261
257
  },
262
- [options?.invalidatesTags]
258
+ [mutationFn, options?.invalidatesTags]
263
259
  );
264
260
  const reset = (0, import_react2.useCallback)(() => {
265
261
  setState({ data: null, isMutating: false });
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>(options?: FetchOptions) {\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 (\n fetchFn: () => Promise<HttpResponse<T>>,\n execOptions: { 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 (fetchFn: () => Promise<HttpResponse<T>>) => {\n return execute(fetchFn, { isRefresh: false });\n },\n [execute]\n );\n\n const refreshFetchFn = useCallback(() => {\n if (lastFetchFn.current) {\n return execute(lastFetchFn.current, { isRefresh: true });\n }\n return null;\n }, [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 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>(options?: MutationOptions) {\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 mutationFn: () => Promise<HttpResponse<T>>,\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 [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 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 {\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,WAAN,cAAuB,MAAM;AAAA,EAIlC,YAAY,SAAiB,WAAoB,YAAqB;AACpE,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,YAAY;AACjB,SAAK,aAAa;AAAA,EACpB;AACF;;;ACRA,IAAI,mBAAsC;AAOnC,IAAM,WAAW,CAAC,WAA6B;AACpD,qBAAmB;AAAA,IACjB,GAAG;AAAA,IACH,SAAS,OAAO,WAAW,CAAC;AAAA,EAC9B;AACF;AAOO,IAAM,mBAAmB,CAAC,WAAsC;AACrE,MAAI,CAAC,kBAAkB;AACrB,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AAEA,qBAAmB;AAAA,IACjB,GAAG;AAAA,IACH,GAAG;AAAA,IACH,SAAS;AAAA,MACP,GAAG,iBAAiB;AAAA,MACpB,GAAG,OAAO;AAAA,IACZ;AAAA,EACF;AACF;AAOO,IAAM,gBAAgB,MAAkB;AAC7C,MAAI,CAAC,kBAAkB;AACrB,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,SAAO;AACT;;;ACrCA,eAAsB,QACpB,UACA,UAAuB,CAAC,GACE;AAC1B,QAAM,SAAS,cAAc;AAC7B,QAAM,MAAM,GAAG,OAAO,OAAO,GAAG,QAAQ;AACxC,QAAM,cAAc,MAAM,OAAO,SAAS;AAE1C,QAAM,UAAuB;AAAA,IAC3B,gBAAgB;AAAA,IAChB,GAAI,cAAc,EAAE,eAAe,UAAU,WAAW,GAAG,IAAI,CAAC;AAAA,IAChE,GAAG,OAAO;AAAA,IACV,GAAG,QAAQ;AAAA,EACb;AAEA,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,KAAK,EAAE,GAAG,SAAS,QAAQ,CAAC;AAEzD,QAAI,CAAC,SAAS,IAAI;AAChB,UAAI;AACJ,UAAI;AACF,oBAAY,MAAM,SAAS,KAAK;AAAA,MAClC,QAAQ;AACN,oBAAY,EAAE,SAAS,wBAAwB,OAAO,UAAU;AAAA,MAClE;AAEA,YAAM,WAAW,IAAI;AAAA,QACnB,UAAU;AAAA,QACV,UAAU;AAAA,QACV,SAAS;AAAA,MACX;AAGA,YAAM,0BACJ,OAAO,2BAA2B,OAAO,wBAAwB,SAAS,IACtE,OAAO,0BACP,CAAC,GAAG;AAEV,YAAM,uBACJ,OAAO,wBAAwB,OAAO,qBAAqB,SAAS,IAChE,OAAO,uBACP,CAAC,GAAG;AAGV,UACE,OAAO,cAAc,kBACrB,wBAAwB,SAAS,SAAS,MAAM,GAChD;AACA,eAAO,aAAa,eAAe,QAAQ;AAAA,MAC7C,WACE,OAAO,cAAc,eACrB,qBAAqB,SAAS,SAAS,MAAM,GAC7C;AACA,eAAO,aAAa,YAAY,QAAQ;AAAA,MAC1C,WAAW,OAAO,cAAc,SAAS;AACvC,eAAO,aAAa,QAAQ,QAAQ;AAAA,MACtC;AAEA,YAAM;AAAA,IACR;AAEA,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,SAAS,OAAO;AACd,QAAI,iBAAiB,SAAU,OAAM;AACrC,UAAM,IAAI;AAAA,MACR,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MACzC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;;;AC/EA,mBAAyD;;;ACEzD,IAAM,eAAN,MAAmB;AAAA,EAAnB;AACE,SAAQ,SAAqC,CAAC;AAAA;AAAA,EAE9C,KAAK,OAAe;AAClB,QAAI,CAAC,KAAK,OAAO,KAAK,EAAG;AACzB,SAAK,OAAO,KAAK,EAAE,QAAQ,CAAC,aAAa,SAAS,CAAC;AAAA,EACrD;AAAA,EAEA,YAAY,OAAe,UAAoB;AAC7C,QAAI,CAAC,KAAK,OAAO,KAAK,GAAG;AACvB,WAAK,OAAO,KAAK,IAAI,CAAC;AAAA,IACxB;AACA,SAAK,OAAO,KAAK,EAAE,KAAK,QAAQ;AAEhC,WAAO;AAAA,MACL,QAAQ,MAAM;AACZ,aAAK,OAAO,KAAK,IAAI,KAAK,OAAO,KAAK,EAAE,OAAO,CAAC,MAAM,MAAM,QAAQ;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,eAAe,IAAI,aAAa;;;ADPtC,SAAS,WACd,SACA,SACA;AACA,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAwB;AAAA,IAChD,MAAM;AAAA,IACN,WAAW;AAAA,IACX,cAAc;AAAA,IACd,OAAO;AAAA,EACT,CAAC;AAED,QAAM,gBAAY,qBAAgB,IAAI;AACtC,QAAM,kBAAc,qBAAgD,IAAI;AAExE,8BAAU,MAAM;AACd,cAAU,UAAU;AACpB,WAAO,MAAM;AACX,gBAAU,UAAU;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,cAAU;AAAA,IACd,OAAO,gBAEgC;AACrC,kBAAY,UAAU;AAEtB,eAAS,CAAC,UAAU;AAAA,QAClB,GAAG;AAAA,QACH,WAAW,CAAC,YAAY;AAAA,QACxB,cAAc,CAAC,CAAC,YAAY;AAAA,QAC5B,OAAO;AAAA,MACT,EAAE;AAEF,UAAI;AACF,cAAM,WAAW,MAAM,QAAQ;AAE/B,YAAI,UAAU,SAAS;AACrB,mBAAS;AAAA,YACP,MAAM,SAAS,QAAQ;AAAA,YACvB,WAAW;AAAA,YACX,cAAc;AAAA,YACd,OAAO;AAAA,UACT,CAAC;AAAA,QACH;AACA,eAAO;AAAA,MACT,SAAS,OAAO;AACd,cAAM,WAAW;AACjB,YAAI,UAAU,SAAS;AACrB,mBAAS;AAAA,YACP,MAAM;AAAA,YACN,WAAW;AAAA,YACX,cAAc;AAAA,YACd,OAAO;AAAA,UACT,CAAC;AAAA,QACH;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,qBAAiB;AAAA,IACrB,MAAM,QAAQ,EAAE,WAAW,MAAM,CAAC;AAAA,IAClC,CAAC,OAAO;AAAA,EACV;AACA,QAAM,qBAAiB,0BAAY,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;AAEhF,8BAAU,MAAM;AACd,QAAI,CAAC,SAAS,QAAQ,QAAQ,KAAK,WAAW,EAAG;AAEjD,UAAM,gBAAgB,QAAQ,KAAK;AAAA,MAAI,CAAC,QACtC,aAAa,YAAY,KAAK,MAAM;AAClC,uBAAe;AAAA,MACjB,CAAC;AAAA,IACH;AACA,WAAO,MAAM,cAAc,QAAQ,CAAC,QAAQ,IAAI,OAAO,CAAC;AAAA,EAC1D,GAAG,CAAC,SAAS,MAAM,cAAc,CAAC;AAElC,SAAO,EAAE,GAAG,OAAO,gBAAgB,eAAe;AACpD;;;AEjGA,IAAAA,gBAAyD;AAqDlD,SAAS,cACd,YACA,SACA;AACA,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAA2B;AAAA,IACnD,MAAM;AAAA,IACN,YAAY;AAAA,EACd,CAAC;AACD,QAAM,gBAAY,sBAAgB,IAAI;AAEtC,+BAAU,MAAM;AACd,cAAU,UAAU;AACpB,WAAO,MAAM;AACX,gBAAU,UAAU;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,wBAAoB;AAAA,IACxB,OACE,UACA,cACoC;AACpC,YAAM,aAAa,cAAc;AACjC,YAAM,YAAa,aAAa,WAAW;AAC3C,YAAM,iBAAiB,aAAa,YAAY;AAEhD,eAAS,CAAC,UAAU,EAAE,GAAG,MAAM,YAAY,KAAK,EAAE;AAElD,UAAI;AACF,cAAM,WAAW,MAAM,WAAW,SAAS;AAE3C,YAAI,UAAU,SAAS;AACrB,mBAAS;AAAA,YACP,MAAM,SAAS,QAAQ;AAAA,YACvB,YAAY;AAAA,UACd,CAAC;AAED,mBAAS,iBAAiB,QAAQ,CAAC,QAAQ;AACzC,yBAAa,KAAK,GAAG;AAAA,UACvB,CAAC;AAED,cAAI,SAAS,QAAQ,KAAM,iBAAgB,YAAY,SAAS,IAAI;AAAA,cAC/D,iBAAgB,YAAY,IAAoB;AAAA,QACvD;AACA,eAAO;AAAA,MACT,SAAS,OAAO;AACd,cAAM,WAAW;AACjB,YAAI,UAAU,SAAS;AACrB,mBAAS;AAAA,YACP,MAAM;AAAA,YACN,YAAY;AAAA,UACd,CAAC;AACD,0BAAgB,UAAU,QAAQ;AAAA,QACpC;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,CAAC,YAAY,SAAS,eAAe;AAAA,EACvC;AAEA,QAAM,YAAQ,2BAAY,MAAM;AAC9B,aAAS,EAAE,MAAM,MAAM,YAAY,MAAM,CAAC;AAAA,EAC5C,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,GAAG,OAAO,mBAAmB,MAAM;AAC9C;","names":["import_react"]}
package/dist/index.mjs CHANGED
@@ -110,7 +110,7 @@ var EventEmitter = class {
110
110
  var eventEmitter = new EventEmitter();
111
111
 
112
112
  // src/hook/use-fetch-fn.ts
113
- function useFetchFn(options) {
113
+ function useFetchFn(fetchFn, options) {
114
114
  const [state, setState] = useState({
115
115
  data: null,
116
116
  isLoading: false,
@@ -126,7 +126,7 @@ function useFetchFn(options) {
126
126
  };
127
127
  }, []);
128
128
  const execute = useCallback(
129
- async (fetchFn, execOptions) => {
129
+ async (execOptions) => {
130
130
  lastFetchFn.current = fetchFn;
131
131
  setState((prev) => ({
132
132
  ...prev,
@@ -161,17 +161,10 @@ function useFetchFn(options) {
161
161
  []
162
162
  );
163
163
  const executeFetchFn = useCallback(
164
- (fetchFn) => {
165
- return execute(fetchFn, { isRefresh: false });
166
- },
164
+ () => execute({ isRefresh: false }),
167
165
  [execute]
168
166
  );
169
- const refreshFetchFn = useCallback(() => {
170
- if (lastFetchFn.current) {
171
- return execute(lastFetchFn.current, { isRefresh: true });
172
- }
173
- return null;
174
- }, [execute]);
167
+ const refreshFetchFn = useCallback(() => execute({ isRefresh: true }), [execute]);
175
168
  useEffect(() => {
176
169
  if (!options?.tags || options.tags.length === 0) return;
177
170
  const subscriptions = options.tags.map(
@@ -186,7 +179,7 @@ function useFetchFn(options) {
186
179
 
187
180
  // src/hook/use-mutation-fn.ts
188
181
  import { useState as useState2, useCallback as useCallback2, useRef as useRef2, useEffect as useEffect2 } from "react";
189
- function useMutationFn(options) {
182
+ function useMutationFn(mutationFn, options) {
190
183
  const [state, setState] = useState2({
191
184
  data: null,
192
185
  isMutating: false
@@ -199,19 +192,22 @@ function useMutationFn(options) {
199
192
  };
200
193
  }, []);
201
194
  const executeMutationFn = useCallback2(
202
- async (mutationFn, executeOptions) => {
195
+ async (firstArg, secondArg) => {
196
+ const hasTwoArgs = secondArg !== void 0;
197
+ const variables = hasTwoArgs ? firstArg : void 0;
198
+ const executeOptions = hasTwoArgs ? secondArg : firstArg;
203
199
  setState((prev) => ({ ...prev, isMutating: true }));
204
200
  try {
205
- const response = await mutationFn();
201
+ const response = await mutationFn(variables);
206
202
  if (isMounted.current) {
207
203
  setState({
208
- data: response.data || null,
204
+ data: response.data ?? null,
209
205
  isMutating: false
210
206
  });
211
207
  options?.invalidatesTags?.forEach((tag) => {
212
208
  eventEmitter.emit(tag);
213
209
  });
214
- if (response.data) executeOptions?.onSuccess?.(response.data);
210
+ if (response.data != null) executeOptions?.onSuccess?.(response.data);
215
211
  else executeOptions?.onSuccess?.(null);
216
212
  }
217
213
  return response;
@@ -222,12 +218,12 @@ function useMutationFn(options) {
222
218
  data: null,
223
219
  isMutating: false
224
220
  });
225
- if (apiError) executeOptions?.onError?.(apiError);
221
+ executeOptions?.onError?.(apiError);
226
222
  }
227
223
  return null;
228
224
  }
229
225
  },
230
- [options?.invalidatesTags]
226
+ [mutationFn, options?.invalidatesTags]
231
227
  );
232
228
  const reset = useCallback2(() => {
233
229
  setState({ data: null, isMutating: false });
@@ -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>(options?: FetchOptions) {\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 (\n fetchFn: () => Promise<HttpResponse<T>>,\n execOptions: { 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 (fetchFn: () => Promise<HttpResponse<T>>) => {\n return execute(fetchFn, { isRefresh: false });\n },\n [execute]\n );\n\n const refreshFetchFn = useCallback(() => {\n if (lastFetchFn.current) {\n return execute(lastFetchFn.current, { isRefresh: true });\n }\n return null;\n }, [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 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>(options?: MutationOptions) {\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 mutationFn: () => Promise<HttpResponse<T>>,\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 [options?.invalidatesTags]\n );\n\n const reset = useCallback(() => {\n setState({ data: null, isMutating: false });\n }, []);\n\n return { ...state, executeMutationFn, reset };\n}"],"mappings":";AAAO,IAAM,WAAN,cAAuB,MAAM;AAAA,EAIlC,YAAY,SAAiB,WAAoB,YAAqB;AACpE,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,YAAY;AACjB,SAAK,aAAa;AAAA,EACpB;AACF;;;ACRA,IAAI,mBAAsC;AAOnC,IAAM,WAAW,CAAC,WAA6B;AACpD,qBAAmB;AAAA,IACjB,GAAG;AAAA,IACH,SAAS,OAAO,WAAW,CAAC;AAAA,EAC9B;AACF;AAOO,IAAM,mBAAmB,CAAC,WAAsC;AACrE,MAAI,CAAC,kBAAkB;AACrB,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AAEA,qBAAmB;AAAA,IACjB,GAAG;AAAA,IACH,GAAG;AAAA,IACH,SAAS;AAAA,MACP,GAAG,iBAAiB;AAAA,MACpB,GAAG,OAAO;AAAA,IACZ;AAAA,EACF;AACF;AAOO,IAAM,gBAAgB,MAAkB;AAC7C,MAAI,CAAC,kBAAkB;AACrB,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,SAAO;AACT;;;ACrCA,eAAsB,QACpB,UACA,UAAuB,CAAC,GACE;AAC1B,QAAM,SAAS,cAAc;AAC7B,QAAM,MAAM,GAAG,OAAO,OAAO,GAAG,QAAQ;AACxC,QAAM,cAAc,MAAM,OAAO,SAAS;AAE1C,QAAM,UAAuB;AAAA,IAC3B,gBAAgB;AAAA,IAChB,GAAI,cAAc,EAAE,eAAe,UAAU,WAAW,GAAG,IAAI,CAAC;AAAA,IAChE,GAAG,OAAO;AAAA,IACV,GAAG,QAAQ;AAAA,EACb;AAEA,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,KAAK,EAAE,GAAG,SAAS,QAAQ,CAAC;AAEzD,QAAI,CAAC,SAAS,IAAI;AAChB,UAAI;AACJ,UAAI;AACF,oBAAY,MAAM,SAAS,KAAK;AAAA,MAClC,QAAQ;AACN,oBAAY,EAAE,SAAS,wBAAwB,OAAO,UAAU;AAAA,MAClE;AAEA,YAAM,WAAW,IAAI;AAAA,QACnB,UAAU;AAAA,QACV,UAAU;AAAA,QACV,SAAS;AAAA,MACX;AAGA,YAAM,0BACJ,OAAO,2BAA2B,OAAO,wBAAwB,SAAS,IACtE,OAAO,0BACP,CAAC,GAAG;AAEV,YAAM,uBACJ,OAAO,wBAAwB,OAAO,qBAAqB,SAAS,IAChE,OAAO,uBACP,CAAC,GAAG;AAGV,UACE,OAAO,cAAc,kBACrB,wBAAwB,SAAS,SAAS,MAAM,GAChD;AACA,eAAO,aAAa,eAAe,QAAQ;AAAA,MAC7C,WACE,OAAO,cAAc,eACrB,qBAAqB,SAAS,SAAS,MAAM,GAC7C;AACA,eAAO,aAAa,YAAY,QAAQ;AAAA,MAC1C,WAAW,OAAO,cAAc,SAAS;AACvC,eAAO,aAAa,QAAQ,QAAQ;AAAA,MACtC;AAEA,YAAM;AAAA,IACR;AAEA,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,SAAS,OAAO;AACd,QAAI,iBAAiB,SAAU,OAAM;AACrC,UAAM,IAAI;AAAA,MACR,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MACzC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;;;AC/EA,SAAS,UAAU,QAAQ,WAAW,mBAAmB;;;ACEzD,IAAM,eAAN,MAAmB;AAAA,EAAnB;AACE,SAAQ,SAAqC,CAAC;AAAA;AAAA,EAE9C,KAAK,OAAe;AAClB,QAAI,CAAC,KAAK,OAAO,KAAK,EAAG;AACzB,SAAK,OAAO,KAAK,EAAE,QAAQ,CAAC,aAAa,SAAS,CAAC;AAAA,EACrD;AAAA,EAEA,YAAY,OAAe,UAAoB;AAC7C,QAAI,CAAC,KAAK,OAAO,KAAK,GAAG;AACvB,WAAK,OAAO,KAAK,IAAI,CAAC;AAAA,IACxB;AACA,SAAK,OAAO,KAAK,EAAE,KAAK,QAAQ;AAEhC,WAAO;AAAA,MACL,QAAQ,MAAM;AACZ,aAAK,OAAO,KAAK,IAAI,KAAK,OAAO,KAAK,EAAE,OAAO,CAAC,MAAM,MAAM,QAAQ;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,eAAe,IAAI,aAAa;;;ADPtC,SAAS,WAAc,SAAwB;AACpD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB;AAAA,IAChD,MAAM;AAAA,IACN,WAAW;AAAA,IACX,cAAc;AAAA,IACd,OAAO;AAAA,EACT,CAAC;AAED,QAAM,YAAY,OAAgB,IAAI;AACtC,QAAM,cAAc,OAAgD,IAAI;AAExE,YAAU,MAAM;AACd,cAAU,UAAU;AACpB,WAAO,MAAM;AACX,gBAAU,UAAU;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU;AAAA,IACd,OACE,SACA,gBACoC;AACpC,kBAAY,UAAU;AAEtB,eAAS,CAAC,UAAU;AAAA,QAClB,GAAG;AAAA,QACH,WAAW,CAAC,YAAY;AAAA,QACxB,cAAc,CAAC,CAAC,YAAY;AAAA,QAC5B,OAAO;AAAA,MACT,EAAE;AAEF,UAAI;AACF,cAAM,WAAW,MAAM,QAAQ;AAE/B,YAAI,UAAU,SAAS;AACrB,mBAAS;AAAA,YACP,MAAM,SAAS,QAAQ;AAAA,YACvB,WAAW;AAAA,YACX,cAAc;AAAA,YACd,OAAO;AAAA,UACT,CAAC;AAAA,QACH;AACA,eAAO;AAAA,MACT,SAAS,OAAO;AACd,cAAM,WAAW;AACjB,YAAI,UAAU,SAAS;AACrB,mBAAS;AAAA,YACP,MAAM;AAAA,YACN,WAAW;AAAA,YACX,cAAc;AAAA,YACd,OAAO;AAAA,UACT,CAAC;AAAA,QACH;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,iBAAiB;AAAA,IACrB,CAAC,YAA4C;AAC3C,aAAO,QAAQ,SAAS,EAAE,WAAW,MAAM,CAAC;AAAA,IAC9C;AAAA,IACA,CAAC,OAAO;AAAA,EACV;AAEA,QAAM,iBAAiB,YAAY,MAAM;AACvC,QAAI,YAAY,SAAS;AACvB,aAAO,QAAQ,YAAY,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,IACzD;AACA,WAAO;AAAA,EACT,GAAG,CAAC,OAAO,CAAC;AAEZ,YAAU,MAAM;AACd,QAAI,CAAC,SAAS,QAAQ,QAAQ,KAAK,WAAW,EAAG;AAEjD,UAAM,gBAAgB,QAAQ,KAAK;AAAA,MAAI,CAAC,QACtC,aAAa,YAAY,KAAK,MAAM;AAClC,uBAAe;AAAA,MACjB,CAAC;AAAA,IACH;AACA,WAAO,MAAM,cAAc,QAAQ,CAAC,QAAQ,IAAI,OAAO,CAAC;AAAA,EAC1D,GAAG,CAAC,SAAS,MAAM,cAAc,CAAC;AAElC,SAAO,EAAE,GAAG,OAAO,gBAAgB,eAAe;AACpD;;;AEvGA,SAAS,YAAAA,WAAU,eAAAC,cAAa,UAAAC,SAAQ,aAAAC,kBAAiB;AAelD,SAAS,cAAiB,SAA2B;AAC1D,QAAM,CAAC,OAAO,QAAQ,IAAIC,UAA2B;AAAA,IACnD,MAAM;AAAA,IACN,YAAY;AAAA,EACd,CAAC;AACD,QAAM,YAAYC,QAAgB,IAAI;AAEtC,EAAAC,WAAU,MAAM;AACd,cAAU,UAAU;AACpB,WAAO,MAAM;AACX,gBAAU,UAAU;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAoBC;AAAA,IACxB,OACE,YACA,mBACoC;AACpC,eAAS,CAAC,UAAU,EAAE,GAAG,MAAM,YAAY,KAAK,EAAE;AAElD,UAAI;AACF,cAAM,WAAW,MAAM,WAAW;AAElC,YAAI,UAAU,SAAS;AACrB,mBAAS;AAAA,YACP,MAAM,SAAS,QAAQ;AAAA,YACvB,YAAY;AAAA,UACd,CAAC;AAED,mBAAS,iBAAiB,QAAQ,CAAC,QAAQ;AACzC,yBAAa,KAAK,GAAG;AAAA,UACvB,CAAC;AAED,cAAI,SAAS,KAAM,iBAAgB,YAAY,SAAS,IAAI;AAAA,cACvD,iBAAgB,YAAY,IAAoB;AAAA,QACvD;AACA,eAAO;AAAA,MACT,SAAS,OAAO;AACd,cAAM,WAAW;AACjB,YAAI,UAAU,SAAS;AACrB,mBAAS;AAAA,YACP,MAAM;AAAA,YACN,YAAY;AAAA,UACd,CAAC;AACD,cAAI,SAAU,iBAAgB,UAAU,QAAQ;AAAA,QAClD;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,CAAC,SAAS,eAAe;AAAA,EAC3B;AAEA,QAAM,QAAQA,aAAY,MAAM;AAC9B,aAAS,EAAE,MAAM,MAAM,YAAY,MAAM,CAAC;AAAA,EAC5C,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,GAAG,OAAO,mBAAmB,MAAM;AAC9C;","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 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 {\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": "1.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "A lightweight, focused API fetching library for React and React Native applications.",
5
5
  "repository": {
6
6
  "type": "git",