endpoint-fetcher 1.0.1 → 1.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 +24 -14
- package/dist/index.d.mts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +26 -3
- package/dist/index.mjs +26 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -146,7 +146,7 @@ await api.deleteUser({ id: '123' });
|
|
|
146
146
|
|
|
147
147
|
### 6. Custom Handler for Special Cases
|
|
148
148
|
|
|
149
|
-
Use custom handlers when you need full control over the request (e.g., file uploads, custom headers, non-JSON responses).
|
|
149
|
+
Use custom handlers when you need full control over the request (e.g., file uploads, custom headers, non-JSON responses). The handler receives `{ input, fetch, method, path, baseUrl }`.
|
|
150
150
|
|
|
151
151
|
```typescript
|
|
152
152
|
const api = createApiClient(
|
|
@@ -154,12 +154,12 @@ const api = createApiClient(
|
|
|
154
154
|
uploadFile: {
|
|
155
155
|
method: 'POST',
|
|
156
156
|
path: '/upload',
|
|
157
|
-
handler: async ({ input, fetch }) => {
|
|
157
|
+
handler: async ({ input, fetch, path, baseUrl }) => {
|
|
158
158
|
const formData = new FormData();
|
|
159
159
|
formData.append('file', input.file);
|
|
160
160
|
formData.append('category', input.category);
|
|
161
161
|
|
|
162
|
-
const response = await fetch(
|
|
162
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
163
163
|
method: 'POST',
|
|
164
164
|
body: formData,
|
|
165
165
|
// Note: Don't set Content-Type for FormData
|
|
@@ -171,17 +171,17 @@ const api = createApiClient(
|
|
|
171
171
|
|
|
172
172
|
return response.json();
|
|
173
173
|
},
|
|
174
|
-
} as EndpointConfig
|
|
174
|
+
} as EndpointConfig<
|
|
175
175
|
{ file: File; category: string },
|
|
176
176
|
{ url: string; id: string }
|
|
177
177
|
>,
|
|
178
178
|
|
|
179
179
|
downloadFile: {
|
|
180
180
|
method: 'GET',
|
|
181
|
-
path:
|
|
182
|
-
handler: async ({ input, fetch }) => {
|
|
181
|
+
path: (input: { id: string }) => `/files/${input.id}`,
|
|
182
|
+
handler: async ({ input, fetch, path, baseUrl }) => {
|
|
183
183
|
const response = await fetch(
|
|
184
|
-
|
|
184
|
+
`${baseUrl}${path}`,
|
|
185
185
|
{ method: 'GET' }
|
|
186
186
|
);
|
|
187
187
|
|
|
@@ -353,21 +353,21 @@ const api = createApiClient(
|
|
|
353
353
|
searchPosts: {
|
|
354
354
|
method: 'GET',
|
|
355
355
|
path: '/posts/search',
|
|
356
|
-
handler: async ({ input, fetch, path }) => {
|
|
356
|
+
handler: async ({ input, fetch, path, baseUrl }) => {
|
|
357
357
|
const params = new URLSearchParams({
|
|
358
358
|
q: input.query,
|
|
359
359
|
...(input.limit && { limit: input.limit.toString() }),
|
|
360
360
|
});
|
|
361
361
|
|
|
362
362
|
const response = await fetch(
|
|
363
|
-
|
|
363
|
+
`${baseUrl}${path}?${params}`,
|
|
364
364
|
{ method: 'GET' }
|
|
365
365
|
);
|
|
366
366
|
|
|
367
367
|
if (!response.ok) throw new Error('Search failed');
|
|
368
368
|
return response.json();
|
|
369
369
|
},
|
|
370
|
-
} as EndpointConfig
|
|
370
|
+
} as EndpointConfig<
|
|
371
371
|
{ query: string; limit?: number },
|
|
372
372
|
Post[],
|
|
373
373
|
ApiError
|
|
@@ -398,18 +398,28 @@ Creates a type-safe API client.
|
|
|
398
398
|
**Parameters:**
|
|
399
399
|
|
|
400
400
|
- `endpoints`: Object mapping endpoint names to configurations
|
|
401
|
-
|
|
402
401
|
- `method`: HTTP method ('GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE')
|
|
403
402
|
- `path`: Static string or function `(input) => string`
|
|
404
|
-
- `handler`: Optional custom handler function (receives `{ input, fetch, method, path }`)
|
|
403
|
+
- `handler`: Optional custom handler function (receives `{ input, fetch, method, path, baseUrl }`)
|
|
405
404
|
- `config`: Client configuration
|
|
406
|
-
|
|
407
405
|
- `baseUrl`: Base URL for all requests
|
|
408
406
|
- `fetch`: Optional custom fetch instance
|
|
409
407
|
- `defaultHeaders`: Optional headers applied to all requests
|
|
410
408
|
|
|
411
409
|
**Returns:** Type-safe client object with methods for each endpoint
|
|
412
410
|
|
|
411
|
+
### Custom Handler Parameters
|
|
412
|
+
|
|
413
|
+
When using a custom handler, you receive an object with:
|
|
414
|
+
|
|
415
|
+
- `input`: The typed input parameters passed to the endpoint
|
|
416
|
+
- `fetch`: The fetch instance (either custom or global)
|
|
417
|
+
- `method`: The HTTP method for this endpoint
|
|
418
|
+
- `path`: The resolved path (after applying input to path function if applicable)
|
|
419
|
+
- `baseUrl`: The base URL from the client configuration
|
|
420
|
+
|
|
421
|
+
This allows you to construct full URLs using `${baseUrl}${path}` in your custom handlers.
|
|
422
|
+
|
|
413
423
|
## TypeScript Support
|
|
414
424
|
|
|
415
425
|
Full TypeScript support with generic types:
|
|
@@ -424,4 +434,4 @@ EndpointConfig<TInput, TOutput, TError>
|
|
|
424
434
|
|
|
425
435
|
## License
|
|
426
436
|
|
|
427
|
-
MIT
|
|
437
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -7,15 +7,28 @@ type EndpointConfig<TInput = any, TOutput = any, TError = any> = {
|
|
|
7
7
|
fetch: typeof fetch;
|
|
8
8
|
method: HttpMethod;
|
|
9
9
|
path: string;
|
|
10
|
+
baseUrl: string;
|
|
10
11
|
}) => Promise<TOutput>;
|
|
11
12
|
};
|
|
13
|
+
type Hooks = {
|
|
14
|
+
beforeRequest?: (url: string, init: RequestInit) => Promise<{
|
|
15
|
+
url: string;
|
|
16
|
+
init: RequestInit;
|
|
17
|
+
}> | {
|
|
18
|
+
url: string;
|
|
19
|
+
init: RequestInit;
|
|
20
|
+
};
|
|
21
|
+
afterResponse?: (response: Response, url: string, init: RequestInit) => Promise<Response> | Response;
|
|
22
|
+
onError?: (error: unknown) => Promise<void> | void;
|
|
23
|
+
};
|
|
12
24
|
type ApiConfig = {
|
|
13
25
|
baseUrl: string;
|
|
14
26
|
fetch?: typeof fetch;
|
|
15
27
|
defaultHeaders?: HeadersInit;
|
|
28
|
+
hooks?: Hooks;
|
|
16
29
|
};
|
|
17
30
|
type EndpointDefinitions = Record<string, EndpointConfig>;
|
|
18
31
|
|
|
19
32
|
declare function createApiClient<TEndpoints extends EndpointDefinitions>(endpoints: TEndpoints, config: ApiConfig): { [K in keyof TEndpoints]: TEndpoints[K] extends infer T ? T extends TEndpoints[K] ? T extends EndpointConfig<infer TInput, infer TOutput, any> ? (input: TInput) => Promise<TOutput> : never : never : never; };
|
|
20
33
|
|
|
21
|
-
export { type ApiConfig, type EndpointConfig, type EndpointDefinitions, type HttpMethod, createApiClient };
|
|
34
|
+
export { type ApiConfig, type EndpointConfig, type EndpointDefinitions, type Hooks, type HttpMethod, createApiClient };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,15 +7,28 @@ type EndpointConfig<TInput = any, TOutput = any, TError = any> = {
|
|
|
7
7
|
fetch: typeof fetch;
|
|
8
8
|
method: HttpMethod;
|
|
9
9
|
path: string;
|
|
10
|
+
baseUrl: string;
|
|
10
11
|
}) => Promise<TOutput>;
|
|
11
12
|
};
|
|
13
|
+
type Hooks = {
|
|
14
|
+
beforeRequest?: (url: string, init: RequestInit) => Promise<{
|
|
15
|
+
url: string;
|
|
16
|
+
init: RequestInit;
|
|
17
|
+
}> | {
|
|
18
|
+
url: string;
|
|
19
|
+
init: RequestInit;
|
|
20
|
+
};
|
|
21
|
+
afterResponse?: (response: Response, url: string, init: RequestInit) => Promise<Response> | Response;
|
|
22
|
+
onError?: (error: unknown) => Promise<void> | void;
|
|
23
|
+
};
|
|
12
24
|
type ApiConfig = {
|
|
13
25
|
baseUrl: string;
|
|
14
26
|
fetch?: typeof fetch;
|
|
15
27
|
defaultHeaders?: HeadersInit;
|
|
28
|
+
hooks?: Hooks;
|
|
16
29
|
};
|
|
17
30
|
type EndpointDefinitions = Record<string, EndpointConfig>;
|
|
18
31
|
|
|
19
32
|
declare function createApiClient<TEndpoints extends EndpointDefinitions>(endpoints: TEndpoints, config: ApiConfig): { [K in keyof TEndpoints]: TEndpoints[K] extends infer T ? T extends TEndpoints[K] ? T extends EndpointConfig<infer TInput, infer TOutput, any> ? (input: TInput) => Promise<TOutput> : never : never : never; };
|
|
20
33
|
|
|
21
|
-
export { type ApiConfig, type EndpointConfig, type EndpointDefinitions, type HttpMethod, createApiClient };
|
|
34
|
+
export { type ApiConfig, type EndpointConfig, type EndpointDefinitions, type Hooks, type HttpMethod, createApiClient };
|
package/dist/index.js
CHANGED
|
@@ -24,11 +24,33 @@ __export(index_exports, {
|
|
|
24
24
|
module.exports = __toCommonJS(index_exports);
|
|
25
25
|
function createApiClient(endpoints, config) {
|
|
26
26
|
const fetchInstance = config.fetch ?? globalThis.fetch;
|
|
27
|
+
const hooks = config.hooks;
|
|
27
28
|
const buildUrl = (path, baseUrl) => {
|
|
28
29
|
const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
29
30
|
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
30
31
|
return `${normalizedBase}${normalizedPath}`;
|
|
31
32
|
};
|
|
33
|
+
const enhancedFetch = async (input, init) => {
|
|
34
|
+
let url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
35
|
+
let finalInit = { ...init };
|
|
36
|
+
if (hooks == null ? void 0 : hooks.beforeRequest) {
|
|
37
|
+
const result = await hooks.beforeRequest(url, finalInit);
|
|
38
|
+
url = result.url;
|
|
39
|
+
finalInit = result.init;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
let response = await fetchInstance(url, finalInit);
|
|
43
|
+
if (hooks == null ? void 0 : hooks.afterResponse) {
|
|
44
|
+
response = await hooks.afterResponse(response, url, finalInit);
|
|
45
|
+
}
|
|
46
|
+
return response;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (hooks == null ? void 0 : hooks.onError) {
|
|
49
|
+
await hooks.onError(error);
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
32
54
|
const defaultHandler = async (method, path, input) => {
|
|
33
55
|
const url = buildUrl(path, config.baseUrl);
|
|
34
56
|
const options = {
|
|
@@ -41,7 +63,7 @@ function createApiClient(endpoints, config) {
|
|
|
41
63
|
if (method !== "GET" && method !== "DELETE" && input !== void 0) {
|
|
42
64
|
options.body = JSON.stringify(input);
|
|
43
65
|
}
|
|
44
|
-
const response = await
|
|
66
|
+
const response = await enhancedFetch(url, options);
|
|
45
67
|
if (!response.ok) {
|
|
46
68
|
const error = await response.json().catch(() => ({}));
|
|
47
69
|
throw {
|
|
@@ -59,9 +81,10 @@ function createApiClient(endpoints, config) {
|
|
|
59
81
|
if (endpoint.handler) {
|
|
60
82
|
return endpoint.handler({
|
|
61
83
|
input,
|
|
62
|
-
fetch:
|
|
84
|
+
fetch: enhancedFetch,
|
|
63
85
|
method: endpoint.method,
|
|
64
|
-
path
|
|
86
|
+
path,
|
|
87
|
+
baseUrl: config.baseUrl
|
|
65
88
|
});
|
|
66
89
|
}
|
|
67
90
|
return defaultHandler(endpoint.method, path, input);
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
function createApiClient(endpoints, config) {
|
|
3
3
|
const fetchInstance = config.fetch ?? globalThis.fetch;
|
|
4
|
+
const hooks = config.hooks;
|
|
4
5
|
const buildUrl = (path, baseUrl) => {
|
|
5
6
|
const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
6
7
|
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
7
8
|
return `${normalizedBase}${normalizedPath}`;
|
|
8
9
|
};
|
|
10
|
+
const enhancedFetch = async (input, init) => {
|
|
11
|
+
let url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
12
|
+
let finalInit = { ...init };
|
|
13
|
+
if (hooks == null ? void 0 : hooks.beforeRequest) {
|
|
14
|
+
const result = await hooks.beforeRequest(url, finalInit);
|
|
15
|
+
url = result.url;
|
|
16
|
+
finalInit = result.init;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
let response = await fetchInstance(url, finalInit);
|
|
20
|
+
if (hooks == null ? void 0 : hooks.afterResponse) {
|
|
21
|
+
response = await hooks.afterResponse(response, url, finalInit);
|
|
22
|
+
}
|
|
23
|
+
return response;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (hooks == null ? void 0 : hooks.onError) {
|
|
26
|
+
await hooks.onError(error);
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
9
31
|
const defaultHandler = async (method, path, input) => {
|
|
10
32
|
const url = buildUrl(path, config.baseUrl);
|
|
11
33
|
const options = {
|
|
@@ -18,7 +40,7 @@ function createApiClient(endpoints, config) {
|
|
|
18
40
|
if (method !== "GET" && method !== "DELETE" && input !== void 0) {
|
|
19
41
|
options.body = JSON.stringify(input);
|
|
20
42
|
}
|
|
21
|
-
const response = await
|
|
43
|
+
const response = await enhancedFetch(url, options);
|
|
22
44
|
if (!response.ok) {
|
|
23
45
|
const error = await response.json().catch(() => ({}));
|
|
24
46
|
throw {
|
|
@@ -36,9 +58,10 @@ function createApiClient(endpoints, config) {
|
|
|
36
58
|
if (endpoint.handler) {
|
|
37
59
|
return endpoint.handler({
|
|
38
60
|
input,
|
|
39
|
-
fetch:
|
|
61
|
+
fetch: enhancedFetch,
|
|
40
62
|
method: endpoint.method,
|
|
41
|
-
path
|
|
63
|
+
path,
|
|
64
|
+
baseUrl: config.baseUrl
|
|
42
65
|
});
|
|
43
66
|
}
|
|
44
67
|
return defaultHandler(endpoint.method, path, input);
|