make-service 0.0.3 → 0.1.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 CHANGED
@@ -155,21 +155,21 @@ import { typedResponse } from 'make-service'
155
155
  import type { TypedResponse } from 'make-service'
156
156
 
157
157
  // With JSON
158
- const response: TypedResponse = new Response(JSON.stringify({ foo: "bar" }))
159
- const json = await typedResponse(response).json()
158
+ const response: TypedResponse = typedResponse(new Response(JSON.stringify({ foo: "bar" })))
159
+ const json = await response.json()
160
160
  // ^? unknown
161
- const json = await typedResponse(response).json<{ foo: string }>()
161
+ const json = await response.json<{ foo: string }>()
162
162
  // ^? { foo: string }
163
- const json = await typedResponse(response).json(z.object({ foo: z.string() }))
163
+ const json = await response.json(z.object({ foo: z.string() }))
164
164
  // ^? { foo: string }
165
165
 
166
166
  // With text
167
- const response: TypedResponse = new Response("foo")
168
- const text = await typedResponse(response).text()
167
+ const response: TypedResponse = typedResponse(new Response("foo"))
168
+ const text = await response.text()
169
169
  // ^? string
170
- const text = await typedResponse(response).text<`foo${string}`>()
170
+ const text = await response.text<`foo${string}`>()
171
171
  // ^? `foo${string}`
172
- const text = await typedResponse(response).text(z.string().email())
172
+ const text = await response.text(z.string().email())
173
173
  // ^? string
