make-service 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -3
- package/dist/index.d.ts +22 -16
- package/dist/index.js +39 -30
- package/dist/index.mjs +38 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ Its [`typedResponse`](#typedresponse) can also be parsed with a zod schema. Here
|
|
|
76
76
|
```ts
|
|
77
77
|
const response = await api.get("/users", {
|
|
78
78
|
query: { search: "John" },
|
|
79
|
-
trace: (
|
|
79
|
+
trace: (url, requestInit) => console.log(url, requestInit),
|
|
80
80
|
})
|
|
81
81
|
const json = await response.json(
|
|
82
82
|
z.object({
|
|
@@ -104,6 +104,38 @@ await api.head("/users")
|
|
|
104
104
|
await api.options("/users")
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
+
This function can also correctly merge any sort of `URL`, `URLSearchParams`, and `Headers`.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { makeService } from 'make-service'
|
|
111
|
+
|
|
112
|
+
const api = makeService(new URL("https://example.com/api"), new Headers({
|
|
113
|
+
authorization: "Bearer 123"
|
|
114
|
+
}))
|
|
115
|
+
|
|
116
|
+
const response = await api.get("/users?admin=true", {
|
|
117
|
+
headers: [['accept', 'application/json']],
|
|
118
|
+
query: { page: "2" },
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// It will call "https://example.com/api/users?admin=true&page=2"
|
|
122
|
+
// with headers: { authorization: "Bearer 123", accept: "application/json" }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
In case you want to delete a header previously set you can pass `undefined` or `'undefined'` as its value:
|
|
126
|
+
```ts
|
|
127
|
+
const api = makeService("https://example.com/api", { authorization: "Bearer 123" })
|
|
128
|
+
const response = await api.get("/users", {
|
|
129
|
+
headers: new Headers({ authorization: 'undefined', "Content-Type": undefined }),
|
|
130
|
+
})
|
|
131
|
+
// headers will be empty.
|
|
132
|
+
```
|
|
133
|
+
Note: Don't forget headers are case insensitive.
|
|
134
|
+
```ts
|
|
135
|
+
const headers = new Headers({ 'Content-Type': 'application/json' })
|
|
136
|
+
Object.fromEntries(headers) // equals to: { 'content-type': 'application/json' }
|
|
137
|
+
```
|
|
138
|
+
|
|
107
139
|
## enhancedFetch
|
|
108
140
|
|
|
109
141
|
A wrapper around the `fetch` API.
|
|
@@ -121,7 +153,7 @@ const json = await response.json()
|
|
|
121
153
|
// You can pass it a generic or schema to type the result
|
|
122
154
|
```
|
|
123
155
|
|
|
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 `
|
|
156
|
+
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 `url` and `requestInit` arguments.
|
|
125
157
|
|
|
126
158
|
This slightly different `RequestInit` is typed as `EnhancedRequestInit`.
|
|
127
159
|
|
|
@@ -132,7 +164,7 @@ await enhancedFetch("https://example.com/api/users", {
|
|
|
132
164
|
method: 'POST',
|
|
133
165
|
body: { some: { object: { as: { body } } } },
|
|
134
166
|
query: { page: "1" },
|
|
135
|
-
trace: (
|
|
167
|
+
trace: (url, requestInit) => console.log(url, requestInit)
|
|
136
168
|
})
|
|
137
169
|
|
|
138
170
|
// The trace function will be called with the following arguments:
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const HTTP_METHODS: readonly ["
|
|
1
|
+
declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"];
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* It returns the JSON object or throws an error if the response is not ok.
|
|
@@ -26,12 +26,22 @@ type TypedResponse = Omit<Response, 'json' | 'text'> & {
|
|
|
26
26
|
type EnhancedRequestInit = Omit<RequestInit, 'body'> & {
|
|
27
27
|
body?: JSONValue;
|
|
28
28
|
query?: SearchParams;
|
|
29
|
+
params?: Record<string, string>;
|
|
29
30
|
trace?: (...args: Parameters<typeof fetch>) => void;
|
|
30
31
|
};
|
|
31
32
|
type ServiceRequestInit = Omit<EnhancedRequestInit, 'method'>;
|
|
32
33
|
type HTTPMethod = (typeof HTTP_METHODS)[number];
|
|
33
34
|
type TypedResponseJson = ReturnType<typeof getJson>;
|
|
34
35
|
type TypedResponseText = ReturnType<typeof getText>;
|
|
36
|
+
type Prettify<T> = {
|
|
37
|
+
[K in keyof T]: T[K];
|
|
38
|
+
} & {};
|
|
39
|
+
type NoEmpty<T> = keyof T extends never ? never : T;
|
|
40
|
+
type PathParams<T extends string> = NoEmpty<T extends `${infer _}:${infer Param}/${infer Rest}` ? Prettify<{
|
|
41
|
+
[K in Param]: string;
|
|
42
|
+
} & PathParams<Rest>> : T extends `${infer _}:${infer Param}` ? {
|
|
43
|
+
[K in Param]: string;
|
|
44
|
+
} : {}>;
|
|
35
45
|
|
|
36
46
|
/**
|
|
37
47
|
* It merges multiple HeadersInit objects into a single Headers object
|
|
@@ -40,11 +50,15 @@ type TypedResponseText = ReturnType<typeof getText>;
|
|
|
40
50
|
*/
|
|
41
51
|
declare function mergeHeaders(...entries: (HeadersInit | [string, undefined][] | Record<string, undefined>)[]): Headers;
|
|
42
52
|
/**
|
|
43
|
-
* @param
|
|
53
|
+
* @param url a string or URL to which the query parameters will be added
|
|
44
54
|
* @param searchParams the query parameters
|
|
45
|
-
* @returns the
|
|
55
|
+
* @returns the url with the query parameters added with the same type as the url
|
|
46
56
|
*/
|
|
47
|
-
declare function
|
|
57
|
+
declare function addQueryToUrl(url: string | URL, searchParams?: SearchParams): string | URL;
|
|
58
|
+
/**
|
|
59
|
+
* @deprecated method renamed to addQueryToUrl
|
|
60
|
+
*/
|
|
61
|
+
declare const addQueryToInput: typeof addQueryToUrl;
|
|
48
62
|
/**
|
|
49
63
|
* @param baseURL the base path to the API
|
|
50
64
|
* @returns a function that receives a path and an object of query parameters and returns a URL
|
|
@@ -72,7 +86,7 @@ declare function typedResponse(response: Response): TypedResponse;
|
|
|
72
86
|
declare function ensureStringBody(body?: JSONValue): string | undefined;
|
|
73
87
|
/**
|
|
74
88
|
*
|
|
75
|
-
* @param
|
|
89
|
+
* @param url a string or URL to be fetched
|
|
76
90
|
* @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.
|
|
77
91
|
* @param requestInit.body the body of the request. It will be automatically stringified so you can send a JSON-like object
|
|
78
92
|
* @param requestInit.query the query parameters to be added to the URL
|
|
@@ -84,7 +98,7 @@ declare function ensureStringBody(body?: JSONValue): string | undefined;
|
|
|
84
98
|
* const untyped = await response.json();
|
|
85
99
|
* // ^? unknown
|
|
86
100
|
*/
|
|
87
|
-
declare function enhancedFetch(
|
|
101
|
+
declare function enhancedFetch(url: string | URL, requestInit?: EnhancedRequestInit): Promise<TypedResponse>;
|
|
88
102
|
/**
|
|
89
103
|
*
|
|
90
104
|
* @param baseURL the base URL to the API
|
|
@@ -96,14 +110,6 @@ declare function enhancedFetch(input: string | URL, requestInit?: EnhancedReques
|
|
|
96
110
|
* const users = await response.json(userSchema);
|
|
97
111
|
* // ^? User[]
|
|
98
112
|
*/
|
|
99
|
-
declare function makeService(baseURL: string | URL, baseHeaders?: HeadersInit):
|
|
100
|
-
get: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
101
|
-
post: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
102
|
-
put: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
103
|
-
delete: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
104
|
-
patch: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
105
|
-
options: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
106
|
-
head: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
|
|
107
|
-
};
|
|
113
|
+
declare function makeService(baseURL: string | URL, baseHeaders?: HeadersInit): Record<"delete" | "get" | "post" | "put" | "patch" | "options" | "head", (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>>;
|
|
108
114
|
|
|
109
|
-
export { EnhancedRequestInit, HTTPMethod, JSONValue, Schema, SearchParams, ServiceRequestInit, TypedResponse, TypedResponseJson, TypedResponseText, addQueryToInput, enhancedFetch, ensureStringBody, makeGetApiUrl, makeService, mergeHeaders, typedResponse };
|
|
115
|
+
export { EnhancedRequestInit, HTTPMethod, JSONValue, PathParams, Schema, SearchParams, ServiceRequestInit, TypedResponse, TypedResponseJson, TypedResponseText, addQueryToInput, addQueryToUrl, enhancedFetch, ensureStringBody, makeGetApiUrl, makeService, mergeHeaders, typedResponse };
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
addQueryToInput: () => addQueryToInput,
|
|
24
|
+
addQueryToUrl: () => addQueryToUrl,
|
|
24
25
|
enhancedFetch: () => enhancedFetch,
|
|
25
26
|
ensureStringBody: () => ensureStringBody,
|
|
26
27
|
makeGetApiUrl: () => makeGetApiUrl,
|
|
@@ -32,13 +33,13 @@ module.exports = __toCommonJS(src_exports);
|
|
|
32
33
|
|
|
33
34
|
// src/constants.ts
|
|
34
35
|
var HTTP_METHODS = [
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
36
|
+
"GET",
|
|
37
|
+
"POST",
|
|
38
|
+
"PUT",
|
|
39
|
+
"DELETE",
|
|
40
|
+
"PATCH",
|
|
41
|
+
"OPTIONS",
|
|
42
|
+
"HEAD"
|
|
42
43
|
];
|
|
43
44
|
|
|
44
45
|
// src/internals.ts
|
|
@@ -57,8 +58,14 @@ function getText(response) {
|
|
|
57
58
|
return schema ? schema.parse(text) : text;
|
|
58
59
|
};
|
|
59
60
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
61
|
+
function replaceUrlParams(url, params) {
|
|
62
|
+
if (!params)
|
|
63
|
+
return url;
|
|
64
|
+
let urlString = String(url);
|
|
65
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
66
|
+
urlString = urlString.replace(new RegExp(`:${key}($|/)`), `${value}$1`);
|
|
67
|
+
});
|
|
68
|
+
return url instanceof URL ? new URL(urlString) : urlString;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
// src/make-service.ts
|
|
@@ -76,27 +83,28 @@ function mergeHeaders(...entries) {
|
|
|
76
83
|
}
|
|
77
84
|
return new Headers(Array.from(result.entries()));
|
|
78
85
|
}
|
|
79
|
-
function
|
|
86
|
+
function addQueryToUrl(url, searchParams) {
|
|
80
87
|
if (!searchParams)
|
|
81
|
-
return
|
|
82
|
-
if (typeof
|
|
83
|
-
const separator =
|
|
84
|
-
return `${
|
|
88
|
+
return url;
|
|
89
|
+
if (typeof url === "string") {
|
|
90
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
91
|
+
return `${url}${separator}${new URLSearchParams(searchParams)}`;
|
|
85
92
|
}
|
|
86
|
-
if (searchParams &&
|
|
93
|
+
if (searchParams && url instanceof URL) {
|
|
87
94
|
for (const [key, value] of Object.entries(
|
|
88
95
|
new URLSearchParams(searchParams)
|
|
89
96
|
)) {
|
|
90
|
-
|
|
97
|
+
url.searchParams.set(key, value);
|
|
91
98
|
}
|
|
92
99
|
}
|
|
93
|
-
return
|
|
100
|
+
return url;
|
|
94
101
|
}
|
|
102
|
+
var addQueryToInput = addQueryToUrl;
|
|
95
103
|
function makeGetApiUrl(baseURL) {
|
|
96
104
|
const base = baseURL instanceof URL ? baseURL.toString() : baseURL;
|
|
97
105
|
return (path, searchParams) => {
|
|
98
106
|
const url = `${base}${path}`.replace(/([^https?:]\/)\/+/g, "$1");
|
|
99
|
-
return
|
|
107
|
+
return addQueryToUrl(url, searchParams);
|
|
100
108
|
};
|
|
101
109
|
}
|
|
102
110
|
function typedResponse(response) {
|
|
@@ -117,8 +125,8 @@ function ensureStringBody(body) {
|
|
|
117
125
|
return body;
|
|
118
126
|
return JSON.stringify(body);
|
|
119
127
|
}
|
|
120
|
-
async function enhancedFetch(
|
|
121
|
-
var _a;
|
|
128
|
+
async function enhancedFetch(url, requestInit) {
|
|
129
|
+
var _a, _b;
|
|
122
130
|
const { query, trace, ...reqInit } = requestInit != null ? requestInit : {};
|
|
123
131
|
const headers = mergeHeaders(
|
|
124
132
|
{
|
|
@@ -126,11 +134,12 @@ async function enhancedFetch(input, requestInit) {
|
|
|
126
134
|
},
|
|
127
135
|
(_a = reqInit.headers) != null ? _a : {}
|
|
128
136
|
);
|
|
129
|
-
const
|
|
137
|
+
const withParams = replaceUrlParams(url, (_b = reqInit.params) != null ? _b : {});
|
|
138
|
+
const fullUrl = addQueryToUrl(withParams, query);
|
|
130
139
|
const body = ensureStringBody(reqInit.body);
|
|
131
140
|
const enhancedReqInit = { ...reqInit, headers, body };
|
|
132
|
-
trace == null ? void 0 : trace(
|
|
133
|
-
const response = await fetch(
|
|
141
|
+
trace == null ? void 0 : trace(fullUrl, enhancedReqInit);
|
|
142
|
+
const response = await fetch(fullUrl, enhancedReqInit);
|
|
134
143
|
return typedResponse(response);
|
|
135
144
|
}
|
|
136
145
|
function makeService(baseURL, baseHeaders) {
|
|
@@ -146,17 +155,17 @@ function makeService(baseURL, baseHeaders) {
|
|
|
146
155
|
return response;
|
|
147
156
|
};
|
|
148
157
|
};
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
});
|
|
158
|
+
let api = {};
|
|
159
|
+
for (const method of HTTP_METHODS) {
|
|
160
|
+
const lowerMethod = method.toLowerCase();
|
|
161
|
+
api[lowerMethod] = service(method);
|
|
162
|
+
}
|
|
163
|
+
return api;
|
|
156
164
|
}
|
|
157
165
|
// Annotate the CommonJS export names for ESM import in node:
|
|
158
166
|
0 && (module.exports = {
|
|
159
167
|
addQueryToInput,
|
|
168
|
+
addQueryToUrl,
|
|
160
169
|
enhancedFetch,
|
|
161
170
|
ensureStringBody,
|
|
162
171
|
makeGetApiUrl,
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// src/constants.ts
|
|
2
2
|
var HTTP_METHODS = [
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
3
|
+
"GET",
|
|
4
|
+
"POST",
|
|
5
|
+
"PUT",
|
|
6
|
+
"DELETE",
|
|
7
|
+
"PATCH",
|
|
8
|
+
"OPTIONS",
|
|
9
|
+
"HEAD"
|
|
10
10
|
];
|
|
11
11
|
|
|
12
12
|
// src/internals.ts
|
|
@@ -25,8 +25,14 @@ function getText(response) {
|
|
|
25
25
|
return schema ? schema.parse(text) : text;
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
function
|
|
29
|
-
|
|
28
|
+
function replaceUrlParams(url, params) {
|
|
29
|
+
if (!params)
|
|
30
|
+
return url;
|
|
31
|
+
let urlString = String(url);
|
|
32
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
33
|
+
urlString = urlString.replace(new RegExp(`:${key}($|/)`), `${value}$1`);
|
|
34
|
+
});
|
|
35
|
+
return url instanceof URL ? new URL(urlString) : urlString;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
// src/make-service.ts
|
|
@@ -44,27 +50,28 @@ function mergeHeaders(...entries) {
|
|
|
44
50
|
}
|
|
45
51
|
return new Headers(Array.from(result.entries()));
|
|
46
52
|
}
|
|
47
|
-
function
|
|
53
|
+
function addQueryToUrl(url, searchParams) {
|
|
48
54
|
if (!searchParams)
|
|
49
|
-
return
|
|
50
|
-
if (typeof
|
|
51
|
-
const separator =
|
|
52
|
-
return `${
|
|
55
|
+
return url;
|
|
56
|
+
if (typeof url === "string") {
|
|
57
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
58
|
+
return `${url}${separator}${new URLSearchParams(searchParams)}`;
|
|
53
59
|
}
|
|
54
|
-
if (searchParams &&
|
|
60
|
+
if (searchParams && url instanceof URL) {
|
|
55
61
|
for (const [key, value] of Object.entries(
|
|
56
62
|
new URLSearchParams(searchParams)
|
|
57
63
|
)) {
|
|
58
|
-
|
|
64
|
+
url.searchParams.set(key, value);
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
|
-
return
|
|
67
|
+
return url;
|
|
62
68
|
}
|
|
69
|
+
var addQueryToInput = addQueryToUrl;
|
|
63
70
|
function makeGetApiUrl(baseURL) {
|
|
64
71
|
const base = baseURL instanceof URL ? baseURL.toString() : baseURL;
|
|
65
72
|
return (path, searchParams) => {
|
|
66
73
|
const url = `${base}${path}`.replace(/([^https?:]\/)\/+/g, "$1");
|
|
67
|
-
return
|
|
74
|
+
return addQueryToUrl(url, searchParams);
|
|
68
75
|
};
|
|
69
76
|
}
|
|
70
77
|
function typedResponse(response) {
|
|
@@ -85,8 +92,8 @@ function ensureStringBody(body) {
|
|
|
85
92
|
return body;
|
|
86
93
|
return JSON.stringify(body);
|
|
87
94
|
}
|
|
88
|
-
async function enhancedFetch(
|
|
89
|
-
var _a;
|
|
95
|
+
async function enhancedFetch(url, requestInit) {
|
|
96
|
+
var _a, _b;
|
|
90
97
|
const { query, trace, ...reqInit } = requestInit != null ? requestInit : {};
|
|
91
98
|
const headers = mergeHeaders(
|
|
92
99
|
{
|
|
@@ -94,11 +101,12 @@ async function enhancedFetch(input, requestInit) {
|
|
|
94
101
|
},
|
|
95
102
|
(_a = reqInit.headers) != null ? _a : {}
|
|
96
103
|
);
|
|
97
|
-
const
|
|
104
|
+
const withParams = replaceUrlParams(url, (_b = reqInit.params) != null ? _b : {});
|
|
105
|
+
const fullUrl = addQueryToUrl(withParams, query);
|
|
98
106
|
const body = ensureStringBody(reqInit.body);
|
|
99
107
|
const enhancedReqInit = { ...reqInit, headers, body };
|
|
100
|
-
trace == null ? void 0 : trace(
|
|
101
|
-
const response = await fetch(
|
|
108
|
+
trace == null ? void 0 : trace(fullUrl, enhancedReqInit);
|
|
109
|
+
const response = await fetch(fullUrl, enhancedReqInit);
|
|
102
110
|
return typedResponse(response);
|
|
103
111
|
}
|
|
104
112
|
function makeService(baseURL, baseHeaders) {
|
|
@@ -114,16 +122,16 @@ function makeService(baseURL, baseHeaders) {
|
|
|
114
122
|
return response;
|
|
115
123
|
};
|
|
116
124
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
125
|
+
let api = {};
|
|
126
|
+
for (const method of HTTP_METHODS) {
|
|
127
|
+
const lowerMethod = method.toLowerCase();
|
|
128
|
+
api[lowerMethod] = service(method);
|
|
129
|
+
}
|
|
130
|
+
return api;
|
|
124
131
|
}
|
|
125
132
|
export {
|
|
126
133
|
addQueryToInput,
|
|
134
|
+
addQueryToUrl,
|
|
127
135
|
enhancedFetch,
|
|
128
136
|
ensureStringBody,
|
|
129
137
|
makeGetApiUrl,
|