make-service 3.1.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -23
- package/dist/index.d.mts +15 -9
- package/dist/index.d.ts +15 -9
- package/dist/index.js +46 -18
- package/dist/index.mjs +42 -15
- package/package.json +28 -12
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.
|
|
@@ -415,7 +443,7 @@ The `trace` function can also return a `Promise<void>` in order to send traces t
|
|
|
415
443
|
|
|
416
444
|
## typedResponse
|
|
417
445
|
|
|
418
|
-
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.
|
|
419
447
|
|
|
420
448
|
```ts
|
|
421
449
|
import { typedResponse } from 'make-service'
|
|
@@ -449,11 +477,11 @@ import { deepCamelKeys, deepKebabKeys } from 'string-ts'
|
|
|
449
477
|
|
|
450
478
|
const service = makeService("https://example.com/api")
|
|
451
479
|
const response = service.get("/users")
|
|
452
|
-
const
|
|
480
|
+
const json = await response.json(
|
|
453
481
|
z
|
|
454
482
|
.array(z.object({ "first-name": z.string(), contact: z.object({ "home-address": z.string() }) }))
|
|
455
|
-
.transform(deepCamelKeys)
|
|
456
483
|
)
|
|
484
|
+
const users = deepCamelKeys(json)
|
|
457
485
|
console.log(users)
|
|
458
486
|
// ^? { firstName: string, contact: { homeAddress: string } }[]
|
|
459
487
|
|
|
@@ -468,7 +496,7 @@ This little library has plenty of other useful functions that you can use to bui
|
|
|
468
496
|
## addQueryToURL
|
|
469
497
|
It receives a URL instance or URL string and an object-like query and returns a new URL with the query appended to it.
|
|
470
498
|
|
|
471
|
-
It will preserve the original query if it exists and will also preserve the type of the given URL.
|
|
499
|
+
It will preserve the original query if it exists and will also preserve the type of the given URL. When a `URL` object is provided, the original instance is left untouched and a new one is returned.
|
|
472
500
|
|
|
473
501
|
```ts
|
|
474
502
|
import { addQueryToURL } from 'make-service'
|
|
@@ -482,17 +510,17 @@ addQueryToURL(
|
|
|
482
510
|
)
|
|
483
511
|
// https://example.com/api/users?role=admin&page=2
|
|
484
512
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
{ page: "2" },
|
|
488
|
-
)
|
|
513
|
+
const url = new URL("https://example.com/api/users")
|
|
514
|
+
addQueryToURL(url, { page: "2" })
|
|
489
515
|
// https://example.com/api/users?page=2
|
|
516
|
+
url.toString()
|
|
517
|
+
// 'https://example.com/api/users'
|
|
490
518
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
{ page: "2" },
|
|
494
|
-
)
|
|
519
|
+
const urlWithRole = new URL("https://example.com/api/users?role=admin")
|
|
520
|
+
addQueryToURL(urlWithRole, { page: "2" })
|
|
495
521
|
// https://example.com/api/users?role=admin&page=2
|
|
522
|
+
urlWithRole.toString()
|
|
523
|
+
// 'https://example.com/api/users?role=admin'
|
|
496
524
|
```
|
|
497
525
|
|
|
498
526
|
## ensureStringBody
|
|
@@ -592,9 +620,9 @@ The params will be **strongly-typed** which means they will be validated against
|
|
|
592
620
|
<tbody>
|
|
593
621
|
<tr>
|
|
594
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>
|
|
595
|
-
<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>
|
|
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>
|
|
596
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>
|
|
597
|
-
<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></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>
|
|
598
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>
|
|
599
627
|
</tr>
|
|
600
628
|
</tbody>
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
|
|
1
3
|
declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT"];
|
|
2
4
|
|
|
3
|
-
type Schema<T> = {
|
|
4
|
-
parse: (d: unknown) => T;
|
|
5
|
-
};
|
|
6
5
|
type JSONValue = string | number | boolean | Date | {
|
|
7
6
|
[x: string]: JSONValue | undefined | null;
|
|
8
7
|
} | Array<JSONValue | undefined | null>;
|
|
@@ -11,7 +10,7 @@ type TypedResponse = Omit<Response, 'json' | 'text'> & {
|
|
|
11
10
|
json: TypedResponseJson;
|
|
12
11
|
text: TypedResponseText;
|
|
13
12
|
};
|
|
14
|
-
type PathParams<T> = T extends string ? ExtractPathParams<T> extends Record<string, unknown> ? ExtractPathParams<T> : Record<string, string> : Record<string, string>;
|
|
13
|
+
type PathParams<T> = T extends string ? ExtractPathParams<T> extends Record<string, unknown> ? ExtractPathParams<T> : Record<string, string | number> : Record<string, string | number>;
|
|
15
14
|
type EnhancedRequestInit<T = string> = Omit<RequestInit, 'body' | 'method'> & {
|
|
16
15
|
method?: HTTPMethod | Lowercase<HTTPMethod>;
|
|
17
16
|
body?: JSONValue | BodyInit | null;
|
|
@@ -28,17 +27,17 @@ type BaseOptions = {
|
|
|
28
27
|
responseTransformer?: ResponseTransformer;
|
|
29
28
|
};
|
|
30
29
|
type HTTPMethod = (typeof HTTP_METHODS)[number];
|
|
31
|
-
type TypedResponseJson = <
|
|
32
|
-
type TypedResponseText = <
|
|
30
|
+
type TypedResponseJson = <Input = unknown, Output = Input>(schema?: StandardSchemaV1<Input, Output>) => Promise<Output>;
|
|
31
|
+
type TypedResponseText = <Input extends string = string, Output = Input>(schema?: StandardSchemaV1<Input, Output>) => Promise<Output>;
|
|
33
32
|
type GetJson = (response: Response) => TypedResponseJson;
|
|
34
33
|
type GetText = (response: Response) => TypedResponseText;
|
|
35
34
|
type Prettify<T> = {
|
|
36
35
|
[K in keyof T]: T[K];
|
|
37
36
|
} & {};
|
|
38
37
|
type ExtractPathParams<T extends string> = T extends `${infer _}:${infer Param}/${infer Rest}` ? Prettify<Omit<{
|
|
39
|
-
[K in Param]: string;
|
|
38
|
+
[K in Param]: string | number;
|
|
40
39
|
} & ExtractPathParams<Rest>, ''>> : T extends `${infer _}:${infer Param}` ? {
|
|
41
|
-
[K in Param]: string;
|
|
40
|
+
[K in Param]: string | number;
|
|
42
41
|
} : {};
|
|
43
42
|
|
|
44
43
|
/**
|
|
@@ -140,5 +139,12 @@ declare function replaceURLParams<T extends string | URL>(url: T, params: PathPa
|
|
|
140
139
|
* @returns the type of the value
|
|
141
140
|
*/
|
|
142
141
|
declare function typeOf(t: unknown): "array" | "arraybuffer" | "bigint" | "blob" | "boolean" | "formdata" | "function" | "null" | "number" | "object" | "readablestream" | "string" | "symbol" | "undefined" | "url" | "urlsearchparams";
|
|
142
|
+
/**
|
|
143
|
+
* Error thrown when the response cannot be parsed.
|
|
144
|
+
*/
|
|
145
|
+
declare class ParseResponseError extends Error {
|
|
146
|
+
issues: readonly StandardSchemaV1.Issue[];
|
|
147
|
+
constructor(message: string, issues: readonly StandardSchemaV1.Issue[]);
|
|
148
|
+
}
|
|
143
149
|
|
|
144
|
-
export { type BaseOptions, type EnhancedRequestInit, type GetJson, type GetText, type HTTPMethod, type JSONValue, type PathParams, type RequestTransformer, type ResponseTransformer, type
|
|
150
|
+
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,7 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
|
|
1
3
|
declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT"];
|
|
2
4
|
|
|
3
|
-
type Schema<T> = {
|
|
4
|
-
parse: (d: unknown) => T;
|
|
5
|
-
};
|
|
6
5
|
type JSONValue = string | number | boolean | Date | {
|
|
7
6
|
[x: string]: JSONValue | undefined | null;
|
|
8
7
|
} | Array<JSONValue | undefined | null>;
|
|
@@ -11,7 +10,7 @@ type TypedResponse = Omit<Response, 'json' | 'text'> & {
|
|
|
11
10
|
json: TypedResponseJson;
|
|
12
11
|
text: TypedResponseText;
|
|
13
12
|
};
|
|
14
|
-
type PathParams<T> = T extends string ? ExtractPathParams<T> extends Record<string, unknown> ? ExtractPathParams<T> : Record<string, string> : Record<string, string>;
|
|
13
|
+
type PathParams<T> = T extends string ? ExtractPathParams<T> extends Record<string, unknown> ? ExtractPathParams<T> : Record<string, string | number> : Record<string, string | number>;
|
|
15
14
|
type EnhancedRequestInit<T = string> = Omit<RequestInit, 'body' | 'method'> & {
|
|
16
15
|
method?: HTTPMethod | Lowercase<HTTPMethod>;
|
|
17
16
|
body?: JSONValue | BodyInit | null;
|
|
@@ -28,17 +27,17 @@ type BaseOptions = {
|
|
|
28
27
|
responseTransformer?: ResponseTransformer;
|
|
29
28
|
};
|
|
30
29
|
type HTTPMethod = (typeof HTTP_METHODS)[number];
|
|
31
|
-
type TypedResponseJson = <
|
|
32
|
-
type TypedResponseText = <
|
|
30
|
+
type TypedResponseJson = <Input = unknown, Output = Input>(schema?: StandardSchemaV1<Input, Output>) => Promise<Output>;
|
|
31
|
+
type TypedResponseText = <Input extends string = string, Output = Input>(schema?: StandardSchemaV1<Input, Output>) => Promise<Output>;
|
|
33
32
|
type GetJson = (response: Response) => TypedResponseJson;
|
|
34
33
|
type GetText = (response: Response) => TypedResponseText;
|
|
35
34
|
type Prettify<T> = {
|
|
36
35
|
[K in keyof T]: T[K];
|
|
37
36
|
} & {};
|
|
38
37
|
type ExtractPathParams<T extends string> = T extends `${infer _}:${infer Param}/${infer Rest}` ? Prettify<Omit<{
|
|
39
|
-
[K in Param]: string;
|
|
38
|
+
[K in Param]: string | number;
|
|
40
39
|
} & ExtractPathParams<Rest>, ''>> : T extends `${infer _}:${infer Param}` ? {
|
|
41
|
-
[K in Param]: string;
|
|
40
|
+
[K in Param]: string | number;
|
|
42
41
|
} : {};
|
|
43
42
|
|
|
44
43
|
/**
|
|
@@ -140,5 +139,12 @@ declare function replaceURLParams<T extends string | URL>(url: T, params: PathPa
|
|
|
140
139
|
* @returns the type of the value
|
|
141
140
|
*/
|
|
142
141
|
declare function typeOf(t: unknown): "array" | "arraybuffer" | "bigint" | "blob" | "boolean" | "formdata" | "function" | "null" | "number" | "object" | "readablestream" | "string" | "symbol" | "undefined" | "url" | "urlsearchparams";
|
|
142
|
+
/**
|
|
143
|
+
* Error thrown when the response cannot be parsed.
|
|
144
|
+
*/
|
|
145
|
+
declare class ParseResponseError extends Error {
|
|
146
|
+
issues: readonly StandardSchemaV1.Issue[];
|
|
147
|
+
constructor(message: string, issues: readonly StandardSchemaV1.Issue[]);
|
|
148
|
+
}
|
|
143
149
|
|
|
144
|
-
export { type BaseOptions, type EnhancedRequestInit, type GetJson, type GetText, type HTTPMethod, type JSONValue, type PathParams, type RequestTransformer, type ResponseTransformer, type
|
|
150
|
+
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,16 +47,6 @@ 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
52
|
if (!searchParams) return url;
|
|
@@ -64,9 +55,11 @@ function addQueryToURL(url, searchParams) {
|
|
|
64
55
|
return `${url}${separator}${new URLSearchParams(searchParams)}`;
|
|
65
56
|
}
|
|
66
57
|
if (searchParams && url instanceof URL) {
|
|
58
|
+
const result = new URL(url.toString());
|
|
67
59
|
for (const [key, value] of new URLSearchParams(searchParams).entries()) {
|
|
68
|
-
|
|
60
|
+
result.searchParams.set(key, value);
|
|
69
61
|
}
|
|
62
|
+
return result;
|
|
70
63
|
}
|
|
71
64
|
return url;
|
|
72
65
|
}
|
|
@@ -83,7 +76,7 @@ function makeGetApiURL(baseURL) {
|
|
|
83
76
|
};
|
|
84
77
|
}
|
|
85
78
|
function mergeHeaders(...entries) {
|
|
86
|
-
const result =
|
|
79
|
+
const result = new Headers();
|
|
87
80
|
for (const entry of entries) {
|
|
88
81
|
const headers = new Headers(entry);
|
|
89
82
|
for (const [key, value] of headers.entries()) {
|
|
@@ -94,19 +87,53 @@ function mergeHeaders(...entries) {
|
|
|
94
87
|
}
|
|
95
88
|
}
|
|
96
89
|
}
|
|
97
|
-
return
|
|
90
|
+
return result;
|
|
98
91
|
}
|
|
99
92
|
function replaceURLParams(url, params) {
|
|
100
93
|
if (!params) return url;
|
|
101
94
|
let urlString = String(url);
|
|
102
|
-
|
|
95
|
+
for (const [key, value] of Object.entries(params)) {
|
|
103
96
|
urlString = urlString.replace(new RegExp(`:${key}($|/)`), `${value}$1`);
|
|
104
|
-
}
|
|
97
|
+
}
|
|
105
98
|
return url instanceof URL ? new URL(urlString) : urlString;
|
|
106
99
|
}
|
|
107
100
|
function typeOf(t) {
|
|
108
101
|
return Object.prototype.toString.call(t).replace(/^\[object (.+)\]$/, "$1").toLowerCase();
|
|
109
102
|
}
|
|
103
|
+
var ParseResponseError = class extends Error {
|
|
104
|
+
constructor(message, issues) {
|
|
105
|
+
super(JSON.stringify({ message, issues }, null, 2));
|
|
106
|
+
this.issues = issues;
|
|
107
|
+
this.name = "ParseResponseError";
|
|
108
|
+
this.issues = issues;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/internals.ts
|
|
113
|
+
var getJson = (response) => async (schema) => {
|
|
114
|
+
const json = await response.json();
|
|
115
|
+
if (!schema) return json;
|
|
116
|
+
const result = await schema["~standard"].validate(json);
|
|
117
|
+
if (result.issues) {
|
|
118
|
+
throw new ParseResponseError(
|
|
119
|
+
"Failed to parse response.json",
|
|
120
|
+
result.issues
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return result.value;
|
|
124
|
+
};
|
|
125
|
+
var getText = (response) => async (schema) => {
|
|
126
|
+
const text = await response.text();
|
|
127
|
+
if (!schema) return text;
|
|
128
|
+
const result = await schema["~standard"].validate(text);
|
|
129
|
+
if (result.issues) {
|
|
130
|
+
throw new ParseResponseError(
|
|
131
|
+
"Failed to parse response.text",
|
|
132
|
+
result.issues
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return result.value;
|
|
136
|
+
};
|
|
110
137
|
|
|
111
138
|
// src/api.ts
|
|
112
139
|
var identity = (value) => value;
|
|
@@ -170,6 +197,7 @@ function makeService(baseURL, baseOptions) {
|
|
|
170
197
|
}
|
|
171
198
|
// Annotate the CommonJS export names for ESM import in node:
|
|
172
199
|
0 && (module.exports = {
|
|
200
|
+
ParseResponseError,
|
|
173
201
|
addQueryToURL,
|
|
174
202
|
enhancedFetch,
|
|
175
203
|
ensureStringBody,
|
package/dist/index.mjs
CHANGED
|
@@ -11,16 +11,6 @@ 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
16
|
if (!searchParams) return url;
|
|
@@ -29,9 +19,11 @@ function addQueryToURL(url, searchParams) {
|
|
|
29
19
|
return `${url}${separator}${new URLSearchParams(searchParams)}`;
|
|
30
20
|
}
|
|
31
21
|
if (searchParams && url instanceof URL) {
|
|
22
|
+
const result = new URL(url.toString());
|
|
32
23
|
for (const [key, value] of new URLSearchParams(searchParams).entries()) {
|
|
33
|
-
|
|
24
|
+
result.searchParams.set(key, value);
|
|
34
25
|
}
|
|
26
|
+
return result;
|
|
35
27
|
}
|
|
36
28
|
return url;
|
|
37
29
|
}
|
|
@@ -48,7 +40,7 @@ function makeGetApiURL(baseURL) {
|
|
|
48
40
|
};
|
|
49
41
|
}
|
|
50
42
|
function mergeHeaders(...entries) {
|
|
51
|
-
const result =
|
|
43
|
+
const result = new Headers();
|
|
52
44
|
for (const entry of entries) {
|
|
53
45
|
const headers = new Headers(entry);
|
|
54
46
|
for (const [key, value] of headers.entries()) {
|
|
@@ -59,19 +51,53 @@ function mergeHeaders(...entries) {
|
|
|
59
51
|
}
|
|
60
52
|
}
|
|
61
53
|
}
|
|
62
|
-
return
|
|
54
|
+
return result;
|
|
63
55
|
}
|
|
64
56
|
function replaceURLParams(url, params) {
|
|
65
57
|
if (!params) return url;
|
|
66
58
|
let urlString = String(url);
|
|
67
|
-
|
|
59
|
+
for (const [key, value] of Object.entries(params)) {
|
|
68
60
|
urlString = urlString.replace(new RegExp(`:${key}($|/)`), `${value}$1`);
|
|
69
|
-
}
|
|
61
|
+
}
|
|
70
62
|
return url instanceof URL ? new URL(urlString) : urlString;
|
|
71
63
|
}
|
|
72
64
|
function typeOf(t) {
|
|
73
65
|
return Object.prototype.toString.call(t).replace(/^\[object (.+)\]$/, "$1").toLowerCase();
|
|
74
66
|
}
|
|
67
|
+
var ParseResponseError = class extends Error {
|
|
68
|
+
constructor(message, issues) {
|
|
69
|
+
super(JSON.stringify({ message, issues }, null, 2));
|
|
70
|
+
this.issues = issues;
|
|
71
|
+
this.name = "ParseResponseError";
|
|
72
|
+
this.issues = issues;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/internals.ts
|
|
77
|
+
var getJson = (response) => async (schema) => {
|
|
78
|
+
const json = await response.json();
|
|
79
|
+
if (!schema) return json;
|
|
80
|
+
const result = await schema["~standard"].validate(json);
|
|
81
|
+
if (result.issues) {
|
|
82
|
+
throw new ParseResponseError(
|
|
83
|
+
"Failed to parse response.json",
|
|
84
|
+
result.issues
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return result.value;
|
|
88
|
+
};
|
|
89
|
+
var getText = (response) => async (schema) => {
|
|
90
|
+
const text = await response.text();
|
|
91
|
+
if (!schema) return text;
|
|
92
|
+
const result = await schema["~standard"].validate(text);
|
|
93
|
+
if (result.issues) {
|
|
94
|
+
throw new ParseResponseError(
|
|
95
|
+
"Failed to parse response.text",
|
|
96
|
+
result.issues
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return result.value;
|
|
100
|
+
};
|
|
75
101
|
|
|
76
102
|
// src/api.ts
|
|
77
103
|
var identity = (value) => value;
|
|
@@ -134,6 +160,7 @@ function makeService(baseURL, baseOptions) {
|
|
|
134
160
|
return service;
|
|
135
161
|
}
|
|
136
162
|
export {
|
|
163
|
+
ParseResponseError,
|
|
137
164
|
addQueryToURL,
|
|
138
165
|
enhancedFetch,
|
|
139
166
|
ensureStringBody,
|
package/package.json
CHANGED
|
@@ -1,31 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "make-service",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
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",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"import": "./dist/index.mjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"fetch",
|
|
17
|
+
"api",
|
|
18
|
+
"wrapper",
|
|
19
|
+
"service",
|
|
20
|
+
"typescript"
|
|
21
|
+
],
|
|
8
22
|
"author": "Gustavo Guichard <@gugaguichard>",
|
|
9
23
|
"license": "MIT",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
10
27
|
"scripts": {
|
|
11
28
|
"build": "tsup ./src/index.ts --format esm,cjs --dts",
|
|
12
29
|
"dev": "tsup ./src/index.ts --format esm,cjs --watch --dts",
|
|
13
|
-
"lint": "
|
|
30
|
+
"lint": "node_modules/.bin/biome check --write --error-on-warnings",
|
|
14
31
|
"tsc": "tsc --noEmit",
|
|
15
32
|
"test": "vitest run"
|
|
16
33
|
},
|
|
17
34
|
"devDependencies": {
|
|
18
|
-
"@
|
|
19
|
-
"@
|
|
20
|
-
"@
|
|
21
|
-
"
|
|
22
|
-
"jsdom": "^
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"typescript": "^5.5.4",
|
|
35
|
+
"@biomejs/biome": "^2.0.0",
|
|
36
|
+
"@standard-schema/spec": "^1.0.0",
|
|
37
|
+
"@types/node": "^24.0.3",
|
|
38
|
+
"arktype": "^2.1.20",
|
|
39
|
+
"jsdom": "^26.1.0",
|
|
40
|
+
"string-ts": "^2.2.1",
|
|
41
|
+
"tsup": "^8.5.0",
|
|
42
|
+
"typescript": "^5.8.3",
|
|
27
43
|
"vitest": "latest",
|
|
28
|
-
"zod": "
|
|
44
|
+
"zod": "3.25.67"
|
|
29
45
|
},
|
|
30
46
|
"files": [
|
|
31
47
|
"README.md",
|