make-service 1.1.0 → 2.0.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
@@ -6,18 +6,20 @@ It adds a set of little features and allows you to parse responses with [zod](ht
6
6
 
7
7
  ## Features
8
8
  - 🤩 Type-safe return of `response.json()` and `response.text()`. Defaults to `unknown` instead of `any`.
9
- - 🚦 Easily setup an API with a `baseURL` and common `headers` for every request.
9
+ - 🚦 Easily setup an API with a `baseURL` and common options like `headers` for every request.
10
10
  - 🏗️ Compose URL from the base by just calling the endpoints and an object-like `query`.
11
11
  - 🐾 Replaces URL wildcards with a **strongly-typed** object of `params`.
12
12
  - 🧙‍♀️ Automatically stringifies the `body` of a request so you can give it a JSON-like structure.
13
13
  - 🐛 Accepts a `trace` function for debugging.
14
- - 🔥 Transforms responses and payloads back and forth to support interchangeability of casing styles (kebab-case -> camelCase -> snake_case -> kebab-case).
14
+ - 🔥 It can transform responses and payloads back and forth to (e.g.) support interchangeability of casing styles (kebab-case -> camelCase -> snake_case -> kebab-case).
15
15
 
16
16
  ## Example
17
17
 
18
18
  ```ts
19
19
  const service = makeService("https://example.com/api", {
20
- Authorization: "Bearer 123",
20
+ headers: {
21
+ Authorization: "Bearer 123",
22
+ },
21
23
  });
22
24
 
23
25
  const response = await service.get("/users")
@@ -33,9 +35,12 @@ const users = await response.json(usersSchema);
33
35
  - [Runtime type-checking and parsing the response body](#runtime-type-checking-and-parsing-the-response-body)
34
36
  - [Supported HTTP Verbs](#supported-http-verbs)
35
37
  - [Headers](#headers)
36
- - [Passing a function as `baseHeaders`](#passing-a-function-as-baseheaders)
38
+ - [Passing a function as `headers`](#passing-a-function-as-headers)
37
39
  - [Deleting a previously set header](#deleting-a-previously-set-header)
38
40
  - [Base URL](#base-url)
41
+ - [Transformers](#transformers)
42
+ - [Request transformers](#request-transformers)
43
+ - [Response transformers](#response-transformers)
39
44
  - [Body](#body)
40
45
  - [Query](#query)
41
46
  - [Params](#params)
@@ -43,7 +48,7 @@ const users = await response.json(usersSchema);
43
48
  - [makeFetcher](#makefetcher)
44
49
  - [enhancedFetch](#enhancedfetch)
45
50
  - [typedResponse](#typedresponse)
46
- - [Payload transformers](#payload-transformers)
51
+ - [Transform the Payload](#transform-the-payload)
47
52
  - [Other available primitives](#other-available-primitives)
48
53
  - [addQueryToURL](#addquerytourl)
49
54
  - [ensureStringBody](#ensurestringbody)
@@ -68,7 +73,7 @@ import { makeService } from "https://deno.land/x/make_service/mod.ts";
68
73
  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.
69
74
 
70
75
  ## makeService
71
- 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.
76
+ 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 options like `headers` for every request.
72
77
 
73
78
  This service object can be called with every HTTP method and it will return a [`typedResponse`](#typedresponse).
74
79
 
@@ -76,7 +81,9 @@ This service object can be called with every HTTP method and it will return a [`
76
81
  import { makeService } from 'make-service'
77
82
 
78
83
  const service = makeService("https://example.com/api", {
79
- authorization: "Bearer 123"
84
+ headers :{
85
+ authorization: "Bearer 123",
86
+ },
80
87
  })
81
88
 
82
89
  const response = await service.get("/users")
@@ -146,15 +153,17 @@ await service.options("/users")
146
153
 
147
154
  ### Headers
148
155
  The `headers` argument can be a `Headers` object, a `Record<string, string>`, or an array of `[key, value]` tuples (entries).
149
- The `baseHeaders` and the `headers` will be merged together, with the `headers` taking precedence.
156
+ The `headers` option on `baseOptions` and the `headers` argument will be merged together, with the `headers` argument taking precedence.
150
157
 
151
158
  ```ts
152
159
  import { makeService } from 'make-service'
153
160
 
154
- const service = makeService("https://example.com/api", new Headers({
155
- authorization: "Bearer 123",
156
- accept: "*/*",
157
- }))
161
+ const service = makeService("https://example.com/api", {
162
+ headers: new Headers({
163
+ authorization: "Bearer 123",
164
+ accept: "*/*",
165
+ }),
166
+ })
158
167
 
159
168
  const response = await service.get("/users", {
160
169
  headers: [['accept', 'application/json']],
@@ -164,8 +173,8 @@ const response = await service.get("/users", {
164
173
  // with headers: { authorization: "Bearer 123", accept: "application/json" }
165
174
  ```
166
175
 
167
- #### Passing a function as `baseHeaders`
168
- The given `baseHeaders` can be a sync or async function that will run in every request before it gets merged with the other headers.
176
+ #### Passing a function as `headers`
177
+ The `headers` option on `baseOptions` can be a sync or async function that will run in every request before it gets merged with the other headers.
169
178
  This is particularly useful when you need to send a refreshed token or add a timestamp to the request.
170
179
 
171
180
  ```ts
@@ -173,16 +182,21 @@ import { makeService } from 'make-service'
173
182
 
174
183
  declare getAuthorizationToken: () => Promise<HeadersInit>
175
184
 
176
- const service = makeService("https://example.com/api", async () => ({
177
- authorization: await getAuthorizationToken(),
178
- }))
185
+ const service = makeService("https://example.com/api", {
186
+ headers: async () => ({
187
+ authorization: await getAuthorizationToken(),
188
+ }),
189
+ })
179
190
 
180
191
  ```
181
192
 
182
193
  #### Deleting a previously set header
183
194
  In case you want to delete a header previously set you can pass `undefined` or `'undefined'` as its value:
184
195
  ```ts
185
- const service = makeService("https://example.com/api", { authorization: "Bearer 123" })
196
+ const service = makeService("https://example.com/api", {
197
+ headers: { authorization: "Bearer 123" },
198
+ })
199
+
186
200
  const response = await service.get("/users", {
187
201
  headers: new Headers({ authorization: 'undefined', "Content-Type": undefined }),
188
202
  })
@@ -210,6 +224,37 @@ const response = await service.get("/users?admin=true")
210
224
  ```
211
225
  You can use the [`makeGetApiUrl`](#makegetapiurl) method to do that kind of URL composition.
212
226
 
227
+ ### Transformers
228
+ `makeService` can also receive `requestTransformer` and `responseTransformer` as options that will be applied to all requests.
229
+
230
+ #### Request transformers
231
+ You can transform the request in any way you want, like:
232
+
233
+ ```ts
234
+ const service = makeService('https://example.com/api', {
235
+ requestTransformer: (request) => ({ ...request, query: { admin: 'true' } }),
236
+ })
237
+
238
+ const response = await service.get("/users")
239
+
240
+ // It will call "https://example.com/api/users?admin=true"
241
+ ```
242
+
243
+ Please note that the `headers` option will be applied _after_ the request transformer runs. If you're using a request transformer, we recommend adding custom headers inside your transformer instead of using both options.
244
+
245
+ #### Response transformers
246
+ You can also transform the response in any way you want, like:
247
+
248
+ ```ts
249
+ const service = makeService('https://example.com/api', {
250
+ responseTransformer: (response) => ({ ...response, statusText: 'It worked!' }),
251
+ })
252
+
253
+ const response = await service.get("/users")
254
+
255
+ // response.statusText will be 'It worked!'
256
+ ```
257
+
213
258
  ### Body
214
259
  The function can also receive a `body` object that will be stringified and sent as the request body:
215
260
 
@@ -392,33 +437,27 @@ const text = await response.text(z.string().email())
392
437
  // ^? string
393
438
  ```
394
439
 
395
- # Payload transformers
396
- The `make-service` library has a few payload transformers that you can use to transform the request body before sending it or the response body after returning from the server.
440
+ # Transform the payload
441
+ The combination of `make-service` and [`string-ts`](https://github.com/gustavoguichard/string-ts) libraries makes it easy to work with APIs that follow a different convention for object key's casing, so you can transform the request body before sending it or the response body after returning from the server.
397
442
  The resulting type will be **properly typed** 🤩.
398
443
  ```ts
399
- import { makeService, kebabToCamel, camelToKebab } from 'make-service'
444
+ import { makeService } from 'make-service'
445
+ import { deepCamelKeys, deepKebabKeys } from 'string-ts'
400
446
 
401
447
  const service = makeService("https://example.com/api")
402
448
  const response = service.get("/users")
403
449
  const users = await response.json(
404
450
  z
405
451
  .array(z.object({ "first-name": z.string(), contact: z.object({ "home-address": z.string() }) }))
406
- .transform(kebabToCamel)
452
+ .transform(deepCamelKeys)
407
453
  )
408
454
  console.log(users)
409
455
  // ^? { firstName: string, contact: { homeAddress: string } }[]
410
456
 
411
- const body = camelToKebab({ firstName: "John", contact: { homeAddress: "123 Main St" } })
457
+ const body = deepKebabKeys({ firstName: "John", contact: { homeAddress: "123 Main St" } })
412
458
  // ^? { "first-name": string, contact: { "home-address": string } }
413
459
  service.patch("/users/:id", { body, params: { id: "1" } })
414
460
  ```
415
- The available transformations are:
416
- - `camelToKebab`: `"someProp" -> "some-prop"`
417
- - `camelToSnake`: `"someProp" -> "some_prop"`
418
- - `kebabToCamel`: `"some-prop" -> "someProp"`
419
- - `kebabToSnake`: `"some-prop" -> "some_prop"`
420
- - `snakeToCamel`: `"some_prop" -> "someProp"`
421
- - `snakeToKebab`: `"some_prop" -> "some-prop"`
422
461
 
423
462
  # Other available primitives
424
463
  This little library has plenty of other useful functions that you can use to build your own services and interactions with external APIs.
package/dist/index.d.ts CHANGED
@@ -1,17 +1,5 @@
1
1
  declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT"];
2
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
3
  type Schema<T> = {
16
4
  parse: (d: unknown) => T;
17
5
  };
@@ -32,9 +20,18 @@ type EnhancedRequestInit<T = string> = Omit<RequestInit, 'body' | 'method'> & {
32
20
  trace?: (...args: Parameters<typeof fetch>) => void;
33
21
  };
34
22
  type ServiceRequestInit<T = string> = Omit<EnhancedRequestInit<T>, 'method'>;
23
+ type RequestTransformer = (request: EnhancedRequestInit) => EnhancedRequestInit | Promise<EnhancedRequestInit>;
24
+ type ResponseTransformer = (response: TypedResponse) => TypedResponse | Promise<TypedResponse>;
25
+ type BaseOptions = {
26
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
27
+ requestTransformer?: RequestTransformer;
28
+ responseTransformer?: ResponseTransformer;
29
+ };
35
30
  type HTTPMethod = (typeof HTTP_METHODS)[number];
36
- type TypedResponseJson = ReturnType<typeof getJson>;
37
- type TypedResponseText = ReturnType<typeof getText>;
31
+ type TypedResponseJson = <T = unknown>(schema?: Schema<T>) => Promise<T>;
32
+ type TypedResponseText = <T extends string = string>(schema?: Schema<T>) => Promise<T>;
33
+ type GetJson = (response: Response) => TypedResponseJson;
34
+ type GetText = (response: Response) => TypedResponseText;
38
35
  type Prettify<T> = {
39
36
  [K in keyof T]: T[K];
40
37
  } & {};
@@ -58,7 +55,10 @@ type ExtractPathParams<T extends string> = T extends `${infer _}:${infer Param}/
58
55
  * const typedJson = await response.json<User[]>();
59
56
  * // ^? User[]
60
57
  */
61
- declare function typedResponse(response: Response): TypedResponse;
58
+ declare function typedResponse(response: Response, options?: {
59
+ getJson?: GetJson;
60
+ getText?: GetText;
61
+ }): TypedResponse;
62
62
  /**
63
63
  *
64
64
  * @param url a string or URL to be fetched
@@ -77,7 +77,10 @@ declare function enhancedFetch<T extends string | URL>(url: T, requestInit?: Enh
77
77
  /**
78
78
  *
79
79
  * @param baseURL the base URL to be fetched in every request
80
- * @param baseHeaders any headers that should be sent with every request
80
+ * @param baseOptions options that will be applied to all requests
81
+ * @param baseOptions.headers any headers that should be sent with every request
82
+ * @param baseOptions.requestTransformer a function that will transform the enhanced request init of every request
83
+ * @param baseOptions.responseTransformer a function that will transform the typed response of every request
81
84
  * @returns a function that receive a path and requestInit and return a serialized json response that can be typed or not.
82
85
  * @example const headers = { Authorization: "Bearer 123" }
83
86
  * const fetcher = makeFetcher("https://example.com/api", headers);
@@ -85,11 +88,14 @@ declare function enhancedFetch<T extends string | URL>(url: T, requestInit?: Enh
85
88
  * const users = await response.json(userSchema);
86
89
  * // ^? User[]
87
90
  */
88
- declare function makeFetcher(baseURL: string | URL, baseHeaders?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>)): <T extends string>(path: T, requestInit?: EnhancedRequestInit<T>) => Promise<TypedResponse>;
91
+ declare function makeFetcher(baseURL: string | URL, baseOptions?: BaseOptions): <T extends string>(path: T, requestInit?: EnhancedRequestInit<T>) => Promise<TypedResponse>;
89
92
  /**
90
93
  *
91
94
  * @param baseURL the base URL to the API
92
- * @param baseHeaders any headers that should be sent with every request
95
+ * @param baseOptions options that will be applied to all requests
96
+ * @param baseOptions.headers any headers that should be sent with every request
97
+ * @param baseOptions.requestTransformer a function that will transform the enhanced request init of every request
98
+ * @param baseOptions.responseTransformer a function that will transform the typed response of every request
93
99
  * @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.
94
100
  * @example const headers = { Authorization: "Bearer 123" }
95
101
  * const api = makeService("https://example.com/api", headers);
@@ -97,7 +103,7 @@ declare function makeFetcher(baseURL: string | URL, baseHeaders?: HeadersInit |
97
103
  * const users = await response.json(userSchema);
98
104
  * // ^? User[]
99
105
  */
100
- declare function makeService(baseURL: string | URL, baseHeaders?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>)): Record<"get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "connect", <T extends string>(path: T, requestInit?: ServiceRequestInit<T>) => Promise<TypedResponse>>;
106
+ declare function makeService(baseURL: string | URL, baseOptions?: BaseOptions): Record<"get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "connect", <T extends string>(path: T, requestInit?: ServiceRequestInit<T>) => Promise<TypedResponse>>;
101
107
 
