make-service 3.0.0 → 4.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/README.md +47 -16
- package/dist/index.d.mts +151 -0
- package/dist/index.d.ts +19 -12
- package/dist/index.js +56 -47
- package/dist/index.mjs +52 -44
- package/package.json +13 -9
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
A type-safe thin wrapper around the `fetch` API to better interact with external APIs.
|
|
8
8
|
|
|
9
|
-
It adds a set of little features and allows you to parse responses with [
|
|
9
|
+
It adds a set of little features and allows you to parse responses with [standard-schema libraries](https://standardschema.dev).
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
- 🤩 Type-safe return of `response.json()` and `response.text()`. Defaults to `unknown` instead of `any`.
|
|
@@ -37,6 +37,7 @@ const users = await response.json(usersSchema);
|
|
|
37
37
|
- [makeService](#makeservice)
|
|
38
38
|
- [Type-checking the response body](#type-checking-the-response-body)
|
|
39
39
|
- [Runtime type-checking and parsing the response body](#runtime-type-checking-and-parsing-the-response-body)
|
|
40
|
+
- [Dealing with parsing errors](#dealing-with-parsing-errors)
|
|
40
41
|
- [Supported HTTP Verbs](#supported-http-verbs)
|
|
41
42
|
- [Headers](#headers)
|
|
42
43
|
- [Passing a function as `headers`](#passing-a-function-as-headers)
|
|
@@ -52,13 +53,14 @@ const users = await response.json(usersSchema);
|
|
|
52
53
|
- [makeFetcher](#makefetcher)
|
|
53
54
|
- [enhancedFetch](#enhancedfetch)
|
|
54
55
|
- [typedResponse](#typedresponse)
|
|
55
|
-
- [Transform the
|
|
56
|
+
- [Transform the payload](#transform-the-payload)
|
|
56
57
|
- [Other available primitives](#other-available-primitives)
|
|
57
58
|
- [addQueryToURL](#addquerytourl)
|
|
58
59
|
- [ensureStringBody](#ensurestringbody)
|
|
59
60
|
- [makeGetApiURL](#makegetapiurl)
|
|
60
61
|
- [mergeHeaders](#mergeheaders)
|
|
61
62
|
- [replaceURLParams](#replaceurlparams)
|
|
63
|
+
- [Contributors](#contributors)
|
|
62
64
|
- [Acknowledgements](#acknowledgements)
|
|
63
65
|
|
|
64
66
|
# Installation
|
|
@@ -119,8 +121,8 @@ const content = await response.text<`${string}@${string}`>()
|
|
|
119
121
|
```
|
|
120
122
|
|
|
121
123
|
### Runtime type-checking and parsing the response body
|
|
122
|
-
Its [`typedResponse`](#typedresponse) can also be parsed with a zod schema. Here follows a little more complex example:
|
|
123
124
|
|
|
125
|
+
Its [`typedResponse`](#typedresponse) can also be parsed with a standard schema parser. Here follows a little more complex example with Zod:
|
|
124
126
|
```ts
|
|
125
127
|
const response = await service.get("/users")
|
|
126
128
|
const json = await response.json(
|
|
@@ -142,6 +144,20 @@ const content = await response.text(z.string().email())
|
|
|
142
144
|
```
|
|
143
145
|
You can transform any `Response` in a `TypedResponse` like that by using the [`typedResponse`](#typedresponse) function.
|
|
144
146
|
|
|
147
|
+
#### Dealing with parsing errors
|
|
148
|
+
If the response body does not match the given schema, it will throw a **ParseResponseError** which will have a message carrying all the parsing issues and its messages. You can catch it to inspect the issues:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
try {
|
|
152
|
+
const response = await service.get("/users")
|
|
153
|
+
return await response.json(userSchema)
|
|
154
|
+
} catch(error) {
|
|
155
|
+
if (error instanceof ParseResponseError) {
|
|
156
|
+
console.log(error.issues)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
145
161
|
### Supported HTTP Verbs
|
|
146
162
|
Other than the `get` it also accepts more HTTP verbs:
|
|
147
163
|
```ts
|
|
@@ -210,8 +226,8 @@ Note: Don't forget headers are case insensitive.
|
|
|
210
226
|
const headers = new Headers({ 'Content-Type': 'application/json' })
|
|
211
227
|
Object.fromEntries(headers) // equals to: { 'content-type': 'application/json' }
|
|
212
228
|
```
|
|
213
|
-
All the features above are done by using the [`mergeHeaders`](#mergeheaders) function internally.
|
|
214
229
|
|
|
230
|
+
All the features above are done by using the [`mergeHeaders`](#mergeheaders) function internally.
|
|
215
231
|
|
|
216
232
|
### Base URL
|
|
217
233
|
The service function can receive a `string` or `URL` as base `url` and it will be able to merge them correctly with the given path:
|
|
@@ -231,16 +247,28 @@ You can use the [`makeGetApiUrl`](#makegetapiurl) method to do that kind of URL
|
|
|
231
247
|
`makeService` can also receive `requestTransformer` and `responseTransformer` as options that will be applied to all requests.
|
|
232
248
|
|
|
233
249
|
#### Request transformers
|
|
234
|
-
You can transform the request in any way you want
|
|
250
|
+
You can transform the request in any way you want passing a transformer function as a parameter. This will be applied to all requests for that service.
|
|
251
|
+
A useful example is to implement a global request timeout for all endpoints of a service:
|
|
235
252
|
|
|
236
253
|
```ts
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
254
|
+
function timeoutRequestIn30Seconds(
|
|
255
|
+
request: EnhancedRequestInit<string>,
|
|
256
|
+
): EnhancedRequestInit<string> {
|
|
257
|
+
const terminator = new AbortController()
|
|
258
|
+
terminator.signal.throwIfAborted()
|
|
259
|
+
setTimeout(() => terminator.abort(), 30000)
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
...request,
|
|
263
|
+
signal: terminator.signal,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const service = makeService('https://example.com/api', { requestTransformer: timeoutRequestIn30Seconds })
|
|
240
268
|
|
|
241
269
|
const response = await service.get("/users")
|
|
242
270
|
|
|
243
|
-
// It will call "https://example.com/api/users
|
|
271
|
+
// It will call "https://example.com/api/users" aborting (and throwing an exception) if it takes more than 30 seconds.
|
|
244
272
|
```
|
|
245
273
|
|
|
246
274
|
Please note that the `headers` option will be applied _after_ the request transformer runs. If you're using a request transformer, we recommend adding custom headers inside your transformer instead of using both options.
|
|
@@ -408,11 +436,14 @@ await enhancedFetch("https://example.com/api/users/:role", {
|
|
|
408
436
|
// method: 'POST',
|
|
409
437
|
// body: '{"some":{"object":{"as":{"body":{}}}}}',
|
|
410
438
|
// }
|
|
439
|
+
// Response {}
|
|
411
440
|
```
|
|
412
441
|
|
|
442
|
+
The `trace` function can also return a `Promise<void>` in order to send traces to an external service or database.
|
|
443
|
+
|
|
413
444
|
## typedResponse
|
|
414
445
|
|
|
415
|
-
A type-safe wrapper around the `Response` object. It adds a `json` and `text` method that will parse the response with a given
|
|
446
|
+
A type-safe wrapper around the `Response` object. It adds a `json` and `text` method that will parse the response with a given standard schema library. If you don't provide a schema, it will return `unknown` instead of `any`, then you can also give it a generic to type cast the result.
|
|
416
447
|
|
|
417
448
|
```ts
|
|
418
449
|
import { typedResponse } from 'make-service'
|
|
@@ -446,11 +477,11 @@ import { deepCamelKeys, deepKebabKeys } from 'string-ts'
|
|
|
446
477
|
|
|
447
478
|
const service = makeService("https://example.com/api")
|
|
448
479
|
const response = service.get("/users")
|
|
449
|
-
const
|
|
480
|
+
const json = await response.json(
|
|
450
481
|
z
|
|
451
482
|
.array(z.object({ "first-name": z.string(), contact: z.object({ "home-address": z.string() }) }))
|
|
452
|
-
.transform(deepCamelKeys)
|
|
453
483
|
)
|
|
484
|
+
const users = deepCamelKeys(json)
|
|
454
485
|
console.log(users)
|
|
455
486
|
// ^? { firstName: string, contact: { homeAddress: string } }[]
|
|
456
487
|
|
|
@@ -525,7 +556,7 @@ You create a `getApiURL` function by giving it a `baseURL` and then it accepts a
|
|
|
525
556
|
import { makeGetApiURL } from 'make-service'
|
|
526
557
|
|
|
527
558
|
const getApiURL = makeGetApiURL("https://example.com/api")
|
|
528
|
-
const url = getApiURL("/users?admin=true", {
|
|
559
|
+
const url = getApiURL("/users?admin=true", { page: "2" })
|
|
529
560
|
|
|
530
561
|
// "https://example.com/api/users?admin=true&page=2"
|
|
531
562
|
```
|
|
@@ -589,10 +620,10 @@ The params will be **strongly-typed** which means they will be validated against
|
|
|
589
620
|
<tbody>
|
|
590
621
|
<tr>
|
|
591
622
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gustavoguichard"><img src="https://avatars.githubusercontent.com/u/566971?v=4?s=100" width="100px;" alt="Guga Guichard"/><br /><sub><b>Guga Guichard</b></sub></a><br /><a href="#code-gustavoguichard" title="Code">💻</a> <a href="#projectManagement-gustavoguichard" title="Project Management">📆</a> <a href="#promotion-gustavoguichard" title="Promotion">📣</a> <a href="#maintenance-gustavoguichard" title="Maintenance">🚧</a> <a href="#doc-gustavoguichard" title="Documentation">📖</a> <a href="#bug-gustavoguichard" title="Bug reports">🐛</a> <a href="#infra-gustavoguichard" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#question-gustavoguichard" title="Answering Questions">💬</a> <a href="#research-gustavoguichard" title="Research">🔬</a> <a href="#review-gustavoguichard" title="Reviewed Pull Requests">👀</a> <a href="#ideas-gustavoguichard" title="Ideas, Planning, & Feedback">🤔</a> <a href="#example-gustavoguichard" title="Examples">💡</a></td>
|
|
592
|
-
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/danielweinmann"><img src="https://avatars.githubusercontent.com/u/204765?v=4?s=100" width="100px;" alt="Daniel Weinmann"/><br /><sub><b>Daniel Weinmann</b></sub></a><br /><a href="#code-danielweinmann" title="Code">💻</a> <a href="#promotion-danielweinmann" title="Promotion">📣</a> <a href="#ideas-danielweinmann" title="Ideas, Planning, & Feedback">🤔</a> <a href="#doc-danielweinmann" title="Documentation">📖</a> <a href="#bug-danielweinmann" title="Bug reports">🐛</a></td>
|
|
593
|
-
<td align="center" valign="top" width="14.28%"><a href="https://
|
|
623
|
+
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/danielweinmann"><img src="https://avatars.githubusercontent.com/u/204765?v=4?s=100" width="100px;" alt="Daniel Weinmann"/><br /><sub><b>Daniel Weinmann</b></sub></a><br /><a href="#code-danielweinmann" title="Code">💻</a> <a href="#promotion-danielweinmann" title="Promotion">📣</a> <a href="#ideas-danielweinmann" title="Ideas, Planning, & Feedback">🤔</a> <a href="#doc-danielweinmann" title="Documentation">📖</a> <a href="#bug-danielweinmann" title="Bug reports">🐛</a> <a href="#review-danielweinmann" title="Reviewed Pull Requests">👀</a></td>
|
|
624
|
+
<td align="center" valign="top" width="14.28%"><a href="https://luca.md"><img src="https://avatars.githubusercontent.com/u/1881266?v=4?s=100" width="100px;" alt="Andrei Luca"/><br /><sub><b>Andrei Luca</b></sub></a><br /><a href="#doc-iamandrewluca" title="Documentation">📖</a> <a href="#code-iamandrewluca" title="Code">💻</a> <a href="#promotion-iamandrewluca" title="Promotion">📣</a> <a href="#maintenance-iamandrewluca" title="Maintenance">🚧</a> <a href="#bug-iamandrewluca" title="Bug reports">🐛</a> <a href="#ideas-iamandrewluca" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
625
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/diogob"><img src="https://avatars.githubusercontent.com/u/20662?v=4?s=100" width="100px;" alt="Diogo Biazus"/><br /><sub><b>Diogo Biazus</b></sub></a><br /><a href="#code-diogob" title="Code">💻</a> <a href="#doc-diogob" title="Documentation">📖</a></td>
|
|
594
626
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/garusis"><img src="https://avatars.githubusercontent.com/u/15615652?v=4?s=100" width="100px;" alt="Marcos Javier Alvarez Maestre"/><br /><sub><b>Marcos Javier Alvarez Maestre</b></sub></a><br /><a href="#code-garusis" title="Code">💻</a> <a href="#bug-garusis" title="Bug reports">🐛</a></td>
|
|
595
|
-
<td align="center" valign="top" width="14.28%"><a href="https://luca.md"><img src="https://avatars.githubusercontent.com/u/1881266?v=4?s=100" width="100px;" alt="Andrei Luca"/><br /><sub><b>Andrei Luca</b></sub></a><br /><a href="#doc-iamandrewluca" title="Documentation">📖</a></td>
|
|
596
627
|
</tr>
|
|
597
628
|
</tbody>
|
|
598
629
|
</table>
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from 'zod/lib/standard-schema';
|
|
2
|
+
import { StandardSchemaV1 as StandardSchemaV1$1 } from '@standard-schema/spec';
|
|
3
|
+
|
|
4
|
+
declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT"];
|
|
5
|
+
|
|
6
|
+
type JSONValue = string | number | boolean | Date | {
|
|
7
|
+
[x: string]: JSONValue | undefined | null;
|
|
8
|
+
} | Array<JSONValue | undefined | null>;
|
|
9
|
+
type SearchParams = ConstructorParameters<typeof URLSearchParams>[0];
|
|
10
|
+
type TypedResponse = Omit<Response, 'json' | 'text'> & {
|
|
11
|
+
json: TypedResponseJson;
|
|
12
|
+
text: TypedResponseText;
|
|
13
|
+
};
|
|
14
|
+
type PathParams<T> = T extends string ? ExtractPathParams<T> extends Record<string, unknown> ? ExtractPathParams<T> : Record<string, string | number> : Record<string, string | number>;
|
|
15
|
+
type EnhancedRequestInit<T = string> = Omit<RequestInit, 'body' | 'method'> & {
|
|
16
|
+
method?: HTTPMethod | Lowercase<HTTPMethod>;
|
|
17
|
+
body?: JSONValue | BodyInit | null;
|
|
18
|
+
query?: SearchParams;
|
|
19
|
+
params?: PathParams<T>;
|
|
20
|
+
trace?: (fullUrl: string | URL, init: EnhancedRequestInit, response: TypedResponse) => void | Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
type ServiceRequestInit<T = string> = Omit<EnhancedRequestInit<T>, 'method'>;
|
|
23
|
+
type RequestTransformer = (request: EnhancedRequestInit) => EnhancedRequestInit | Promise<EnhancedRequestInit>;
|
|
24
|
+
type ResponseTransformer = (response: TypedResponse) => TypedResponse | Promise<TypedResponse>;
|
|
25
|
+
type BaseOptions = {
|
|
26
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
27
|
+
requestTransformer?: RequestTransformer;
|
|
28
|
+
responseTransformer?: ResponseTransformer;
|
|
29
|
+
};
|
|
30
|
+
type HTTPMethod = (typeof HTTP_METHODS)[number];
|
|
31
|
+
type TypedResponseJson = <T = unknown>(schema?: StandardSchemaV1<T>) => Promise<T>;
|
|
32
|
+
type TypedResponseText = <T extends string = string>(schema?: StandardSchemaV1<T>) => Promise<T>;
|
|
33
|
+
type GetJson = (response: Response) => TypedResponseJson;
|
|
34
|
+
type GetText = (response: Response) => TypedResponseText;
|
|
35
|
+
type Prettify<T> = {
|
|
36
|
+
[K in keyof T]: T[K];
|
|
37
|
+
} & {};
|
|
38
|
+
type ExtractPathParams<T extends string> = T extends `${infer _}:${infer Param}/${infer Rest}` ? Prettify<Omit<{
|
|
39
|
+
[K in Param]: string | number;
|
|
40
|
+
} & ExtractPathParams<Rest>, ''>> : T extends `${infer _}:${infer Param}` ? {
|
|
41
|
+
[K in Param]: string | number;
|
|
42
|
+
} : {};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* It hacks the Response object to add typed json and text methods
|
|
46
|
+
* @param response the Response to be proxied
|
|
47
|
+
* @returns a Response with typed json and text methods
|
|
48
|
+
* @example const response = await fetch("https://example.com/api/users");
|
|
49
|
+
* const users = await response.json(userSchema);
|
|
50
|
+
* // ^? User[]
|
|
51
|
+
* const untyped = await response.json();
|
|
52
|
+
* // ^? unknown
|
|
53
|
+
* const text = await response.text();
|
|
54
|
+
* // ^? string
|
|
55
|
+
* const typedJson = await response.json<User[]>();
|
|
56
|
+
* // ^? User[]
|
|
57
|
+
*/
|
|
58
|
+
declare function typedResponse(response: Response, options?: {
|
|
59
|
+
getJson?: GetJson;
|
|
60
|
+
getText?: GetText;
|
|
61
|
+
}): TypedResponse;
|
|
62
|
+
/**
|
|
63
|
+
*
|
|
64
|
+
* @param url a string or URL to be fetched
|
|
65
|
+
* @param requestInit the requestInit to be passed to the fetch request. It is the same as the `RequestInit` type, but it also accepts a JSON-like `body` and an object-like `query` parameter.
|
|
66
|
+
* @param requestInit.body the body of the request. It will be automatically stringified so you can send a JSON-like object
|
|
67
|
+
* @param requestInit.query the query parameters to be added to the URL
|
|
68
|
+
* @param requestInit.trace a function that receives the URL, the requestInit and a clone of the response in order to log or troubleshoot the request
|
|
69
|
+
* @returns a Response with typed json and text methods
|
|
70
|
+
* @example const response = await fetch("https://example.com/api/users");
|
|
71
|
+
* const users = await response.json(userSchema);
|
|
72
|
+
* // ^? User[]
|
|
73
|
+
* const untyped = await response.json();
|
|
74
|
+
* // ^? unknown
|
|
75
|
+
*/
|
|
76
|
+
declare function enhancedFetch<T extends string | URL>(url: T, requestInit?: EnhancedRequestInit<T>): Promise<TypedResponse>;
|
|
77
|
+
/**
|
|
78
|
+
*
|
|
79
|
+
* @param baseURL the base URL to be fetched in every request
|
|
80
|
+
* @param baseOptions options that will be applied to all requests
|
|
81
|
+
* @param baseOptions.headers any headers that should be sent with every request
|
|
82
|
+
* @param baseOptions.requestTransformer a function that will transform the enhanced request init of every request
|
|
83
|
+
* @param baseOptions.responseTransformer a function that will transform the typed response of every request
|
|
84
|
+
* @returns a function that receive a path and requestInit and return a serialized json response that can be typed or not.
|
|
85
|
+
* @example const headers = { Authorization: "Bearer 123" }
|
|
86
|
+
* const fetcher = makeFetcher("https://example.com/api", headers);
|
|
87
|
+
* const response = await fetcher("/users", { method: "GET" })
|
|
88
|
+
* const users = await response.json(userSchema);
|
|
89
|
+
* // ^? User[]
|
|
90
|
+
*/
|
|
91
|
+
declare function makeFetcher(baseURL: string | URL, baseOptions?: BaseOptions): <T extends string>(path: T, requestInit?: EnhancedRequestInit<T>) => Promise<TypedResponse>;
|
|
92
|
+
/**
|
|
93
|
+
*
|
|
94
|
+
* @param baseURL the base URL to the API
|
|
95
|
+
* @param baseOptions options that will be applied to all requests
|
|
96
|
+
* @param baseOptions.headers any headers that should be sent with every request
|
|
97
|
+
* @param baseOptions.requestTransformer a function that will transform the enhanced request init of every request
|
|
98
|
+
* @param baseOptions.responseTransformer a function that will transform the typed response of every request
|
|
99
|
+
* @returns a service object with HTTP methods that are functions that receive a path and requestInit and return a serialized json response that can be typed or not.
|
|
100
|
+
* @example const headers = { Authorization: "Bearer 123" }
|
|
101
|
+
* const api = makeService("https://example.com/api", headers);
|
|
102
|
+
* const response = await api.get("/users")
|
|
103
|
+
* const users = await response.json(userSchema);
|
|
104
|
+
* // ^? User[]
|
|
105
|
+
*/
|
|
106
|
+
declare function makeService(baseURL: string | URL, baseOptions?: BaseOptions): Record<"get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "connect", <T extends string>(path: T, requestInit?: ServiceRequestInit<T>) => Promise<TypedResponse>>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param url a string or URL to which the query parameters will be added
|
|
110
|
+
* @param searchParams the query parameters
|
|
111
|
+
* @returns the url with the query parameters added with the same type as the url
|
|
112
|
+
*/
|
|
113
|
+
declare function addQueryToURL<T extends string | URL>(url: T, searchParams?: SearchParams): T;
|
|
114
|
+
/**
|
|
115
|
+
* @param body the JSON-like body of the request
|
|
116
|
+
* @returns the body is stringified if it is not a string and it is a JSON-like object. It also accepts other types of BodyInit such as Blob, ReadableStream, etc.
|
|
117
|
+
*/
|
|
118
|
+
declare function ensureStringBody<B extends JSONValue | BodyInit | null>(body?: B): B extends JSONValue ? string : B;
|
|
119
|
+
/**
|
|
120
|
+
* @param baseURL the base path to the API
|
|
121
|
+
* @returns a function that receives a path and an object of query parameters and returns a URL
|
|
122
|
+
*/
|
|
123
|
+
declare function makeGetApiURL<T extends string | URL>(baseURL: T): (path: string, searchParams?: SearchParams) => T;
|
|
124
|
+
/**
|
|
125
|
+
* It merges multiple HeadersInit objects into a single Headers object
|
|
126
|
+
* @param entries Any number of HeadersInit objects
|
|
127
|
+
* @returns a new Headers object with the merged headers
|
|
128
|
+
*/
|
|
129
|
+
declare function mergeHeaders(...entries: (HeadersInit | [string, undefined][] | Record<string, undefined>)[]): Headers;
|
|
130
|
+
/**
|
|
131
|
+
*
|
|
132
|
+
* @param url the url string or URL object to replace the params
|
|
133
|
+
* @param params the params map to be replaced in the url
|
|
134
|
+
* @returns the url with the params replaced and with the same type as the given url
|
|
135
|
+
*/
|
|
136
|
+
declare function replaceURLParams<T extends string | URL>(url: T, params: PathParams<T>): T;
|
|
137
|
+
/**
|
|
138
|
+
* This is an enhanced version of the typeof operator to check the type of more complex values.
|
|
139
|
+
* @param t the value to be checked
|
|
140
|
+
* @returns the type of the value
|
|
141
|
+
*/
|
|
142
|
+
declare function typeOf(t: unknown): "array" | "arraybuffer" | "bigint" | "blob" | "boolean" | "formdata" | "function" | "null" | "number" | "object" | "readablestream" | "string" | "symbol" | "undefined" | "url" | "urlsearchparams";
|
|
143
|
+
/**
|
|
144
|
+
* Error thrown when the response cannot be parsed.
|
|
145
|
+
*/
|
|
146
|
+
declare class ParseResponseError extends Error {
|
|
147
|
+
issues: readonly StandardSchemaV1$1.Issue[];
|
|
148
|
+
constructor(message: string, issues: readonly StandardSchemaV1$1.Issue[]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { type BaseOptions, type EnhancedRequestInit, type GetJson, type GetText, type HTTPMethod, type JSONValue, ParseResponseError, type PathParams, type RequestTransformer, type ResponseTransformer, type SearchParams, type ServiceRequestInit, type TypedResponse, type TypedResponseJson, type TypedResponseText, addQueryToURL, enhancedFetch, ensureStringBody, makeFetcher, makeGetApiURL, makeService, mergeHeaders, replaceURLParams, typeOf, typedResponse };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from 'zod/lib/standard-schema';
|
|
2
|
+
import { StandardSchemaV1 as StandardSchemaV1$1 } from '@standard-schema/spec';
|
|
3
|
+
|
|
1
4
|
declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT"];
|
|
2
5
|
|
|
3
|
-
type Schema<T> = {
|
|
4
|
-
parse: (d: unknown) => T;
|
|
5
|
-
};
|
|
6
6
|
type JSONValue = string | number | boolean | Date | {
|
|
7
7
|
[x: string]: JSONValue | undefined | null;
|
|
8
8
|
} | Array<JSONValue | undefined | null>;
|
|
@@ -11,13 +11,13 @@ type TypedResponse = Omit<Response, 'json' | 'text'> & {
|
|
|
11
11
|
json: TypedResponseJson;
|
|
12
12
|
text: TypedResponseText;
|
|
13
13
|
};
|
|
14
|
-
type PathParams<T> = T extends string ? ExtractPathParams<T> extends Record<string, unknown> ? ExtractPathParams<T> : Record<string, string> : Record<string, string>;
|
|
14
|
+
type PathParams<T> = T extends string ? ExtractPathParams<T> extends Record<string, unknown> ? ExtractPathParams<T> : Record<string, string | number> : Record<string, string | number>;
|
|
15
15
|
type EnhancedRequestInit<T = string> = Omit<RequestInit, 'body' | 'method'> & {
|
|
16
16
|
method?: HTTPMethod | Lowercase<HTTPMethod>;
|
|
17
17
|
body?: JSONValue | BodyInit | null;
|
|
18
18
|
query?: SearchParams;
|
|
19
19
|
params?: PathParams<T>;
|
|
20
|
-
trace?: (
|
|
20
|
+
trace?: (fullUrl: string | URL, init: EnhancedRequestInit, response: TypedResponse) => void | Promise<void>;
|
|
21
21
|
};
|
|
22
22
|
type ServiceRequestInit<T = string> = Omit<EnhancedRequestInit<T>, 'method'>;
|
|
23
23
|
type RequestTransformer = (request: EnhancedRequestInit) => EnhancedRequestInit | Promise<EnhancedRequestInit>;
|
|
@@ -28,17 +28,17 @@ type BaseOptions = {
|
|
|
28
28
|
responseTransformer?: ResponseTransformer;
|
|
29
29
|
};
|
|
30
30
|
type HTTPMethod = (typeof HTTP_METHODS)[number];
|
|
31
|
-
type TypedResponseJson = <T = unknown>(schema?:
|
|
32
|
-
type TypedResponseText = <T extends string = string>(schema?:
|
|
31
|
+
type TypedResponseJson = <T = unknown>(schema?: StandardSchemaV1<T>) => Promise<T>;
|
|
32
|
+
type TypedResponseText = <T extends string = string>(schema?: StandardSchemaV1<T>) => Promise<T>;
|
|
33
33
|
type GetJson = (response: Response) => TypedResponseJson;
|
|
34
34
|
type GetText = (response: Response) => TypedResponseText;
|
|
35
35
|
type Prettify<T> = {
|
|
36
36
|
[K in keyof T]: T[K];
|
|
37
37
|
} & {};
|
|
38
38
|
type ExtractPathParams<T extends string> = T extends `${infer _}:${infer Param}/${infer Rest}` ? Prettify<Omit<{
|
|
39
|
-
[K in Param]: string;
|
|
39
|
+
[K in Param]: string | number;
|
|
40
40
|
} & ExtractPathParams<Rest>, ''>> : T extends `${infer _}:${infer Param}` ? {
|
|
41
|
-
[K in Param]: string;
|
|
41
|
+
[K in Param]: string | number;
|
|
42
42
|
} : {};
|
|
43
43
|
|
|
44
44
|
/**
|
|
@@ -65,7 +65,7 @@ declare function typedResponse(response: Response, options?: {
|
|
|
65
65
|
* @param requestInit the requestInit to be passed to the fetch request. It is the same as the `RequestInit` type, but it also accepts a JSON-like `body` and an object-like `query` parameter.
|
|
66
66
|
* @param requestInit.body the body of the request. It will be automatically stringified so you can send a JSON-like object
|
|
67
67
|
* @param requestInit.query the query parameters to be added to the URL
|
|
68
|
-
* @param requestInit.trace a function that receives the URL
|
|
68
|
+
* @param requestInit.trace a function that receives the URL, the requestInit and a clone of the response in order to log or troubleshoot the request
|
|
69
69
|
* @returns a Response with typed json and text methods
|
|
70
70
|
* @example const response = await fetch("https://example.com/api/users");
|
|
71
71
|
* const users = await response.json(userSchema);
|
|
@@ -139,6 +139,13 @@ declare function replaceURLParams<T extends string | URL>(url: T, params: PathPa
|
|
|
139
139
|
* @param t the value to be checked
|
|
140
140
|
* @returns the type of the value
|
|
141
141
|
*/
|
|
142
|
-
declare function typeOf(t: unknown): "
|
|
142
|
+
declare function typeOf(t: unknown): "array" | "arraybuffer" | "bigint" | "blob" | "boolean" | "formdata" | "function" | "null" | "number" | "object" | "readablestream" | "string" | "symbol" | "undefined" | "url" | "urlsearchparams";
|
|
143
|
+
/**
|
|
144
|
+
* Error thrown when the response cannot be parsed.
|
|
145
|
+
*/
|
|
146
|
+
declare class ParseResponseError extends Error {
|
|
147
|
+
issues: readonly StandardSchemaV1$1.Issue[];
|
|
148
|
+
constructor(message: string, issues: readonly StandardSchemaV1$1.Issue[]);
|
|
149
|
+
}
|
|
143
150
|
|
|
144
|
-
export { BaseOptions, EnhancedRequestInit, GetJson, GetText, HTTPMethod, JSONValue, PathParams, RequestTransformer, ResponseTransformer,
|
|
151
|
+
export { type BaseOptions, type EnhancedRequestInit, type GetJson, type GetText, type HTTPMethod, type JSONValue, ParseResponseError, type PathParams, type RequestTransformer, type ResponseTransformer, type SearchParams, type ServiceRequestInit, type TypedResponse, type TypedResponseJson, type TypedResponseText, addQueryToURL, enhancedFetch, ensureStringBody, makeFetcher, makeGetApiURL, makeService, mergeHeaders, replaceURLParams, typeOf, typedResponse };
|
package/dist/index.js
CHANGED
|
@@ -18,8 +18,9 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
20
|
// src/index.ts
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ParseResponseError: () => ParseResponseError,
|
|
23
24
|
addQueryToURL: () => addQueryToURL,
|
|
24
25
|
enhancedFetch: () => enhancedFetch,
|
|
25
26
|
ensureStringBody: () => ensureStringBody,
|
|
@@ -31,7 +32,7 @@ __export(src_exports, {
|
|
|
31
32
|
typeOf: () => typeOf,
|
|
32
33
|
typedResponse: () => typedResponse
|
|
33
34
|
});
|
|
34
|
-
module.exports = __toCommonJS(
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
36
|
|
|
36
37
|
// src/constants.ts
|
|
37
38
|
var HTTP_METHODS = [
|
|
@@ -46,20 +47,9 @@ var HTTP_METHODS = [
|
|
|
46
47
|
// 'TRACE', it has no support in most browsers yet
|
|
47
48
|
];
|
|
48
49
|
|
|
49
|
-
// src/internals.ts
|
|
50
|
-
var getJson = (response) => async (schema) => {
|
|
51
|
-
const json = await response.json();
|
|
52
|
-
return schema ? schema.parse(json) : json;
|
|
53
|
-
};
|
|
54
|
-
var getText = (response) => async (schema) => {
|
|
55
|
-
const text = await response.text();
|
|
56
|
-
return schema ? schema.parse(text) : text;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
50
|
// src/primitives.ts
|
|
60
51
|
function addQueryToURL(url, searchParams) {
|
|
61
|
-
if (!searchParams)
|
|
62
|
-
return url;
|
|
52
|
+
if (!searchParams) return url;
|
|
63
53
|
if (typeof url === "string") {
|
|
64
54
|
const separator = url.includes("?") ? "&" : "?";
|
|
65
55
|
return `${url}${separator}${new URLSearchParams(searchParams)}`;
|
|
@@ -72,10 +62,8 @@ function addQueryToURL(url, searchParams) {
|
|
|
72
62
|
return url;
|
|
73
63
|
}
|
|
74
64
|
function ensureStringBody(body) {
|
|
75
|
-
if (typeof body === "undefined")
|
|
76
|
-
|
|
77
|
-
if (typeof body === "string")
|
|
78
|
-
return body;
|
|
65
|
+
if (typeof body === "undefined") return body;
|
|
66
|
+
if (typeof body === "string") return body;
|
|
79
67
|
return ["number", "boolean", "array", "object"].includes(typeOf(body)) ? JSON.stringify(body) : body;
|
|
80
68
|
}
|
|
81
69
|
function makeGetApiURL(baseURL) {
|
|
@@ -86,7 +74,7 @@ function makeGetApiURL(baseURL) {
|
|
|
86
74
|
};
|
|
87
75
|
}
|
|
88
76
|
function mergeHeaders(...entries) {
|
|
89
|
-
const result =
|
|
77
|
+
const result = new Headers();
|
|
90
78
|
for (const entry of entries) {
|
|
91
79
|
const headers = new Headers(entry);
|
|
92
80
|
for (const [key, value] of headers.entries()) {
|
|
@@ -97,11 +85,10 @@ function mergeHeaders(...entries) {
|
|
|
97
85
|
}
|
|
98
86
|
}
|
|
99
87
|
}
|
|
100
|
-
return
|
|
88
|
+
return result;
|
|
101
89
|
}
|
|
102
90
|
function replaceURLParams(url, params) {
|
|
103
|
-
if (!params)
|
|
104
|
-
return url;
|
|
91
|
+
if (!params) return url;
|
|
105
92
|
let urlString = String(url);
|
|
106
93
|
Object.entries(params).forEach(([key, value]) => {
|
|
107
94
|
urlString = urlString.replace(new RegExp(`:${key}($|/)`), `${value}$1`);
|
|
@@ -111,19 +98,44 @@ function replaceURLParams(url, params) {
|
|
|
111
98
|
function typeOf(t) {
|
|
112
99
|
return Object.prototype.toString.call(t).replace(/^\[object (.+)\]$/, "$1").toLowerCase();
|
|
113
100
|
}
|
|
101
|
+
var ParseResponseError = class extends Error {
|
|
102
|
+
constructor(message, issues) {
|
|
103
|
+
super(JSON.stringify({ message, issues }, null, 2));
|
|
104
|
+
this.issues = issues;
|
|
105
|
+
this.name = "ParseResponseError";
|
|
106
|
+
this.issues = issues;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// src/internals.ts
|
|
111
|
+
var getJson = (response) => async (schema) => {
|
|
112
|
+
const json = await response.json();
|
|
113
|
+
if (!schema) return json;
|
|
114
|
+
const result = await schema["~standard"].validate(json);
|
|
115
|
+
if (result.issues) {
|
|
116
|
+
throw new ParseResponseError("Failed to parse response.json", result.issues);
|
|
117
|
+
}
|
|
118
|
+
return result.value;
|
|
119
|
+
};
|
|
120
|
+
var getText = (response) => async (schema) => {
|
|
121
|
+
const text = await response.text();
|
|
122
|
+
if (!schema) return text;
|
|
123
|
+
const result = await schema["~standard"].validate(text);
|
|
124
|
+
if (result.issues) {
|
|
125
|
+
throw new ParseResponseError("Failed to parse response.text", result.issues);
|
|
126
|
+
}
|
|
127
|
+
return result.value;
|
|
128
|
+
};
|
|
114
129
|
|
|
115
130
|
// src/api.ts
|
|
116
131
|
var identity = (value) => value;
|
|
117
132
|
function typedResponse(response, options) {
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
const getTextFn = (_b = options == null ? void 0 : options.getText) != null ? _b : getText;
|
|
133
|
+
const getJsonFn = options?.getJson ?? getJson;
|
|
134
|
+
const getTextFn = options?.getText ?? getText;
|
|
121
135
|
return new Proxy(response, {
|
|
122
136
|
get(target, prop) {
|
|
123
|
-
if (prop === "json")
|
|
124
|
-
|
|
125
|
-
if (prop === "text")
|
|
126
|
-
return getTextFn(target);
|
|
137
|
+
if (prop === "json") return getJsonFn(target);
|
|
138
|
+
if (prop === "text") return getTextFn(target);
|
|
127
139
|
const value = Reflect.get(target, prop);
|
|
128
140
|
if (typeof value === "function") {
|
|
129
141
|
return value.bind(target);
|
|
@@ -133,32 +145,28 @@ function typedResponse(response, options) {
|
|
|
133
145
|
});
|
|
134
146
|
}
|
|
135
147
|
async function enhancedFetch(url, requestInit) {
|
|
136
|
-
|
|
137
|
-
const { query, trace, ...reqInit } = requestInit != null ? requestInit : {};
|
|
148
|
+
const { query, trace, ...reqInit } = requestInit ?? {};
|
|
138
149
|
const body = ensureStringBody(reqInit.body);
|
|
139
|
-
const withParams = replaceURLParams(url,
|
|
150
|
+
const withParams = replaceURLParams(url, reqInit.params ?? {});
|
|
140
151
|
const fullURL = addQueryToURL(withParams, query);
|
|
141
152
|
const enhancedReqInit = { ...reqInit, body };
|
|
142
|
-
trace == null ? void 0 : trace(fullURL, enhancedReqInit);
|
|
143
153
|
const response = await fetch(fullURL, enhancedReqInit);
|
|
154
|
+
await trace?.(fullURL, enhancedReqInit, typedResponse(response.clone()));
|
|
144
155
|
return typedResponse(response);
|
|
145
156
|
}
|
|
146
157
|
function makeFetcher(baseURL, baseOptions = {}) {
|
|
147
158
|
return async (path, requestInit = {}) => {
|
|
148
|
-
var _a, _b;
|
|
149
159
|
const { headers } = baseOptions;
|
|
150
|
-
const requestTransformer =
|
|
151
|
-
const responseTransformer =
|
|
152
|
-
const headerTransformer = async (ri) => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
headers
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
};
|
|
161
|
-
};
|
|
160
|
+
const requestTransformer = baseOptions.requestTransformer ?? identity;
|
|
161
|
+
const responseTransformer = baseOptions.responseTransformer ?? identity;
|
|
162
|
+
const headerTransformer = async (ri) => ({
|
|
163
|
+
...ri,
|
|
164
|
+
headers: mergeHeaders(
|
|
165
|
+
typeof headers === "function" ? await headers() : headers ?? {},
|
|
166
|
+
ri.headers ?? {},
|
|
167
|
+
requestInit?.headers ?? {}
|
|
168
|
+
)
|
|
169
|
+
});
|
|
162
170
|
const url = makeGetApiURL(baseURL)(path);
|
|
163
171
|
const response = await enhancedFetch(
|
|
164
172
|
url,
|
|
@@ -181,6 +189,7 @@ function makeService(baseURL, baseOptions) {
|
|
|
181
189
|
}
|
|
182
190
|
// Annotate the CommonJS export names for ESM import in node:
|
|
183
191
|
0 && (module.exports = {
|
|
192
|
+
ParseResponseError,
|
|
184
193
|
addQueryToURL,
|
|
185
194
|
enhancedFetch,
|
|
186
195
|
ensureStringBody,
|
package/dist/index.mjs
CHANGED
|
@@ -11,20 +11,9 @@ var HTTP_METHODS = [
|
|
|
11
11
|
// 'TRACE', it has no support in most browsers yet
|
|
12
12
|
];
|
|
13
13
|
|
|
14
|
-
// src/internals.ts
|
|
15
|
-
var getJson = (response) => async (schema) => {
|
|
16
|
-
const json = await response.json();
|
|
17
|
-
return schema ? schema.parse(json) : json;
|
|
18
|
-
};
|
|
19
|
-
var getText = (response) => async (schema) => {
|
|
20
|
-
const text = await response.text();
|
|
21
|
-
return schema ? schema.parse(text) : text;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
14
|
// src/primitives.ts
|
|
25
15
|
function addQueryToURL(url, searchParams) {
|
|
26
|
-
if (!searchParams)
|
|
27
|
-
return url;
|
|
16
|
+
if (!searchParams) return url;
|
|
28
17
|
if (typeof url === "string") {
|
|
29
18
|
const separator = url.includes("?") ? "&" : "?";
|
|
30
19
|
return `${url}${separator}${new URLSearchParams(searchParams)}`;
|
|
@@ -37,10 +26,8 @@ function addQueryToURL(url, searchParams) {
|
|
|
37
26
|
return url;
|
|
38
27
|
}
|
|
39
28
|
function ensureStringBody(body) {
|
|
40
|
-
if (typeof body === "undefined")
|
|
41
|
-
|
|
42
|
-
if (typeof body === "string")
|
|
43
|
-
return body;
|
|
29
|
+
if (typeof body === "undefined") return body;
|
|
30
|
+
if (typeof body === "string") return body;
|
|
44
31
|
return ["number", "boolean", "array", "object"].includes(typeOf(body)) ? JSON.stringify(body) : body;
|
|
45
32
|
}
|
|
46
33
|
function makeGetApiURL(baseURL) {
|
|
@@ -51,7 +38,7 @@ function makeGetApiURL(baseURL) {
|
|
|
51
38
|
};
|
|
52
39
|
}
|
|
53
40
|
function mergeHeaders(...entries) {
|
|
54
|
-
const result =
|
|
41
|
+
const result = new Headers();
|
|
55
42
|
for (const entry of entries) {
|
|
56
43
|
const headers = new Headers(entry);
|
|
57
44
|
for (const [key, value] of headers.entries()) {
|
|
@@ -62,11 +49,10 @@ function mergeHeaders(...entries) {
|
|
|
62
49
|
}
|
|
63
50
|
}
|
|
64
51
|
}
|
|
65
|
-
return
|
|
52
|
+
return result;
|
|
66
53
|
}
|
|
67
54
|
function replaceURLParams(url, params) {
|
|
68
|
-
if (!params)
|
|
69
|
-
return url;
|
|
55
|
+
if (!params) return url;
|
|
70
56
|
let urlString = String(url);
|
|
71
57
|
Object.entries(params).forEach(([key, value]) => {
|
|
72
58
|
urlString = urlString.replace(new RegExp(`:${key}($|/)`), `${value}$1`);
|
|
@@ -76,19 +62,44 @@ function replaceURLParams(url, params) {
|
|
|
76
62
|
function typeOf(t) {
|
|
77
63
|
return Object.prototype.toString.call(t).replace(/^\[object (.+)\]$/, "$1").toLowerCase();
|
|
78
64
|
}
|
|
65
|
+
var ParseResponseError = class extends Error {
|
|
66
|
+
constructor(message, issues) {
|
|
67
|
+
super(JSON.stringify({ message, issues }, null, 2));
|
|
68
|
+
this.issues = issues;
|
|
69
|
+
this.name = "ParseResponseError";
|
|
70
|
+
this.issues = issues;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/internals.ts
|
|
75
|
+
var getJson = (response) => async (schema) => {
|
|
76
|
+
const json = await response.json();
|
|
77
|
+
if (!schema) return json;
|
|
78
|
+
const result = await schema["~standard"].validate(json);
|
|
79
|
+
if (result.issues) {
|
|
80
|
+
throw new ParseResponseError("Failed to parse response.json", result.issues);
|
|
81
|
+
}
|
|
82
|
+
return result.value;
|
|
83
|
+
};
|
|
84
|
+
var getText = (response) => async (schema) => {
|
|
85
|
+
const text = await response.text();
|
|
86
|
+
if (!schema) return text;
|
|
87
|
+
const result = await schema["~standard"].validate(text);
|
|
88
|
+
if (result.issues) {
|
|
89
|
+
throw new ParseResponseError("Failed to parse response.text", result.issues);
|
|
90
|
+
}
|
|
91
|
+
return result.value;
|
|
92
|
+
};
|
|
79
93
|
|
|
80
94
|
// src/api.ts
|
|
81
95
|
var identity = (value) => value;
|
|
82
96
|
function typedResponse(response, options) {
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const getTextFn = (_b = options == null ? void 0 : options.getText) != null ? _b : getText;
|
|
97
|
+
const getJsonFn = options?.getJson ?? getJson;
|
|
98
|
+
const getTextFn = options?.getText ?? getText;
|
|
86
99
|
return new Proxy(response, {
|
|
87
100
|
get(target, prop) {
|
|
88
|
-
if (prop === "json")
|
|
89
|
-
|
|
90
|
-
if (prop === "text")
|
|
91
|
-
return getTextFn(target);
|
|
101
|
+
if (prop === "json") return getJsonFn(target);
|
|
102
|
+
if (prop === "text") return getTextFn(target);
|
|
92
103
|
const value = Reflect.get(target, prop);
|
|
93
104
|
if (typeof value === "function") {
|
|
94
105
|
return value.bind(target);
|
|
@@ -98,32 +109,28 @@ function typedResponse(response, options) {
|
|
|
98
109
|
});
|
|
99
110
|
}
|
|
100
111
|
async function enhancedFetch(url, requestInit) {
|
|
101
|
-
|
|
102
|
-
const { query, trace, ...reqInit } = requestInit != null ? requestInit : {};
|
|
112
|
+
const { query, trace, ...reqInit } = requestInit ?? {};
|
|
103
113
|
const body = ensureStringBody(reqInit.body);
|
|
104
|
-
const withParams = replaceURLParams(url,
|
|
114
|
+
const withParams = replaceURLParams(url, reqInit.params ?? {});
|
|
105
115
|
const fullURL = addQueryToURL(withParams, query);
|
|
106
116
|
const enhancedReqInit = { ...reqInit, body };
|
|
107
|
-
trace == null ? void 0 : trace(fullURL, enhancedReqInit);
|
|
108
117
|
const response = await fetch(fullURL, enhancedReqInit);
|
|
118
|
+
await trace?.(fullURL, enhancedReqInit, typedResponse(response.clone()));
|
|
109
119
|
return typedResponse(response);
|
|
110
120
|
}
|
|
111
121
|
function makeFetcher(baseURL, baseOptions = {}) {
|
|
112
122
|
return async (path, requestInit = {}) => {
|
|
113
|
-
var _a, _b;
|
|
114
123
|
const { headers } = baseOptions;
|
|
115
|
-
const requestTransformer =
|
|
116
|
-
const responseTransformer =
|
|
117
|
-
const headerTransformer = async (ri) => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
headers
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
};
|
|
126
|
-
};
|
|
124
|
+
const requestTransformer = baseOptions.requestTransformer ?? identity;
|
|
125
|
+
const responseTransformer = baseOptions.responseTransformer ?? identity;
|
|
126
|
+
const headerTransformer = async (ri) => ({
|
|
127
|
+
...ri,
|
|
128
|
+
headers: mergeHeaders(
|
|
129
|
+
typeof headers === "function" ? await headers() : headers ?? {},
|
|
130
|
+
ri.headers ?? {},
|
|
131
|
+
requestInit?.headers ?? {}
|
|
132
|
+
)
|
|
133
|
+
});
|
|
127
134
|
const url = makeGetApiURL(baseURL)(path);
|
|
128
135
|
const response = await enhancedFetch(
|
|
129
136
|
url,
|
|
@@ -145,6 +152,7 @@ function makeService(baseURL, baseOptions) {
|
|
|
145
152
|
return service;
|
|
146
153
|
}
|
|
147
154
|
export {
|
|
155
|
+
ParseResponseError,
|
|
148
156
|
addQueryToURL,
|
|
149
157
|
enhancedFetch,
|
|
150
158
|
ensureStringBody,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "make-service",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Some utilities to extend the 'fetch' API to better interact with external APIs.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -10,18 +10,22 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsup ./src/index.ts --format esm,cjs --dts",
|
|
12
12
|
"dev": "tsup ./src/index.ts --format esm,cjs --watch --dts",
|
|
13
|
-
"lint": "eslint
|
|
14
|
-
"tsc": "tsc",
|
|
13
|
+
"lint": "eslint --config eslint.config.mjs .",
|
|
14
|
+
"tsc": "tsc --noEmit",
|
|
15
15
|
"test": "vitest run"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@
|
|
19
|
-
"
|
|
20
|
-
"
|
|
18
|
+
"@standard-schema/spec": "^1.0.0",
|
|
19
|
+
"@types/node": "^22.1.0",
|
|
20
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
21
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
22
|
+
"arktype": "^2.0.4",
|
|
23
|
+
"eslint": "^9.8.0",
|
|
24
|
+
"jsdom": "^24.1.1",
|
|
21
25
|
"prettier": "latest",
|
|
22
|
-
"string-ts": "^
|
|
23
|
-
"tsup": "^
|
|
24
|
-
"typescript": "^5.
|
|
26
|
+
"string-ts": "^2.2.0",
|
|
27
|
+
"tsup": "^8.2.4",
|
|
28
|
+
"typescript": "^5.5.4",
|
|
25
29
|
"vitest": "latest",
|
|
26
30
|
"zod": "latest"
|
|
27
31
|
},
|