fetchwire 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +568 -0
- package/dist/index.d.mts +172 -0
- package/dist/index.d.ts +172 -0
- package/dist/index.js +279 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +246 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Doan Vinh Phu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
# fetchwire
|
|
2
|
+
|
|
3
|
+
A lightweight, focused API fetching library for React and React Native applications.
|
|
4
|
+
|
|
5
|
+
**fetchwire** wraps the native `fetch` API in a global configuration layer. It is designed to make it easy to:
|
|
6
|
+
|
|
7
|
+
- Centralize your API base URL, auth token, and common headers.
|
|
8
|
+
- Handle errors consistently through a single `ApiError` type.
|
|
9
|
+
|
|
10
|
+
### When to use fetchwire
|
|
11
|
+
- **React / React Native apps** that:
|
|
12
|
+
- Want a **simple**, centralized way to call HTTP APIs.
|
|
13
|
+
- Prefer plain hooks over a heavier state management or query library.
|
|
14
|
+
- Need basic tag-based invalidation without a full cache layer.
|
|
15
|
+
- Handle errors in a consistent way across screens.
|
|
16
|
+
|
|
17
|
+
### When not to use fetchwire
|
|
18
|
+
- Consider a more full-featured solution (e.g. TanStack Query / React Query, SWR, RTK Query) if:
|
|
19
|
+
- You need advanced, automatic caching strategies.
|
|
20
|
+
- You need built-in pagination helpers, infinite queries.
|
|
21
|
+
- You need a more powerful data-fetching library and you want to avoid overlap.
|
|
22
|
+
|
|
23
|
+
## Support
|
|
24
|
+
If you find **fetchwire** helpful and want to support its development, you can buy me a coffee via:
|
|
25
|
+
|
|
26
|
+
[](https://ko-fi.com/doanvinhphu)
|
|
27
|
+
[](https://paypal.me/doanvinhphu)
|
|
28
|
+
|
|
29
|
+
Your support helps maintain the library and keep it up to date!
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **Global configuration with `initWire`**
|
|
34
|
+
- Configure `baseUrl`, default headers, and how to read the auth token.
|
|
35
|
+
- Optionally register global interceptors for 401/403/other errors.
|
|
36
|
+
- Customize which HTTP status codes are treated as **unauthorized** or **forbidden** via `unauthorizedStatusCodes` and `forbiddenStatusCodes` (defaults to `[401]` and `[403]`).
|
|
37
|
+
|
|
38
|
+
- **Typed HTTP wrapper with `wireApi`**
|
|
39
|
+
- Thin wrapper around `fetch` that:
|
|
40
|
+
- Appends the endpoint to a global `baseUrl`.
|
|
41
|
+
- Adds auth headers from `getToken`.
|
|
42
|
+
- Merges default and per-request headers.
|
|
43
|
+
- Converts server/network errors into a typed `ApiError`.
|
|
44
|
+
|
|
45
|
+
- **React hooks for requests**
|
|
46
|
+
- **`useFetchFn<T>`** for data fetching
|
|
47
|
+
- **`useMutationFn<T>`** for mutations
|
|
48
|
+
|
|
49
|
+
- **Tag-based invalidation**
|
|
50
|
+
- `useFetchFn` can subscribe to **tags**.
|
|
51
|
+
- `useMutationFn` can **invalidate tags** after a successful mutation.
|
|
52
|
+
- This gives you a simple, explicit way to refetch related data without a complex cache layer.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install fetchwire
|
|
60
|
+
# or
|
|
61
|
+
yarn add fetchwire
|
|
62
|
+
# or
|
|
63
|
+
pnpm add fetchwire
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Peer expectations
|
|
67
|
+
|
|
68
|
+
- React or React Native project using function components and hooks.
|
|
69
|
+
- TypeScript is recommended but not required.
|
|
70
|
+
- For React Native / Expo, make sure the global `fetch` is available (default in modern RN/Expo).
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Getting Started
|
|
75
|
+
|
|
76
|
+
### 1. Initialize fetchwire once at app startup
|
|
77
|
+
|
|
78
|
+
Call `initWire` once, as early as possible in your app lifecycle.
|
|
79
|
+
|
|
80
|
+
#### Simple React example
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// src/api/wire.ts
|
|
84
|
+
import { initWire } from 'fetchwire';
|
|
85
|
+
|
|
86
|
+
export function setupWire() {
|
|
87
|
+
initWire({
|
|
88
|
+
baseUrl: 'https://api.example.com',
|
|
89
|
+
headers: {
|
|
90
|
+
'x-client': 'web',
|
|
91
|
+
},
|
|
92
|
+
getToken: async () => {
|
|
93
|
+
// Read token from localStorage (or any storage you prefer)
|
|
94
|
+
return localStorage.getItem('access_token');
|
|
95
|
+
},
|
|
96
|
+
// Optional: customize which status codes should trigger auth interceptors
|
|
97
|
+
unauthorizedStatusCodes: [401, 419], // defaults to [401] if omitted
|
|
98
|
+
forbiddenStatusCodes: [403], // defaults to [403] if omitted
|
|
99
|
+
interceptors: {
|
|
100
|
+
onUnauthorized: (error) => {
|
|
101
|
+
// e.g. redirect to login, clear token, show toast, etc.
|
|
102
|
+
},
|
|
103
|
+
onForbidden: (error) => {
|
|
104
|
+
// e.g. show "no permission" message
|
|
105
|
+
},
|
|
106
|
+
onError: (error) => {
|
|
107
|
+
// fallback handler for other error statuses
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
// src/main.tsx or src/index.tsx
|
|
116
|
+
import React from 'react';
|
|
117
|
+
import ReactDOM from 'react-dom/client';
|
|
118
|
+
import App from './App';
|
|
119
|
+
import { setupWire } from './api/wire';
|
|
120
|
+
|
|
121
|
+
setupWire();
|
|
122
|
+
|
|
123
|
+
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
|
124
|
+
<React.StrictMode>
|
|
125
|
+
<App />
|
|
126
|
+
</React.StrictMode>,
|
|
127
|
+
);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
You **must** call `initWire` (directly or via a helper like `setupWire`) before using `wireApi`, `useFetchFn`, or `useMutationFn`.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Usage
|
|
135
|
+
|
|
136
|
+
### 1. Define API helpers with `wireApi`
|
|
137
|
+
|
|
138
|
+
A common pattern is to define small API helper functions in `src/api/*` that wrap your backend endpoints. For example, a simple CRUD helper for `Todo`:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// src/api/todo-api.ts
|
|
142
|
+
import { wireApi } from 'fetchwire';
|
|
143
|
+
|
|
144
|
+
export type Todo = {
|
|
145
|
+
id: string;
|
|
146
|
+
title: string;
|
|
147
|
+
completed: boolean;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export async function getTodosApi() {
|
|
151
|
+
return wireApi<Todo[]>('/todos', { method: 'GET' });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function createTodoApi(input: { title: string }) {
|
|
155
|
+
return wireApi<Todo>('/todos', {
|
|
156
|
+
method: 'POST',
|
|
157
|
+
body: JSON.stringify(input),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function toggleTodoApi(id: string) {
|
|
162
|
+
return wireApi<Todo>(`/todos/${id}/toggle`, {
|
|
163
|
+
method: 'POST',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function deleteTodoApi(id: string) {
|
|
168
|
+
return wireApi<null>(`/todos/${id}`, {
|
|
169
|
+
method: 'DELETE',
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
You can organize similar helpers for users, invoices, organizations, uploads, etc., all using `wireApi`.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
### 2. Fetch data with `useFetchFn`
|
|
179
|
+
|
|
180
|
+
`useFetchFn<T>` is a generic hook that manages state for running an async function returning `{ data: T }`.
|
|
181
|
+
|
|
182
|
+
**Key ideas:**
|
|
183
|
+
|
|
184
|
+
- You **do not** pass the async function into the hook directly.
|
|
185
|
+
- Instead, the hook returns `executeFetchFn`, which you call with a function that performs your API request.
|
|
186
|
+
- The hook tracks:
|
|
187
|
+
- `data: T | null`
|
|
188
|
+
- `isLoading: boolean`
|
|
189
|
+
- `isRefreshing: boolean`
|
|
190
|
+
- `error: ApiError | null`
|
|
191
|
+
- `executeFetchFn(fetchFn)`
|
|
192
|
+
- `refreshFetchFn()`
|
|
193
|
+
|
|
194
|
+
Example: loading and refreshing a todo list in a React component:
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
// src/components/TodoList.tsx
|
|
198
|
+
import { useEffect } from 'react';
|
|
199
|
+
import { useFetchFn } from 'fetchwire';
|
|
200
|
+
import { getTodosApi, type Todo } from '../api/todo-api';
|
|
201
|
+
|
|
202
|
+
export function TodoList() {
|
|
203
|
+
const {
|
|
204
|
+
data: todos,
|
|
205
|
+
isLoading,
|
|
206
|
+
isRefreshing,
|
|
207
|
+
error,
|
|
208
|
+
executeFetchFn: fetchTodos,
|
|
209
|
+
refreshFetchFn: refreshTodos,
|
|
210
|
+
} = useFetchFn<Todo[]>({
|
|
211
|
+
tags: ['todos'],
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
fetchTodos(() => getTodosApi());
|
|
216
|
+
}, [fetchTodos]);
|
|
217
|
+
|
|
218
|
+
if (isLoading) return <div>Loading...</div>;
|
|
219
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div>
|
|
223
|
+
<button onClick={() => refreshTodos()} disabled={isRefreshing}>
|
|
224
|
+
{isRefreshing ? 'Refreshing...' : 'Refresh'}
|
|
225
|
+
</button>
|
|
226
|
+
|
|
227
|
+
<ul>
|
|
228
|
+
{(todos ?? []).map((todo) => (
|
|
229
|
+
<li key={todo.id}>
|
|
230
|
+
{todo.title} {todo.completed ? '(done)' : ''}
|
|
231
|
+
</li>
|
|
232
|
+
))}
|
|
233
|
+
</ul>
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
### 3. Mutate data with `useMutationFn`
|
|
242
|
+
|
|
243
|
+
`useMutationFn<T>` is a hook for mutations (create/update/delete). It:
|
|
244
|
+
|
|
245
|
+
- Tracks `data` and `isMutating`.
|
|
246
|
+
- Lets you invalidate **tags** after a successful mutation.
|
|
247
|
+
- Accepts per-call `onSuccess` and `onError` callbacks.
|
|
248
|
+
|
|
249
|
+
Signature:
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
const {
|
|
253
|
+
data,
|
|
254
|
+
isMutating,
|
|
255
|
+
executeMutationFn,
|
|
256
|
+
reset,
|
|
257
|
+
} = useMutationFn<T>({ invalidatesTags?: string[] });
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Example: creating and toggling todos with `useMutationFn`:
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
// src/components/TodoActions.tsx
|
|
264
|
+
import { FormEvent, useState } from 'react';
|
|
265
|
+
import { useMutationFn } from 'fetchwire';
|
|
266
|
+
import {
|
|
267
|
+
createTodoApi,
|
|
268
|
+
toggleTodoApi,
|
|
269
|
+
deleteTodoApi,
|
|
270
|
+
type Todo,
|
|
271
|
+
} from '../api/todo-api';
|
|
272
|
+
|
|
273
|
+
export function TodoActions() {
|
|
274
|
+
const [title, setTitle] = useState('');
|
|
275
|
+
|
|
276
|
+
const {
|
|
277
|
+
isMutating: isCreating,
|
|
278
|
+
executeMutationFn: createTodo,
|
|
279
|
+
} = useMutationFn<Todo>({
|
|
280
|
+
invalidatesTags: ['todos'],
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const {
|
|
284
|
+
isMutating: isToggling,
|
|
285
|
+
executeMutationFn: toggleTodo,
|
|
286
|
+
} = useMutationFn<Todo>({
|
|
287
|
+
invalidatesTags: ['todos'],
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const {
|
|
291
|
+
isMutating: isDeleting,
|
|
292
|
+
executeMutationFn: deleteTodo,
|
|
293
|
+
} = useMutationFn<null>({
|
|
294
|
+
invalidatesTags: ['todos'],
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const handleCreate = (e: FormEvent) => {
|
|
298
|
+
e.preventDefault();
|
|
299
|
+
if (!title.trim()) return;
|
|
300
|
+
|
|
301
|
+
createTodo(() => createTodoApi({ title }), {
|
|
302
|
+
onSuccess: () => setTitle(''),
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Example usage of toggleTodo and deleteTodo in your UI:
|
|
307
|
+
// toggleTodo(() => toggleTodoApi(todoId))
|
|
308
|
+
// deleteTodo(() => deleteTodoApi(todoId))
|
|
309
|
+
|
|
310
|
+
return (
|
|
311
|
+
<form onSubmit={handleCreate}>
|
|
312
|
+
<input
|
|
313
|
+
value={title}
|
|
314
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
315
|
+
placeholder="New todo"
|
|
316
|
+
/>
|
|
317
|
+
<button type="submit" disabled={isCreating}>
|
|
318
|
+
{isCreating ? 'Adding...' : 'Add'}
|
|
319
|
+
</button>
|
|
320
|
+
</form>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
### 4. Tag-based invalidation and auto-refresh
|
|
328
|
+
|
|
329
|
+
Tags provide a simple way to coordinate refetches across your app:
|
|
330
|
+
|
|
331
|
+
- `useFetchFn({ tags: [...] })` subscribes the hook to one or more **tags**.
|
|
332
|
+
- `useMutationFn({ invalidatesTags: [...] })` emits those tags after a **successful** mutation.
|
|
333
|
+
- When a tag is emitted, all subscribed fetch hooks will automatically **call `refreshFetchFn`**.
|
|
334
|
+
|
|
335
|
+
This pattern keeps your code explicit and small, without introducing a full query cache library.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Error Handling
|
|
340
|
+
|
|
341
|
+
### Response object shape
|
|
342
|
+
|
|
343
|
+
By default, `wireApi` assumes your backend returns an object compatible with:
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
type HttpResponse<T> = {
|
|
347
|
+
data?: T;
|
|
348
|
+
message?: string;
|
|
349
|
+
status?: number;
|
|
350
|
+
};
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Successful response example:**
|
|
354
|
+
|
|
355
|
+
```json
|
|
356
|
+
{
|
|
357
|
+
"data": {
|
|
358
|
+
"id": "123",
|
|
359
|
+
"email": "user@example.com"
|
|
360
|
+
},
|
|
361
|
+
"message": "OK",
|
|
362
|
+
"status": 200
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Error response example (from server):**
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
{
|
|
370
|
+
"message": "Something went wrong",
|
|
371
|
+
"error": "ERROR_CODE"
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
If the response body cannot be parsed as JSON or a network error occurs, fetchwire falls back to a synthetic error with:
|
|
376
|
+
|
|
377
|
+
- `message`: from the thrown `Error` or `"Network error"`
|
|
378
|
+
- `errorCode`: `"NETWORK_ERROR"`
|
|
379
|
+
- `statusCode`: `520`
|
|
380
|
+
|
|
381
|
+
### ApiError
|
|
382
|
+
|
|
383
|
+
All errors are normalized to an `ApiError` instance. It extends `Error` and typically includes:
|
|
384
|
+
|
|
385
|
+
- `message: string`
|
|
386
|
+
- `errorCode: string | undefined` (e.g. from server `error` field or `'NETWORK_ERROR'`)
|
|
387
|
+
- `statusCode: number | undefined` (e.g. 401, 403, 500, 520, etc.)
|
|
388
|
+
|
|
389
|
+
### Using ApiError in components
|
|
390
|
+
|
|
391
|
+
With `useMutationFn`, you commonly handle errors with `onError`:
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
import { ApiError } from 'fetchwire';
|
|
395
|
+
|
|
396
|
+
executeMutationFn(() => someMutationApi(), {
|
|
397
|
+
onSuccess: () => {
|
|
398
|
+
// success logic
|
|
399
|
+
},
|
|
400
|
+
onError: (error: ApiError) => {
|
|
401
|
+
Alert.alert('Login failed', error.message || 'Unexpected error');
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
You can also read `error` directly from `useFetchFn` state if you want to render error messages in your UI.
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Configuration Reference
|
|
411
|
+
|
|
412
|
+
### `initWire(config)`
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
type WireInterceptors = {
|
|
416
|
+
onUnauthorized?: (error: ApiError) => void;
|
|
417
|
+
onForbidden?: (error: ApiError) => void;
|
|
418
|
+
onError?: (error: ApiError) => void;
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
type WireConfig = {
|
|
422
|
+
baseUrl: string;
|
|
423
|
+
headers?: Record<string, string>;
|
|
424
|
+
getToken: () => Promise<string | null>;
|
|
425
|
+
interceptors?: WireInterceptors;
|
|
426
|
+
unauthorizedStatusCodes?: number[];
|
|
427
|
+
forbiddenStatusCodes?: number[];
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
function initWire(config: WireConfig): void;
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
- **`baseUrl`**: Base API URL (e.g. `'https://api.example.com'`).
|
|
434
|
+
- **`headers`**: Global headers to apply to every request.
|
|
435
|
+
- **`getToken`**: Async function that returns a bearer token or `null`. If present, fetchwire adds `Authorization: Bearer <token>`.
|
|
436
|
+
- **`interceptors`** (optional):
|
|
437
|
+
- `onUnauthorized(error)`: Called when a 401 is returned.
|
|
438
|
+
- `onForbidden(error)`: Called when a 403 is returned.
|
|
439
|
+
- `onError(error)`: Called for other error statuses.
|
|
440
|
+
- **`unauthorizedStatusCodes`** (optional): List of HTTP status codes that should be treated as unauthorized (defaults to `[401]`).
|
|
441
|
+
- **`forbiddenStatusCodes`** (optional): List of HTTP status codes that should be treated as forbidden (defaults to `[403]`).
|
|
442
|
+
|
|
443
|
+
### `updateWireConfig(configPartial)`
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
function updateWireConfig(config: Partial<WireConfig>): void;
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
- Merges new configuration into the existing global config.
|
|
450
|
+
- Merges header objects deeply, so you can safely add new headers at runtime.
|
|
451
|
+
- Throws if called before `initWire`.
|
|
452
|
+
|
|
453
|
+
Use this if you need to adjust base URL, headers, or interceptors after startup.
|
|
454
|
+
|
|
455
|
+
### `getWireConfig()`
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
function getWireConfig(): WireConfig;
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
- Returns the current configuration.
|
|
462
|
+
- Throws if called before `initWire`.
|
|
463
|
+
- Intended for advanced usage (e.g. custom hooks or libraries that build on top of fetchwire).
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## API Reference
|
|
468
|
+
|
|
469
|
+
### `wireApi<T>(endpoint, options?)`
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
async function wireApi<T>(
|
|
473
|
+
endpoint: string,
|
|
474
|
+
options?: RequestInit & { headers?: Record<string, string> },
|
|
475
|
+
): Promise<HttpResponse<T>>;
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
- **`endpoint`**: Path relative to `baseUrl`, e.g. `'/invoice'`.
|
|
479
|
+
- **`options`**: Standard `fetch` options (method, body, headers, etc).
|
|
480
|
+
- **Return value**: Resolves to the parsed JSON body in the standard shape `{ data?: T; message?: string; status?: number }`.
|
|
481
|
+
- **Errors**: Throws `ApiError` on non-OK responses or network issues.
|
|
482
|
+
|
|
483
|
+
Usage:
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
const result = await wireApi<UserResponse>('/user/me', { method: 'GET' });
|
|
487
|
+
// result.data is your typed data
|
|
488
|
+
// result.message and result.status are available if your backend provides them
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
### `useFetchFn<T>(options?)`
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
type FetchOptions = {
|
|
497
|
+
tags?: string[];
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
function useFetchFn<T>(options?: FetchOptions): {
|
|
501
|
+
data: T | null;
|
|
502
|
+
isLoading: boolean;
|
|
503
|
+
isRefreshing: boolean;
|
|
504
|
+
error: ApiError | null;
|
|
505
|
+
executeFetchFn: (fetchFn: () => Promise<{ data: T }>) => Promise<{ data: T } | null>;
|
|
506
|
+
refreshFetchFn: () => Promise<{ data: T } | null> | null;
|
|
507
|
+
};
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
- **`options.tags`**: Optional array of tag strings to subscribe to. When a mutation invalidates these tags, `refreshFetchFn` is called automatically.
|
|
511
|
+
- **`executeFetchFn`**:
|
|
512
|
+
- Executes the provided async function.
|
|
513
|
+
- Updates `data`, `isLoading`, `error`.
|
|
514
|
+
- Stores the last function so it can be used by `refreshFetchFn`.
|
|
515
|
+
- **`refreshFetchFn`**:
|
|
516
|
+
- Re-runs the last `executeFetchFn` call, setting `isRefreshing` during the call.
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
### `useMutationFn<T>(options?)`
|
|
521
|
+
|
|
522
|
+
```ts
|
|
523
|
+
type MutationOptions = {
|
|
524
|
+
invalidatesTags?: string[];
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
type ExecuteOptions<T> = {
|
|
528
|
+
onSuccess?: (data: T | null) => void;
|
|
529
|
+
onError?: (error: ApiError) => void;
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
function useMutationFn<T>(options?: MutationOptions): {
|
|
533
|
+
data: T | null;
|
|
534
|
+
isMutating: boolean;
|
|
535
|
+
executeMutationFn: (
|
|
536
|
+
mutationFn: () => Promise<{ data: T }>,
|
|
537
|
+
executeOptions?: ExecuteOptions<T>,
|
|
538
|
+
) => Promise<{ data: T } | null>;
|
|
539
|
+
reset: () => void;
|
|
540
|
+
};
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
- **`options.invalidatesTags`**:
|
|
544
|
+
- List of tags to emit after a **successful** mutation.
|
|
545
|
+
- All `useFetchFn` hooks that subscribed to any of these tags will be refreshed.
|
|
546
|
+
- **`executeMutationFn`**:
|
|
547
|
+
- Executes the provided `mutationFn`.
|
|
548
|
+
- Sets `isMutating` while running.
|
|
549
|
+
- On success:
|
|
550
|
+
- Updates `data`.
|
|
551
|
+
- Emits all `invalidatesTags`.
|
|
552
|
+
- Calls `onSuccess` with `response.data` (or `null`).
|
|
553
|
+
- On error:
|
|
554
|
+
- Resets `isMutating`.
|
|
555
|
+
- Calls `onError` with an `ApiError` instance.
|
|
556
|
+
- **`reset`**:
|
|
557
|
+
- Resets `data` and `isMutating` to initial values.
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## License
|
|
562
|
+
|
|
563
|
+
**MIT License**
|
|
564
|
+
|
|
565
|
+
Copyright (c) Doanvinhphu
|
|
566
|
+
|
|
567
|
+
See the `LICENSE` file for details (or include the standard MIT text directly in your repository).
|
|
568
|
+
|