102
108
  /**
103
109
  * @param url a string or URL to which the query parameters will be added
@@ -129,50 +135,4 @@ declare function mergeHeaders(...entries: (HeadersInit | [string, undefined][] |
129
135
  */
130
136
  declare function replaceURLParams<T extends string | URL>(url: T, params: PathParams<T>): T;
131
137
 
132
- type KebabToCamel<Str> = Str extends `${infer First}-${infer Rest}` ? `${First}${Capitalize<KebabToCamel<Rest>>}` : Str;
133
- type SnakeToCamel<Str> = Str extends `${infer First}_${infer Rest}` ? `${First}${Capitalize<SnakeToCamel<Rest>>}` : Str;
134
- type KebabToSnake<Str> = Str extends `${infer First}-${infer Rest}` ? `${First}_${KebabToSnake<Rest>}` : Str;
135
- type SnakeToKebab<Str> = Str extends `${infer First}_${infer Rest}` ? `${First}-${SnakeToKebab<Rest>}` : Str;
136
- type HandleFirstChar<Str> = Str extends `${infer First}${infer Rest}` ? `${Lowercase<First>}${Rest}` : Str;
137
- type CamelToSnakeFn<Str> = Str extends `${infer First}${infer Rest}` ? `${First extends Capitalize<First> ? '_' : ''}${Lowercase<First>}${CamelToSnakeFn<Rest>}` : Str;
138
- type CamelToSnake<Str> = CamelToSnakeFn<HandleFirstChar<Str>>;
139
- type CamelToKebabFn<Str> = Str extends `${infer First}${infer Rest}` ? `${First extends Capitalize<First> ? '-' : ''}${Lowercase<First>}${CamelToKebabFn<Rest>}` : Str;
140
- type CamelToKebab<Str> = CamelToKebabFn<HandleFirstChar<Str>>;
141
- type DeepKebabToCamel<T> = T extends [any, ...any] ? {
142
- [I in keyof T]: DeepKebabToCamel<T[I]>;
143
- } : T extends (infer V)[] ? DeepKebabToCamel<V>[] : {
144
- [K in keyof T as KebabToCamel<K>]: DeepKebabToCamel<T[K]>;
145
- };
146
- declare function kebabToCamel<T>(obj: T): DeepKebabToCamel<T>;
147
- type DeepSnakeToCamel<T> = T extends [any, ...any] ? {
148
- [I in keyof T]: DeepSnakeToCamel<T[I]>;
149
- } : T extends (infer V)[] ? DeepSnakeToCamel<V>[] : {
150
- [K in keyof T as SnakeToCamel<K>]: DeepSnakeToCamel<T[K]>;
151
- };
152
- declare function snakeToCamel<T>(obj: T): DeepSnakeToCamel<T>;
153
- type DeepCamelToSnake<T> = T extends [any, ...any] ? {
154
- [I in keyof T]: DeepCamelToSnake<T[I]>;
155
- } : T extends (infer V)[] ? DeepCamelToSnake<V>[] : {
156
- [K in keyof T as CamelToSnake<K>]: DeepCamelToSnake<T[K]>;
157
- };
158
- declare function camelToSnake<T>(obj: T): DeepCamelToSnake<T>;
159
- type DeepCamelToKebab<T> = T extends [any, ...any] ? {
160
- [I in keyof T]: DeepCamelToKebab<T[I]>;
161
- } : T extends (infer V)[] ? DeepCamelToKebab<V>[] : {
162
- [K in keyof T as CamelToKebab<K>]: DeepCamelToKebab<T[K]>;
163
- };
164
- declare function camelToKebab<T>(obj: T): DeepCamelToKebab<T>;
165
- type DeepSnakeToKebab<T> = T extends [any, ...any] ? {
166
- [I in keyof T]: DeepSnakeToKebab<T[I]>;
167
- } : T extends (infer V)[] ? DeepSnakeToKebab<V>[] : {
168
- [K in keyof T as SnakeToKebab<K>]: DeepSnakeToKebab<T[K]>;
169
- };
170
- declare function snakeToKebab<T>(obj: T): DeepSnakeToKebab<T>;
171
- type DeepKebabToSnake<T> = T extends [any, ...any] ? {
172
- [I in keyof T]: DeepKebabToSnake<T[I]>;
173
- } : T extends (infer V)[] ? DeepKebabToSnake<V>[] : {
174
- [K in keyof T as KebabToSnake<K>]: DeepKebabToSnake<T[K]>;
175
- };
176
- declare function kebabToSnake<T>(obj: T): DeepKebabToSnake<T>;
177
-
178
- export { CamelToKebab, CamelToSnake, DeepCamelToKebab, DeepCamelToSnake, DeepKebabToCamel, DeepKebabToSnake, DeepSnakeToCamel, DeepSnakeToKebab, EnhancedRequestInit, HTTPMethod, JSONValue, KebabToCamel, KebabToSnake, PathParams, Schema, SearchParams, ServiceRequestInit, SnakeToCamel, SnakeToKebab, TypedResponse, TypedResponseJson, TypedResponseText, addQueryToURL, camelToKebab, camelToSnake, enhancedFetch, ensureStringBody, kebabToCamel, kebabToSnake, makeFetcher, makeGetApiURL, makeService, mergeHeaders, replaceURLParams, snakeToCamel, snakeToKebab, typedResponse };
138
+ export { BaseOptions, EnhancedRequestInit, GetJson, GetText, HTTPMethod, JSONValue, PathParams, RequestTransformer, ResponseTransformer, Schema, SearchParams, ServiceRequestInit, TypedResponse, TypedResponseJson, TypedResponseText, addQueryToURL, enhancedFetch, ensureStringBody, makeFetcher, makeGetApiURL, makeService, mergeHeaders, replaceURLParams, typedResponse };
package/dist/index.js CHANGED
@@ -21,19 +21,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  addQueryToURL: () => addQueryToURL,
24
- camelToKebab: () => camelToKebab,
25
- camelToSnake: () => camelToSnake,
26
24
  enhancedFetch: () => enhancedFetch,
