make-service 0.0.1 → 0.0.3
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 +71 -121
- package/dist/index.d.ts +103 -0
- package/dist/index.js +135 -0
- package/dist/index.mjs +103 -0
- package/package.json +30 -26
- package/esm/_dnt.shims.js +0 -70
- package/esm/constants.js +0 -10
- package/esm/index.js +0 -1
- package/esm/internals.js +0 -29
- package/esm/make-service.js +0 -126
- package/esm/package.json +0 -3
- package/esm/types.js +0 -1
- package/script/_dnt.shims.js +0 -80
- package/script/constants.js +0 -13
- package/script/index.js +0 -10
- package/script/internals.js +0 -34
- package/script/make-service.js +0 -157
- package/script/package.json +0 -3
- package/script/types.js +0 -2
- package/types/_dnt.shims.d.ts +0 -13
- package/types/constants.d.ts +0 -2
- package/types/index.d.ts +0 -1
- package/types/internals.d.ts +0 -15
- package/types/make-service.d.ts +0 -77
- package/types/types.d.ts +0 -11
package/README.md
CHANGED
|
@@ -34,93 +34,80 @@ Or you can use it with Deno:
|
|
|
34
34
|
import { makeService } from "https://deno.land/x/make_service/mod.ts";
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
#
|
|
37
|
+
# API
|
|
38
38
|
|
|
39
|
-
This library exports the `makeService` function and
|
|
39
|
+
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
40
|
|
|
41
|
-
|
|
41
|
+
# makeService
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
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
44
|
|
|
45
|
-
|
|
46
|
-
import { addQueryToInput } from 'make-service'
|
|
45
|
+
This service object can be called with every HTTP method and it will return a [`typedResponse`](#typedresponse) object as it uses the [`enhancedFetch`](#enhancedfetch) internally.
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
```ts
|
|
48
|
+
import { makeService } from 'make-service'
|
|
50
49
|
|
|
51
|
-
const
|
|
52
|
-
|
|
50
|
+
const api = makeService("https://example.com/api", {
|
|
51
|
+
authorization: "Bearer 123"
|
|
52
|
+
})
|
|
53
53
|
|
|
54
|
-
const
|
|
55
|
-
|
|
54
|
+
const response = await api.get("/users")
|
|
55
|
+
const json = await response.json()
|
|
56
|
+
// ^? unknown
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Creates a function that will add an endpoint and a query to the base URL.
|
|
61
|
-
It uses the `addQueryToInput` function internally.
|
|
59
|
+
On the example above, the `api.get` will call the [`enhancedFetch`](#enhancedfetch) with the following arguments:
|
|
62
60
|
|
|
63
61
|
```ts
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
//
|
|
62
|
+
// "https://example.com/api/users"
|
|
63
|
+
// {
|
|
64
|
+
// method: 'GET',
|
|
65
|
+
// headers: {
|
|
66
|
+
// 'content-type': 'application/json',
|
|
67
|
+
// 'authorization': 'Bearer 123',
|
|
68
|
+
// }
|
|
69
|
+
// }
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
The `api` object can be called with the same arguments as the [`enhancedFetch`](#enhancedfetch), such as `query`, object-like `body`, and `trace`.
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
Its [`typedResponse`](#typedresponse) can also be parsed with a zod schema. Here follows a little more complex example:
|
|
75
75
|
|
|
76
76
|
```ts
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// body1 = '{"foo":"bar"}'
|
|
81
|
-
await fetch("https://example.com/api/users", {
|
|
82
|
-
method: 'POST',
|
|
83
|
-
body: body1
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
const body2 = ensureStringBody('{"foo":"bar"}')
|
|
87
|
-
// body2 = '{"foo":"bar"}'
|
|
88
|
-
await fetch("https://example.com/api/users", {
|
|
89
|
-
method: 'POST',
|
|
90
|
-
body: body2
|
|
77
|
+
const response = await api.get("/users", {
|
|
78
|
+
query: { search: "John" },
|
|
79
|
+
trace: (input, requestInit) => console.log(input, requestInit),
|
|
91
80
|
})
|
|
81
|
+
const json = await response.json(
|
|
82
|
+
z.object({
|
|
83
|
+
data: z.object({
|
|
84
|
+
users: z.array(z.object({
|
|
85
|
+
name: z.string()
|
|
86
|
+
}))
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
// transformed and caught
|
|
90
|
+
.transform(({ data: { users } }) => users)
|
|
91
|
+
.catch([])
|
|
92
|
+
)
|
|
93
|
+
// type of json will be { name: string }[]
|
|
94
|
+
// the URL called will be "https://example.com/api/users?search=John"
|
|
92
95
|
```
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
A type-safe wrapper around the `Response` object. It adds a `json` and `text` method that will parse the response with a given zod schema. 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.
|
|
97
|
-
|
|
97
|
+
It accepts more HTTP verbs:
|
|
98
98
|
```ts
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const json = await typedResponse(response).json<{ foo: string }>()
|
|
106
|
-
// ^? { foo: string }
|
|
107
|
-
const json = await typedResponse(response).json(z.object({ foo: z.string() }))
|
|
108
|
-
// ^? { foo: string }
|
|
109
|
-
|
|
110
|
-
// With text
|
|
111
|
-
const response = new Response("foo")
|
|
112
|
-
const text = await typedResponse(response).text()
|
|
113
|
-
// ^? string
|
|
114
|
-
const text = await typedResponse(response).text<`foo${string}`>()
|
|
115
|
-
// ^? `foo${string}`
|
|
116
|
-
const text = await typedResponse(response).text(z.string().email())
|
|
117
|
-
// ^? string
|
|
99
|
+
await api.post("/users", { body: { name: "John" } })
|
|
100
|
+
await api.put("/users/1", { body: { name: "John" } })
|
|
101
|
+
await api.patch("/users/1", { body: { name: "John" } })
|
|
102
|
+
await api.delete("/users/1")
|
|
103
|
+
await api.head("/users")
|
|
104
|
+
await api.options("/users")
|
|
118
105
|
```
|
|
119
106
|
|
|
120
107
|
## enhancedFetch
|
|
121
108
|
|
|
122
109
|
A wrapper around the `fetch` API.
|
|
123
|
-
It
|
|
110
|
+
It returns a [`TypedResponse`](#typedresponse) instead of a `Response`.
|
|
124
111
|
|
|
125
112
|
```ts
|
|
126
113
|
import { enhancedFetch } from 'make-service'
|
|
@@ -134,7 +121,9 @@ const json = await response.json()
|
|
|
134
121
|
// You can pass it a generic or schema to type the result
|
|
135
122
|
```
|
|
136
123
|
|
|
137
|
-
This function accepts the same arguments as the `fetch` API - with exception of JSON-like body -, and it also accepts an object-like `query` and a `trace` function that will be called with the `input` and `requestInit` arguments.
|
|
124
|
+
This function accepts the same arguments as the `fetch` API - with exception of [JSON-like body](/src/make-service.ts) -, and it also accepts an object-like [`query`](/src/make-service.ts) and a `trace` function that will be called with the `input` and `requestInit` arguments.
|
|
125
|
+
|
|
126
|
+
This slightly different `RequestInit` is typed as `EnhancedRequestInit`.
|
|
138
127
|
|
|
139
128
|
```ts
|
|
140
129
|
import { enhancedFetch } from 'make-service'
|
|
@@ -157,70 +146,31 @@ await enhancedFetch("https://example.com/api/users", {
|
|
|
157
146
|
|
|
158
147
|
Notice: the `enhancedFetch` adds a `'content-type': 'application/json'` header by default.
|
|
159
148
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
The main function of this lib is built on top of the previous primitives and it allows you to create an "API" object with a `baseURL` and common `headers` for every request.
|
|
149
|
+
## typedResponse
|
|
163
150
|
|
|
164
|
-
|
|
151
|
+
A type-safe wrapper around the `Response` object. It adds a `json` and `text` method that will parse the response with a given zod schema. 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.
|
|
165
152
|
|
|
166
153
|
```ts
|
|
167
|
-
import {
|
|
168
|
-
|
|
169
|
-
const api = makeService("https://example.com/api", {
|
|
170
|
-
authorization: "Bearer 123"
|
|
171
|
-
})
|
|
154
|
+
import { typedResponse } from 'make-service'
|
|
155
|
+
import type { TypedResponse } from 'make-service'
|
|
172
156
|
|
|
173
|
-
|
|
174
|
-
const
|
|
157
|
+
// With JSON
|
|
158
|
+
const response: TypedResponse = new Response(JSON.stringify({ foo: "bar" }))
|
|
159
|
+
const json = await typedResponse(response).json()
|
|
175
160
|
// ^? unknown
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
```ts
|
|
181
|
-
// "https://example.com/api/users"
|
|
182
|
-
// {
|
|
183
|
-
// method: 'GET',
|
|
184
|
-
// headers: {
|
|
185
|
-
// 'content-type': 'application/json',
|
|
186
|
-
// 'authorization': 'Bearer 123',
|
|
187
|
-
// }
|
|
188
|
-
// }
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
The `api` object can be called with the same arguments as the `enhancedFetch`, such as `query`, object-like `body`, and `trace`.
|
|
192
|
-
|
|
193
|
-
Its `typedResponse` can also be parsed with a zod schema. Here follows a little more complex example:
|
|
194
|
-
|
|
195
|
-
```ts
|
|
196
|
-
const response = await api.get("/users", {
|
|
197
|
-
query: { search: "John" },
|
|
198
|
-
trace: (...args: any[]) => console.log(...args)
|
|
199
|
-
})
|
|
200
|
-
const json = await response.json(
|
|
201
|
-
z.object({
|
|
202
|
-
data: z.object({
|
|
203
|
-
users: z.array(z.object({
|
|
204
|
-
name: z.string()
|
|
205
|
-
}))
|
|
206
|
-
})
|
|
207
|
-
})
|
|
208
|
-
// transformed and catched
|
|
209
|
-
.transform(({ data: { users } }) => users)
|
|
210
|
-
.catch([])
|
|
211
|
-
)
|
|
212
|
-
// type of json will be { name: string }[]
|
|
213
|
-
// the URL called will be "https://example.com/api/users?search=John"
|
|
214
|
-
```
|
|
161
|
+
const json = await typedResponse(response).json<{ foo: string }>()
|
|
162
|
+
// ^? { foo: string }
|
|
163
|
+
const json = await typedResponse(response).json(z.object({ foo: z.string() }))
|
|
164
|
+
// ^? { foo: string }
|
|
215
165
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
await
|
|
223
|
-
|
|
166
|
+
// With text
|
|
167
|
+
const response: TypedResponse = new Response("foo")
|
|
168
|
+
const text = await typedResponse(response).text()
|
|
169
|
+
// ^? string
|
|
170
|
+
const text = await typedResponse(response).text<`foo${string}`>()
|
|
171
|
+
// ^? `foo${string}`
|
|
172
|
+
const text = await typedResponse(response).text(z.string().email())
|
|
173
|
+
// ^? string
|
|
224
174
|
```
|
|
225
175
|
|
|
226
176
|
## Thank you
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
declare const HTTP_METHODS: readonly ["get", "post", "put", "delete", "patch", "options", "head"];
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* It returns the JSON object or throws an error if the response is not ok.
|
|
5
|
+
* @param response the Response to be parsed
|
|
6
|
+
* @returns the response.json method that accepts a type or Zod schema for a typed json response
|
|
7
|
+
*/
|
|
8
|
+
declare function getJson(response: Response): <T = unknown>(schema?: Schema<T> | undefined) => Promise<T>;
|
|
9
|
+
/**
|
|
10
|
+
* @param response the Response to be parsed
|
|
11
|
+
* @returns the response.text method that accepts a type or Zod schema for a typed response
|
|
12
|
+
*/
|
|
13
|
+
declare function getText(response: Response): <T extends string = string>(schema?: Schema<T> | undefined) => Promise<T>;
|
|
14
|
+
|
|
15
|
+
type Schema<T> = {
|
|
16
|
+
parse: (d: unknown) => T;
|
|
17
|
+
};
|
|
18
|
+
type JSONValue = string | number | boolean | {
|
|
19
|
+
[x: string]: JSONValue;
|
|
20
|
+
} | Array<JSONValue>;
|
|
21
|
+
type SearchParams = ConstructorParameters<typeof URLSearchParams>[0];
|
|
22
|
+
type TypedResponse = Omit<Response, 'json' | 'text'> & {
|
|
23
|
+
json: TypedResponseJson;
|
|
24
|
+
text: TypedResponseText;
|
|
25
|
+
};
|
|
26
|
+
type EnhancedRequestInit = Omit<RequestInit, 'body'> & {
|
|
27
|
+
body?: JSONValue;
|
|
28
|
+
query?: SearchParams;
|
|
29
|
+
trace?: (...args: Parameters<typeof fetch>) => void;
|
|
30
|
+
};
|
|
31
|
+
type ServiceRequestInit = Omit<EnhancedRequestInit, 'method'>;
|
|
32
|
+
type HTTPMethod = (typeof HTTP_METHODS)[number];
|
|
33
|
+
type TypedResponseJson = ReturnType<typeof getJson>;
|
|
34
|
+
type TypedResponseText = ReturnType<typeof getText>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param input a string or URL to which the query parameters will be added
|
|
38
|
+
* @param searchParams the query parameters
|
|
39
|
+
* @returns the input with the query parameters added with the same type as the input
|
|
40
|
+
*/
|
|
41
|
+
declare function addQueryToInput(input: string | URL, searchParams?: SearchParams): string | URL;
|
|
42
|
+
/**
|
|
43
|
+
* @param baseURL the base path to the API
|
|
44
|
+
* @returns a function that receives a path and an object of query parameters and returns a URL
|
|
45
|
+
*/
|
|
46
|
+
declare function makeGetApiUrl(baseURL: string): (path: string, searchParams?: SearchParams) => string | URL;
|
|
47
|
+
/**
|
|
48
|
+
* It hacks the Response object to add typed json and text methods
|
|
49
|
+
* @param response the Response to be proxied
|
|
50
|
+
* @returns a Response with typed json and text methods
|
|
51
|
+
* @example const response = await fetch("https://example.com/api/users");
|
|
52
|
+
* const users = await response.json(userSchema);
|
|
53
|
+
* // ^? User[]
|
|
54
|
+
* const untyped = await response.json();
|
|
55
|
+
* // ^? unknown
|
|
56
|
+
* const text = await response.text();
|
|
57
|
+
* // ^? string
|
|
58
|
+
* const typedJson = await response.json<User[]>();
|
|
59
|
+
* // ^? User[]
|
|
60
|
+
*/
|
|
61
|
+
declare function typedResponse(response: Response): TypedResponse;
|
|
62
|
+
/**
|
|
63
|
+
* @param body the JSON-like body of the request
|
|
64
|
+
* @returns the body stringified if it is not a string
|
|
65
|
+
*/
|
|
66
|
+
declare function ensureStringBody(body?: JSONValue): string | undefined;
|
|
67
|
+
/**
|
|
68
|
+
*
|
|
69
|
+
* @param input a string or URL to be fetched
|
|
70
|
+
* @param requestInit the requestInit to be passed to the fetch request. It is the same as the `RequestInit` type, but it also accepts a JSON-like `body` and an object-like `query` parameter.
|
|
71
|
+
* @param requestInit.body the body of the request. It will be automatically stringified so you can send a JSON-like object
|
|
72
|
+
* @param requestInit.query the query parameters to be added to the URL
|
|
73
|
+
* @param requestInit.trace a function that receives the URL and the requestInit and can be used to log the request
|
|
74
|
+
* @returns a Response with typed json and text methods
|
|
75
|
+
* @example const response = await fetch("https://example.com/api/users");
|
|
76
|
+
* const users = await response.json(userSchema);
|
|
77
|
+
* // ^? User[]
|
|
78
|
+
* const untyped = await response.json();
|
|
79
|
+
* // ^? unknown
|
|
80
|
+
*/
|
|
81
|
+
declare function enhancedFetch(input: string | URL, requestInit?: EnhancedRequestInit): Promise<TypedResponse>;
|
|
82
|
+
/**
|
|
83
|
+
*
|
|
84
|
+
* @param baseURL the base URL to the API
|
|
85
|
+
* @param baseHeaders any headers that should be sent with every request
|
|
86
|
+
* @returns a service object with HTTP methods that are functions that receive a path and requestInit and return a serialized json response that can be typed or not.
|
|
87
|
+
* @example const headers = { Authorization: "Bearer 123" }
|
|
88
|
+
* const api = makeService("https://example.com/api", headers);
|
|
89
|
+
* const response = await api.get("/users")
|
|
90
|
+
* const users = await response.json(userSchema);
|
|
91
|
+
* // ^? User[]
|
|
92
|
+
*/
|
|
93
|
+
declare function makeService(baseURL: string, baseHeaders?: HeadersInit): {
|
|
94
|
+
get: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
95
|
+
post: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
96
|
+
put: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
97
|
+
delete: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
98
|
+
patch: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
99
|
+
options: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
100
|
+
head: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export { EnhancedRequestInit, HTTPMethod, JSONValue, Schema, SearchParams, ServiceRequestInit, TypedResponse, TypedResponseJson, TypedResponseText, addQueryToInput, enhancedFetch, ensureStringBody, makeGetApiUrl, makeService, typedResponse };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
addQueryToInput: () => addQueryToInput,
|
|
24
|
+
enhancedFetch: () => enhancedFetch,
|
|
25
|
+
ensureStringBody: () => ensureStringBody,
|
|
26
|
+
makeGetApiUrl: () => makeGetApiUrl,
|
|
27
|
+
makeService: () => makeService,
|
|
28
|
+
typedResponse: () => typedResponse
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(src_exports);
|
|
31
|
+
|
|
32
|
+
// src/constants.ts
|
|
33
|
+
var HTTP_METHODS = [
|
|
34
|
+
"get",
|
|
35
|
+
"post",
|
|
36
|
+
"put",
|
|
37
|
+
"delete",
|
|
38
|
+
"patch",
|
|
39
|
+
"options",
|
|
40
|
+
"head"
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// src/internals.ts
|
|
44
|
+
function getJson(response) {
|
|
45
|
+
return async (schema) => {
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(await response.text());
|
|
48
|
+
}
|
|
49
|
+
const json = await response.json();
|
|
50
|
+
return schema ? schema.parse(json) : json;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function getText(response) {
|
|
54
|
+
return async (schema) => {
|
|
55
|
+
const text = await response.text();
|
|
56
|
+
return schema ? schema.parse(text) : text;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function isHTTPMethod(method) {
|
|
60
|
+
return HTTP_METHODS.includes(method);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/make-service.ts
|
|
64
|
+
function addQueryToInput(input, searchParams) {
|
|
65
|
+
if (!searchParams)
|
|
66
|
+
return input;
|
|
67
|
+
if (searchParams && typeof input === "string") {
|
|
68
|
+
const separator = input.includes("?") ? "&" : "?";
|
|
69
|
+
return `${input}${separator}${new URLSearchParams(searchParams)}`;
|
|
70
|
+
}
|
|
71
|
+
if (searchParams && input instanceof URL) {
|
|
72
|
+
input.search = new URLSearchParams(searchParams).toString();
|
|
73
|
+
}
|
|
74
|
+
return input;
|
|
75
|
+
}
|
|
76
|
+
function makeGetApiUrl(baseURL) {
|
|
77
|
+
return (path, searchParams) => addQueryToInput(`${baseURL}${path}`, searchParams);
|
|
78
|
+
}
|
|
79
|
+
function typedResponse(response) {
|
|
80
|
+
return new Proxy(response, {
|
|
81
|
+
get(target, prop) {
|
|
82
|
+
if (prop === "json")
|
|
83
|
+
return getJson(target);
|
|
84
|
+
if (prop === "text")
|
|
85
|
+
return getText(target);
|
|
86
|
+
return target[prop];
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function ensureStringBody(body) {
|
|
91
|
+
if (typeof body === "undefined")
|
|
92
|
+
return;
|
|
93
|
+
if (typeof body === "string")
|
|
94
|
+
return body;
|
|
95
|
+
return JSON.stringify(body);
|
|
96
|
+
}
|
|
97
|
+
async function enhancedFetch(input, requestInit) {
|
|
98
|
+
const { query, trace, ...reqInit } = requestInit != null ? requestInit : {};
|
|
99
|
+
const headers = { "content-type": "application/json", ...reqInit.headers };
|
|
100
|
+
const url = addQueryToInput(input, query);
|
|
101
|
+
const body = ensureStringBody(reqInit.body);
|
|
102
|
+
const enhancedReqInit = { ...reqInit, headers, body };
|
|
103
|
+
trace == null ? void 0 : trace(url, enhancedReqInit);
|
|
104
|
+
const request = new Request(url, enhancedReqInit);
|
|
105
|
+
const response = await fetch(request);
|
|
106
|
+
return typedResponse(response);
|
|
107
|
+
}
|
|
108
|
+
function makeService(baseURL, baseHeaders) {
|
|
109
|
+
const service = (method) => {
|
|
110
|
+
return async (path, requestInit = {}) => {
|
|
111
|
+
const response = await enhancedFetch(`${baseURL}${path}`, {
|
|
112
|
+
...requestInit,
|
|
113
|
+
method,
|
|
114
|
+
headers: { ...baseHeaders, ...requestInit == null ? void 0 : requestInit.headers }
|
|
115
|
+
});
|
|
116
|
+
return response;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
return new Proxy({}, {
|
|
120
|
+
get(_target, prop) {
|
|
121
|
+
if (isHTTPMethod(prop))
|
|
122
|
+
return service(prop);
|
|
123
|
+
throw new Error(`Invalid HTTP method: ${prop.toString()}`);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
128
|
+
0 && (module.exports = {
|
|
129
|
+
addQueryToInput,
|
|
130
|
+
enhancedFetch,
|
|
131
|
+
ensureStringBody,
|
|
132
|
+
makeGetApiUrl,
|
|
133
|
+
makeService,
|
|
134
|
+
typedResponse
|
|
135
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var HTTP_METHODS = [
|
|
3
|
+
"get",
|
|
4
|
+
"post",
|
|
5
|
+
"put",
|
|
6
|
+
"delete",
|
|
7
|
+
"patch",
|
|
8
|
+
"options",
|
|
9
|
+
"head"
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
// src/internals.ts
|
|
13
|
+
function getJson(response) {
|
|
14
|
+
return async (schema) => {
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new Error(await response.text());
|
|
17
|
+
}
|
|
18
|
+
const json = await response.json();
|
|
19
|
+
return schema ? schema.parse(json) : json;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function getText(response) {
|
|
23
|
+
return async (schema) => {
|
|
24
|
+
const text = await response.text();
|
|
25
|
+
return schema ? schema.parse(text) : text;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function isHTTPMethod(method) {
|
|
29
|
+
return HTTP_METHODS.includes(method);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/make-service.ts
|
|
33
|
+
function addQueryToInput(input, searchParams) {
|
|
34
|
+
if (!searchParams)
|
|
35
|
+
return input;
|
|
36
|
+
if (searchParams && typeof input === "string") {
|
|
37
|
+
const separator = input.includes("?") ? "&" : "?";
|
|
38
|
+
return `${input}${separator}${new URLSearchParams(searchParams)}`;
|
|
39
|
+
}
|
|
40
|
+
if (searchParams && input instanceof URL) {
|
|
41
|
+
input.search = new URLSearchParams(searchParams).toString();
|
|
42
|
+
}
|
|
43
|
+
return input;
|
|
44
|
+
}
|
|
45
|
+
function makeGetApiUrl(baseURL) {
|
|
46
|
+
return (path, searchParams) => addQueryToInput(`${baseURL}${path}`, searchParams);
|
|
47
|
+
}
|
|
48
|
+
function typedResponse(response) {
|
|
49
|
+
return new Proxy(response, {
|
|
50
|
+
get(target, prop) {
|
|
51
|
+
if (prop === "json")
|
|
52
|
+
return getJson(target);
|
|
53
|
+
if (prop === "text")
|
|
54
|
+
return getText(target);
|
|
55
|
+
return target[prop];
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function ensureStringBody(body) {
|
|
60
|
+
if (typeof body === "undefined")
|
|
61
|
+
return;
|
|
62
|
+
if (typeof body === "string")
|
|
63
|
+
return body;
|
|
64
|
+
return JSON.stringify(body);
|
|
65
|
+
}
|
|
66
|
+
async function enhancedFetch(input, requestInit) {
|
|
67
|
+
const { query, trace, ...reqInit } = requestInit != null ? requestInit : {};
|
|
68
|
+
const headers = { "content-type": "application/json", ...reqInit.headers };
|
|
69
|
+
const url = addQueryToInput(input, query);
|
|
70
|
+
const body = ensureStringBody(reqInit.body);
|
|
71
|
+
const enhancedReqInit = { ...reqInit, headers, body };
|
|
72
|
+
trace == null ? void 0 : trace(url, enhancedReqInit);
|
|
73
|
+
const request = new Request(url, enhancedReqInit);
|
|
74
|
+
const response = await fetch(request);
|
|
75
|
+
return typedResponse(response);
|
|
76
|
+
}
|
|
77
|
+
function makeService(baseURL, baseHeaders) {
|
|
78
|
+
const service = (method) => {
|
|
79
|
+
return async (path, requestInit = {}) => {
|
|
80
|
+
const response = await enhancedFetch(`${baseURL}${path}`, {
|
|
81
|
+
...requestInit,
|
|
82
|
+
method,
|
|
83
|
+
headers: { ...baseHeaders, ...requestInit == null ? void 0 : requestInit.headers }
|
|
84
|
+
});
|
|
85
|
+
return response;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
return new Proxy({}, {
|
|
89
|
+
get(_target, prop) {
|
|
90
|
+
if (isHTTPMethod(prop))
|
|
91
|
+
return service(prop);
|
|
92
|
+
throw new Error(`Invalid HTTP method: ${prop.toString()}`);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
addQueryToInput,
|
|
98
|
+
enhancedFetch,
|
|
99
|
+
ensureStringBody,
|
|
100
|
+
makeGetApiUrl,
|
|
101
|
+
makeService,
|
|
102
|
+
typedResponse
|
|
103
|
+
};
|
package/package.json
CHANGED
|
@@ -1,33 +1,37 @@
|
|
|
1
1
|
{
|
|
2
|
-
"module": "./esm/index.js",
|
|
3
|
-
"main": "./script/index.js",
|
|
4
|
-
"types": "./types/index.d.ts",
|
|
5
2
|
"name": "make-service",
|
|
6
|
-
"version": "0.0.
|
|
7
|
-
"description": "Some utilities to extend the
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "Some utilities to extend the 'fetch' API to better interact with external APIs.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"author": "Gustavo Guichard <@gugaguichard>",
|
|
8
9
|
"license": "MIT",
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
"
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup ./src/index.ts --format esm,cjs --dts",
|
|
12
|
+
"dev": "tsup ./src/index.ts --format esm,cjs --watch --dts",
|
|
13
|
+
"lint": "eslint *.ts*",
|
|
14
|
+
"tsc": "tsc",
|
|
15
|
+
"test": "vitest run"
|
|
12
16
|
},
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"types": "./types/index.d.ts",
|
|
22
|
-
"default": "./script/index.js"
|
|
23
|
-
}
|
|
24
|
-
}
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"eslint": "latest",
|
|
19
|
+
"jsdom": "^21.1.1",
|
|
20
|
+
"prettier": "latest",
|
|
21
|
+
"tsup": "^6.7.0",
|
|
22
|
+
"typescript": "^5.0.4",
|
|
23
|
+
"vitest": "latest",
|
|
24
|
+
"zod": "latest"
|
|
25
25
|
},
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
26
|
+
"files": [
|
|
27
|
+
"README.md",
|
|
28
|
+
"./dist/*"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/gugaguichard/make-service.git"
|
|
29
33
|
},
|
|
30
|
-
"
|
|
31
|
-
"
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/gugaguichard/make-service/issues"
|
|
32
36
|
}
|
|
33
|
-
}
|
|
37
|
+
}
|