-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconvenience.ts
More file actions
192 lines (176 loc) · 5.53 KB
/
convenience.ts
File metadata and controls
192 lines (176 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/**
* @fileoverview Thin convenience wrappers over `httpRequest` —
* `httpJson` and `httpText`. Both set sensible default `Accept`
* (and `Content-Type` when a body is present) headers, then delegate
* to `httpRequest`. User-supplied headers always win in the merge.
*
* Each wrapper throws `HttpResponseError` on non-2xx responses
* (parallel to the `throwOnError` mode of `httpRequest`) so callers
* never have to inspect `.ok` themselves.
*/
import { ErrorCtor } from '../primordials/error'
import { httpRequest } from './request'
import { HttpResponseError } from './types'
import type { HttpRequestOptions } from './types'
/**
* Perform an HTTP request and parse JSON response.
* Convenience wrapper around `httpRequest` for JSON API calls.
* Automatically sets appropriate headers for JSON requests:
* - `Accept: application/json` (always)
* - `Content-Type: application/json` (when body is present)
* User-provided headers override these defaults.
*
* @template T - Expected JSON response type (defaults to `unknown`)
* @param url - The URL to request (must start with http:// or https://)
* @param options - Request configuration options
* @returns Promise resolving to parsed JSON data
* @throws {Error} When request fails, response is not ok (status < 200 or >= 300), or JSON parsing fails
*
* @example
* ```ts
* // Simple JSON GET (automatically sets Accept: application/json)
* const data = await httpJson('https://api.example.com/data')
* console.log(data)
*
* // With type safety
* interface User { id: number; name: string; email: string }
* const user = await httpJson<User>('https://api.example.com/user/123')
* console.log(user.name, user.email)
*
* // POST with JSON body (automatically sets Content-Type: application/json)
* const result = await httpJson('https://api.example.com/users', {
* method: 'POST',
* body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })
* })
*
* // With custom headers and retries
* const data = await httpJson('https://api.example.com/data', {
* headers: {
* 'Authorization': 'Bearer token123'
* },
* retries: 3,
* retryDelay: 1000
* })
* ```
*/
export async function httpJson<T = unknown>(
url: string,
options?: HttpRequestOptions | undefined,
): Promise<T> {
const {
body,
headers = {},
...restOptions
} = {
__proto__: null,
...options,
} as HttpRequestOptions
// Set default headers for JSON requests
const defaultHeaders: Record<string, string> = {
Accept: 'application/json',
}
// Add Content-Type when body is present
if (body) {
defaultHeaders['Content-Type'] = 'application/json'
}
// Merge headers: user headers override defaults
const mergedHeaders = {
...defaultHeaders,
...headers,
}
// httpRequest may throw HttpResponseError when throwOnError is enabled.
// Let it propagate — don't mask it with a generic Error.
const response = await httpRequest(url, {
body,
headers: mergedHeaders,
...restOptions,
})
if (!response.ok) {
throw new HttpResponseError(response)
}
try {
return response.json<T>()
} catch (e) {
throw new ErrorCtor('Failed to parse JSON response', { cause: e })
}
}
/**
* Perform an HTTP request and return text response.
* Convenience wrapper around `httpRequest` for fetching text content.
* Automatically sets appropriate headers for text requests:
* - `Accept: text/plain` (always)
* - `Content-Type: text/plain` (when body is present)
* User-provided headers override these defaults.
*
* @param url - The URL to request (must start with http:// or https://)
* @param options - Request configuration options
* @returns Promise resolving to response body as UTF-8 string
* @throws {Error} When request fails or response is not ok (status < 200 or >= 300)
*
* @example
* ```ts
* // Fetch HTML (automatically sets Accept: text/plain)
* const html = await httpText('https://example.com')
* console.log(html.includes('<!DOCTYPE html>'))
*
* // Fetch plain text
* const text = await httpText('https://example.com/file.txt')
* console.log(text)
*
* // POST with text body (automatically sets Content-Type: text/plain)
* const result = await httpText('https://example.com/api', {
* method: 'POST',
* body: 'raw text data'
* })
*
* // With custom headers (override defaults)
* const text = await httpText('https://example.com/data.txt', {
* headers: {
* 'Authorization': 'Bearer token123',
* 'Accept': 'text/html' // Override default Accept header
* }
* })
*
* // With timeout
* const text = await httpText('https://example.com/large-file.txt', {
* timeout: 60000 // 1 minute
* })
* ```
*/
export async function httpText(
url: string,
options?: HttpRequestOptions | undefined,
): Promise<string> {
const {
body,
headers = {},
...restOptions
} = {
__proto__: null,
...options,
} as HttpRequestOptions
// Set default headers for text requests
const defaultHeaders: Record<string, string> = {
Accept: 'text/plain',
}
// Add Content-Type when body is present
if (body) {
defaultHeaders['Content-Type'] = 'text/plain'
}
// Merge headers: user headers override defaults
const mergedHeaders = {
...defaultHeaders,
...headers,
}
// httpRequest may throw HttpResponseError when throwOnError is enabled.
// Let it propagate — don't mask it with a generic Error.
const response = await httpRequest(url, {
body,
headers: mergedHeaders,
...restOptions,
})
if (!response.ok) {
throw new HttpResponseError(response)
}
return response.text()
}