27
25
  ensureStringBody: () => ensureStringBody,
28
- kebabToCamel: () => kebabToCamel,
29
- kebabToSnake: () => kebabToSnake,
30
26
  makeFetcher: () => makeFetcher,
31
27
  makeGetApiURL: () => makeGetApiURL,
32
28
  makeService: () => makeService,
33
29
  mergeHeaders: () => mergeHeaders,
34
30
  replaceURLParams: () => replaceURLParams,
35
- snakeToCamel: () => snakeToCamel,
36
- snakeToKebab: () => snakeToKebab,
37
31
  typedResponse: () => typedResponse
38
32
  });
39
33
  module.exports = __toCommonJS(src_exports);
@@ -52,18 +46,14 @@ var HTTP_METHODS = [
52
46
  ];
53
47
 
54
48
  // src/internals.ts
55
- function getJson(response) {
56
- return async (schema) => {
57
- const json = await response.json();
58
- return schema ? schema.parse(json) : json;
59
- };
60
- }
61
- function getText(response) {
62
- return async (schema) => {
63
- const text = await response.text();
64
- return schema ? schema.parse(text) : text;
65
- };
66
- }
49
+ var getJson = (response) => async (schema) => {
50
+ const json = await response.json();
51
+ return schema ? schema.parse(json) : json;
52
+ };
53
+ var getText = (response) => async (schema) => {
54
+ const text = await response.text();
55
+ return schema ? schema.parse(text) : text;
56
+ };
67
57
  function typeOf(t) {
68
58
  return Object.prototype.toString.call(t).replace(/^\[object (.+)\]$/, "$1").toLowerCase();
69
59
  }
