make-service 0.2.0 → 1.0.0-next.2
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 +333 -35
- package/dist/index.d.ts +48 -31
- package/dist/index.js +75 -64
- package/dist/index.mjs +71 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,22 +8,49 @@ It adds a set of little features and allows you to parse responses with [zod](ht
|
|
|
8
8
|
- 🤩 Type-safe return of `response.json()` and `response.text()`. Defaults to `unknown` instead of `any`.
|
|
9
9
|
- 🚦 Easily setup an API with a `baseURL` and common `headers` for every request.
|
|
10
10
|
- 🏗️ Compose URL from the base by just calling the endpoints and an object-like `query`.
|
|
11
|
+
- 🐾 Replaces URL wildcards with an object of `params`.
|
|
11
12
|
- 🧙♀️ Automatically stringifies the `body` of a request so you can give it a JSON-like structure.
|
|
12
13
|
- 🐛 Accepts a `trace` function for debugging.
|
|
13
14
|
|
|
14
15
|
## Example
|
|
15
16
|
|
|
16
17
|
```ts
|
|
17
|
-
const
|
|
18
|
+
const service = makeService("https://example.com/api", {
|
|
18
19
|
Authorization: "Bearer 123",
|
|
19
20
|
});
|
|
20
21
|
|
|
21
|
-
const response = await
|
|
22
|
+
const response = await service.get("/users")
|
|
22
23
|
const users = await response.json(usersSchema);
|
|
23
24
|
// ^? User[]
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
|
|
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
|
|
27
54
|
|
|
28
55
|
```sh
|
|
29
56
|
npm install make-service
|
|
@@ -38,25 +65,24 @@ import { makeService } from "https://deno.land/x/make_service/mod.ts";
|
|
|
38
65
|
|
|
39
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.
|
|
40
67
|
|
|
41
|
-
|
|
42
|
-
|
|
68
|
+
## makeService
|
|
43
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.
|
|
44
70
|
|
|
45
|
-
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).
|
|
46
72
|
|
|
47
73
|
```ts
|
|
48
74
|
import { makeService } from 'make-service'
|
|
49
75
|
|
|
50
|
-
const
|
|
76
|
+
const service = makeService("https://example.com/api", {
|
|
51
77
|
authorization: "Bearer 123"
|
|
52
78
|
})
|
|
53
79
|
|
|
54
|
-
const response = await
|
|
80
|
+
const response = await service.get("/users")
|
|
55
81
|
const json = await response.json()
|
|
56
82
|
// ^? unknown
|
|
57
83
|
```
|
|
58
84
|
|
|
59
|
-
On the example above, the
|
|
85
|
+
On the example above, the request will be sent with the following arguments:
|
|
60
86
|
|
|
61
87
|
```ts
|
|
62
88
|
// "https://example.com/api/users"
|
|
@@ -69,15 +95,22 @@ On the example above, the `api.get` will call the [`enhancedFetch`](#enhancedfet
|
|
|
69
95
|
// }
|
|
70
96
|
```
|
|
71
97
|
|
|
72
|
-
|
|
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
|
+
```
|
|
73
108
|
|
|
109
|
+
### Runtime type-checking and parsing the response body
|
|
74
110
|
Its [`typedResponse`](#typedresponse) can also be parsed with a zod schema. Here follows a little more complex example:
|
|
75
111
|
|
|
76
112
|
```ts
|
|
77
|
-
const response = await
|
|
78
|
-
query: { search: "John" },
|
|
79
|
-
trace: (url, requestInit) => console.log(url, requestInit),
|
|
80
|
-
})
|
|
113
|
+
const response = await service.get("/users")
|
|
81
114
|
const json = await response.json(
|
|
82
115
|
z.object({
|
|
83
116
|
data: z.object({
|
|
@@ -91,41 +124,64 @@ const json = await response.json(
|
|
|
91
124
|
.catch([])
|
|
92
125
|
)
|
|
93
126
|
// type of json will be { name: string }[]
|
|
94
|
-
|
|
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
|
|
95
130
|
```
|
|
131
|
+
You can transform any `Response` in a `TypedResponse` like that by using the [`typedResponse`](#typedresponse) function.
|
|
96
132
|
|
|
97
|
-
|
|
133
|
+
### Supported HTTP Verbs
|
|
134
|
+
Other than the `get` it also accepts more HTTP verbs:
|
|
98
135
|
```ts
|
|
99
|
-
await
|
|
100
|
-
await
|
|
101
|
-
await
|
|
102
|
-
await
|
|
103
|
-
await
|
|
104
|
-
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")
|
|
105
143
|
```
|
|
106
144
|
|
|
107
|
-
|
|
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.
|
|
108
148
|
|
|
109
149
|
```ts
|
|
110
150
|
import { makeService } from 'make-service'
|
|
111
151
|
|
|
112
|
-
const
|
|
113
|
-
authorization: "Bearer 123"
|
|
152
|
+
const service = makeService("https://example.com/api", new Headers({
|
|
153
|
+
authorization: "Bearer 123",
|
|
154
|
+
accept: "*/*",
|
|
114
155
|
}))
|
|
115
156
|
|
|
116
|
-
const response = await
|
|
157
|
+
const response = await service.get("/users", {
|
|
117
158
|
headers: [['accept', 'application/json']],
|
|
118
|
-
query: { page: "2" },
|
|
119
159
|
})
|
|
120
160
|
|
|
121
|
-
// It will call "https://example.com/api/users
|
|
161
|
+
// It will call "https://example.com/api/users"
|
|
122
162
|
// with headers: { authorization: "Bearer 123", accept: "application/json" }
|
|
123
163
|
```
|
|
124
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
|
|
125
181
|
In case you want to delete a header previously set you can pass `undefined` or `'undefined'` as its value:
|
|
126
182
|
```ts
|
|
127
|
-
const
|
|
128
|
-
const response = await
|
|
183
|
+
const service = makeService("https://example.com/api", { authorization: "Bearer 123" })
|
|
184
|
+
const response = await service.get("/users", {
|
|
129
185
|
headers: new Headers({ authorization: 'undefined', "Content-Type": undefined }),
|
|
130
186
|
})
|
|
131
187
|
// headers will be empty.
|
|
@@ -135,10 +191,132 @@ Note: Don't forget headers are case insensitive.
|
|
|
135
191
|
const headers = new Headers({ 'Content-Type': 'application/json' })
|
|
136
192
|
Object.fromEntries(headers) // equals to: { 'content-type': 'application/json' }
|
|
137
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).
|
|
138
316
|
|
|
139
317
|
## enhancedFetch
|
|
140
318
|
|
|
141
|
-
A wrapper around the `fetch`
|
|
319
|
+
A wrapper around the `fetch` service.
|
|
142
320
|
It returns a [`TypedResponse`](#typedresponse) instead of a `Response`.
|
|
143
321
|
|
|
144
322
|
```ts
|
|
@@ -153,22 +331,23 @@ const json = await response.json()
|
|
|
153
331
|
// You can pass it a generic or schema to type the result
|
|
154
332
|
```
|
|
155
333
|
|
|
156
|
-
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).
|
|
157
335
|
|
|
158
336
|
This slightly different `RequestInit` is typed as `EnhancedRequestInit`.
|
|
159
337
|
|
|
160
338
|
```ts
|
|
161
339
|
import { enhancedFetch } from 'make-service'
|
|
162
340
|
|
|
163
|
-
await enhancedFetch("https://example.com/api/users", {
|
|
341
|
+
await enhancedFetch("https://example.com/api/users/:role", {
|
|
164
342
|
method: 'POST',
|
|
165
343
|
body: { some: { object: { as: { body } } } },
|
|
166
344
|
query: { page: "1" },
|
|
167
|
-
|
|
345
|
+
params: { role: "admin" },
|
|
346
|
+
trace: console.log,
|
|
168
347
|
})
|
|
169
348
|
|
|
170
349
|
// The trace function will be called with the following arguments:
|
|
171
|
-
// "https://example.com/api/users?page=1"
|
|
350
|
+
// "https://example.com/api/users/admin?page=1"
|
|
172
351
|
// {
|
|
173
352
|
// method: 'POST',
|
|
174
353
|
// body: '{"some":{"object":{"as":{"body":{}}}}}',
|
|
@@ -205,5 +384,124 @@ const text = await response.text(z.string().email())
|
|
|
205
384
|
// ^? string
|
|
206
385
|
```
|
|
207
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
|
+
|
|
208
506
|
## Thank you
|
|
209
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,8 +23,9 @@ type TypedResponse = Omit<Response, 'json' | 'text'> & {
|
|
|
23
23
|
json: TypedResponseJson;
|
|
24
24
|
text: TypedResponseText;
|
|
25
25
|
};
|
|
26
|
-
type EnhancedRequestInit = Omit<RequestInit, 'body'> & {
|
|
27
|
-
|
|
26
|
+
type EnhancedRequestInit = Omit<RequestInit, 'body' | 'method'> & {
|
|
27
|
+
method?: HTTPMethod | Lowercase<HTTPMethod>;
|
|
28
|
+
body?: JSONValue | BodyInit | null;
|
|
28
29
|
query?: SearchParams;
|
|
29
30
|
params?: Record<string, string>;
|
|
30
31
|
trace?: (...args: Parameters<typeof fetch>) => void;
|
|
@@ -43,27 +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
|
-
* @deprecated method renamed to addQueryToUrl
|
|
60
|
-
*/
|
|
61
|
-
declare const addQueryToInput: typeof addQueryToUrl;
|
|
62
|
-
/**
|
|
63
|
-
* @param baseURL the base path to the API
|
|
64
|
-
* @returns a function that receives a path and an object of query parameters and returns a URL
|
|
65
|
-
*/
|
|
66
|
-
declare function makeGetApiUrl(baseURL: string | URL): (path: string, searchParams?: SearchParams) => string | URL;
|
|
67
47
|
/**
|
|
68
48
|
* It hacks the Response object to add typed json and text methods
|
|
69
49
|
* @param response the Response to be proxied
|
|
@@ -79,11 +59,6 @@ declare function makeGetApiUrl(baseURL: string | URL): (path: string, searchPara
|
|
|
79
59
|
* // ^? User[]
|
|
80
60
|
*/
|
|
81
61
|
declare function typedResponse(response: Response): TypedResponse;
|
|
82
|
-
/**
|
|
83
|
-
* @param body the JSON-like body of the request
|
|
84
|
-
* @returns the body stringified if it is not a string
|
|
85
|
-
*/
|
|
86
|
-
declare function ensureStringBody(body?: JSONValue): string | undefined;
|
|
87
62
|
/**
|
|
88
63
|
*
|
|
89
64
|
* @param url a string or URL to be fetched
|
|
@@ -99,6 +74,18 @@ declare function ensureStringBody(body?: JSONValue): string | undefined;
|
|
|
99
74
|
* // ^? unknown
|
|
100
75
|
*/
|
|
101
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>;
|
|
102
89
|
/**
|
|
103
90
|
*
|
|
104
91
|
* @param baseURL the base URL to the API
|
|
@@ -110,6 +97,36 @@ declare function enhancedFetch(url: string | URL, requestInit?: EnhancedRequestI
|
|
|
110
97
|
* const users = await response.json(userSchema);
|
|
111
98
|
* // ^? User[]
|
|
112
99
|
*/
|
|
113
|
-
declare function makeService(baseURL: string | URL, baseHeaders?: 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;
|
|
114
131
|
|
|
115
|
-
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,13 +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
|
-
|
|
24
|
-
addQueryToUrl: () => addQueryToUrl,
|
|
23
|
+
addQueryToURL: () => addQueryToURL,
|
|
25
24
|
enhancedFetch: () => enhancedFetch,
|
|
26
25
|
ensureStringBody: () => ensureStringBody,
|
|
27
|
-
|
|
26
|
+
makeFetcher: () => makeFetcher,
|
|
27
|
+
makeGetApiURL: () => makeGetApiURL,
|
|
28
28
|
makeService: () => makeService,
|
|
29
29
|
mergeHeaders: () => mergeHeaders,
|
|
30
|
+
replaceURLParams: () => replaceURLParams,
|
|
30
31
|
typedResponse: () => typedResponse
|
|
31
32
|
});
|
|
32
33
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -39,15 +40,14 @@ var HTTP_METHODS = [
|
|
|
39
40
|
"DELETE",
|
|
40
41
|
"PATCH",
|
|
41
42
|
"OPTIONS",
|
|
42
|
-
"HEAD"
|
|
43
|
+
"HEAD",
|
|
44
|
+
"CONNECT"
|
|
45
|
+
// 'TRACE', it has no support in most browsers yet
|
|
43
46
|
];
|
|
44
47
|
|
|
45
48
|
// src/internals.ts
|
|
46
49
|
function getJson(response) {
|
|
47
50
|
return async (schema) => {
|
|
48
|
-
if (!response.ok) {
|
|
49
|
-
throw new Error(await response.text());
|
|
50
|
-
}
|
|
51
51
|
const json = await response.json();
|
|
52
52
|
return schema ? schema.parse(json) : json;
|
|
53
53
|
};
|
|
@@ -58,32 +58,12 @@ function getText(response) {
|
|
|
58
58
|
return schema ? schema.parse(text) : text;
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
return url;
|
|
64
|
-
let urlString = String(url);
|
|
65
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
66
|
-
urlString = urlString.replace(new RegExp(`:${key}($|/)`), `${value}$1`);
|
|
67
|
-
});
|
|
68
|
-
return url instanceof URL ? new URL(urlString) : urlString;
|
|
61
|
+
function typeOf(t) {
|
|
62
|
+
return Object.prototype.toString.call(t).replace(/^\[object (.+)\]$/, "$1").toLowerCase();
|
|
69
63
|
}
|
|
70
64
|
|
|
71
|
-
// src/
|
|
72
|
-
function
|
|
73
|
-
const result = /* @__PURE__ */ new Map();
|
|
74
|
-
for (const entry of entries) {
|
|
75
|
-
const headers = new Headers(entry);
|
|
76
|
-
for (const [key, value] of headers.entries()) {
|
|
77
|
-
if (value === void 0 || value === "undefined") {
|
|
78
|
-
result.delete(key);
|
|
79
|
-
} else {
|
|
80
|
-
result.set(key, value);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return new Headers(Array.from(result.entries()));
|
|
85
|
-
}
|
|
86
|
-
function addQueryToUrl(url, searchParams) {
|
|
65
|
+
// src/primitives.ts
|
|
66
|
+
function addQueryToURL(url, searchParams) {
|
|
87
67
|
if (!searchParams)
|
|
88
68
|
return url;
|
|
89
69
|
if (typeof url === "string") {
|
|
@@ -99,14 +79,45 @@ function addQueryToUrl(url, searchParams) {
|
|
|
99
79
|
}
|
|
100
80
|
return url;
|
|
101
81
|
}
|
|
102
|
-
|
|
103
|
-
|
|
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) {
|
|
104
90
|
const base = baseURL instanceof URL ? baseURL.toString() : baseURL;
|
|
105
91
|
return (path, searchParams) => {
|
|
106
|
-
const url = `${base}
|
|
107
|
-
return
|
|
92
|
+
const url = `${base}/${path}`.replace(/([^https?:]\/)\/+/g, "$1");
|
|
93
|
+
return addQueryToURL(url, searchParams);
|
|
108
94
|
};
|
|
109
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
|
|
110
121
|
function typedResponse(response) {
|
|
111
122
|
return new Proxy(response, {
|
|
112
123
|
get(target, prop) {
|
|
@@ -118,13 +129,6 @@ function typedResponse(response) {
|
|
|
118
129
|
}
|
|
119
130
|
});
|
|
120
131
|
}
|
|
121
|
-
function ensureStringBody(body) {
|
|
122
|
-
if (typeof body === "undefined")
|
|
123
|
-
return;
|
|
124
|
-
if (typeof body === "string")
|
|
125
|
-
return body;
|
|
126
|
-
return JSON.stringify(body);
|
|
127
|
-
}
|
|
128
132
|
async function enhancedFetch(url, requestInit) {
|
|
129
133
|
var _a, _b;
|
|
130
134
|
const { query, trace, ...reqInit } = requestInit != null ? requestInit : {};
|
|
@@ -134,42 +138,49 @@ async function enhancedFetch(url, requestInit) {
|
|
|
134
138
|
},
|
|
135
139
|
(_a = reqInit.headers) != null ? _a : {}
|
|
136
140
|
);
|
|
137
|
-
const withParams =
|
|
138
|
-
const
|
|
141
|
+
const withParams = replaceURLParams(url, (_b = reqInit.params) != null ? _b : {});
|
|
142
|
+
const fullURL = addQueryToURL(withParams, query);
|
|
139
143
|
const body = ensureStringBody(reqInit.body);
|
|
140
144
|
const enhancedReqInit = { ...reqInit, headers, body };
|
|
141
|
-
trace == null ? void 0 : trace(
|
|
142
|
-
const response = await fetch(
|
|
145
|
+
trace == null ? void 0 : trace(fullURL, enhancedReqInit);
|
|
146
|
+
const response = await fetch(fullURL, enhancedReqInit);
|
|
143
147
|
return typedResponse(response);
|
|
144
148
|
}
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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;
|
|
157
161
|
};
|
|
158
|
-
|
|
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 = {};
|
|
159
169
|
for (const method of HTTP_METHODS) {
|
|
160
170
|
const lowerMethod = method.toLowerCase();
|
|
161
|
-
|
|
171
|
+
service[lowerMethod] = appliedService(method);
|
|
162
172
|
}
|
|
163
|
-
return
|
|
173
|
+
return service;
|
|
164
174
|
}
|
|
165
175
|
// Annotate the CommonJS export names for ESM import in node:
|
|
166
176
|
0 && (module.exports = {
|
|
167
|
-
|
|
168
|
-
addQueryToUrl,
|
|
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,15 +6,14 @@ 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
|
|
13
15
|
function getJson(response) {
|
|
14
16
|
return async (schema) => {
|
|
15
|
-
if (!response.ok) {
|
|
16
|
-
throw new Error(await response.text());
|
|
17
|
-
}
|
|
18
17
|
const json = await response.json();
|
|
19
18
|
return schema ? schema.parse(json) : json;
|
|
20
19
|
};
|
|
@@ -25,32 +24,12 @@ function getText(response) {
|
|
|
25
24
|
return schema ? schema.parse(text) : text;
|
|
26
25
|
};
|
|
27
26
|
}
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
return url;
|
|
31
|
-
let urlString = String(url);
|
|
32
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
33
|
-
urlString = urlString.replace(new RegExp(`:${key}($|/)`), `${value}$1`);
|
|
34
|
-
});
|
|
35
|
-
return url instanceof URL ? new URL(urlString) : urlString;
|
|
27
|
+
function typeOf(t) {
|
|
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,14 +45,45 @@ function addQueryToUrl(url, searchParams) {
|
|
|
66
45
|
}
|
|
67
46
|
return url;
|
|
68
47
|
}
|
|
69
|
-
|
|
70
|
-
|
|
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) {
|
|
71
56
|
const base = baseURL instanceof URL ? baseURL.toString() : baseURL;
|
|
72
57
|
return (path, searchParams) => {
|
|
73
|
-
const url = `${base}
|
|
74
|
-
return
|
|
58
|
+
const url = `${base}/${path}`.replace(/([^https?:]\/)\/+/g, "$1");
|
|
59
|
+
return addQueryToURL(url, searchParams);
|
|
75
60
|
};
|
|
76
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
|
|
77
87
|
function typedResponse(response) {
|
|
78
88
|
return new Proxy(response, {
|
|
79
89
|
get(target, prop) {
|
|
@@ -85,13 +95,6 @@ function typedResponse(response) {
|
|
|
85
95
|
}
|
|
86
96
|
});
|
|
87
97
|
}
|
|
88
|
-
function ensureStringBody(body) {
|
|
89
|
-
if (typeof body === "undefined")
|
|
90
|
-
return;
|
|
91
|
-
if (typeof body === "string")
|
|
92
|
-
return body;
|
|
93
|
-
return JSON.stringify(body);
|
|
94
|
-
}
|
|
95
98
|
async function enhancedFetch(url, requestInit) {
|
|
96
99
|
var _a, _b;
|
|
97
100
|
const { query, trace, ...reqInit } = requestInit != null ? requestInit : {};
|
|
@@ -101,41 +104,48 @@ async function enhancedFetch(url, requestInit) {
|
|
|
101
104
|
},
|
|
102
105
|
(_a = reqInit.headers) != null ? _a : {}
|
|
103
106
|
);
|
|
104
|
-
const withParams =
|
|
105
|
-
const
|
|
107
|
+
const withParams = replaceURLParams(url, (_b = reqInit.params) != null ? _b : {});
|
|
108
|
+
const fullURL = addQueryToURL(withParams, query);
|
|
106
109
|
const body = ensureStringBody(reqInit.body);
|
|
107
110
|
const enhancedReqInit = { ...reqInit, headers, body };
|
|
108
|
-
trace == null ? void 0 : trace(
|
|
109
|
-
const response = await fetch(
|
|
111
|
+
trace == null ? void 0 : trace(fullURL, enhancedReqInit);
|
|
112
|
+
const response = await fetch(fullURL, enhancedReqInit);
|
|
110
113
|
return typedResponse(response);
|
|
111
114
|
}
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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;
|
|
124
127
|
};
|
|
125
|
-
|
|
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 = {};
|
|
126
135
|
for (const method of HTTP_METHODS) {
|
|
127
136
|
const lowerMethod = method.toLowerCase();
|
|
128
|
-
|
|
137
|
+
service[lowerMethod] = appliedService(method);
|
|
129
138
|
}
|
|
130
|
-
return
|
|
139
|
+
return service;
|
|
131
140
|
}
|
|
132
141
|
export {
|
|
133
|
-
|
|
134
|
-
addQueryToUrl,
|
|
142
|
+
addQueryToURL,
|
|
135
143
|
enhancedFetch,
|
|
136
144
|
ensureStringBody,
|
|
137
|
-
|
|
145
|
+
makeFetcher,
|
|
146
|
+
makeGetApiURL,
|
|
138
147
|
makeService,
|
|
139
148
|
mergeHeaders,
|
|
149
|
+
replaceURLParams,
|
|
140
150
|
typedResponse
|
|
141
151
|
};
|
package/package.json
CHANGED