Skip to content

Commit 5aa3cc9

Browse files
authored
net - feat: adding in get response (#1294)
1 parent bd1ae8b commit 5aa3cc9

File tree

4 files changed

+155
-25
lines changed

4 files changed

+155
-25
lines changed

packages/net/src/fetch.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,43 @@ export async function fetch(
6363
}) as UndiciResponse;
6464
}
6565

66+
export type GetResponse<T = unknown> = {
67+
data: T;
68+
response: UndiciResponse;
69+
};
70+
6671
/**
6772
* Perform a GET request to a URL with optional request options.
6873
* @param {string} url The URL to fetch.
6974
* @param {Omit<FetchOptions, 'method'>} options Optional request options. The `cache` property is required.
70-
* @returns {Promise<UndiciResponse>} The response from the fetch.
75+
* @returns {Promise<GetResponse<T>>} The typed data and response from the fetch.
7176
*/
72-
export async function get(
77+
export async function get<T = unknown>(
7378
url: string,
7479
options: Omit<FetchOptions, "method">,
75-
): Promise<UndiciResponse> {
76-
return fetch(url, { ...options, method: "GET" });
80+
): Promise<GetResponse<T>> {
81+
const response = await fetch(url, { ...options, method: "GET" });
82+
const text = await response.text();
83+
let data: T;
84+
85+
try {
86+
data = JSON.parse(text) as T;
87+
} catch {
88+
// If not JSON, return as is
89+
data = text as T;
90+
}
91+
92+
// Create a new response with the text already consumed
93+
const newResponse = new Response(text, {
94+
status: response.status,
95+
statusText: response.statusText,
96+
headers: response.headers as HeadersInit,
97+
}) as UndiciResponse;
98+
99+
return {
100+
data,
101+
response: newResponse,
102+
};
77103
}
78104

79105
export type Response = UndiciResponse;

packages/net/src/index.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
type FetchRequestInit,
66
type Response as FetchResponse,
77
fetch,
8+
type GetResponse,
89
} from "./fetch.js";
910

