make-service 0.0.1 → 0.0.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 +68 -121
- package/{types/make-service.d.ts → dist/index.d.ts} +31 -14
- 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/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 an "API" object with a `baseURL` and common `headers` for every request.
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
import { addQueryToInput } from 'make-service'
|
|
45
|
+
This "api" 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,7 @@ 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.
|
|
138
125
|
|
|
139
126
|
```ts
|
|
140
127
|
import { enhancedFetch } from 'make-service'
|
|
@@ -157,70 +144,30 @@ await enhancedFetch("https://example.com/api/users", {
|
|
|
157
144
|
|
|
158
145
|
Notice: the `enhancedFetch` adds a `'content-type': 'application/json'` header by default.
|
|
159
146
|
|
|
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.
|
|
147
|
+
## typedResponse
|
|
163
148
|
|
|
164
|
-
|
|
149
|
+
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
150
|
|
|
166
151
|
```ts
|
|
167
|
-
import {
|
|
168
|
-
|
|
169
|
-
const api = makeService("https://example.com/api", {
|
|
170
|
-
authorization: "Bearer 123"
|
|
171
|
-
})
|
|
152
|
+
import { typedResponse } from 'make-service'
|
|
172
153
|
|
|
173
|
-
|
|
174
|
-
const
|
|
154
|
+
// With JSON
|
|
155
|
+
const response = new Response(JSON.stringify({ foo: "bar" }))
|
|
156
|
+
const json = await typedResponse(response).json()
|
|
175
157
|
// ^? 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
|
-
```
|
|
158
|
+
const json = await typedResponse(response).json<{ foo: string }>()
|
|
159
|
+
// ^? { foo: string }
|
|
160
|
+
const json = await typedResponse(response).json(z.object({ foo: z.string() }))
|
|
161
|
+
// ^? { foo: string }
|
|
215
162
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
await
|
|
223
|
-
|
|
163
|
+
// With text
|
|
164
|
+
const response = new Response("foo")
|
|
165
|
+
const text = await typedResponse(response).text()
|
|
166
|
+
// ^? string
|
|
167
|
+
const text = await typedResponse(response).text<`foo${string}`>()
|
|
168
|
+
// ^? `foo${string}`
|
|
169
|
+
const text = await typedResponse(response).text(z.string().email())
|
|
170
|
+
// ^? string
|
|
224
171
|
```
|
|
225
172
|
|
|
226
173
|
## Thank you
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
type Schema<T> = {
|
|
2
|
+
parse: (d: unknown) => T;
|
|
3
|
+
};
|
|
4
|
+
type JSONValue = string | number | boolean | {
|
|
5
|
+
[x: string]: JSONValue;
|
|
6
|
+
} | Array<JSONValue>;
|
|
7
|
+
type SearchParams = ConstructorParameters<typeof URLSearchParams>[0];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* It returns the JSON object or throws an error if the response is not ok.
|
|
11
|
+
* @param response the Response to be parsed
|
|
12
|
+
* @returns the response.json method that accepts a type or Zod schema for a typed json response
|
|
13
|
+
*/
|
|
14
|
+
declare function getJson(response: Response): <T = unknown>(schema?: Schema<T> | undefined) => Promise<T>;
|
|
15
|
+
/**
|
|
16
|
+
* @param response the Response to be parsed
|
|
17
|
+
* @returns the response.text method that accepts a type or Zod schema for a typed response
|
|
18
|
+
*/
|
|
19
|
+
declare function getText(response: Response): <T extends string = string>(schema?: Schema<T> | undefined) => Promise<T>;
|
|
20
|
+
|
|
5
21
|
/**
|
|
6
22
|
* @param input a string or URL to which the query parameters will be added
|
|
7
23
|
* @param searchParams the query parameters
|
|
@@ -27,7 +43,7 @@ declare function makeGetApiUrl(baseURL: string): (path: string, searchParams?: S
|
|
|
27
43
|
* const typedJson = await response.json<User[]>();
|
|
28
44
|
* // ^? User[]
|
|
29
45
|
*/
|
|
30
|
-
declare function typedResponse(response:
|
|
46
|
+
declare function typedResponse(response: Response): Omit<Response, "json" | "text"> & {
|
|
31
47
|
json: ReturnType<typeof getJson>;
|
|
32
48
|
text: ReturnType<typeof getText>;
|
|
33
49
|
};
|
|
@@ -36,10 +52,10 @@ declare function typedResponse(response: dntShim.Response): Omit<dntShim.Respons
|
|
|
36
52
|
* @returns the body stringified if it is not a string
|
|
37
53
|
*/
|
|
38
54
|
declare function ensureStringBody(body?: JSONValue): string | undefined;
|
|
39
|
-
type Options = Omit<
|
|
55
|
+
type Options = Omit<RequestInit, 'body'> & {
|
|
40
56
|
body?: JSONValue;
|
|
41
57
|
query?: SearchParams;
|
|
42
|
-
trace?: (...args: Parameters<typeof
|
|
58
|
+
trace?: (...args: Parameters<typeof fetch>) => void;
|
|
43
59
|
};
|
|
44
60
|
/**
|
|
45
61
|
*
|
|
@@ -55,9 +71,9 @@ type Options = Omit<dntShim.RequestInit, 'body'> & {
|
|
|
55
71
|
* const untyped = await response.json();
|
|
56
72
|
* // ^? unknown
|
|
57
73
|
*/
|
|
58
|
-
declare function enhancedFetch(input: string | URL, options?: Options): Promise<Omit<
|
|
59
|
-
json: <T = unknown>(schema?:
|
|
60
|
-
text: <T_1 extends string = string>(schema?:
|
|
74
|
+
declare function enhancedFetch(input: string | URL, options?: Options): Promise<Omit<Response, "json" | "text"> & {
|
|
75
|
+
json: <T = unknown>(schema?: Schema<T> | undefined) => Promise<T>;
|
|
76
|
+
text: <T_1 extends string = string>(schema?: Schema<T_1> | undefined) => Promise<T_1>;
|
|
61
77
|
}>;
|
|
62
78
|
/**
|
|
63
79
|
*
|
|
@@ -70,8 +86,9 @@ declare function enhancedFetch(input: string | URL, options?: Options): Promise<
|
|
|
70
86
|
* const users = await response.json(userSchema);
|
|
71
87
|
* // ^? User[]
|
|
72
88
|
*/
|
|
73
|
-
declare function makeService(baseURL: string, baseHeaders?:
|
|
74
|
-
json: <T = unknown>(schema?:
|
|
75
|
-
text: <T_1 extends string = string>(schema?:
|
|
89
|
+
declare function makeService(baseURL: string, baseHeaders?: HeadersInit): Record<"get" | "post" | "put" | "delete" | "patch" | "options" | "head", (path: string, options?: Omit<Options, 'method'>) => Promise<Omit<Response, "json" | "text"> & {
|
|
90
|
+
json: <T = unknown>(schema?: Schema<T> | undefined) => Promise<T>;
|
|
91
|
+
text: <T_1 extends string = string>(schema?: Schema<T_1> | undefined) => Promise<T_1>;
|
|
76
92
|
}>>;
|
|
77
|
-
|
|
93
|
+
|
|
94
|
+
export { 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, options) {
|
|
98
|
+
const { query, trace, ...reqInit } = options != null ? options : {};
|
|
99
|
+
const headers = { "content-type": "application/json", ...reqInit.headers };
|
|
100
|
+
const url = addQueryToInput(input, query);
|
|
101
|
+
const body = ensureStringBody(reqInit.body);
|
|
102
|
+
const requestInit = { ...reqInit, headers, body };
|
|
103
|
+
trace == null ? void 0 : trace(url, requestInit);
|
|
104
|
+
const request = new Request(url, requestInit);
|
|
105
|
+
const response = await fetch(request);
|
|
106
|
+
return typedResponse(response);
|
|
107
|
+
}
|
|
108
|
+
function makeService(baseURL, baseHeaders) {
|
|
109
|
+
const api = (method) => {
|
|
110
|
+
return async (path, options = {}) => {
|
|
111
|
+
const response = await enhancedFetch(`${baseURL}${path}`, {
|
|
112
|
+
...options,
|
|
113
|
+
method,
|
|
114
|
+
headers: { ...baseHeaders, ...options == null ? void 0 : options.headers }
|
|
115
|
+
});
|
|
116
|
+
return response;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
return new Proxy({}, {
|
|
120
|
+
get(_target, prop) {
|
|
121
|
+
if (isHTTPMethod(prop))
|
|
122
|
+
return api(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, options) {
|
|
67
|
+
const { query, trace, ...reqInit } = options != null ? options : {};
|
|
68
|
+
const headers = { "content-type": "application/json", ...reqInit.headers };
|
|
69
|
+
const url = addQueryToInput(input, query);
|
|
70
|
+
const body = ensureStringBody(reqInit.body);
|
|
71
|
+
const requestInit = { ...reqInit, headers, body };
|
|
72
|
+
trace == null ? void 0 : trace(url, requestInit);
|
|
73
|
+
const request = new Request(url, requestInit);
|
|
74
|
+
const response = await fetch(request);
|
|
75
|
+
return typedResponse(response);
|
|
76
|
+
}
|
|
77
|
+
function makeService(baseURL, baseHeaders) {
|
|
78
|
+
const api = (method) => {
|
|
79
|
+
return async (path, options = {}) => {
|
|
80
|
+
const response = await enhancedFetch(`${baseURL}${path}`, {
|
|
81
|
+
...options,
|
|
82
|
+
method,
|
|
83
|
+
headers: { ...baseHeaders, ...options == null ? void 0 : options.headers }
|
|
84
|
+
});
|
|
85
|
+
return response;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
return new Proxy({}, {
|
|
89
|
+
get(_target, prop) {
|
|
90
|
+
if (isHTTPMethod(prop))
|
|
91
|
+
return api(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.2",
|
|
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
|
+
}
|
package/esm/_dnt.shims.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { Deno } from "@deno/shim-deno";
|
|
2
|
-
export { Deno } from "@deno/shim-deno";
|
|
3
|
-
import { fetch, File, FormData, Headers, Request, Response } from "undici";
|
|
4
|
-
export { fetch, File, FormData, Headers, Request, Response } from "undici";
|
|
5
|
-
const dntGlobals = {
|
|
6
|
-
Deno,
|
|
7
|
-
fetch,
|
|
8
|
-
File,
|
|
9
|
-
FormData,
|
|
10
|
-
Headers,
|
|
11
|
-
Request,
|
|
12
|
-
Response,
|
|
13
|
-
};
|
|
14
|
-
export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
|
|
15
|
-
// deno-lint-ignore ban-types
|
|
16
|
-
function createMergeProxy(baseObj, extObj) {
|
|
17
|
-
return new Proxy(baseObj, {
|
|
18
|
-
get(_target, prop, _receiver) {
|
|
19
|
-
if (prop in extObj) {
|
|
20
|
-
return extObj[prop];
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
return baseObj[prop];
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
set(_target, prop, value) {
|
|
27
|
-
if (prop in extObj) {
|
|
28
|
-
delete extObj[prop];
|
|
29
|
-
}
|
|
30
|
-
baseObj[prop] = value;
|
|
31
|
-
return true;
|
|
32
|
-
},
|
|
33
|
-
deleteProperty(_target, prop) {
|
|
34
|
-
let success = false;
|
|
35
|
-
if (prop in extObj) {
|
|
36
|
-
delete extObj[prop];
|
|
37
|
-
success = true;
|
|
38
|
-
}
|
|
39
|
-
if (prop in baseObj) {
|
|
40
|
-
delete baseObj[prop];
|
|
41
|
-
success = true;
|
|
42
|
-
}
|
|
43
|
-
return success;
|
|
44
|
-
},
|
|
45
|
-
ownKeys(_target) {
|
|
46
|
-
const baseKeys = Reflect.ownKeys(baseObj);
|
|
47
|
-
const extKeys = Reflect.ownKeys(extObj);
|
|
48
|
-
const extKeysSet = new Set(extKeys);
|
|
49
|
-
return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
|
|
50
|
-
},
|
|
51
|
-
defineProperty(_target, prop, desc) {
|
|
52
|
-
if (prop in extObj) {
|
|
53
|
-
delete extObj[prop];
|
|
54
|
-
}
|
|
55
|
-
Reflect.defineProperty(baseObj, prop, desc);
|
|
56
|
-
return true;
|
|
57
|
-
},
|
|
58
|
-
getOwnPropertyDescriptor(_target, prop) {
|
|
59
|
-
if (prop in extObj) {
|
|
60
|
-
return Reflect.getOwnPropertyDescriptor(extObj, prop);
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
return Reflect.getOwnPropertyDescriptor(baseObj, prop);
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
has(_target, prop) {
|
|
67
|
-
return prop in extObj || prop in baseObj;
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
}
|
package/esm/constants.js
DELETED
package/esm/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { addQueryToInput, ensureStringBody, enhancedFetch, makeService, makeGetApiUrl, typedResponse, } from './make-service.js';
|
package/esm/internals.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { HTTP_METHODS } from './constants.js';
|
|
2
|
-
/**
|
|
3
|
-
* It returns the JSON object or throws an error if the response is not ok.
|
|
4
|
-
* @param response the Response to be parsed
|
|
5
|
-
* @returns the response.json method that accepts a type or Zod schema for a typed json response
|
|
6
|
-
*/
|
|
7
|
-
function getJson(response) {
|
|
8
|
-
return async (schema) => {
|
|
9
|
-
if (!response.ok) {
|
|
10
|
-
throw new Error(await response.text());
|
|
11
|
-
}
|
|
12
|
-
const json = await response.json();
|
|
13
|
-
return schema ? schema.parse(json) : json;
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* @param response the Response to be parsed
|
|
18
|
-
* @returns the response.text method that accepts a type or Zod schema for a typed response
|
|
19
|
-
*/
|
|
20
|
-
function getText(response) {
|
|
21
|
-
return async (schema) => {
|
|
22
|
-
const text = await response.text();
|
|
23
|
-
return schema ? schema.parse(text) : text;
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
function isHTTPMethod(method) {
|
|
27
|
-
return HTTP_METHODS.includes(method);
|
|
28
|
-
}
|
|
29
|
-
export { getJson, getText, isHTTPMethod };
|
package/esm/make-service.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import * as dntShim from "./_dnt.shims.js";
|
|
2
|
-
import { getJson, getText, isHTTPMethod } from './internals.js';
|
|
3
|
-
/**
|
|
4
|
-
* @param input a string or URL to which the query parameters will be added
|
|
5
|
-
* @param searchParams the query parameters
|
|
6
|
-
* @returns the input with the query parameters added with the same type as the input
|
|
7
|
-
*/
|
|
8
|
-
function addQueryToInput(input, searchParams) {
|
|
9
|
-
if (!searchParams)
|
|
10
|
-
return input;
|
|
11
|
-
if (searchParams && typeof input === 'string') {
|
|
12
|
-
const separator = input.includes('?') ? '&' : '?';
|
|
13
|
-
return `${input}${separator}${new URLSearchParams(searchParams)}`;
|
|
14
|
-
}
|
|
15
|
-
if (searchParams && input instanceof URL) {
|
|
16
|
-
input.search = new URLSearchParams(searchParams).toString();
|
|
17
|
-
}
|
|
18
|
-
return input;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* @param baseURL the base path to the API
|
|
22
|
-
* @returns a function that receives a path and an object of query parameters and returns a URL
|
|
23
|
-
*/
|
|
24
|
-
function makeGetApiUrl(baseURL) {
|
|
25
|
-
return (path, searchParams) => addQueryToInput(`${baseURL}${path}`, searchParams);
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* It hacks the Response object to add typed json and text methods
|
|
29
|
-
* @param response the Response to be proxied
|
|
30
|
-
* @returns a Response with typed json and text methods
|
|
31
|
-
* @example const response = await fetch("https://example.com/api/users");
|
|
32
|
-
* const users = await response.json(userSchema);
|
|
33
|
-
* // ^? User[]
|
|
34
|
-
* const untyped = await response.json();
|
|
35
|
-
* // ^? unknown
|
|
36
|
-
* const text = await response.text();
|
|
37
|
-
* // ^? string
|
|
38
|
-
* const typedJson = await response.json<User[]>();
|
|
39
|
-
* // ^? User[]
|
|
40
|
-
*/
|
|
41
|
-
function typedResponse(response) {
|
|
42
|
-
return new Proxy(response, {
|
|
43
|
-
get(target, prop) {
|
|
44
|
-
if (prop === 'json')
|
|
45
|
-
return getJson(target);
|
|
46
|
-
if (prop === 'text')
|
|
47
|
-
return getText(target);
|
|
48
|
-
return target[prop];
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* @param body the JSON-like body of the request
|
|
54
|
-
* @returns the body stringified if it is not a string
|
|
55
|
-
*/
|
|
56
|
-
function ensureStringBody(body) {
|
|
57
|
-
if (typeof body === 'undefined')
|
|
58
|
-
return;
|
|
59
|
-
if (typeof body === 'string')
|
|
60
|
-
return body;
|
|
61
|
-
return JSON.stringify(body);
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
*
|
|
65
|
-
* @param input a string or URL to be fetched
|
|
66
|
-
* @param options the options 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.
|
|
67
|
-
* @param options.body the body of the request. It will be automatically stringified so you can send a JSON-like object
|
|
68
|
-
* @param options.query the query parameters to be added to the URL
|
|
69
|
-
* @param options.trace a function that receives the URL and the requestInit and can be used to log the request
|
|
70
|
-
* @returns a Response with typed json and text methods
|
|
71
|
-
* @example const response = await fetch("https://example.com/api/users");
|
|
72
|
-
* const users = await response.json(userSchema);
|
|
73
|
-
* // ^? User[]
|
|
74
|
-
* const untyped = await response.json();
|
|
75
|
-
* // ^? unknown
|
|
76
|
-
*/
|
|
77
|
-
async function enhancedFetch(input, options) {
|
|
78
|
-
const { query, trace, ...reqInit } = options ?? {};
|
|
79
|
-
const headers = { 'content-type': 'application/json', ...reqInit.headers };
|
|
80
|
-
const url = addQueryToInput(input, query);
|
|
81
|
-
const body = ensureStringBody(reqInit.body);
|
|
82
|
-
const requestInit = { ...reqInit, headers, body };
|
|
83
|
-
trace?.(url, requestInit);
|
|
84
|
-
const request = new dntShim.Request(url, requestInit);
|
|
85
|
-
const response = await dntShim.fetch(request);
|
|
86
|
-
return typedResponse(response);
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
*
|
|
90
|
-
* @param baseURL the base URL to the API
|
|
91
|
-
* @param baseHeaders any headers that should be sent with every request
|
|
92
|
-
* @returns an API object with HTTP methods that are functions that receive a path and options and return a serialized json response that can be typed or not.
|
|
93
|
-
* @example const headers = { Authorization: "Bearer 123" }
|
|
94
|
-
* const api = makeService("https://example.com/api", headers);
|
|
95
|
-
* const response = await api.get("/users")
|
|
96
|
-
* const users = await response.json(userSchema);
|
|
97
|
-
* // ^? User[]
|
|
98
|
-
*/
|
|
99
|
-
function makeService(baseURL, baseHeaders) {
|
|
100
|
-
/**
|
|
101
|
-
* A function that receives a path and options and returns a serialized json response that can be typed or not.
|
|
102
|
-
* @param method the HTTP method
|
|
103
|
-
* @returns the API function for the given HTTP method
|
|
104
|
-
*/
|
|
105
|
-
const api = (method) => {
|
|
106
|
-
return async (path, options = {}) => {
|
|
107
|
-
const response = await enhancedFetch(`${baseURL}${path}`, {
|
|
108
|
-
...options,
|
|
109
|
-
method,
|
|
110
|
-
headers: { ...baseHeaders, ...options?.headers },
|
|
111
|
-
});
|
|
112
|
-
return response;
|
|
113
|
-
};
|
|
114
|
-
};
|
|
115
|
-
/**
|
|
116
|
-
* It returns a proxy that returns the api function for each HTTP method
|
|
117
|
-
*/
|
|
118
|
-
return new Proxy({}, {
|
|
119
|
-
get(_target, prop) {
|
|
120
|
-
if (isHTTPMethod(prop))
|
|
121
|
-
return api(prop);
|
|
122
|
-
throw new Error(`Invalid HTTP method: ${prop.toString()}`);
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
export { addQueryToInput, ensureStringBody, enhancedFetch, makeService, makeGetApiUrl, typedResponse, };
|
package/esm/package.json
DELETED
package/esm/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/script/_dnt.shims.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.dntGlobalThis = exports.Response = exports.Request = exports.Headers = exports.FormData = exports.File = exports.fetch = exports.Deno = void 0;
|
|
4
|
-
const shim_deno_1 = require("@deno/shim-deno");
|
|
5
|
-
var shim_deno_2 = require("@deno/shim-deno");
|
|
6
|
-
Object.defineProperty(exports, "Deno", { enumerable: true, get: function () { return shim_deno_2.Deno; } });
|
|
7
|
-
const undici_1 = require("undici");
|
|
8
|
-
var undici_2 = require("undici");
|
|
9
|
-
Object.defineProperty(exports, "fetch", { enumerable: true, get: function () { return undici_2.fetch; } });
|
|
10
|
-
Object.defineProperty(exports, "File", { enumerable: true, get: function () { return undici_2.File; } });
|
|
11
|
-
Object.defineProperty(exports, "FormData", { enumerable: true, get: function () { return undici_2.FormData; } });
|
|
12
|
-
Object.defineProperty(exports, "Headers", { enumerable: true, get: function () { return undici_2.Headers; } });
|
|
13
|
-
Object.defineProperty(exports, "Request", { enumerable: true, get: function () { return undici_2.Request; } });
|
|
14
|
-
Object.defineProperty(exports, "Response", { enumerable: true, get: function () { return undici_2.Response; } });
|
|
15
|
-
const dntGlobals = {
|
|
16
|
-
Deno: shim_deno_1.Deno,
|
|
17
|
-
fetch: undici_1.fetch,
|
|
18
|
-
File: undici_1.File,
|
|
19
|
-
FormData: undici_1.FormData,
|
|
20
|
-
Headers: undici_1.Headers,
|
|
21
|
-
Request: undici_1.Request,
|
|
22
|
-
Response: undici_1.Response,
|
|
23
|
-
};
|
|
24
|
-
exports.dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
|
|
25
|
-
// deno-lint-ignore ban-types
|
|
26
|
-
function createMergeProxy(baseObj, extObj) {
|
|
27
|
-
return new Proxy(baseObj, {
|
|
28
|
-
get(_target, prop, _receiver) {
|
|
29
|
-
if (prop in extObj) {
|
|
30
|
-
return extObj[prop];
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
return baseObj[prop];
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
set(_target, prop, value) {
|
|
37
|
-
if (prop in extObj) {
|
|
38
|
-
delete extObj[prop];
|
|
39
|
-
}
|
|
40
|
-
baseObj[prop] = value;
|
|
41
|
-
return true;
|
|
42
|
-
},
|
|
43
|
-
deleteProperty(_target, prop) {
|
|
44
|
-
let success = false;
|
|
45
|
-
if (prop in extObj) {
|
|
46
|
-
delete extObj[prop];
|
|
47
|
-
success = true;
|
|
48
|
-
}
|
|
49
|
-
if (prop in baseObj) {
|
|
50
|
-
delete baseObj[prop];
|
|
51
|
-
success = true;
|
|
52
|
-
}
|
|
53
|
-
return success;
|
|
54
|
-
},
|
|
55
|
-
ownKeys(_target) {
|
|
56
|
-
const baseKeys = Reflect.ownKeys(baseObj);
|
|
57
|
-
const extKeys = Reflect.ownKeys(extObj);
|
|
58
|
-
const extKeysSet = new Set(extKeys);
|
|
59
|
-
return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
|
|
60
|
-
},
|
|
61
|
-
defineProperty(_target, prop, desc) {
|
|
62
|
-
if (prop in extObj) {
|
|
63
|
-
delete extObj[prop];
|
|
64
|
-
}
|
|
65
|
-
Reflect.defineProperty(baseObj, prop, desc);
|
|
66
|
-
return true;
|
|
67
|
-
},
|
|
68
|
-
getOwnPropertyDescriptor(_target, prop) {
|
|
69
|
-
if (prop in extObj) {
|
|
70
|
-
return Reflect.getOwnPropertyDescriptor(extObj, prop);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
return Reflect.getOwnPropertyDescriptor(baseObj, prop);
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
has(_target, prop) {
|
|
77
|
-
return prop in extObj || prop in baseObj;
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
}
|
package/script/constants.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HTTP_METHODS = void 0;
|
|
4
|
-
const HTTP_METHODS = [
|
|
5
|
-
'get',
|
|
6
|
-
'post',
|
|
7
|
-
'put',
|
|
8
|
-
'delete',
|
|
9
|
-
'patch',
|
|
10
|
-
'options',
|
|
11
|
-
'head',
|
|
12
|
-
];
|
|
13
|
-
exports.HTTP_METHODS = HTTP_METHODS;
|
package/script/index.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.typedResponse = exports.makeGetApiUrl = exports.makeService = exports.enhancedFetch = exports.ensureStringBody = exports.addQueryToInput = void 0;
|
|
4
|
-
var make_service_js_1 = require("./make-service.js");
|
|
5
|
-
Object.defineProperty(exports, "addQueryToInput", { enumerable: true, get: function () { return make_service_js_1.addQueryToInput; } });
|
|
6
|
-
Object.defineProperty(exports, "ensureStringBody", { enumerable: true, get: function () { return make_service_js_1.ensureStringBody; } });
|
|
7
|
-
Object.defineProperty(exports, "enhancedFetch", { enumerable: true, get: function () { return make_service_js_1.enhancedFetch; } });
|
|
8
|
-
Object.defineProperty(exports, "makeService", { enumerable: true, get: function () { return make_service_js_1.makeService; } });
|
|
9
|
-
Object.defineProperty(exports, "makeGetApiUrl", { enumerable: true, get: function () { return make_service_js_1.makeGetApiUrl; } });
|
|
10
|
-
Object.defineProperty(exports, "typedResponse", { enumerable: true, get: function () { return make_service_js_1.typedResponse; } });
|
package/script/internals.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isHTTPMethod = exports.getText = exports.getJson = void 0;
|
|
4
|
-
const constants_js_1 = require("./constants.js");
|
|
5
|
-
/**
|
|
6
|
-
* It returns the JSON object or throws an error if the response is not ok.
|
|
7
|
-
* @param response the Response to be parsed
|
|
8
|
-
* @returns the response.json method that accepts a type or Zod schema for a typed json response
|
|
9
|
-
*/
|
|
10
|
-
function getJson(response) {
|
|
11
|
-
return async (schema) => {
|
|
12
|
-
if (!response.ok) {
|
|
13
|
-
throw new Error(await response.text());
|
|
14
|
-
}
|
|
15
|
-
const json = await response.json();
|
|
16
|
-
return schema ? schema.parse(json) : json;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
exports.getJson = getJson;
|
|
20
|
-
/**
|
|
21
|
-
* @param response the Response to be parsed
|
|
22
|
-
* @returns the response.text method that accepts a type or Zod schema for a typed response
|
|
23
|
-
*/
|
|
24
|
-
function getText(response) {
|
|
25
|
-
return async (schema) => {
|
|
26
|
-
const text = await response.text();
|
|
27
|
-
return schema ? schema.parse(text) : text;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
exports.getText = getText;
|
|
31
|
-
function isHTTPMethod(method) {
|
|
32
|
-
return constants_js_1.HTTP_METHODS.includes(method);
|
|
33
|
-
}
|
|
34
|
-
exports.isHTTPMethod = isHTTPMethod;
|
package/script/make-service.js
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.typedResponse = exports.makeGetApiUrl = exports.makeService = exports.enhancedFetch = exports.ensureStringBody = exports.addQueryToInput = void 0;
|
|
27
|
-
const dntShim = __importStar(require("./_dnt.shims.js"));
|
|
28
|
-
const internals_js_1 = require("./internals.js");
|
|
29
|
-
/**
|
|
30
|
-
* @param input a string or URL to which the query parameters will be added
|
|
31
|
-
* @param searchParams the query parameters
|
|
32
|
-
* @returns the input with the query parameters added with the same type as the input
|
|
33
|
-
*/
|
|
34
|
-
function addQueryToInput(input, searchParams) {
|
|
35
|
-
if (!searchParams)
|
|
36
|
-
return input;
|
|
37
|
-
if (searchParams && typeof input === 'string') {
|
|
38
|
-
const separator = input.includes('?') ? '&' : '?';
|
|
39
|
-
return `${input}${separator}${new URLSearchParams(searchParams)}`;
|
|
40
|
-
}
|
|
41
|
-
if (searchParams && input instanceof URL) {
|
|
42
|
-
input.search = new URLSearchParams(searchParams).toString();
|
|
43
|
-
}
|
|
44
|
-
return input;
|
|
45
|
-
}
|
|
46
|
-
exports.addQueryToInput = addQueryToInput;
|
|
47
|
-
/**
|
|
48
|
-
* @param baseURL the base path to the API
|
|
49
|
-
* @returns a function that receives a path and an object of query parameters and returns a URL
|
|
50
|
-
*/
|
|
51
|
-
function makeGetApiUrl(baseURL) {
|
|
52
|
-
return (path, searchParams) => addQueryToInput(`${baseURL}${path}`, searchParams);
|
|
53
|
-
}
|
|
54
|
-
exports.makeGetApiUrl = makeGetApiUrl;
|
|
55
|
-
/**
|
|
56
|
-
* It hacks the Response object to add typed json and text methods
|
|
57
|
-
* @param response the Response to be proxied
|
|
58
|
-
* @returns a Response with typed json and text methods
|
|
59
|
-
* @example const response = await fetch("https://example.com/api/users");
|
|
60
|
-
* const users = await response.json(userSchema);
|
|
61
|
-
* // ^? User[]
|
|
62
|
-
* const untyped = await response.json();
|
|
63
|
-
* // ^? unknown
|
|
64
|
-
* const text = await response.text();
|
|
65
|
-
* // ^? string
|
|
66
|
-
* const typedJson = await response.json<User[]>();
|
|
67
|
-
* // ^? User[]
|
|
68
|
-
*/
|
|
69
|
-
function typedResponse(response) {
|
|
70
|
-
return new Proxy(response, {
|
|
71
|
-
get(target, prop) {
|
|
72
|
-
if (prop === 'json')
|
|
73
|
-
return (0, internals_js_1.getJson)(target);
|
|
74
|
-
if (prop === 'text')
|
|
75
|
-
return (0, internals_js_1.getText)(target);
|
|
76
|
-
return target[prop];
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
exports.typedResponse = typedResponse;
|
|
81
|
-
/**
|
|
82
|
-
* @param body the JSON-like body of the request
|
|
83
|
-
* @returns the body stringified if it is not a string
|
|
84
|
-
*/
|
|
85
|
-
function ensureStringBody(body) {
|
|
86
|
-
if (typeof body === 'undefined')
|
|
87
|
-
return;
|
|
88
|
-
if (typeof body === 'string')
|
|
89
|
-
return body;
|
|
90
|
-
return JSON.stringify(body);
|
|
91
|
-
}
|
|
92
|
-
exports.ensureStringBody = ensureStringBody;
|
|
93
|
-
/**
|
|
94
|
-
*
|
|
95
|
-
* @param input a string or URL to be fetched
|
|
96
|
-
* @param options the options 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.
|
|
97
|
-
* @param options.body the body of the request. It will be automatically stringified so you can send a JSON-like object
|
|
98
|
-
* @param options.query the query parameters to be added to the URL
|
|
99
|
-
* @param options.trace a function that receives the URL and the requestInit and can be used to log the request
|
|
100
|
-
* @returns a Response with typed json and text methods
|
|
101
|
-
* @example const response = await fetch("https://example.com/api/users");
|
|
102
|
-
* const users = await response.json(userSchema);
|
|
103
|
-
* // ^? User[]
|
|
104
|
-
* const untyped = await response.json();
|
|
105
|
-
* // ^? unknown
|
|
106
|
-
*/
|
|
107
|
-
async function enhancedFetch(input, options) {
|
|
108
|
-
const { query, trace, ...reqInit } = options ?? {};
|
|
109
|
-
const headers = { 'content-type': 'application/json', ...reqInit.headers };
|
|
110
|
-
const url = addQueryToInput(input, query);
|
|
111
|
-
const body = ensureStringBody(reqInit.body);
|
|
112
|
-
const requestInit = { ...reqInit, headers, body };
|
|
113
|
-
trace?.(url, requestInit);
|
|
114
|
-
const request = new dntShim.Request(url, requestInit);
|
|
115
|
-
const response = await dntShim.fetch(request);
|
|
116
|
-
return typedResponse(response);
|
|
117
|
-
}
|
|
118
|
-
exports.enhancedFetch = enhancedFetch;
|
|
119
|
-
/**
|
|
120
|
-
*
|
|
121
|
-
* @param baseURL the base URL to the API
|
|
122
|
-
* @param baseHeaders any headers that should be sent with every request
|
|
123
|
-
* @returns an API object with HTTP methods that are functions that receive a path and options and return a serialized json response that can be typed or not.
|
|
124
|
-
* @example const headers = { Authorization: "Bearer 123" }
|
|
125
|
-
* const api = makeService("https://example.com/api", headers);
|
|
126
|
-
* const response = await api.get("/users")
|
|
127
|
-
* const users = await response.json(userSchema);
|
|
128
|
-
* // ^? User[]
|
|
129
|
-
*/
|
|
130
|
-
function makeService(baseURL, baseHeaders) {
|
|
131
|
-
/**
|
|
132
|
-
* A function that receives a path and options and returns a serialized json response that can be typed or not.
|
|
133
|
-
* @param method the HTTP method
|
|
134
|
-
* @returns the API function for the given HTTP method
|
|
135
|
-
*/
|
|
136
|
-
const api = (method) => {
|
|
137
|
-
return async (path, options = {}) => {
|
|
138
|
-
const response = await enhancedFetch(`${baseURL}${path}`, {
|
|
139
|
-
...options,
|
|
140
|
-
method,
|
|
141
|
-
headers: { ...baseHeaders, ...options?.headers },
|
|
142
|
-
});
|
|
143
|
-
return response;
|
|
144
|
-
};
|
|
145
|
-
};
|
|
146
|
-
/**
|
|
147
|
-
* It returns a proxy that returns the api function for each HTTP method
|
|
148
|
-
*/
|
|
149
|
-
return new Proxy({}, {
|
|
150
|
-
get(_target, prop) {
|
|
151
|
-
if ((0, internals_js_1.isHTTPMethod)(prop))
|
|
152
|
-
return api(prop);
|
|
153
|
-
throw new Error(`Invalid HTTP method: ${prop.toString()}`);
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
exports.makeService = makeService;
|
package/script/package.json
DELETED
package/script/types.js
DELETED
package/types/_dnt.shims.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Deno } from "@deno/shim-deno";
|
|
2
|
-
export { Deno } from "@deno/shim-deno";
|
|
3
|
-
import { fetch, File, FormData, Headers, Request, Response } from "undici";
|
|
4
|
-
export { fetch, File, FormData, Headers, Request, Response, type BodyInit, type HeadersInit, type RequestInit, type ResponseInit } from "undici";
|
|
5
|
-
export declare const dntGlobalThis: Omit<typeof globalThis, "Deno" | "fetch" | "File" | "FormData" | "Headers" | "Request" | "Response"> & {
|
|
6
|
-
Deno: typeof Deno;
|
|
7
|
-
fetch: typeof fetch;
|
|
8
|
-
File: typeof File;
|
|
9
|
-
FormData: typeof FormData;
|
|
10
|
-
Headers: typeof Headers;
|
|
11
|
-
Request: typeof Request;
|
|
12
|
-
Response: typeof Response;
|
|
13
|
-
};
|
package/types/constants.d.ts
DELETED
package/types/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { addQueryToInput, ensureStringBody, enhancedFetch, makeService, makeGetApiUrl, typedResponse, } from './make-service.js';
|
package/types/internals.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import * as dntShim from "./_dnt.shims.js";
|
|
2
|
-
import { HTTPMethod, Schema } from './types.js';
|
|
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: dntShim.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: dntShim.Response): <T extends string = string>(schema?: Schema<T> | undefined) => Promise<T>;
|
|
14
|
-
declare function isHTTPMethod(method: string | symbol): method is HTTPMethod;
|
|
15
|
-
export { getJson, getText, isHTTPMethod };
|
package/types/types.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { HTTP_METHODS } from './constants.js';
|
|
3
|
-
type Schema<T> = {
|
|
4
|
-
parse: (d: unknown) => T;
|
|
5
|
-
};
|
|
6
|
-
type JSONValue = string | number | boolean | {
|
|
7
|
-
[x: string]: JSONValue;
|
|
8
|
-
} | Array<JSONValue>;
|
|
9
|
-
type SearchParams = ConstructorParameters<typeof URLSearchParams>[0];
|
|
10
|
-
type HTTPMethod = typeof HTTP_METHODS[number];
|
|
11
|
-
export type { HTTPMethod, JSONValue, Schema, SearchParams };
|