@@ -124,13 +114,17 @@ function replaceURLParams(url, params) {
124
114
  }
125
115
 
126
116
  // src/api.ts
127
- function typedResponse(response) {
117
+ var identity = (value) => value;
118
+ function typedResponse(response, options) {
119
+ var _a, _b;
120
+ const getJsonFn = (_a = options == null ? void 0 : options.getJson) != null ? _a : getJson;
121
+ const getTextFn = (_b = options == null ? void 0 : options.getText) != null ? _b : getText;
128
122
  return new Proxy(response, {
129
123
  get(target, prop) {
130
124
  if (prop === "json")
131
- return getJson(target);
125
+ return getJsonFn(target);
132
126
  if (prop === "text")
133
- return getText(target);
127
+ return getTextFn(target);
134
128
  return target[prop];
135
129
  }
136
130
  });
@@ -152,95 +146,51 @@ async function enhancedFetch(url, requestInit) {
152
146
  const response = await fetch(fullURL, enhancedReqInit);
153
147
  return typedResponse(response);
154
148
  }
155
- function makeFetcher(baseURL, baseHeaders) {
149
+ function makeFetcher(baseURL, baseOptions = {}) {
156
150
  return async (path, requestInit = {}) => {
157
- var _a;
151
+ var _a, _b;
152
+ const { headers } = baseOptions;
153
+ const requestTransformer = (_a = baseOptions.requestTransformer) != null ? _a : identity;
154
+ const responseTransformer = (_b = baseOptions.responseTransformer) != null ? _b : identity;
155
+ const headerTransformer = async (ri) => {
156
+ var _a2;
157
+ return {
158
+ ...ri,
159
+ headers: mergeHeaders(
160
+ typeof headers === "function" ? await headers() : headers != null ? headers : {},
161
+ (_a2 = requestInit == null ? void 0 : requestInit.headers) != null ? _a2 : {}
162
+ )
163
+ };
164
+ };
158
165
  const url = makeGetApiURL(baseURL)(path);
159
- const response = await enhancedFetch(url, {
160
- ...requestInit,
161
- headers: mergeHeaders(
162
- typeof baseHeaders === "function" ? await baseHeaders() : baseHeaders != null ? baseHeaders : {},
163
- (_a = requestInit == null ? void 0 : requestInit.headers) != null ? _a : {}
164
- )
165
- });
166
- return response;
166
+ const response = await enhancedFetch(
167
+ url,
168
+ await headerTransformer(await requestTransformer(requestInit))
169
+ );
170
+ return responseTransformer(response);
167
171
  };
168
172
  }
169
- function makeService(baseURL, baseHeaders) {
170
- const fetcher = makeFetcher(baseURL, baseHeaders);
173
+ function makeService(baseURL, baseOptions) {
174
+ const fetcher = makeFetcher(baseURL, baseOptions);
171
175
  function appliedService(method) {
172
176
  return async (path, requestInit = {}) => fetcher(path, { ...requestInit, method });
173
177
  }
174
- let service = {};
178
+ const service = {};
175
179
  for (const method of HTTP_METHODS) {
176
180
  const lowerMethod = method.toLowerCase();
177
181
  service[lowerMethod] = appliedService(method);
178
182
  }
179
183
  return service;
180
184
  }
181
-
182
- // src/transforms.ts
183
- function words(str) {
184
- const matches = str.match(
185
- /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g
186
- );
187
- return matches ? Array.from(matches) : [str];
188
- }
189
- function toCamelCase(str) {
190
- const result = words(str).map((x) => x.slice(0, 1).toUpperCase() + x.slice(1).toLowerCase()).join("");
191
- return result.slice(0, 1).toLowerCase() + result.slice(1);
192
- }
193
- function toKebabCase(str) {
194
- return words(str).map((x) => x.toLowerCase()).join("-");
195
- }
196
- function toSnakeCase(str) {
197
- return words(str).map((x) => x.toLowerCase()).join("_");
198
- }
199
- function deepTransformKeys(obj, transform) {
200
- if (!["object", "array"].includes(typeOf(obj)))
201
- return obj;
202
- if (Array.isArray(obj)) {
203
- return obj.map((x) => deepTransformKeys(x, transform));
204
- }
205
- const res = {};
206
- for (const key in obj) {
207
- res[transform(key)] = deepTransformKeys(obj[key], transform);
208
- }
209
- return res;
210
- }
211
- function kebabToCamel(obj) {
212
- return deepTransformKeys(obj, toCamelCase);
213
- }
214
- function snakeToCamel(obj) {
215
- return deepTransformKeys(obj, toCamelCase);
216
- }
217
- function camelToSnake(obj) {
218
- return deepTransformKeys(obj, toSnakeCase);
219
- }
220
- function camelToKebab(obj) {
221
- return deepTransformKeys(obj, toKebabCase);
222
- }
223
- function snakeToKebab(obj) {
224
- return deepTransformKeys(obj, toKebabCase);
225
- }
226
- function kebabToSnake(obj) {
227
- return deepTransformKeys(obj, toSnakeCase);
228
- }
229
185
  // Annotate the CommonJS export names for ESM import in node:
230
186
  0 && (module.exports = {
231
187
  addQueryToURL,
232
- camelToKebab,
233
- camelToSnake,
234
188
  enhancedFetch,
235
189
  ensureStringBody,
236
- kebabToCamel,
237
- kebabToSnake,
238
190
  makeFetcher,
239
191
  makeGetApiURL,
240
192
  makeService,
241
193
  mergeHeaders,
242
194
  replaceURLParams,
243
- snakeToCamel,
244
- snakeToKebab,
245
195
  typedResponse
246
196
  });
package/dist/index.mjs CHANGED
@@ -12,18 +12,14 @@ var HTTP_METHODS = [
12
12
  ];
13
13
 
14
14
  // src/internals.ts
15
- function getJson(response) {
16
- return async (schema) => {
17
- const json = await response.json();
18
- return schema ? schema.parse(json) : json;
19
- };
20
- }
21
- function getText(response) {
22
- return async (schema) => {
23
- const text = await response.text();
24
- return schema ? schema.parse(text) : text;
25
- };
26
- }
15
+ var getJson = (response) => async (schema) => {
16
+ const json = await response.json();
17
+ return schema ? schema.parse(json) : json;
18
+ };
19
+ var getText = (response) => async (schema) => {
20
+ const text = await response.text();
21
+ return schema ? schema.parse(text) : text;
22
+ };
27
23
  function typeOf(t) {
28
24
  return Object.prototype.toString.call(t).replace(/^\[object (.+)\]$/, "$1").toLowerCase();
29
25
  }
@@ -84,13 +80,17 @@ function replaceURLParams(url, params) {
84
80
  }
85
81
 
86
82
  // src/api.ts
87
- function typedResponse(response) {
83
+ var identity = (value) => value;
84
+ function typedResponse(response, options) {
85
+ var _a, _b;
86
+ const getJsonFn = (_a = options == null ? void 0 : options.getJson) != null ? _a : getJson;
87
+ const getTextFn = (_b = options == null ? void 0 : options.getText) != null ? _b : getText;
88
88
  return new Proxy(response, {
89
89
  get(target, prop) {
90
90
  if (prop === "json")
91
- return getJson(target);
91
+ return getJsonFn(target);
92
92
  if (prop === "text")
93
- return getText(target);
93
+ return getTextFn(target);
94
94
  return target[prop];
95
95
  }
96
96
  });
@@ -112,94 +112,50 @@ async function enhancedFetch(url, requestInit) {
112
112
  const response = await fetch(fullURL, enhancedReqInit);
113
113
  return typedResponse(response);
114
114
  }
115
- function makeFetcher(baseURL, baseHeaders) {
115
+ function makeFetcher(baseURL, baseOptions = {}) {
116
116
  return async (path, requestInit = {}) => {
117
- var _a;
117
+ var _a, _b;
118
+ const { headers } = baseOptions;
119
+ const requestTransformer = (_a = baseOptions.requestTransformer) != null ? _a : identity;
120
+ const responseTransformer = (_b = baseOptions.responseTransformer) != null ? _b : identity;
121
+ const headerTransformer = async (ri) => {
122
+ var _a2;
123
+ return {
124
+ ...ri,
125
+ headers: mergeHeaders(
126
+ typeof headers === "function" ? await headers() : headers != null ? headers : {},
127
+ (_a2 = requestInit == null ? void 0 : requestInit.headers) != null ? _a2 : {}
128
+ )
129
+ };
130
+ };
118
131
  const url = makeGetApiURL(baseURL)(path);
119
- const response = await enhancedFetch(url, {
120
- ...requestInit,
121
- headers: mergeHeaders(
122
- typeof baseHeaders === "function" ? await baseHeaders() : baseHeaders != null ? baseHeaders : {},
123
- (_a = requestInit == null ? void 0 : requestInit.headers) != null ? _a : {}
124
- )
125
- });
126
- return response;
132
+ const response = await enhancedFetch(
133
+ url,
134
+ await headerTransformer(await requestTransformer(requestInit))
135
+ );
136
+ return responseTransformer(response);
127
137
  };
128
138
  }
129
- function makeService(baseURL, baseHeaders) {
130
- const fetcher = makeFetcher(baseURL, baseHeaders);
139
+ function makeService(baseURL, baseOptions) {
140
+ const fetcher = makeFetcher(baseURL, baseOptions);
131
141
  function appliedService(method) {
132
142
  return async (path, requestInit = {}) => fetcher(path, { ...requestInit, method });
133
143
  }
134
- let service = {};
144
+ const service = {};
135
145
  for (const method of HTTP_METHODS) {
136
146
  const lowerMethod = method.toLowerCase();
137
147
  service[lowerMethod] = appliedService(method);
138
148
  }
139
149
  return service;
140
150
  }
141
-
142
- // src/transforms.ts
143
- function words(str) {
144
- const matches = str.match(
145
- /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g
146
- );
147
- return matches ? Array.from(matches) : [str];
148
- }
149
- function toCamelCase(str) {
150
- const result = words(str).map((x) => x.slice(0, 1).toUpperCase() + x.slice(1).toLowerCase()).join("");
151
- return result.slice(0, 1).toLowerCase() + result.slice(1);
152
- }
153
- function toKebabCase(str) {
154
- return words(str).map((x) => x.toLowerCase()).join("-");
155
- }
156
- function toSnakeCase(str) {
157
- return words(str).map((x) => x.toLowerCase()).join("_");
158
- }
159
- function deepTransformKeys(obj, transform) {
160
- if (!["object", "array"].includes(typeOf(obj)))
161
- return obj;
162
- if (Array.isArray(obj)) {
163
- return obj.map((x) => deepTransformKeys(x, transform));
164
- }
165
- const res = {};
166
- for (const key in obj) {
167
- res[transform(key)] = deepTransformKeys(obj[key], transform);
168
- }
169
- return res;
170
- }
171
- function kebabToCamel(obj) {
172
- return deepTransformKeys(obj, toCamelCase);
173
- }
174
- function snakeToCamel(obj) {
175
- return deepTransformKeys(obj, toCamelCase);
176
- }
177
- function camelToSnake(obj) {
178
- return deepTransformKeys(obj, toSnakeCase);
179
- }
180
- function camelToKebab(obj) {
181
- return deepTransformKeys(obj, toKebabCase);
182
- }
183
- function snakeToKebab(obj) {
184
- return deepTransformKeys(obj, toKebabCase);
185
- }
186
- function kebabToSnake(obj) {
187
- return deepTransformKeys(obj, toSnakeCase);
188
- }
189
151
  export {
190
152
  addQueryToURL,
191
- camelToKebab,
192
- camelToSnake,
193
153
  enhancedFetch,
194
154
  ensureStringBody,
195
- kebabToCamel,
196
- kebabToSnake,
197
155
  makeFetcher,
198
156
  makeGetApiURL,
199
157
  makeService,
200
158
  mergeHeaders,
201
159
  replaceURLParams,
202
- snakeToCamel,
203
- snakeToKebab,
204
160
  typedResponse
205
161
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "make-service",
3
- "version": "1.1.0",
3
+ "version": "2.0.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",
@@ -15,11 +15,13 @@
15
15
  "test": "vitest run"
16
16
  },
17
17
  "devDependencies": {
18
+ "@typescript-eslint/eslint-plugin": "^6.3.0",
18
19
  "eslint": "latest",
19
- "jsdom": "^21.1.1",
20
+ "jsdom": "^22.1.0",
20
21
  "prettier": "latest",
22
+ "string-ts": "^0.4.1",
21
23
  "tsup": "^6.7.0",
22
- "typescript": "^5.0.4",
24
+ "typescript": "^5.1.3",
23
25
  "vitest": "latest",
24
26
  "zod": "latest"
25
27
  },