1011
export type CacheableNetOptions = {
@@ -55,13 +56,34 @@ export class CacheableNet extends Hookified {
5556
* Perform a GET request to a URL with optional request options. Will use the cache that is already set in the instance.
5657
* @param {string} url The URL to fetch.
5758
* @param {Omit<FetchRequestInit, 'method'>} options Optional request options (method will be set to GET).
58-
* @returns {Promise<FetchResponse>} The response from the fetch.
59+
* @returns {Promise<GetResponse<T>>} The typed data and response from the fetch.
5960
*/
60-
public async get(
61+
public async get<T = unknown>(
6162
url: string,
6263
options?: Omit<FetchRequestInit, "method">,
63-
): Promise<FetchResponse> {
64-
return this.fetch(url, { ...options, method: "GET" });
64+
): Promise<GetResponse<T>> {
65+
const response = await this.fetch(url, { ...options, method: "GET" });
66+
const text = await response.text();
67+
let data: T;
68+
69+
try {
70+
data = JSON.parse(text) as T;
71+
} catch {
72+
// If not JSON, return as is
73+
data = text as T;
74+
}
75+
76+
// Create a new response with the text already consumed
77+
const newResponse = new Response(text, {
78+
status: response.status,
79+
statusText: response.statusText,
80+
headers: response.headers as HeadersInit,
81+
}) as FetchResponse;
82+
83+
return {
84+
data,
85+
response: newResponse,
86+
};
6587
}
6688
}
6789

@@ -70,6 +92,7 @@ export {
7092
type FetchOptions,
7193
type FetchRequestInit,
7294
fetch,
95+
type GetResponse,
7396
get,
7497
type Response as FetchResponse,
7598
} from "./fetch.js";

packages/net/test/fetch.test.ts

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,24 @@ describe("Fetch", () => {
2121
testTimeout,
2222
);
2323

24+
test(
25+
"should fetch data without method (defaults to GET)",
26+
async () => {
27+
const url = `${testUrl}/get`;
28+
const cache = new Cacheable({ stats: true });
29+
const options: FetchOptions = {
30+
cache,
31+
} as FetchOptions;
32+
const response = await fetch(url, options);
33+
expect(response).toBeDefined();
34+
// Make second request to verify caching with default GET method
35+
const response2 = await fetch(url, options);
36+
expect(response2).toBeDefined();
37+
expect(cache.stats.hits).toBe(1);
38+
},
39+
testTimeout,
40+
);
41+
2442
test(
2543
"should fetch data successfully from cache",
2644
async () => {
@@ -66,9 +84,11 @@ describe("Fetch", () => {
6684
const options = {
6785
cache: new Cacheable(),
6886
};
69-
const response = await get(url, options);
70-
expect(response).toBeDefined();
71-
expect(response.status).toBe(200);
87+
const result = await get(url, options);
88+
expect(result).toBeDefined();
89+
expect(result.data).toBeDefined();
90+
expect(result.response).toBeDefined();
91+
expect(result.response.status).toBe(200);
7292
},
7393
testTimeout,
7494
);
@@ -81,16 +101,36 @@ describe("Fetch", () => {
81101
const options = {
82102
cache,
83103
};
84-
const response = await get(url, options);
85-
const response2 = await get(url, options);
86-
expect(response).toBeDefined();
87-
expect(response2).toBeDefined();
104+
const result1 = await get(url, options);
105+
const result2 = await get(url, options);
106+
expect(result1).toBeDefined();
107+
expect(result2).toBeDefined();
88108
expect(cache.stats).toBeDefined();
89109
expect(cache.stats.hits).toBe(1);
90-
// Verify that both responses have the same text content
91-
const text1 = await response.text();
92-
const text2 = await response2.text();
93-
expect(text1).toEqual(text2);
110+
// Verify that both responses have the same data
111+
expect(result1.data).toEqual(result2.data);
112+
// Verify response objects are valid
113+
expect(result1.response.status).toBe(200);
114+
expect(result2.response.status).toBe(200);
115+
},
116+
testTimeout,
117+
);
118+
119+
test(
120+
"should handle non-JSON response in get helper",
121+
async () => {
122+
const cache = new Cacheable();
123+
// Mock a text response by using a URL that returns plain text
124+
const mockTextUrl = "https://httpbin.org/robots.txt";
125+
const options = {
126+
cache,
127+
};
128+
const result = await get(mockTextUrl, options);
129+
expect(result).toBeDefined();
130+
expect(result.data).toBeDefined();
131+
expect(typeof result.data).toBe("string");
132+
expect(result.response).toBeDefined();
133+
expect(result.response.status).toBe(200);
94134
},
95135
testTimeout,
96136
);

packages/net/test/index.test.ts

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,11 @@ describe("Cacheable Net", () => {
8686
async () => {
8787
const net = new Net();
8888
const url = `${testUrl}/get`;
89-
const response = await net.get(url);
90-
expect(response).toBeDefined();
91-
expect(response.status).toBe(200);
89+
const result = await net.get(url);
90+
expect(result).toBeDefined();
91+
expect(result.data).toBeDefined();
92+
expect(result.response).toBeDefined();
93+
expect(result.response.status).toBe(200);
9294
},
9395
testTimeout,
9496
);
@@ -100,9 +102,48 @@ describe("Cacheable Net", () => {
100102
const options = {
101103
cache: new Cacheable(),
102104
};
103-
const response = await get(url, options);
104-
expect(response).toBeDefined();
105-
expect(response.status).toBe(200);
105+
const result = await get(url, options);
106+
expect(result).toBeDefined();
107+
expect(result.data).toBeDefined();
108+
expect(result.response).toBeDefined();
109+
expect(result.response.status).toBe(200);
110+
},
111+
testTimeout,
112+
);
113+
114+
test(
115+
"should fetch typed data using get with generics",
116+
async () => {
117+
interface TestData {
118+
method: string;
119+
url: string;
120+
}
121+
const net = new Net();
122+
const url = `${testUrl}/get`;
123+
const result = await net.get<TestData>(url);
124+
expect(result).toBeDefined();
125+
expect(result.data).toBeDefined();
126+
expect(result.response).toBeDefined();
127+
// TypeScript will ensure result.data has the TestData type
128+
if (typeof result.data === "object" && result.data !== null) {
129+
expect(result.data).toHaveProperty("method");
130+
}
131+
},
132+
testTimeout,
133+
);
134+
135+
test(
136+
"should handle non-JSON response in CacheableNet get method",
137+
async () => {
138+
const net = new Net();
139+
// Use a URL that returns plain text
140+
const mockTextUrl = "https://httpbin.org/robots.txt";
141+
const result = await net.get(mockTextUrl);
142+
expect(result).toBeDefined();
143+
expect(result.data).toBeDefined();
144+
expect(typeof result.data).toBe("string");
145+
expect(result.response).toBeDefined();
146+
expect(result.response.status).toBe(200);
106147
},
107148
testTimeout,
108149
);

0 commit comments

Comments
 (0)