Skip to content

Commit 59bb4c7

Browse files
committed
refactor: use Proxy for ESM-safe Response wrapping
Replace static property wrapper with Proxy-based delegation to fix critical issues identified in automated reviews: - Fix stale bodyUsed property (P1 bug) - Ensure all Response properties remain live/dynamic - Automatically include all Response methods (including stream()) - Maintain correct 'this' binding for delegated methods - Preserve prototype chain and type checking The Proxy approach provides true delegation while preventing Response mutation, ensuring compatibility with both CommonJS and ESM environments. Addresses feedback on PR #1430
1 parent 839fb08 commit 59bb4c7

File tree

1 file changed

+37
-22
lines changed

1 file changed

+37
-22
lines changed

templates/base/http-clients/fetch-http-client.ejs

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -196,29 +196,44 @@ export class HttpClient<SecurityDataType = unknown> {
196196
body: typeof body === "undefined" || body === null ? null : payloadFormatter(body),
197197
}
198198
).then(async (response) => {
199-
// Create a wrapper object that doesn't mutate the Response
199+
// Create a Proxy that wraps the Response without mutating it
200200
// This ensures compatibility with ESM environments where Response is read-only
201-
const r = {
202-
data: (null as unknown) as T,
203-
error: (null as unknown) as E,
204-
// Delegate Response properties
205-
ok: response.ok,
206-
status: response.status,
207-
statusText: response.statusText,
208-
headers: response.headers,
209-
url: response.url,
210-
redirected: response.redirected,
211-
type: response.type,
212-
body: response.body,
213-
bodyUsed: response.bodyUsed,
214-
// Delegate Response methods
215-
arrayBuffer: () => response.arrayBuffer(),
216-
blob: () => response.blob(),
217-
clone: () => response.clone(),
218-
formData: () => response.formData(),
219-
json: () => response.json(),
220-
text: () => response.text(),
221-
} as HttpResponse<T, E>;
201+
// while maintaining all Response properties and methods as live/dynamic values
202+
const r = new Proxy(response, {
203+
get(target, prop) {
204+
// Custom properties for our API wrapper
205+
if (prop === 'data') {
206+
return target._data !== undefined ? target._data : (null as unknown) as T;
207+
}
208+
if (prop === 'error') {
209+
return target._error !== undefined ? target._error : (null as unknown) as E;
210+
}
211+
212+
// Delegate everything else to the actual Response object
213+
const value = target[prop];
214+
215+
// Bind methods to the original response to maintain correct 'this' context
216+
if (typeof value === 'function') {
217+
return value.bind(target);
218+
}
219+
220+
return value;
221+
},
222+
set(target, prop, value) {
223+
// Allow setting data and error properties
224+
if (prop === 'data') {
225+
target._data = value;
226+
return true;
227+
}
228+
if (prop === 'error') {
229+
target._error = value;
230+
return true;
231+
}
232+
233+
// Prevent mutation of Response properties (ESM safety)
234+
return false;
235+
}
236+
}) as HttpResponse<T, E>;
222237

223238
const responseToParse = responseFormat ? response.clone() : response;
224239
const data = !responseFormat ? r : await responseToParse[responseFormat]()

0 commit comments

Comments
 (0)