@ma-dev/api-client 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -0
- package/dist/http.d.ts +89 -7
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +140 -22
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,11 @@ Shared HTTP client infrastructure for frontend projects.
|
|
|
12
12
|
| `ApiResponse<T>` | Generic response envelope matching the NWC API contract |
|
|
13
13
|
| `HttpClient` | Inferred type of the object returned by `createHttpClient` |
|
|
14
14
|
| `HttpClientConfig` | Config interface for `createHttpClient` |
|
|
15
|
+
| `RequestOptions` | Per-request options (headers, params, signal) |
|
|
16
|
+
| `RetryConfig` | Retry strategy configuration |
|
|
17
|
+
| `QueryParams` | Query parameter map type |
|
|
18
|
+
| `ResponseInterceptor` | Callback type for response interceptors |
|
|
19
|
+
| `ErrorInterceptor` | Callback type for error interceptors |
|
|
15
20
|
|
|
16
21
|
## Installation
|
|
17
22
|
|
|
@@ -34,6 +39,8 @@ import { createHttpClient, tokenStore } from "@ma-dev/api-client";
|
|
|
34
39
|
export const httpClient = createHttpClient({
|
|
35
40
|
baseUrl: import.meta.env.VITE_API_URL,
|
|
36
41
|
getToken: tokenStore.getToken,
|
|
42
|
+
timeoutMs: 10_000,
|
|
43
|
+
retry: { attempts: 2 },
|
|
37
44
|
});
|
|
38
45
|
```
|
|
39
46
|
|
|
@@ -77,6 +84,142 @@ try {
|
|
|
77
84
|
}
|
|
78
85
|
```
|
|
79
86
|
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Features
|
|
90
|
+
|
|
91
|
+
### Query parameters
|
|
92
|
+
|
|
93
|
+
Pass a `params` object in `RequestOptions` instead of building query strings manually. Arrays are serialised as repeated keys.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
// GET /users?page=2&role=admin&role=editor
|
|
97
|
+
const users = await httpClient.get<UserListResponse>("/users", {
|
|
98
|
+
params: { page: 2, role: ["admin", "editor"] },
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`null` and `undefined` values are automatically omitted.
|
|
103
|
+
|
|
104
|
+
### Request timeouts
|
|
105
|
+
|
|
106
|
+
Set a global deadline for every request:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const httpClient = createHttpClient({
|
|
110
|
+
baseUrl: "...",
|
|
111
|
+
timeoutMs: 8_000, // 8 s global timeout
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Or override per-request using a standard `AbortSignal`:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const result = await httpClient.get("/slow-endpoint", {
|
|
119
|
+
signal: AbortSignal.timeout(3_000),
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Retry with exponential back-off
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const httpClient = createHttpClient({
|
|
127
|
+
baseUrl: "...",
|
|
128
|
+
retry: {
|
|
129
|
+
attempts: 3, // up to 3 retries (4 total attempts)
|
|
130
|
+
baseDelayMs: 300, // 300 ms → 600 ms → 1 200 ms
|
|
131
|
+
retryOn: [429, 500, 502, 503, 504], // default if omitted
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Retries are never triggered for `AbortError` (user/timeout cancellations).
|
|
137
|
+
|
|
138
|
+
### Interceptors
|
|
139
|
+
|
|
140
|
+
**Response interceptors** — called after every response, useful for logging or analytics:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const httpClient = createHttpClient({
|
|
144
|
+
baseUrl: "...",
|
|
145
|
+
responseInterceptors: [
|
|
146
|
+
(res, req) => {
|
|
147
|
+
console.log(`[${req.method}] ${req.path} → ${res.status}`);
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Error interceptors** — called before `ApiError` is thrown. Return `true` to suppress the error:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { useNavigate } from "react-router-dom";
|
|
157
|
+
|
|
158
|
+
const httpClient = createHttpClient({
|
|
159
|
+
baseUrl: "...",
|
|
160
|
+
errorInterceptors: [
|
|
161
|
+
(error) => {
|
|
162
|
+
if (error.status === 401) {
|
|
163
|
+
tokenStore.setToken(null);
|
|
164
|
+
window.location.href = "/login";
|
|
165
|
+
return true; // suppress the throw
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### File uploads (`multipart`)
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
const form = new FormData();
|
|
176
|
+
form.append("avatar", fileInput.files[0]);
|
|
177
|
+
|
|
178
|
+
const result = await httpClient.multipart<UploadResponse>("/profile/avatar", form);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`Content-Type` is omitted so the browser automatically supplies the correct `multipart/form-data; boundary=…` header.
|
|
182
|
+
|
|
183
|
+
### HEAD requests
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
await httpClient.head("/healthz");
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## API reference
|
|
192
|
+
|
|
193
|
+
### `createHttpClient(config)`
|
|
194
|
+
|
|
195
|
+
| Option | Type | Description |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `baseUrl` | `string` | Prepended to every path. No trailing slash. |
|
|
198
|
+
| `getToken` | `() => string \| null` | Returns the current bearer token. Read on every request. |
|
|
199
|
+
| `defaultHeaders` | `Record<string, string>` | Static headers merged into every request. |
|
|
200
|
+
| `timeoutMs` | `number` | Global request deadline in ms. Uses `AbortSignal.timeout`. |
|
|
201
|
+
| `retry` | `RetryConfig` | Retry strategy. Default: no retries. |
|
|
202
|
+
| `responseInterceptors` | `ResponseInterceptor[]` | Called after every response. |
|
|
203
|
+
| `errorInterceptors` | `ErrorInterceptor[]` | Called before an `ApiError` is thrown. |
|
|
204
|
+
|
|
205
|
+
### `RequestOptions`
|
|
206
|
+
|
|
207
|
+
| Option | Type | Description |
|
|
208
|
+
|---|---|---|
|
|
209
|
+
| `headers` | `HeadersInit` | Extra headers for this request only. |
|
|
210
|
+
| `params` | `QueryParams` | Query-string parameters. Arrays → repeated keys. |
|
|
211
|
+
| `signal` | `AbortSignal` | Cancellation / per-request timeout. |
|
|
212
|
+
|
|
213
|
+
### `RetryConfig`
|
|
214
|
+
|
|
215
|
+
| Option | Type | Default | Description |
|
|
216
|
+
|---|---|---|---|
|
|
217
|
+
| `attempts` | `number` | — | Max retries after the initial failure. |
|
|
218
|
+
| `baseDelayMs` | `number` | `300` | Base delay in ms for exponential back-off. |
|
|
219
|
+
| `retryOn` | `number[]` | `[408, 429, 500, 502, 503, 504]` | Status codes that trigger a retry. |
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
80
223
|
## Building the package
|
|
81
224
|
|
|
82
225
|
```bash
|
package/dist/http.d.ts
CHANGED
|
@@ -26,6 +26,51 @@ export declare class ApiError extends Error {
|
|
|
26
26
|
body?: unknown | undefined);
|
|
27
27
|
}
|
|
28
28
|
type TokenGetter = () => string | null;
|
|
29
|
+
/** Scalar query-param value types. Arrays are serialised as repeated keys. */
|
|
30
|
+
export type QueryParamValue = string | number | boolean | null | undefined;
|
|
31
|
+
export type QueryParams = Record<string, QueryParamValue | QueryParamValue[]>;
|
|
32
|
+
/** Called after every response — use for logging, analytics, etc. */
|
|
33
|
+
export type ResponseInterceptor = (response: Response, request: {
|
|
34
|
+
method: string;
|
|
35
|
+
path: string;
|
|
36
|
+
}) => void | Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Called when the server returns a non-2xx status **before** `ApiError` is
|
|
39
|
+
* thrown. Return `true` to suppress the error (e.g. redirect to login on 401).
|
|
40
|
+
*/
|
|
41
|
+
export type ErrorInterceptor = (error: ApiError, request: {
|
|
42
|
+
method: string;
|
|
43
|
+
path: string;
|
|
44
|
+
}) => boolean | void | Promise<boolean | void>;
|
|
45
|
+
export interface RetryConfig {
|
|
46
|
+
/**
|
|
47
|
+
* Maximum number of retry attempts after the initial request fails.
|
|
48
|
+
* Default: `0` (no retries).
|
|
49
|
+
*/
|
|
50
|
+
attempts: number;
|
|
51
|
+
/**
|
|
52
|
+
* Base delay in milliseconds between retries. Each subsequent attempt
|
|
53
|
+
* waits `baseDelayMs * 2^(attempt - 1)` (exponential back-off).
|
|
54
|
+
* Default: `300`.
|
|
55
|
+
*/
|
|
56
|
+
baseDelayMs?: number;
|
|
57
|
+
/**
|
|
58
|
+
* HTTP status codes that should trigger a retry.
|
|
59
|
+
* Default: `[408, 429, 500, 502, 503, 504]`.
|
|
60
|
+
*/
|
|
61
|
+
retryOn?: number[];
|
|
62
|
+
}
|
|
63
|
+
export interface RequestOptions {
|
|
64
|
+
/** Additional headers merged into this specific request only. */
|
|
65
|
+
headers?: HeadersInit;
|
|
66
|
+
/** Query string parameters appended to the URL. */
|
|
67
|
+
params?: QueryParams;
|
|
68
|
+
/**
|
|
69
|
+
* Abort signal for request cancellation.
|
|
70
|
+
* Tip: `AbortSignal.timeout(5_000)` for a 5 s deadline.
|
|
71
|
+
*/
|
|
72
|
+
signal?: AbortSignal;
|
|
73
|
+
}
|
|
29
74
|
export interface HttpClientConfig {
|
|
30
75
|
/**
|
|
31
76
|
* Base URL prepended to every request path.
|
|
@@ -39,15 +84,39 @@ export interface HttpClientConfig {
|
|
|
39
84
|
getToken?: TokenGetter;
|
|
40
85
|
/** Static headers merged into every request (e.g. custom API keys). */
|
|
41
86
|
defaultHeaders?: Record<string, string>;
|
|
87
|
+
/**
|
|
88
|
+
* Global request timeout in milliseconds. Applied to every request via
|
|
89
|
+
* `AbortSignal.timeout()` unless the caller supplies their own `signal`.
|
|
90
|
+
* Default: no timeout.
|
|
91
|
+
*/
|
|
92
|
+
timeoutMs?: number;
|
|
93
|
+
/** Retry strategy for transient failures. Default: no retries. */
|
|
94
|
+
retry?: RetryConfig;
|
|
95
|
+
/**
|
|
96
|
+
* Interceptors called after every response (2xx and non-2xx).
|
|
97
|
+
* Useful for logging or analytics.
|
|
98
|
+
*/
|
|
99
|
+
responseInterceptors?: ResponseInterceptor[];
|
|
100
|
+
/**
|
|
101
|
+
* Interceptors called before an `ApiError` is thrown.
|
|
102
|
+
* Return `true` from any interceptor to suppress the error entirely
|
|
103
|
+
* (e.g. redirect to `/login` on 401 and swallow the throw).
|
|
104
|
+
*/
|
|
105
|
+
errorInterceptors?: ErrorInterceptor[];
|
|
42
106
|
}
|
|
43
107
|
/**
|
|
44
108
|
* Creates a thin, fully-typed `fetch` wrapper.
|
|
45
109
|
*
|
|
46
110
|
* Features:
|
|
47
|
-
* - Prepends `baseUrl` to every path.
|
|
111
|
+
* - Prepends `baseUrl` to every request path.
|
|
48
112
|
* - Injects `Authorization: Bearer <token>` when `getToken` returns a value.
|
|
49
113
|
* - Throws `ApiError` on non-2xx responses with a structured message.
|
|
50
114
|
* - Generic return types — callers get typed response bodies with zero casting.
|
|
115
|
+
* - Query param serialisation via `options.params`.
|
|
116
|
+
* - Per-request (or global) timeout via `AbortSignal.timeout`.
|
|
117
|
+
* - Configurable retry with exponential back-off.
|
|
118
|
+
* - Response & error interceptors for cross-cutting concerns.
|
|
119
|
+
* - `multipart` helper for `FormData` / file uploads.
|
|
51
120
|
*
|
|
52
121
|
* @example
|
|
53
122
|
* import { createHttpClient, tokenStore } from "@ma-dev/api-client";
|
|
@@ -55,14 +124,27 @@ export interface HttpClientConfig {
|
|
|
55
124
|
* export const httpClient = createHttpClient({
|
|
56
125
|
* baseUrl: import.meta.env.VITE_API_URL,
|
|
57
126
|
* getToken: tokenStore.getToken,
|
|
127
|
+
* timeoutMs: 10_000,
|
|
128
|
+
* retry: { attempts: 2 },
|
|
58
129
|
* });
|
|
59
130
|
*/
|
|
60
|
-
export declare function createHttpClient({ baseUrl, getToken, defaultHeaders, }: HttpClientConfig): {
|
|
61
|
-
get: <T>(path: string,
|
|
62
|
-
post: <T>(path: string, body: unknown,
|
|
63
|
-
put: <T>(path: string, body: unknown,
|
|
64
|
-
patch: <T>(path: string, body: unknown,
|
|
65
|
-
delete: <T>(path: string,
|
|
131
|
+
export declare function createHttpClient({ baseUrl, getToken, defaultHeaders, timeoutMs, retry, responseInterceptors, errorInterceptors, }: HttpClientConfig): {
|
|
132
|
+
get: <T>(path: string, options?: RequestOptions) => Promise<T>;
|
|
133
|
+
post: <T>(path: string, body: unknown, options?: RequestOptions) => Promise<T>;
|
|
134
|
+
put: <T>(path: string, body: unknown, options?: RequestOptions) => Promise<T>;
|
|
135
|
+
patch: <T>(path: string, body: unknown, options?: RequestOptions) => Promise<T>;
|
|
136
|
+
delete: <T>(path: string, options?: RequestOptions) => Promise<T>;
|
|
137
|
+
head: (path: string, options?: RequestOptions) => Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* Upload `FormData` (e.g. files). Skips `Content-Type: application/json`
|
|
140
|
+
* so the browser can set the correct `multipart/form-data; boundary=…` header.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* const form = new FormData();
|
|
144
|
+
* form.append("file", fileInput.files[0]);
|
|
145
|
+
* await httpClient.multipart<UploadResponse>("/uploads", form);
|
|
146
|
+
*/
|
|
147
|
+
multipart: <T>(path: string, form: FormData, options?: RequestOptions) => Promise<T>;
|
|
66
148
|
};
|
|
67
149
|
/** Inferred type of the object returned by `createHttpClient`. */
|
|
68
150
|
export type HttpClient = ReturnType<typeof createHttpClient>;
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAE/B,6CAA6C;aAC7B,MAAM,EAAE,MAAM;IAG9B,6DAA6D;aAC7C,IAAI,CAAC,EAAE,OAAO;;IAL9B,6CAA6C;IAC7B,MAAM,EAAE,MAAM;IAC9B,mEAAmE;IACnE,OAAO,EAAE,MAAM;IACf,6DAA6D;IAC7C,IAAI,CAAC,EAAE,OAAO,YAAA;CAOjC;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAE/B,6CAA6C;aAC7B,MAAM,EAAE,MAAM;IAG9B,6DAA6D;aAC7C,IAAI,CAAC,EAAE,OAAO;;IAL9B,6CAA6C;IAC7B,MAAM,EAAE,MAAM;IAC9B,mEAAmE;IACnE,OAAO,EAAE,MAAM;IACf,6DAA6D;IAC7C,IAAI,CAAC,EAAE,OAAO,YAAA;CAOjC;AAiED,KAAK,WAAW,GAAG,MAAM,MAAM,GAAG,IAAI,CAAC;AAEvC,8EAA8E;AAC9E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAC3E,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,EAAE,CAAC,CAAC;AAE9E,qEAAqE;AACrE,MAAM,MAAM,mBAAmB,GAAG,CAChC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,KACtC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,KACtC,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;AAE9C,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,mDAAmD;IACnD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC7C;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACxC;AAQD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,QAAQ,EACR,cAAmB,EACnB,SAAS,EACT,KAAK,EACL,oBAAyB,EACzB,iBAAsB,GACvB,EAAE,gBAAgB;UA2HT,CAAC,QAAQ,MAAM,YAAY,cAAc;WAGxC,CAAC,QAAQ,MAAM,QAAQ,OAAO,YAAY,cAAc;UAGzD,CAAC,QAAQ,MAAM,QAAQ,OAAO,YAAY,cAAc;YAGtD,CAAC,QAAQ,MAAM,QAAQ,OAAO,YAAY,cAAc;aAGvD,CAAC,QAAQ,MAAM,YAAY,cAAc;iBAGrC,MAAM,YAAY,cAAc;IAG7C;;;;;;;;OAQG;gBACS,CAAC,QAAQ,MAAM,QAAQ,QAAQ,YAAY,cAAc;EAGxE;AAED,kEAAkE;AAClE,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|
package/dist/http.js
CHANGED
|
@@ -36,15 +36,23 @@ export class ApiError extends Error {
|
|
|
36
36
|
// ---------------------------------------------------------------------------
|
|
37
37
|
// Internal helpers
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
39
|
+
/**
|
|
40
|
+
* BUG FIX: The original called `res.json()` even for `text/plain` responses,
|
|
41
|
+
* which throws a SyntaxError on non-JSON text bodies. Now we correctly use
|
|
42
|
+
* `res.text()` for plain text and only attempt JSON parsing for JSON content.
|
|
43
|
+
*/
|
|
39
44
|
async function parseBody(res) {
|
|
40
45
|
const ct = res.headers.get("Content-Type") ?? "";
|
|
41
46
|
try {
|
|
42
|
-
if (ct.includes("application/json")
|
|
47
|
+
if (ct.includes("application/json")) {
|
|
43
48
|
return await res.json();
|
|
44
49
|
}
|
|
50
|
+
if (ct.includes("text/")) {
|
|
51
|
+
return await res.text();
|
|
52
|
+
}
|
|
45
53
|
}
|
|
46
54
|
catch {
|
|
47
|
-
// Body may be empty or unparseable — treat as null
|
|
55
|
+
// Body may be empty or unparseable — treat as null.
|
|
48
56
|
}
|
|
49
57
|
return null;
|
|
50
58
|
}
|
|
@@ -56,16 +64,42 @@ function extractErrorMessage(body, fallback) {
|
|
|
56
64
|
(typeof b.detail === "string" && b.detail) ||
|
|
57
65
|
fallback);
|
|
58
66
|
}
|
|
67
|
+
if (typeof body === "string" && body.length > 0) {
|
|
68
|
+
return body;
|
|
69
|
+
}
|
|
59
70
|
return fallback;
|
|
60
71
|
}
|
|
72
|
+
function buildQueryString(params) {
|
|
73
|
+
const entries = Object.entries(params).filter(([, v]) => v !== undefined && v !== null);
|
|
74
|
+
if (entries.length === 0)
|
|
75
|
+
return "";
|
|
76
|
+
const qs = entries
|
|
77
|
+
.flatMap(([k, v]) => Array.isArray(v)
|
|
78
|
+
? v.map((item) => `${encodeURIComponent(k)}=${encodeURIComponent(String(item))}`)
|
|
79
|
+
: [`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`])
|
|
80
|
+
.join("&");
|
|
81
|
+
return `?${qs}`;
|
|
82
|
+
}
|
|
83
|
+
function sleep(ms) {
|
|
84
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
85
|
+
}
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// HTTP client factory
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
const DEFAULT_RETRY_ON = [408, 429, 500, 502, 503, 504];
|
|
61
90
|
/**
|
|
62
91
|
* Creates a thin, fully-typed `fetch` wrapper.
|
|
63
92
|
*
|
|
64
93
|
* Features:
|
|
65
|
-
* - Prepends `baseUrl` to every path.
|
|
94
|
+
* - Prepends `baseUrl` to every request path.
|
|
66
95
|
* - Injects `Authorization: Bearer <token>` when `getToken` returns a value.
|
|
67
96
|
* - Throws `ApiError` on non-2xx responses with a structured message.
|
|
68
97
|
* - Generic return types — callers get typed response bodies with zero casting.
|
|
98
|
+
* - Query param serialisation via `options.params`.
|
|
99
|
+
* - Per-request (or global) timeout via `AbortSignal.timeout`.
|
|
100
|
+
* - Configurable retry with exponential back-off.
|
|
101
|
+
* - Response & error interceptors for cross-cutting concerns.
|
|
102
|
+
* - `multipart` helper for `FormData` / file uploads.
|
|
69
103
|
*
|
|
70
104
|
* @example
|
|
71
105
|
* import { createHttpClient, tokenStore } from "@ma-dev/api-client";
|
|
@@ -73,12 +107,18 @@ function extractErrorMessage(body, fallback) {
|
|
|
73
107
|
* export const httpClient = createHttpClient({
|
|
74
108
|
* baseUrl: import.meta.env.VITE_API_URL,
|
|
75
109
|
* getToken: tokenStore.getToken,
|
|
110
|
+
* timeoutMs: 10_000,
|
|
111
|
+
* retry: { attempts: 2 },
|
|
76
112
|
* });
|
|
77
113
|
*/
|
|
78
|
-
export function createHttpClient({ baseUrl, getToken, defaultHeaders = {}, }) {
|
|
79
|
-
|
|
114
|
+
export function createHttpClient({ baseUrl, getToken, defaultHeaders = {}, timeoutMs, retry, responseInterceptors = [], errorInterceptors = [], }) {
|
|
115
|
+
const retryAttempts = retry?.attempts ?? 0;
|
|
116
|
+
const retryBaseDelay = retry?.baseDelayMs ?? 300;
|
|
117
|
+
const retryOn = retry?.retryOn ?? DEFAULT_RETRY_ON;
|
|
118
|
+
function buildHeaders(extra = {}, isFormData = false) {
|
|
80
119
|
const headers = new Headers({
|
|
81
|
-
|
|
120
|
+
// Omit Content-Type for FormData — the browser sets it with the boundary.
|
|
121
|
+
...(!isFormData ? { "Content-Type": "application/json" } : {}),
|
|
82
122
|
Accept: "application/json, text/plain",
|
|
83
123
|
...defaultHeaders,
|
|
84
124
|
...extra,
|
|
@@ -89,25 +129,103 @@ export function createHttpClient({ baseUrl, getToken, defaultHeaders = {}, }) {
|
|
|
89
129
|
}
|
|
90
130
|
return headers;
|
|
91
131
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
132
|
+
function resolveSignal(callerSignal) {
|
|
133
|
+
if (callerSignal)
|
|
134
|
+
return callerSignal;
|
|
135
|
+
if (timeoutMs !== undefined)
|
|
136
|
+
return AbortSignal.timeout(timeoutMs);
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
async function request(method, path, body, options = {}, isFormData = false) {
|
|
140
|
+
const { headers: extraHeaders, params, signal: callerSignal } = options;
|
|
141
|
+
const qs = params ? buildQueryString(params) : "";
|
|
142
|
+
const url = `${baseUrl}${path}${qs}`;
|
|
143
|
+
const signal = resolveSignal(callerSignal);
|
|
144
|
+
let lastError;
|
|
145
|
+
for (let attempt = 0; attempt <= retryAttempts; attempt++) {
|
|
146
|
+
if (attempt > 0) {
|
|
147
|
+
await sleep(retryBaseDelay * 2 ** (attempt - 1));
|
|
148
|
+
}
|
|
149
|
+
let res;
|
|
150
|
+
try {
|
|
151
|
+
res = await fetch(url, {
|
|
152
|
+
method,
|
|
153
|
+
headers: buildHeaders(extraHeaders ?? {}, isFormData),
|
|
154
|
+
body: body instanceof FormData
|
|
155
|
+
? body
|
|
156
|
+
: body !== undefined
|
|
157
|
+
? JSON.stringify(body)
|
|
158
|
+
: undefined,
|
|
159
|
+
signal,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch (networkError) {
|
|
163
|
+
// Network-level failure (offline, DNS, timeout, abort).
|
|
164
|
+
// Only retry if we haven't exhausted attempts and it's not an abort.
|
|
165
|
+
if (networkError instanceof DOMException &&
|
|
166
|
+
networkError.name === "AbortError") {
|
|
167
|
+
throw networkError;
|
|
168
|
+
}
|
|
169
|
+
lastError = networkError;
|
|
170
|
+
if (attempt < retryAttempts)
|
|
171
|
+
continue;
|
|
172
|
+
throw networkError;
|
|
173
|
+
}
|
|
174
|
+
// Run response interceptors (fire-and-forget; errors are swallowed).
|
|
175
|
+
for (const interceptor of responseInterceptors) {
|
|
176
|
+
try {
|
|
177
|
+
await interceptor(res.clone(), { method, path });
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Interceptors must not break the request pipeline.
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const parsed = await parseBody(res);
|
|
184
|
+
if (!res.ok) {
|
|
185
|
+
const message = extractErrorMessage(parsed, `Request failed with status ${res.status}`);
|
|
186
|
+
const apiError = new ApiError(res.status, message, parsed);
|
|
187
|
+
// Run error interceptors.
|
|
188
|
+
let suppressed = false;
|
|
189
|
+
for (const interceptor of errorInterceptors) {
|
|
190
|
+
try {
|
|
191
|
+
const result = await interceptor(apiError, { method, path });
|
|
192
|
+
if (result === true)
|
|
193
|
+
suppressed = true;
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// Interceptors must not break the request pipeline.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (suppressed)
|
|
200
|
+
return undefined;
|
|
201
|
+
// Retry on configured status codes.
|
|
202
|
+
if (attempt < retryAttempts && retryOn.includes(res.status)) {
|
|
203
|
+
lastError = apiError;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
throw apiError;
|
|
207
|
+
}
|
|
208
|
+
return parsed;
|
|
102
209
|
}
|
|
103
|
-
|
|
210
|
+
throw lastError;
|
|
104
211
|
}
|
|
105
212
|
return {
|
|
106
|
-
get: (path,
|
|
107
|
-
post: (path, body,
|
|
108
|
-
put: (path, body,
|
|
109
|
-
patch: (path, body,
|
|
110
|
-
delete: (path,
|
|
213
|
+
get: (path, options) => request("GET", path, undefined, options),
|
|
214
|
+
post: (path, body, options) => request("POST", path, body, options),
|
|
215
|
+
put: (path, body, options) => request("PUT", path, body, options),
|
|
216
|
+
patch: (path, body, options) => request("PATCH", path, body, options),
|
|
217
|
+
delete: (path, options) => request("DELETE", path, undefined, options),
|
|
218
|
+
head: (path, options) => request("HEAD", path, undefined, options),
|
|
219
|
+
/**
|
|
220
|
+
* Upload `FormData` (e.g. files). Skips `Content-Type: application/json`
|
|
221
|
+
* so the browser can set the correct `multipart/form-data; boundary=…` header.
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* const form = new FormData();
|
|
225
|
+
* form.append("file", fileInput.files[0]);
|
|
226
|
+
* await httpClient.multipart<UploadResponse>("/uploads", form);
|
|
227
|
+
*/
|
|
228
|
+
multipart: (path, form, options) => request("POST", path, form, options, true),
|
|
111
229
|
};
|
|
112
230
|
}
|
|
113
231
|
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,gEAAgE;AAChE,8EAA8E;AAE9E;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGf;IAIA;IANlB;IACE,6CAA6C;IAC7B,MAAc;IAC9B,mEAAmE;IACnE,OAAe;IACf,6DAA6D;IAC7C,IAAc;QAE9B,KAAK,CAAC,OAAO,CAAC,CAAC;QANC,WAAM,GAAN,MAAM,CAAQ;QAId,SAAI,GAAJ,IAAI,CAAU;QAG9B,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,sEAAsE;QACtE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IAClD,CAAC;CACF;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,KAAK,UAAU,SAAS,CAAC,GAAa;IACpC,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IACjD,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,10 +6,19 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* import { createHttpClient, tokenStore, ApiError } from "@ma-dev/api-client";
|
|
9
|
-
* import type {
|
|
9
|
+
* import type {
|
|
10
|
+
* ApiResponse,
|
|
11
|
+
* HttpClient,
|
|
12
|
+
* HttpClientConfig,
|
|
13
|
+
* RequestOptions,
|
|
14
|
+
* RetryConfig,
|
|
15
|
+
* QueryParams,
|
|
16
|
+
* ResponseInterceptor,
|
|
17
|
+
* ErrorInterceptor,
|
|
18
|
+
* } from "@ma-dev/api-client";
|
|
10
19
|
*/
|
|
11
|
-
export { ApiError, createHttpClient
|
|
12
|
-
export type { HttpClient, HttpClientConfig, } from "./http";
|
|
20
|
+
export { ApiError, createHttpClient } from "./http";
|
|
21
|
+
export type { HttpClient, HttpClientConfig, RequestOptions, RetryConfig, QueryParams, QueryParamValue, ResponseInterceptor, ErrorInterceptor, } from "./http";
|
|
13
22
|
export { tokenStore } from "./tokenStore";
|
|
14
23
|
export type { ApiResponse } from "./types";
|
|
15
24
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACpD,YAAY,EACV,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,QAAQ,CAAC;AAGhB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,10 +6,19 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* import { createHttpClient, tokenStore, ApiError } from "@ma-dev/api-client";
|
|
9
|
-
* import type {
|
|
9
|
+
* import type {
|
|
10
|
+
* ApiResponse,
|
|
11
|
+
* HttpClient,
|
|
12
|
+
* HttpClientConfig,
|
|
13
|
+
* RequestOptions,
|
|
14
|
+
* RetryConfig,
|
|
15
|
+
* QueryParams,
|
|
16
|
+
* ResponseInterceptor,
|
|
17
|
+
* ErrorInterceptor,
|
|
18
|
+
* } from "@ma-dev/api-client";
|
|
10
19
|
*/
|
|
11
20
|
// HTTP client factory + error class
|
|
12
|
-
export { ApiError, createHttpClient
|
|
21
|
+
export { ApiError, createHttpClient } from "./http";
|
|
13
22
|
// Token store singleton
|
|
14
23
|
export { tokenStore } from "./tokenStore";
|
|
15
24
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,oCAAoC;AACpC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAYpD,wBAAwB;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
|