make-service 1.0.0-next.0 → 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/README.md +329 -33
- package/dist/index.d.ts +47 -26
- package/dist/index.js +73 -62
- package/dist/index.mjs +69 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,16 +15,42 @@ It adds a set of little features and allows you to parse responses with [zod](ht
|
|
|
15
15
|
## Example
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
-
const
|
|
18
|
+
const service = makeService("https://example.com/api", {
|
|
19
19
|
Authorization: "Bearer 123",
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
const response = await
|
|
22
|
+
const response = await service.get("/users")
|
|
23
23
|
const users = await response.json(usersSchema);
|
|
24
24
|
// ^? User[]
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
# Table of Contents
|
|
28
|
+
- [Installation](#installation)
|
|
29
|
+
- [API](#api)
|
|
30
|
+
- [makeService](#makeservice)
|
|
31
|
+
- [Type-checking the response body](#type-checking-the-response-body)
|
|
32
|
+
- [Runtime type-checking and parsing the response body](#runtime-type-checking-and-parsing-the-response-body)
|
|
33
|
+
- [Supported HTTP Verbs](#supported-http-verbs)
|
|
34
|
+
- [Headers](#headers)
|
|
35
|
+
- [Passing a function as `baseHeaders`](#passing-a-function-as-baseheaders)
|
|
36
|
+
- [Deleting a previously set header](#deleting-a-previously-set-header)
|
|
37
|
+
- [Base URL](#base-url)
|
|
38
|
+
- [Body](#body)
|
|
39
|
+
- [Query](#query)
|
|
40
|
+
- [Params](#params)
|
|
41
|
+
- [Trace](#trace)
|
|
42
|
+
- [makeFetcher](#makefetcher)
|
|
43
|
+
- [enhancedFetch](#enhancedfetch)
|
|
44
|
+
- [typedResponse](#typedresponse)
|
|
45
|
+
- [Other available primitives](#other-available-primitives)
|
|
46
|
+
- [addQueryToURL](#addquerytourl)
|
|
47
|
+
- [ensureStringBody](#ensurestringbody)
|
|
48
|
+
- [makeGetApiURL](#makegetapiurl)
|
|
49
|
+
- [mergeHeaders](#mergeheaders)
|
|
50
|
+
- [replaceURLParams](#replaceurlparams)
|
|
51
|
+
- [Thank you](#thank-you)
|
|
52
|
+
|
|
53
|
+
# Installation
|
|
28
54
|
|
|
29
55
|
```sh
|
|
30
56
|
npm install make-service
|
|
@@ -39,25 +65,24 @@ import { makeService } from "https://deno.land/x/make_service/mod.ts";
|
|
|
39
65
|
|
|
40
66
|
This library exports the `makeService` function and some primitives used to build it. You can use the primitives as you wish but the `makeService` will have all the features combined.
|
|
41
67
|
|
|
42
|
-
|
|
43
|
-
|
|
68
|
+
## makeService
|
|
44
69
|
The main function of this lib is built on top of the primitives described in the following sections. It allows you to create a service object with a `baseURL` and common `headers` for every request.
|
|
45
70
|
|
|
46
|
-
This service object can be called with every HTTP method and it will return a [`typedResponse`](#typedresponse)
|
|
71
|
+
This service object can be called with every HTTP method and it will return a [`typedResponse`](#typedresponse).
|
|
47
72
|
|
|
48
73
|
```ts
|
|
49
74
|
import { makeService } from 'make-service'
|
|
50
75
|
|
|
51
|
-
const
|
|
76
|
+
const service = makeService("https://example.com/api", {
|
|
52
77
|
authorization: "Bearer 123"
|
|
53
78
|
})
|
|
54
79
|
|
|
55
|
-
const response = await
|
|
80
|
+
const response = await service.get("/users")
|
|
56
81
|
const json = await response.json()
|
|
57
82
|
// ^? unknown
|
|
58
83
|
```
|
|
59
84
|
|
|
60
|
-
On the example above, the
|
|
85
|
+
On the example above, the request will be sent with the following arguments:
|
|
61
86
|
|
|
62
87
|
```ts
|
|
63
88
|
// "https://example.com/api/users"
|
|
@@ -70,15 +95,22 @@ On the example above, the `api.get` will call the [`enhancedFetch`](#enhancedfet
|
|
|
70
95
|
// }
|
|
71
96
|
```
|
|
72
97
|
|
|
73
|
-
|
|
98
|
+
### Type-checking the response body
|
|
99
|
+
The `response` object returned by the `service` can be type-casted with a given generic type. This will type-check the `response.json()` and `response.text()` methods.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const response = await service.get("/users")
|
|
103
|
+
const users = await response.json<{ data: User[] }>()
|
|
104
|
+
// ^? { data: User[] }
|
|
105
|
+
const content = await response.text<`${string}@${string}`>()
|
|
106
|
+
// ^? `${string}@${string}`
|
|
107
|
+
```
|
|
74
108
|
|
|
109
|
+
### Runtime type-checking and parsing the response body
|
|
75
110
|
Its [`typedResponse`](#typedresponse) can also be parsed with a zod schema. Here follows a little more complex example:
|
|
76
111
|
|
|
77
112
|
```ts
|
|
78
|
-
const response = await
|
|
79
|
-
query: { search: "John" },
|
|
80
|
-
trace: (url, requestInit) => console.log(url, requestInit),
|
|
81
|
-
})
|
|
113
|
+
const response = await service.get("/users")
|
|
82
114
|
const json = await response.json(
|
|
83
115
|
z.object({
|
|
84
116
|
data: z.object({
|
|
@@ -92,41 +124,64 @@ const json = await response.json(
|
|
|
92
124
|
.catch([])
|
|
93
125
|
)
|
|
94
126
|
// type of json will be { name: string }[]
|
|
95
|
-
|
|
127
|
+
|
|
128
|
+
const content = await response.text(z.string().email())
|
|
129
|
+
// It will throw an error if the response.text is not a valid email
|
|
96
130
|
```
|
|
131
|
+
You can transform any `Response` in a `TypedResponse` like that by using the [`typedResponse`](#typedresponse) function.
|
|
97
132
|
|
|
98
|
-
|
|
133
|
+
### Supported HTTP Verbs
|
|
134
|
+
Other than the `get` it also accepts more HTTP verbs:
|
|
99
135
|
```ts
|
|
100
|
-
await
|
|
101
|
-
await
|
|
102
|
-
await
|
|
103
|
-
await
|
|
104
|
-
await
|
|
105
|
-
await
|
|
136
|
+
await service.get("/users")
|
|
137
|
+
await service.post("/users", { body: { name: "John" } })
|
|
138
|
+
await service.put("/users/1", { body: { name: "John" } })
|
|
139
|
+
await service.patch("/users/1", { body: { name: "John" } })
|
|
140
|
+
await service.delete("/users/1")
|
|
141
|
+
await service.head("/users")
|
|
142
|
+
await service.options("/users")
|
|
106
143
|
```
|
|
107
144
|
|
|
108
|
-
|
|
145
|
+
### Headers
|
|
146
|
+
The `headers` argument can be a `Headers` object, a `Record<string, string>`, or an array of `[key, value]` tuples (entries).
|
|
147
|
+
The `baseHeaders` and the `headers` will be merged together, with the `headers` taking precedence.
|
|
109
148
|
|
|
110
149
|
```ts
|
|
111
150
|
import { makeService } from 'make-service'
|
|
112
151
|
|
|
113
|
-
const
|
|
114
|
-
authorization: "Bearer 123"
|
|
152
|
+
const service = makeService("https://example.com/api", new Headers({
|
|
153
|
+
authorization: "Bearer 123",
|
|
154
|
+
accept: "*/*",
|
|
115
155
|
}))
|
|
116
156
|
|
|
117
|
-
const response = await
|
|
157
|
+
const response = await service.get("/users", {
|
|
118
158
|
headers: [['accept', 'application/json']],
|
|
119
|
-
query: { page: "2" },
|
|
120
159
|
})
|
|
121
160
|
|
|
122
|
-
// It will call "https://example.com/api/users
|
|
161
|
+
// It will call "https://example.com/api/users"
|
|
123
162
|
// with headers: { authorization: "Bearer 123", accept: "application/json" }
|
|
124
163
|
```
|
|
125
164
|
|
|
165
|
+
#### Passing a function as `baseHeaders`
|
|
166
|
+
The given `baseHeaders` can be a sync or async function that will run in every request before it gets merged with the other headers.
|
|
167
|
+
This is particularly useful when you need to send a refreshed token or add a timestamp to the request.
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { makeService } from 'make-service'
|
|
171
|
+
|
|
172
|
+
declare getAuthorizationToken: () => Promise<HeadersInit>
|
|
173
|
+
|
|
174
|
+
const service = makeService("https://example.com/api", async () => ({
|
|
175
|
+
authorization: await getAuthorizationToken(),
|
|
176
|
+
}))
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### Deleting a previously set header
|
|
126
181
|
In case you want to delete a header previously set you can pass `undefined` or `'undefined'` as its value:
|
|
127
182
|
```ts
|
|
128
|
-
const
|
|
129
|
-
const response = await
|
|
183
|
+
const service = makeService("https://example.com/api", { authorization: "Bearer 123" })
|
|
184
|
+
const response = await service.get("/users", {
|
|
130
185
|
headers: new Headers({ authorization: 'undefined', "Content-Type": undefined }),
|
|
131
186
|
})
|
|
132
187
|
// headers will be empty.
|
|
@@ -136,10 +191,132 @@ Note: Don't forget headers are case insensitive.
|
|
|
136
191
|
const headers = new Headers({ 'Content-Type': 'application/json' })
|
|
137
192
|
Object.fromEntries(headers) // equals to: { 'content-type': 'application/json' }
|
|
138
193
|
```
|
|
194
|
+
All the features above are done by using the [`mergeHeaders`](#mergeheaders) function internally.
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
### Base URL
|
|
198
|
+
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:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { makeService } from 'make-service'
|
|
202
|
+
|
|
203
|
+
const service = makeService(new URL("https://example.com/api"))
|
|
204
|
+
|
|
205
|
+
const response = await service.get("/users?admin=true")
|
|
206
|
+
|
|
207
|
+
// It will call "https://example.com/api/users?admin=true"
|
|
208
|
+
```
|
|
209
|
+
You can use the [`makeGetApiUrl`](#makegetapiurl) method to do that kind of URL composition.
|
|
210
|
+
|
|
211
|
+
### Body
|
|
212
|
+
The function can also receive a `body` object that will be stringified and sent as the request body:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { makeService } from 'make-service'
|
|
216
|
+
|
|
217
|
+
const service = makeService("https://example.com/api")
|
|
218
|
+
const response = await service.post("/users", {
|
|
219
|
+
body: { person: { firstName: "John", lastName: "Doe" } },
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// It will make a POST request to "https://example.com/api/users"
|
|
223
|
+
// with stringified body: "{\"person\":{\"firstName\":\"John\",\"lastName\":\"Doe\"}}"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
You can also pass any other accepted `BodyInit` values as body, such as `FormData`, `URLSearchParams`, `Blob`, `ReadableStream`, `ArrayBuffer`, etc.
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { makeService } from 'make-service'
|
|
230
|
+
|
|
231
|
+
const service = makeService("https://example.com/api")
|
|
232
|
+
const formData = new FormData([["name", "John"], ["lastName", "Doe"]])
|
|
233
|
+
const response = await service.post("/users", {
|
|
234
|
+
body: formData,
|
|
235
|
+
})
|
|
236
|
+
```
|
|
237
|
+
This is achieved by using the [`ensureStringBody`](#ensurestringbody) function internally.
|
|
238
|
+
|
|
239
|
+
### Query
|
|
240
|
+
The service can also receive an `query` object that can be a `string`, a `URLSearchParams`, or an array of entries and it'll add that to the path as queryString:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { makeService } from 'make-service'
|
|
244
|
+
|
|
245
|
+
const service = makeService(new URL("https://example.com/api"))
|
|
246
|
+
|
|
247
|
+
const response = await service.get("/users?admin=true", {
|
|
248
|
+
query: new URLSearchParams({ page: "2" }),
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
// It will call "https://example.com/api/users?admin=true&page=2"
|
|
252
|
+
|
|
253
|
+
// It could also be:
|
|
254
|
+
const response = await service.get("/users?admin=true", {
|
|
255
|
+
query: [["page", "2"]],
|
|
256
|
+
})
|
|
257
|
+
// or:
|
|
258
|
+
const response = await service.get("/users?admin=true", {
|
|
259
|
+
query: "page=2",
|
|
260
|
+
})
|
|
261
|
+
```
|
|
262
|
+
This is achieved by using the [`addQueryToURL`](#addquerytourl) function internally.
|
|
263
|
+
|
|
264
|
+
### Params
|
|
265
|
+
The function can also receive a `params` object that will be used to replace the `:param` wildcards in the path:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { makeService } from 'make-service'
|
|
269
|
+
|
|
270
|
+
const service = makeService(new URL("https://example.com/api"))
|
|
271
|
+
const response = await service.get("/users/:id/article/:articleId", {
|
|
272
|
+
params: { id: "2", articleId: "3" },
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
// It will call "https://example.com/api/users/2/article/3"
|
|
276
|
+
```
|
|
277
|
+
This is achieved by using the [`replaceURLParams`](#replaceurlparams) function internally.
|
|
278
|
+
|
|
279
|
+
### Trace
|
|
280
|
+
The function can also receive a `trace` function that will be called with the final `url` and `requestInit` arguments.
|
|
281
|
+
Therefore you can know what are the actual arguments that will be passed to the `fetch` API.
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
import { makeService } from 'make-service'
|
|
285
|
+
|
|
286
|
+
const service = makeService("https://example.com/api")
|
|
287
|
+
const response = await service.get("/users/:id", {
|
|
288
|
+
params: { id: "2" },
|
|
289
|
+
query: { page: "2"},
|
|
290
|
+
headers: { Accept: "application/json" },
|
|
291
|
+
trace: (url, requestInit) => {
|
|
292
|
+
console.log("The request was sent to " + url)
|
|
293
|
+
console.log("with the following params: " + JSON.stringify(requestInit))
|
|
294
|
+
},
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
// It will log:
|
|
298
|
+
// "The request was sent to https://example.com/api/users/2?page=2"
|
|
299
|
+
// with the following params: { headers: { "Accept": "application/json", "Content-type": "application/json" } }
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## makeFetcher
|
|
303
|
+
This method is the same as [`makeService`](#make-service) but it doesn't expose the HTTP methods as properties of the returned object.
|
|
304
|
+
This is good for when you want to have a service setup but don't know the methods you'll be calling in advance, like in a proxy.
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { makeFetcher } from 'make-service'
|
|
308
|
+
|
|
309
|
+
const fetcher = makeFetcher("https://example.com/api")
|
|
310
|
+
const response = await fetcher("/users", { method: "POST", body: { email: "john@doe.com" } })
|
|
311
|
+
const json = await response.json()
|
|
312
|
+
// ^? unknown
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Other than having to pass the method in the `RequestInit` this is going to have all the features of [`makeService`](#make-service).
|
|
139
316
|
|
|
140
317
|
## enhancedFetch
|
|
141
318
|
|
|
142
|
-
A wrapper around the `fetch`
|
|
319
|
+
A wrapper around the `fetch` service.
|
|
143
320
|
It returns a [`TypedResponse`](#typedresponse) instead of a `Response`.
|
|
144
321
|
|
|
145
322
|
```ts
|
|
@@ -154,7 +331,7 @@ const json = await response.json()
|
|
|
154
331
|
// You can pass it a generic or schema to type the result
|
|
155
332
|
```
|
|
156
333
|
|
|
157
|
-
This function accepts the same arguments as the `fetch` API - with exception of [JSON-like body](
|
|
334
|
+
This function accepts the same arguments as the `fetch` API - with exception of [JSON-like body](#body) -, and it also accepts an object of [`params`](#params) to replace URL wildcards, an object-like [`query`](#query), and a [`trace`](#trace) function. Those are all described above in [`makeService`](#make-service).
|
|
158
335
|
|
|
159
336
|
This slightly different `RequestInit` is typed as `EnhancedRequestInit`.
|
|
160
337
|
|
|
@@ -166,7 +343,7 @@ await enhancedFetch("https://example.com/api/users/:role", {
|
|
|
166
343
|
body: { some: { object: { as: { body } } } },
|
|
167
344
|
query: { page: "1" },
|
|
168
345
|
params: { role: "admin" },
|
|
169
|
-
trace:
|
|
346
|
+
trace: console.log,
|
|
170
347
|
})
|
|
171
348
|
|
|
172
349
|
// The trace function will be called with the following arguments:
|
|
@@ -207,5 +384,124 @@ const text = await response.text(z.string().email())
|
|
|
207
384
|
// ^? string
|
|
208
385
|
```
|
|
209
386
|
|
|
387
|
+
# Other available primitives
|
|
388
|
+
This little library has plenty of other useful functions that you can use to build your own services and interactions with external APIs.
|
|
389
|
+
|
|
390
|
+
## addQueryToURL
|
|
391
|
+
It receives a URL instance or URL string and an object-like query and returns a new URL with the query appended to it.
|
|
392
|
+
|
|
393
|
+
It will preserve the original query if it exists and will also preserve the type of the given URL.
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
import { addQueryToURL } from 'make-service'
|
|
397
|
+
|
|
398
|
+
addQueryToURL("https://example.com/api/users", { page: "2" })
|
|
399
|
+
// https://example.com/api/users?page=2
|
|
400
|
+
|
|
401
|
+
addQueryToURL(
|
|
402
|
+
"https://example.com/api/users?role=admin",
|
|
403
|
+
{ page: "2" },
|
|
404
|
+
)
|
|
405
|
+
// https://example.com/api/users?role=admin&page=2
|
|
406
|
+
|
|
407
|
+
addQueryToURL(
|
|
408
|
+
new URL("https://example.com/api/users"),
|
|
409
|
+
{ page: "2" },
|
|
410
|
+
)
|
|
411
|
+
// https://example.com/api/users?page=2
|
|
412
|
+
|
|
413
|
+
addQueryToURL(
|
|
414
|
+
new URL("https://example.com/api/users?role=admin"),
|
|
415
|
+
{ page: "2" },
|
|
416
|
+
)
|
|
417
|
+
// https://example.com/api/users?role=admin&page=2
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## ensureStringBody
|
|
421
|
+
It accepts any value considered a `BodyInit` (the type of the body in `fetch`, such as `ReadableStream` | `XMLHttpRequestBodyInit` | `null`) and also accepts a JSON-like structure such as a number, string, boolean, array or object.
|
|
422
|
+
|
|
423
|
+
In case it detects a JSON-like structure it will return a stringified version of that payload. Otherwise the type will be preserved.
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
import { ensureStringBody } from 'make-service'
|
|
427
|
+
|
|
428
|
+
ensureStringBody({ foo: "bar" })
|
|
429
|
+
// '{"foo":"bar"}'
|
|
430
|
+
ensureStringBody("foo")
|
|
431
|
+
// 'foo'
|
|
432
|
+
ensureStringBody(1)
|
|
433
|
+
// '1'
|
|
434
|
+
ensureStringBody(true)
|
|
435
|
+
// 'true'
|
|
436
|
+
ensureStringBody(null)
|
|
437
|
+
// null
|
|
438
|
+
ensureStringBody(new ReadableStream())
|
|
439
|
+
// ReadableStream
|
|
440
|
+
|
|
441
|
+
// and so on...
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## makeGetApiURL
|
|
445
|
+
It creates an URL builder for your API. It works similarly to [`makeFetcher`](#makefetcher) but will return the URL instead of a response.
|
|
446
|
+
|
|
447
|
+
You create a `getApiURL` function by giving it a `baseURL` and then it accepts a path and an optional [query](#query) that will be merged into the final URL.
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
import { makeGetApiURL } from 'make-service'
|
|
451
|
+
|
|
452
|
+
const getApiURL = makeGetApiURL("https://example.com/api")
|
|
453
|
+
const url = getApiURL("/users?admin=true", { query: { page: "2" } })
|
|
454
|
+
|
|
455
|
+
// "https://example.com/api/users?admin=true&page=2"
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Notice the extra slashes are gonna be added or removed as needed.
|
|
459
|
+
```ts
|
|
460
|
+
makeGetApiURL("https://example.com/api/")("/users")
|
|
461
|
+
// "https://example.com/api/users"
|
|
462
|
+
makeGetApiURL("https://example.com/api")("users")
|
|
463
|
+
// "https://example.com/api/users"
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## mergeHeaders
|
|
467
|
+
It merges multiple `HeadersInit` objects into a single `Headers` instance.
|
|
468
|
+
They can be of any type that is accepted by the `Headers` constructor, like a `Headers` instance, a plain object, or an array of entries.
|
|
469
|
+
|
|
470
|
+
```ts
|
|
471
|
+
import { mergeHeaders } from 'make-service'
|
|
472
|
+
|
|
473
|
+
const headers1 = new Headers({ "Content-Type": "application/json" })
|
|
474
|
+
const headers2 = { Accept: "application/json" }
|
|
475
|
+
const headers3 = [["accept", "*/*"]]
|
|
476
|
+
|
|
477
|
+
const merged = mergeHeaders(headers1, headers2, headers3)
|
|
478
|
+
// ^? Headers({ "content-Type": "application/json", "accept": "*/*" })
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
It will delete previous headers if `undefined` or `"undefined"` is given:
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
import { mergeHeaders } from 'make-service'
|
|
485
|
+
|
|
486
|
+
const headers1 = new Headers({ "Content-Type": "application/json", Accept: "application/json" })
|
|
487
|
+
const headers2 = { accept: undefined }
|
|
488
|
+
const headers3 = [["content-type", "undefined"]]
|
|
489
|
+
|
|
490
|
+
const merged = mergeHeaders(headers1, headers2, headers3)
|
|
491
|
+
// ^? Headers({})
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## replaceURLParams
|
|
495
|
+
This function replaces URL wildcards with the given params.
|
|
496
|
+
```ts
|
|
497
|
+
import { replaceURLParams } from 'make-service'
|
|
498
|
+
|
|
499
|
+
const url = replaceURLParams(
|
|
500
|
+
"https://example.com/users/:id/posts/:postId",
|
|
501
|
+
{ id: "2", postId: "3" },
|
|
502
|
+
)
|
|
503
|
+
// It will return: "https://example.com/users/2/posts/3"
|
|
504
|
+
```
|
|
505
|
+
|
|
210
506
|
## Thank you
|
|
211
507
|
I really appreciate your feedback and contributions. If you have any questions, feel free to open an issue or contact me on [Twitter](https://twitter.com/gugaguichard).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"];
|
|
1
|
+
declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT"];
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* It returns the JSON object or throws an error if the response is not ok.
|
|
@@ -23,7 +23,8 @@ type TypedResponse = Omit<Response, 'json' | 'text'> & {
|
|
|
23
23
|
json: TypedResponseJson;
|
|
24
24
|
text: TypedResponseText;
|
|
25
25
|
};
|
|
26
|
-
type EnhancedRequestInit = Omit<RequestInit, 'body'> & {
|
|
26
|
+
type EnhancedRequestInit = Omit<RequestInit, 'body' | 'method'> & {
|
|
27
|
+
method?: HTTPMethod | Lowercase<HTTPMethod>;
|
|
27
28
|
body?: JSONValue | BodyInit | null;
|
|
28
29
|
query?: SearchParams;
|
|
29
30
|
params?: Record<string, string>;
|
|
@@ -43,23 +44,6 @@ type PathParams<T extends string> = NoEmpty<T extends `${infer _}:${infer Param}
|
|
|
43
44
|
[K in Param]: string;
|
|
44
45
|
} : {}>;
|
|
45
46
|
|
|
46
|
-
/**
|
|
47
|
-
* It merges multiple HeadersInit objects into a single Headers object
|
|
48
|
-
* @param entries Any number of HeadersInit objects
|
|
49
|
-
* @returns a new Headers object with the merged headers
|
|
50
|
-
*/
|
|
51
|
-
declare function mergeHeaders(...entries: (HeadersInit | [string, undefined][] | Record<string, undefined>)[]): Headers;
|
|
52
|
-
/**
|
|
53
|
-
* @param url a string or URL to which the query parameters will be added
|
|
54
|
-
* @param searchParams the query parameters
|
|
55
|
-
* @returns the url with the query parameters added with the same type as the url
|
|
56
|
-
*/
|
|
57
|
-
declare function addQueryToUrl(url: string | URL, searchParams?: SearchParams): string | URL;
|
|
58
|
-
/**
|
|
59
|
-
* @param baseURL the base path to the API
|
|
60
|
-
* @returns a function that receives a path and an object of query parameters and returns a URL
|
|
61
|
-
*/
|
|
62
|
-
declare function makeGetApiUrl(baseURL: string | URL): (path: string, searchParams?: SearchParams) => string | URL;
|
|
63
47
|
/**
|
|
64
48
|
* It hacks the Response object to add typed json and text methods
|
|
65
49
|
* @param response the Response to be proxied
|
|
@@ -75,11 +59,6 @@ declare function makeGetApiUrl(baseURL: string | URL): (path: string, searchPara
|
|
|
75
59
|
* // ^? User[]
|
|
76
60
|
*/
|
|
77
61
|
declare function typedResponse(response: Response): TypedResponse;
|
|
78
|
-
/**
|
|
79
|
-
* @param body the JSON-like body of the request
|
|
80
|
-
* @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.
|
|
81
|
-
*/
|
|
82
|
-
declare function ensureStringBody<B extends JSONValue | BodyInit | null>(body?: B): B extends JSONValue ? string : B;
|
|
83
62
|
/**
|
|
84
63
|
*
|
|
85
64
|
* @param url a string or URL to be fetched
|
|
@@ -95,6 +74,18 @@ declare function ensureStringBody<B extends JSONValue | BodyInit | null>(body?:
|
|
|
95
74
|
* // ^? unknown
|
|
96
75
|
*/
|
|
97
76
|
declare function enhancedFetch(url: string | URL, requestInit?: EnhancedRequestInit): Promise<TypedResponse>;
|
|
77
|
+
/**
|
|
78
|
+
*
|
|
79
|
+
* @param baseURL the base URL to be fetched in every request
|
|
80
|
+
* @param baseHeaders any headers that should be sent with every request
|
|
81
|
+
* @returns a function that receive a path and requestInit and return a serialized json response that can be typed or not.
|
|
82
|
+
* @example const headers = { Authorization: "Bearer 123" }
|
|
83
|
+
* const fetcher = makeFetcher("https://example.com/api", headers);
|
|
84
|
+
* const response = await fetcher("/users", { method: "GET" })
|
|
85
|
+
* const users = await response.json(userSchema);
|
|
86
|
+
* // ^? User[]
|
|
87
|
+
*/
|
|
88
|
+
declare function makeFetcher(baseURL: string | URL, baseHeaders?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>)): (path: string, requestInit?: EnhancedRequestInit) => Promise<TypedResponse>;
|
|
98
89
|
/**
|
|
99
90
|
*
|
|
100
91
|
* @param baseURL the base URL to the API
|
|
@@ -106,6 +97,36 @@ declare function enhancedFetch(url: string | URL, requestInit?: EnhancedRequestI
|
|
|
106
97
|
* const users = await response.json(userSchema);
|
|
107
98
|
* // ^? User[]
|
|
108
99
|
*/
|
|
109
|
-
declare function makeService(baseURL: string | URL, baseHeaders?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>)): Record<"
|
|
100
|
+
declare function makeService(baseURL: string | URL, baseHeaders?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>)): Record<"get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "connect", (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param url a string or URL to which the query parameters will be added
|
|
104
|
+
* @param searchParams the query parameters
|
|
105
|
+
* @returns the url with the query parameters added with the same type as the url
|
|
106
|
+
*/
|
|
107
|
+
declare function addQueryToURL(url: string | URL, searchParams?: SearchParams): string | URL;
|
|
108
|
+
/**
|
|
109
|
+
* @param body the JSON-like body of the request
|
|
110
|
+
* @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.
|
|
111
|
+
*/
|
|
112
|
+
declare function ensureStringBody<B extends JSONValue | BodyInit | null>(body?: B): B extends JSONValue ? string : B;
|
|
113
|
+
/**
|
|
114
|
+
* @param baseURL the base path to the API
|
|
115
|
+
* @returns a function that receives a path and an object of query parameters and returns a URL
|
|
116
|
+
*/
|
|
117
|
+
declare function makeGetApiURL<T extends string | URL>(baseURL: T): (path: string, searchParams?: SearchParams) => T;
|
|
118
|
+
/**
|
|
119
|
+
* It merges multiple HeadersInit objects into a single Headers object
|
|
120
|
+
* @param entries Any number of HeadersInit objects
|
|
121
|
+
* @returns a new Headers object with the merged headers
|
|
122
|
+
*/
|
|
123
|
+
declare function mergeHeaders(...entries: (HeadersInit | [string, undefined][] | Record<string, undefined>)[]): Headers;
|
|
124
|
+
/**
|
|
125
|
+
*
|
|
126
|
+
* @param url the url string or URL object to replace the params
|
|
127
|
+
* @param params the params map to be replaced in the url
|
|
128
|
+
* @returns the url with the params replaced and with the same type as the given url
|
|
129
|
+
*/
|
|
130
|
+
declare function replaceURLParams<T extends string | URL>(url: string | URL, params: EnhancedRequestInit['params']): T;
|
|
110
131
|
|
|
111
|
-
export { EnhancedRequestInit, HTTPMethod, JSONValue, PathParams, Schema, SearchParams, ServiceRequestInit, TypedResponse, TypedResponseJson, TypedResponseText,
|
|
132
|
+
export { EnhancedRequestInit, HTTPMethod, JSONValue, PathParams, Schema, SearchParams, ServiceRequestInit, TypedResponse, TypedResponseJson, TypedResponseText, addQueryToURL, enhancedFetch, ensureStringBody, makeFetcher, makeGetApiURL, makeService, mergeHeaders, replaceURLParams, typedResponse };
|
package/dist/index.js
CHANGED
|
@@ -20,12 +20,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
-
|
|
23
|
+
addQueryToURL: () => addQueryToURL,
|
|
24
24
|
enhancedFetch: () => enhancedFetch,
|
|
25
25
|
ensureStringBody: () => ensureStringBody,
|
|
26
|
-
|
|
26
|
+
makeFetcher: () => makeFetcher,
|
|
27
|
+
makeGetApiURL: () => makeGetApiURL,
|
|
27
28
|
makeService: () => makeService,
|
|
28
29
|
mergeHeaders: () => mergeHeaders,
|
|
30
|
+
replaceURLParams: () => replaceURLParams,
|
|
29
31
|
typedResponse: () => typedResponse
|
|
30
32
|
});
|
|
31
33
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -38,7 +40,9 @@ var HTTP_METHODS = [
|
|
|
38
40
|
"DELETE",
|
|
39
41
|
"PATCH",
|
|
40
42
|
"OPTIONS",
|
|
41
|
-
"HEAD"
|
|
43
|
+
"HEAD",
|
|
44
|
+
"CONNECT"
|
|
45
|
+
// 'TRACE', it has no support in most browsers yet
|
|
42
46
|
];
|
|
43
47
|
|
|
44
48
|
// src/internals.ts
|
|
@@ -54,35 +58,12 @@ function getText(response) {
|
|
|
54
58
|
return schema ? schema.parse(text) : text;
|
|
55
59
|
};
|
|
56
60
|
}
|
|
57
|
-
function replaceUrlParams(url, params) {
|
|
58
|
-
if (!params)
|
|
59
|
-
return url;
|
|
60
|
-
let urlString = String(url);
|
|
61
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
62
|
-
urlString = urlString.replace(new RegExp(`:${key}($|/)`), `${value}$1`);
|
|
63
|
-
});
|
|
64
|
-
return url instanceof URL ? new URL(urlString) : urlString;
|
|
65
|
-
}
|
|
66
61
|
function typeOf(t) {
|
|
67
62
|
return Object.prototype.toString.call(t).replace(/^\[object (.+)\]$/, "$1").toLowerCase();
|
|
68
63
|
}
|
|
69
64
|
|
|
70
|
-
// src/
|
|
71
|
-
function
|
|
72
|
-
const result = /* @__PURE__ */ new Map();
|
|
73
|
-
for (const entry of entries) {
|
|
74
|
-
const headers = new Headers(entry);
|
|
75
|
-
for (const [key, value] of headers.entries()) {
|
|
76
|
-
if (value === void 0 || value === "undefined") {
|
|
77
|
-
result.delete(key);
|
|
78
|
-
} else {
|
|
79
|
-
result.set(key, value);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return new Headers(Array.from(result.entries()));
|
|
84
|
-
}
|
|
85
|
-
function addQueryToUrl(url, searchParams) {
|
|
65
|
+
// src/primitives.ts
|
|
66
|
+
function addQueryToURL(url, searchParams) {
|
|
86
67
|
if (!searchParams)
|
|
87
68
|
return url;
|
|
88
69
|
if (typeof url === "string") {
|
|
@@ -98,13 +79,45 @@ function addQueryToUrl(url, searchParams) {
|
|
|
98
79
|
}
|
|
99
80
|
return url;
|
|
100
81
|
}
|
|
101
|
-
function
|
|
82
|
+
function ensureStringBody(body) {
|
|
83
|
+
if (typeof body === "undefined")
|
|
84
|
+
return body;
|
|
85
|
+
if (typeof body === "string")
|
|
86
|
+
return body;
|
|
87
|
+
return ["number", "boolean", "array", "object"].includes(typeOf(body)) ? JSON.stringify(body) : body;
|
|
88
|
+
}
|
|
89
|
+
function makeGetApiURL(baseURL) {
|
|
102
90
|
const base = baseURL instanceof URL ? baseURL.toString() : baseURL;
|
|
103
91
|
return (path, searchParams) => {
|
|
104
|
-
const url = `${base}
|
|
105
|
-
return
|
|
92
|
+
const url = `${base}/${path}`.replace(/([^https?:]\/)\/+/g, "$1");
|
|
93
|
+
return addQueryToURL(url, searchParams);
|
|
106
94
|
};
|
|
107
95
|
}
|
|
96
|
+
function mergeHeaders(...entries) {
|
|
97
|
+
const result = /* @__PURE__ */ new Map();
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
const headers = new Headers(entry);
|
|
100
|
+
for (const [key, value] of headers.entries()) {
|
|
101
|
+
if (value === void 0 || value === "undefined") {
|
|
102
|
+
result.delete(key);
|
|
103
|
+
} else {
|
|
104
|
+
result.set(key, value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return new Headers(Array.from(result.entries()));
|
|
109
|
+
}
|
|
110
|
+
function replaceURLParams(url, params) {
|
|
111
|
+
if (!params)
|
|
112
|
+
return url;
|
|
113
|
+
let urlString = String(url);
|
|
114
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
115
|
+
urlString = urlString.replace(new RegExp(`:${key}($|/)`), `${value}$1`);
|
|
116
|
+
});
|
|
117
|
+
return url instanceof URL ? new URL(urlString) : urlString;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/api.ts
|
|
108
121
|
function typedResponse(response) {
|
|
109
122
|
return new Proxy(response, {
|
|
110
123
|
get(target, prop) {
|
|
@@ -116,13 +129,6 @@ function typedResponse(response) {
|
|
|
116
129
|
}
|
|
117
130
|
});
|
|
118
131
|
}
|
|
119
|
-
function ensureStringBody(body) {
|
|
120
|
-
if (typeof body === "undefined")
|
|
121
|
-
return body;
|
|
122
|
-
if (typeof body === "string")
|
|
123
|
-
return body;
|
|
124
|
-
return ["number", "boolean", "array", "object"].includes(typeOf(body)) ? JSON.stringify(body) : body;
|
|
125
|
-
}
|
|
126
132
|
async function enhancedFetch(url, requestInit) {
|
|
127
133
|
var _a, _b;
|
|
128
134
|
const { query, trace, ...reqInit } = requestInit != null ? requestInit : {};
|
|
@@ -132,44 +138,49 @@ async function enhancedFetch(url, requestInit) {
|
|
|
132
138
|
},
|
|
133
139
|
(_a = reqInit.headers) != null ? _a : {}
|
|
134
140
|
);
|
|
135
|
-
const withParams =
|
|
136
|
-
const
|
|
141
|
+
const withParams = replaceURLParams(url, (_b = reqInit.params) != null ? _b : {});
|
|
142
|
+
const fullURL = addQueryToURL(withParams, query);
|
|
137
143
|
const body = ensureStringBody(reqInit.body);
|
|
138
144
|
const enhancedReqInit = { ...reqInit, headers, body };
|
|
139
|
-
trace == null ? void 0 : trace(
|
|
140
|
-
const response = await fetch(
|
|
145
|
+
trace == null ? void 0 : trace(fullURL, enhancedReqInit);
|
|
146
|
+
const response = await fetch(fullURL, enhancedReqInit);
|
|
141
147
|
return typedResponse(response);
|
|
142
148
|
}
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
headers:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
});
|
|
156
|
-
return response;
|
|
157
|
-
};
|
|
149
|
+
function makeFetcher(baseURL, baseHeaders) {
|
|
150
|
+
return async (path, requestInit = {}) => {
|
|
151
|
+
var _a;
|
|
152
|
+
const url = makeGetApiURL(baseURL)(path);
|
|
153
|
+
const response = await enhancedFetch(url, {
|
|
154
|
+
...requestInit,
|
|
155
|
+
headers: mergeHeaders(
|
|
156
|
+
typeof baseHeaders === "function" ? await baseHeaders() : baseHeaders != null ? baseHeaders : {},
|
|
157
|
+
(_a = requestInit == null ? void 0 : requestInit.headers) != null ? _a : {}
|
|
158
|
+
)
|
|
159
|
+
});
|
|
160
|
+
return response;
|
|
158
161
|
};
|
|
159
|
-
|
|
162
|
+
}
|
|
163
|
+
function makeService(baseURL, baseHeaders) {
|
|
164
|
+
const fetcher = makeFetcher(baseURL, baseHeaders);
|
|
165
|
+
function appliedService(method) {
|
|
166
|
+
return async (path, requestInit = {}) => fetcher(path, { ...requestInit, method });
|
|
167
|
+
}
|
|
168
|
+
let service = {};
|
|
160
169
|
for (const method of HTTP_METHODS) {
|
|
161
170
|
const lowerMethod = method.toLowerCase();
|
|
162
|
-
|
|
171
|
+
service[lowerMethod] = appliedService(method);
|
|
163
172
|
}
|
|
164
|
-
return
|
|
173
|
+
return service;
|
|
165
174
|
}
|
|
166
175
|
// Annotate the CommonJS export names for ESM import in node:
|
|
167
176
|
0 && (module.exports = {
|
|
168
|
-
|
|
177
|
+
addQueryToURL,
|
|
169
178
|
enhancedFetch,
|
|
170
179
|
ensureStringBody,
|
|
171
|
-
|
|
180
|
+
makeFetcher,
|
|
181
|
+
makeGetApiURL,
|
|
172
182
|
makeService,
|
|
173
183
|
mergeHeaders,
|
|
184
|
+
replaceURLParams,
|
|
174
185
|
typedResponse
|
|
175
186
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -6,7 +6,9 @@ var HTTP_METHODS = [
|
|
|
6
6
|
"DELETE",
|
|
7
7
|
"PATCH",
|
|
8
8
|
"OPTIONS",
|
|
9
|
-
"HEAD"
|
|
9
|
+
"HEAD",
|
|
10
|
+
"CONNECT"
|
|
11
|
+
// 'TRACE', it has no support in most browsers yet
|
|
10
12
|
];
|
|
11
13
|
|
|
12
14
|
// src/internals.ts
|
|
@@ -22,35 +24,12 @@ function getText(response) {
|
|
|
22
24
|
return schema ? schema.parse(text) : text;
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
|
-
function replaceUrlParams(url, params) {
|
|
26
|
-
if (!params)
|
|
27
|
-
return url;
|
|
28
|
-
let urlString = String(url);
|
|
29
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
30
|
-
urlString = urlString.replace(new RegExp(`:${key}($|/)`), `${value}$1`);
|
|
31
|
-
});
|
|
32
|
-
return url instanceof URL ? new URL(urlString) : urlString;
|
|
33
|
-
}
|
|
34
27
|
function typeOf(t) {
|
|
35
28
|
return Object.prototype.toString.call(t).replace(/^\[object (.+)\]$/, "$1").toLowerCase();
|
|
36
29
|
}
|
|
37
30
|
|
|
38
|
-
// src/
|
|
39
|
-
function
|
|
40
|
-
const result = /* @__PURE__ */ new Map();
|
|
41
|
-
for (const entry of entries) {
|
|
42
|
-
const headers = new Headers(entry);
|
|
43
|
-
for (const [key, value] of headers.entries()) {
|
|
44
|
-
if (value === void 0 || value === "undefined") {
|
|
45
|
-
result.delete(key);
|
|
46
|
-
} else {
|
|
47
|
-
result.set(key, value);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return new Headers(Array.from(result.entries()));
|
|
52
|
-
}
|
|
53
|
-
function addQueryToUrl(url, searchParams) {
|
|
31
|
+
// src/primitives.ts
|
|
32
|
+
function addQueryToURL(url, searchParams) {
|
|
54
33
|
if (!searchParams)
|
|
55
34
|
return url;
|
|
56
35
|
if (typeof url === "string") {
|
|
@@ -66,13 +45,45 @@ function addQueryToUrl(url, searchParams) {
|
|
|
66
45
|
}
|
|
67
46
|
return url;
|
|
68
47
|
}
|
|
69
|
-
function
|
|
48
|
+
function ensureStringBody(body) {
|
|
49
|
+
if (typeof body === "undefined")
|
|
50
|
+
return body;
|
|
51
|
+
if (typeof body === "string")
|
|
52
|
+
return body;
|
|
53
|
+
return ["number", "boolean", "array", "object"].includes(typeOf(body)) ? JSON.stringify(body) : body;
|
|
54
|
+
}
|
|
55
|
+
function makeGetApiURL(baseURL) {
|
|
70
56
|
const base = baseURL instanceof URL ? baseURL.toString() : baseURL;
|
|
71
57
|
return (path, searchParams) => {
|
|
72
|
-
const url = `${base}
|
|
73
|
-
return
|
|
58
|
+
const url = `${base}/${path}`.replace(/([^https?:]\/)\/+/g, "$1");
|
|
59
|
+
return addQueryToURL(url, searchParams);
|
|
74
60
|
};
|
|
75
61
|
}
|
|
62
|
+
function mergeHeaders(...entries) {
|
|
63
|
+
const result = /* @__PURE__ */ new Map();
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
const headers = new Headers(entry);
|
|
66
|
+
for (const [key, value] of headers.entries()) {
|
|
67
|
+
if (value === void 0 || value === "undefined") {
|
|
68
|
+
result.delete(key);
|
|
69
|
+
} else {
|
|
70
|
+
result.set(key, value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return new Headers(Array.from(result.entries()));
|
|
75
|
+
}
|
|
76
|
+
function replaceURLParams(url, params) {
|
|
77
|
+
if (!params)
|
|
78
|
+
return url;
|
|
79
|
+
let urlString = String(url);
|
|
80
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
81
|
+
urlString = urlString.replace(new RegExp(`:${key}($|/)`), `${value}$1`);
|
|
82
|
+
});
|
|
83
|
+
return url instanceof URL ? new URL(urlString) : urlString;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/api.ts
|
|
76
87
|
function typedResponse(response) {
|
|
77
88
|
return new Proxy(response, {
|
|
78
89
|
get(target, prop) {
|
|
@@ -84,13 +95,6 @@ function typedResponse(response) {
|
|
|
84
95
|
}
|
|
85
96
|
});
|
|
86
97
|
}
|
|
87
|
-
function ensureStringBody(body) {
|
|
88
|
-
if (typeof body === "undefined")
|
|
89
|
-
return body;
|
|
90
|
-
if (typeof body === "string")
|
|
91
|
-
return body;
|
|
92
|
-
return ["number", "boolean", "array", "object"].includes(typeOf(body)) ? JSON.stringify(body) : body;
|
|
93
|
-
}
|
|
94
98
|
async function enhancedFetch(url, requestInit) {
|
|
95
99
|
var _a, _b;
|
|
96
100
|
const { query, trace, ...reqInit } = requestInit != null ? requestInit : {};
|
|
@@ -100,43 +104,48 @@ async function enhancedFetch(url, requestInit) {
|
|
|
100
104
|
},
|
|
101
105
|
(_a = reqInit.headers) != null ? _a : {}
|
|
102
106
|
);
|
|
103
|
-
const withParams =
|
|
104
|
-
const
|
|
107
|
+
const withParams = replaceURLParams(url, (_b = reqInit.params) != null ? _b : {});
|
|
108
|
+
const fullURL = addQueryToURL(withParams, query);
|
|
105
109
|
const body = ensureStringBody(reqInit.body);
|
|
106
110
|
const enhancedReqInit = { ...reqInit, headers, body };
|
|
107
|
-
trace == null ? void 0 : trace(
|
|
108
|
-
const response = await fetch(
|
|
111
|
+
trace == null ? void 0 : trace(fullURL, enhancedReqInit);
|
|
112
|
+
const response = await fetch(fullURL, enhancedReqInit);
|
|
109
113
|
return typedResponse(response);
|
|
110
114
|
}
|
|
111
|
-
function
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
headers:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
124
|
-
return response;
|
|
125
|
-
};
|
|
115
|
+
function makeFetcher(baseURL, baseHeaders) {
|
|
116
|
+
return async (path, requestInit = {}) => {
|
|
117
|
+
var _a;
|
|
118
|
+
const url = makeGetApiURL(baseURL)(path);
|
|
119
|
+
const response = await enhancedFetch(url, {
|
|
120
|
+
...requestInit,
|
|
121
|
+
headers: mergeHeaders(
|
|
122
|
+
typeof baseHeaders === "function" ? await baseHeaders() : baseHeaders != null ? baseHeaders : {},
|
|
123
|
+
(_a = requestInit == null ? void 0 : requestInit.headers) != null ? _a : {}
|
|
124
|
+
)
|
|
125
|
+
});
|
|
126
|
+
return response;
|
|
126
127
|
};
|
|
127
|
-
|
|
128
|
+
}
|
|
129
|
+
function makeService(baseURL, baseHeaders) {
|
|
130
|
+
const fetcher = makeFetcher(baseURL, baseHeaders);
|
|
131
|
+
function appliedService(method) {
|
|
132
|
+
return async (path, requestInit = {}) => fetcher(path, { ...requestInit, method });
|
|
133
|
+
}
|
|
134
|
+
let service = {};
|
|
128
135
|
for (const method of HTTP_METHODS) {
|
|
129
136
|
const lowerMethod = method.toLowerCase();
|
|
130
|
-
|
|
137
|
+
service[lowerMethod] = appliedService(method);
|
|
131
138
|
}
|
|
132
|
-
return
|
|
139
|
+
return service;
|
|
133
140
|
}
|
|
134
141
|
export {
|
|
135
|
-
|
|
142
|
+
addQueryToURL,
|
|
136
143
|
enhancedFetch,
|
|
137
144
|
ensureStringBody,
|
|
138
|
-
|
|
145
|
+
makeFetcher,
|
|
146
|
+
makeGetApiURL,
|
|
139
147
|
makeService,
|
|
140
148
|
mergeHeaders,
|
|
149
|
+
replaceURLParams,
|
|
141
150
|
typedResponse
|
|
142
151
|
};
|
package/package.json
CHANGED