174
174
  ```
175
175
 
package/dist/index.d.ts CHANGED
@@ -33,6 +33,12 @@ type HTTPMethod = (typeof HTTP_METHODS)[number];
33
33
  type TypedResponseJson = ReturnType<typeof getJson>;
34
34
  type TypedResponseText = ReturnType<typeof getText>;
35
35
 
36
+ /**
37
+ * It merges multiple HeadersInit objects into a single Headers object
38
+ * @param entries Any number of HeadersInit objects
39
+ * @returns a new Headers object with the merged headers
40
+ */
41
+ declare function mergeHeaders(...entries: (HeadersInit | [string, undefined][] | Record<string, undefined>)[]): Headers;
36
42
  /**
37
43
  * @param input a string or URL to which the query parameters will be added
38
44
  * @param searchParams the query parameters
@@ -43,7 +49,7 @@ declare function addQueryToInput(input: string | URL, searchParams?: SearchParam
43
49
  * @param baseURL the base path to the API
44
50
  * @returns a function that receives a path and an object of query parameters and returns a URL
45
51
  */
46
- declare function makeGetApiUrl(baseURL: string): (path: string, searchParams?: SearchParams) => string | URL;
52
+ declare function makeGetApiUrl(baseURL: string | URL): (path: string, searchParams?: SearchParams) => string | URL;
47
53
  /**
48
54
  * It hacks the Response object to add typed json and text methods
49
55
  * @param response the Response to be proxied
@@ -90,7 +96,7 @@ declare function enhancedFetch(input: string | URL, requestInit?: EnhancedReques
90
96
  * const users = await response.json(userSchema);
91
97
  * // ^? User[]
92
98
  */
93
- declare function makeService(baseURL: string, baseHeaders?: HeadersInit): {
99
+ declare function makeService(baseURL: string | URL, baseHeaders?: HeadersInit): {
94
100
  get: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
95
101
  post: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
96
102
  put: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
@@ -100,4 +106,4 @@ declare function makeService(baseURL: string, baseHeaders?: HeadersInit): {
100
106
  head: (path: string, requestInit?: ServiceRequestInit) => Promise<TypedResponse>;
101
107
  };
102
108
 
103
- export { EnhancedRequestInit, HTTPMethod, JSONValue, Schema, SearchParams, ServiceRequestInit, TypedResponse, TypedResponseJson, TypedResponseText, addQueryToInput, enhancedFetch, ensureStringBody, makeGetApiUrl, makeService, typedResponse };
109
+ export { EnhancedRequestInit, HTTPMethod, JSONValue, Schema, SearchParams, ServiceRequestInit, TypedResponse, TypedResponseJson, TypedResponseText, addQueryToInput, enhancedFetch, ensureStringBody, makeGetApiUrl, makeService, mergeHeaders, typedResponse };
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ __export(src_exports, {
25
25
  ensureStringBody: () => ensureStringBody,
26
26
  makeGetApiUrl: () => makeGetApiUrl,
27
27
  makeService: () => makeService,
28
+ mergeHeaders: () => mergeHeaders,
28
29
  typedResponse: () => typedResponse
29
30
  });
30
31
  module.exports = __toCommonJS(src_exports);
@@ -61,20 +62,42 @@ function isHTTPMethod(method) {
61
62
  }
62
63
 
63
64
  // src/make-service.ts
65
+ function mergeHeaders(...entries) {
66
+ const result = /* @__PURE__ */ new Map();
67
+ for (const entry of entries) {
68
+ const headers = new Headers(entry);
69
+ for (const [key, value] of headers.entries()) {
70
+ if (value === void 0 || value === "undefined") {
71
+ result.delete(key);
72
+ } else {
73
+ result.set(key, value);
74
+ }
75
+ }
76
+ }
77
+ return new Headers(Array.from(result.entries()));
78
+ }
64
79
  function addQueryToInput(input, searchParams) {
65
80
  if (!searchParams)
66
81
  return input;
67
- if (searchParams && typeof input === "string") {
82
+ if (typeof input === "string") {
68
83
  const separator = input.includes("?") ? "&" : "?";
69
84
  return `${input}${separator}${new URLSearchParams(searchParams)}`;
70
85
  }
71
86
  if (searchParams && input instanceof URL) {
72
- input.search = new URLSearchParams(searchParams).toString();
87
+ for (const [key, value] of Object.entries(
88
+ new URLSearchParams(searchParams)
89
+ )) {
90
+ input.searchParams.set(key, value);
91
+ }
73
92
  }
74
93
  return input;
75
94
  }
76
95
  function makeGetApiUrl(baseURL) {
77
- return (path, searchParams) => addQueryToInput(`${baseURL}${path}`, searchParams);
96
+ const base = baseURL instanceof URL ? baseURL.toString() : baseURL;
97
+ return (path, searchParams) => {
98
+ const url = `${base}${path}`.replace(/([^https?:]\/)\/+/g, "$1");
99
+ return addQueryToInput(url, searchParams);
100
+ };
78
101
  }
79
102
  function typedResponse(response) {
80
103
  return new Proxy(response, {
@@ -95,23 +118,30 @@ function ensureStringBody(body) {
95
118
  return JSON.stringify(body);
96
119
  }
97
120
  async function enhancedFetch(input, requestInit) {
121
+ var _a;
98
122
  const { query, trace, ...reqInit } = requestInit != null ? requestInit : {};
99
- const headers = { "content-type": "application/json", ...reqInit.headers };
123
+ const headers = mergeHeaders(
124
+ {
125
+ "content-type": "application/json"
126
+ },
127
+ (_a = reqInit.headers) != null ? _a : {}
128
+ );
100
129
  const url = addQueryToInput(input, query);
101
130
  const body = ensureStringBody(reqInit.body);
102
131
  const enhancedReqInit = { ...reqInit, headers, body };
103
132
  trace == null ? void 0 : trace(url, enhancedReqInit);
104
- const request = new Request(url, enhancedReqInit);
105
- const response = await fetch(request);
133
+ const response = await fetch(url, enhancedReqInit);
106
134
  return typedResponse(response);
107
135
  }
108
136
  function makeService(baseURL, baseHeaders) {
109
137
  const service = (method) => {
110
138
  return async (path, requestInit = {}) => {
111
- const response = await enhancedFetch(`${baseURL}${path}`, {
139
+ var _a;
140
+ const url = makeGetApiUrl(baseURL)(path);
141
+ const response = await enhancedFetch(url, {
112
142
  ...requestInit,
113
143
  method,
114
- headers: { ...baseHeaders, ...requestInit == null ? void 0 : requestInit.headers }
144
+ headers: mergeHeaders(baseHeaders != null ? baseHeaders : {}, (_a = requestInit == null ? void 0 : requestInit.headers) != null ? _a : {})
115
145
  });
116
146
  return response;
117
147
  };
@@ -119,7 +149,7 @@ function makeService(baseURL, baseHeaders) {
119
149
  return new Proxy({}, {
120
150
  get(_target, prop) {
121
151
  if (isHTTPMethod(prop))
122
- return service(prop);
152
+ return service(prop.toUpperCase());
123
153
  throw new Error(`Invalid HTTP method: ${prop.toString()}`);
124
154
  }
125
155
  });
@@ -131,5 +161,6 @@ function makeService(baseURL, baseHeaders) {
131
161
  ensureStringBody,
132
162
  makeGetApiUrl,
133
163
  makeService,
164
+ mergeHeaders,
134
165
  typedResponse
135
166
  });
package/dist/index.mjs CHANGED
@@ -30,20 +30,42 @@ function isHTTPMethod(method) {
30
30
  }
31
31
 
32
32
  // src/make-service.ts
33
+ function mergeHeaders(...entries) {
34
+ const result = /* @__PURE__ */ new Map();
35
+ for (const entry of entries) {
36
+ const headers = new Headers(entry);
37
+ for (const [key, value] of headers.entries()) {
38
+ if (value === void 0 || value === "undefined") {
39
+ result.delete(key);
40
+ } else {
41
+ result.set(key, value);
42
+ }
43
+ }
44
+ }
45
+ return new Headers(Array.from(result.entries()));
46
+ }
33
47
  function addQueryToInput(input, searchParams) {
34
48
  if (!searchParams)
35
49
  return input;
36
- if (searchParams && typeof input === "string") {
50
+ if (typeof input === "string") {
37
51
  const separator = input.includes("?") ? "&" : "?";
38
52
  return `${input}${separator}${new URLSearchParams(searchParams)}`;
39
53
  }
40
54
  if (searchParams && input instanceof URL) {
41
- input.search = new URLSearchParams(searchParams).toString();
55
+ for (const [key, value] of Object.entries(
56
+ new URLSearchParams(searchParams)
57
+ )) {
58
+ input.searchParams.set(key, value);
59
+ }
42
60
  }
43
61
  return input;
44
62
  }
45
63
  function makeGetApiUrl(baseURL) {
46
- return (path, searchParams) => addQueryToInput(`${baseURL}${path}`, searchParams);
64
+ const base = baseURL instanceof URL ? baseURL.toString() : baseURL;
65
+ return (path, searchParams) => {
66
+ const url = `${base}${path}`.replace(/([^https?:]\/)\/+/g, "$1");
67
+ return addQueryToInput(url, searchParams);
68
+ };
47
69
  }
48
70
  function typedResponse(response) {
49
71
  return new Proxy(response, {
@@ -64,23 +86,30 @@ function ensureStringBody(body) {
64
86
  return JSON.stringify(body);
65
87
  }
66
88
  async function enhancedFetch(input, requestInit) {
89
+ var _a;
67
90
  const { query, trace, ...reqInit } = requestInit != null ? requestInit : {};
68
- const headers = { "content-type": "application/json", ...reqInit.headers };
91
+ const headers = mergeHeaders(
92
+ {
93
+ "content-type": "application/json"
94
+ },
95
+ (_a = reqInit.headers) != null ? _a : {}
96
+ );
69
97
  const url = addQueryToInput(input, query);
70
98
  const body = ensureStringBody(reqInit.body);
71
99
  const enhancedReqInit = { ...reqInit, headers, body };
72
100
  trace == null ? void 0 : trace(url, enhancedReqInit);
73
- const request = new Request(url, enhancedReqInit);
74
- const response = await fetch(request);
101
+ const response = await fetch(url, enhancedReqInit);
75
102
  return typedResponse(response);
76
103
  }
77
104
  function makeService(baseURL, baseHeaders) {
78
105
  const service = (method) => {
79
106
  return async (path, requestInit = {}) => {
80
- const response = await enhancedFetch(`${baseURL}${path}`, {
107
+ var _a;
108
+ const url = makeGetApiUrl(baseURL)(path);
109
+ const response = await enhancedFetch(url, {
81
110
  ...requestInit,
82
111
  method,
83
- headers: { ...baseHeaders, ...requestInit == null ? void 0 : requestInit.headers }
112
+ headers: mergeHeaders(baseHeaders != null ? baseHeaders : {}, (_a = requestInit == null ? void 0 : requestInit.headers) != null ? _a : {})
84
113
  });
85
114
  return response;
86
115
  };
@@ -88,7 +117,7 @@ function makeService(baseURL, baseHeaders) {
88
117
  return new Proxy({}, {
89
118
  get(_target, prop) {
90
119
  if (isHTTPMethod(prop))
91
- return service(prop);
120
+ return service(prop.toUpperCase());
92
121
  throw new Error(`Invalid HTTP method: ${prop.toString()}`);
93
122
  }
94
123
  });
@@ -99,5 +128,6 @@ export {
99
128
  ensureStringBody,
100
129
  makeGetApiUrl,
101
130
  makeService,
131
+ mergeHeaders,
102
132
  typedResponse
103
133
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "make-service",
3
- "version": "0.0.3",
3
+ "version": "0.1.0",
4
4
  "description": "Some utilities to extend the 'fetch' API to better interact with external APIs.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",