From 94418630fa4a8e0f69b703e43d7b5ebe6a21268d Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Thu, 1 Aug 2024 16:31:58 -0400 Subject: [PATCH 1/4] Add third parameter with response to trace and allow it to be asynchronous --- README.md | 3 +++ src/api.test.ts | 2 ++ src/api.ts | 4 ++-- src/types.ts | 6 +++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc953f4..bb6144b 100644 --- a/README.md +++ b/README.md @@ -408,8 +408,11 @@ await enhancedFetch("https://example.com/api/users/:role", { // method: 'POST', // body: '{"some":{"object":{"as":{"body":{}}}}}', // } +// Response {} ``` +The `trace` function can also return a `Promise` in order to send traces to an external service or database. + ## typedResponse 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. diff --git a/src/api.test.ts b/src/api.test.ts index 75840f0..1ab64bc 100644 --- a/src/api.test.ts +++ b/src/api.test.ts @@ -198,6 +198,7 @@ describe('enhancedFetch', () => { method: 'POST', body: `{"id":1,"name":{"first":"John","last":"Doe"}}`, }, + expect.any(Response), ) }) @@ -358,6 +359,7 @@ describe('makeFetcher', () => { body: `{"id":1,"name":{"first":"John","last":"Doe"}}`, headers: new Headers(), }, + expect.any(Response), ) }) }) diff --git a/src/api.ts b/src/api.ts index 5501291..7a65fb8 100644 --- a/src/api.ts +++ b/src/api.ts @@ -67,7 +67,7 @@ function typedResponse( * @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. * @param requestInit.body the body of the request. It will be automatically stringified so you can send a JSON-like object * @param requestInit.query the query parameters to be added to the URL - * @param requestInit.trace a function that receives the URL and the requestInit and can be used to log the request + * @param requestInit.trace a function that receives the URL, the requestInit and a clone of the response in order to log or troubleshoot the request * @returns a Response with typed json and text methods * @example const response = await fetch("https://example.com/api/users"); * const users = await response.json(userSchema); @@ -85,8 +85,8 @@ async function enhancedFetch( const fullURL = addQueryToURL(withParams, query) const enhancedReqInit = { ...reqInit, body } - trace?.(fullURL, enhancedReqInit) const response = await fetch(fullURL, enhancedReqInit) + await trace?.(fullURL, enhancedReqInit, response.clone()) return typedResponse(response) } diff --git a/src/types.ts b/src/types.ts index bf41b59..e46763c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,7 +28,11 @@ type EnhancedRequestInit = Omit & { body?: JSONValue | BodyInit | null query?: SearchParams params?: PathParams - trace?: (...args: Parameters) => void + trace?: ( + input: Parameters[0], + init?: Parameters[1], + response?: Awaited>, + ) => void | Promise } type ServiceRequestInit = Omit, 'method'> From 499e6cfc0a2ef779988edd43dda5b83f43292f57 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Thu, 1 Aug 2024 16:56:56 -0400 Subject: [PATCH 2/4] These parameters are always passed, so having them mandatory in our type make it easier to write callbacks without having to check optional values. It is backwards compatible, --- src/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types.ts b/src/types.ts index e46763c..37ed16a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -30,8 +30,8 @@ type EnhancedRequestInit = Omit & { params?: PathParams trace?: ( input: Parameters[0], - init?: Parameters[1], - response?: Awaited>, + init: Parameters[1], + response: Awaited>, ) => void | Promise } From 38a9d641228891a9f85fee5f09d68db2a5ca1376 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Thu, 1 Aug 2024 17:05:33 -0400 Subject: [PATCH 3/4] We need a more precise type for the fullURL since the value we pass is not necessarly what fetch receives. The same applies to the second parameter. --- src/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types.ts b/src/types.ts index 37ed16a..a5ca95f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -29,8 +29,8 @@ type EnhancedRequestInit = Omit & { query?: SearchParams params?: PathParams trace?: ( - input: Parameters[0], - init: Parameters[1], + fullUrl: string | URL, + init: EnhancedRequestInit, response: Awaited>, ) => void | Promise } From 4cacee3c6ad53e3d87d70516624583033cdc3137 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Fri, 2 Aug 2024 09:35:49 -0400 Subject: [PATCH 4/4] Use a TypedResponse for the trace function --- src/api.ts | 4 ++-- src/types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api.ts b/src/api.ts index 7a65fb8..01ae6c8 100644 --- a/src/api.ts +++ b/src/api.ts @@ -83,11 +83,11 @@ async function enhancedFetch( const body = ensureStringBody(reqInit.body) const withParams = replaceURLParams(url, reqInit.params ?? ({} as never)) const fullURL = addQueryToURL(withParams, query) - const enhancedReqInit = { ...reqInit, body } + const response = await fetch(fullURL, enhancedReqInit) - await trace?.(fullURL, enhancedReqInit, response.clone()) + await trace?.(fullURL, enhancedReqInit, typedResponse(response.clone())) return typedResponse(response) } diff --git a/src/types.ts b/src/types.ts index a5ca95f..ebb598b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -31,7 +31,7 @@ type EnhancedRequestInit = Omit & { trace?: ( fullUrl: string | URL, init: EnhancedRequestInit, - response: Awaited>, + response: TypedResponse, ) => void | Promise }