@atproto-labs/fetch 0.0.1 → 0.1.1
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/CHANGELOG.md +16 -4
- package/dist/fetch-error.d.ts +1 -12
- package/dist/fetch-error.d.ts.map +1 -1
- package/dist/fetch-error.js +24 -39
- package/dist/fetch-error.js.map +1 -1
- package/dist/fetch-request.d.ts +22 -5
- package/dist/fetch-request.d.ts.map +1 -1
- package/dist/fetch-request.js +55 -18
- package/dist/fetch-request.js.map +1 -1
- package/dist/fetch-response.d.ts +30 -12
- package/dist/fetch-response.d.ts.map +1 -1
- package/dist/fetch-response.js +134 -81
- package/dist/fetch-response.js.map +1 -1
- package/dist/fetch-wrap.d.ts +47 -9
- package/dist/fetch-wrap.d.ts.map +1 -1
- package/dist/fetch-wrap.js +112 -61
- package/dist/fetch-wrap.js.map +1 -1
- package/dist/fetch.d.ts +8 -1
- package/dist/fetch.d.ts.map +1 -1
- package/dist/fetch.js +13 -0
- package/dist/fetch.js.map +1 -1
- package/dist/transformed-response.d.ts.map +1 -1
- package/dist/transformed-response.js +5 -2
- package/dist/transformed-response.js.map +1 -1
- package/dist/util.d.ts +46 -14
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +121 -24
- package/dist/util.js.map +1 -1
- package/package.json +6 -5
- package/src/fetch-error.ts +26 -44
- package/src/fetch-request.ts +81 -24
- package/src/fetch-response.ts +177 -111
- package/src/fetch-wrap.ts +139 -90
- package/src/fetch.ts +38 -3
- package/src/transformed-response.ts +5 -2
- package/src/util.ts +142 -25
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +2 -6
package/src/util.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
// TODO: Move to a shared package ?
|
|
1
|
+
// @TODO: Move some of these to a shared package ?
|
|
2
2
|
|
|
3
3
|
export type JsonScalar = string | number | boolean | null
|
|
4
4
|
export type Json = JsonScalar | Json[] | { [key: string]: undefined | Json }
|
|
5
5
|
export type JsonObject = { [key: string]: Json }
|
|
6
6
|
export type JsonArray = Json[]
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
export type ThisParameterOverride<
|
|
9
|
+
C,
|
|
10
|
+
Fn extends (...a: any) => any,
|
|
11
|
+
> = Fn extends (...args: infer P) => infer R
|
|
12
|
+
? ((this: C, ...args: P) => R) & {
|
|
13
|
+
bind(context: C): (...args: P) => R
|
|
14
|
+
}
|
|
15
|
+
: never
|
|
13
16
|
|
|
14
17
|
export function isIp(hostname: string) {
|
|
15
18
|
// IPv4
|
|
@@ -21,10 +24,8 @@ export function isIp(hostname: string) {
|
|
|
21
24
|
return false
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
// TODO: Move to a shared package ?
|
|
25
|
-
|
|
26
27
|
const plainObjectProto = Object.prototype
|
|
27
|
-
export const ifObject = <V>(v
|
|
28
|
+
export const ifObject = <V>(v: V) => {
|
|
28
29
|
if (typeof v === 'object' && v != null && !Array.isArray(v)) {
|
|
29
30
|
const proto = Object.getPrototypeOf(v)
|
|
30
31
|
if (proto === null || proto === plainObjectProto) {
|
|
@@ -33,27 +34,143 @@ export const ifObject = <V>(v?: V) => {
|
|
|
33
34
|
? never
|
|
34
35
|
: V extends Json
|
|
35
36
|
? V
|
|
36
|
-
: // Plain object are (mostly) safe to access
|
|
37
|
-
|
|
37
|
+
: // Plain object are (mostly) safe to access using a string index
|
|
38
|
+
Record<string, unknown>
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
return undefined
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
export const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
export const ifString = <V>(v: V) => (typeof v === 'string' ? v : undefined)
|
|
46
|
+
|
|
47
|
+
export class MaxBytesTransformStream extends TransformStream<
|
|
48
|
+
Uint8Array,
|
|
49
|
+
Uint8Array
|
|
50
|
+
> {
|
|
51
|
+
constructor(maxBytes: number) {
|
|
52
|
+
// Note: negation accounts for invalid value types (NaN, non numbers)
|
|
53
|
+
if (!(maxBytes >= 0)) {
|
|
54
|
+
throw new TypeError('maxBytes must be a non-negative number')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let bytesRead = 0
|
|
58
|
+
|
|
59
|
+
super({
|
|
60
|
+
transform: (
|
|
61
|
+
chunk: Uint8Array,
|
|
62
|
+
ctrl: TransformStreamDefaultController<Uint8Array>,
|
|
63
|
+
) => {
|
|
64
|
+
if ((bytesRead += chunk.length) <= maxBytes) {
|
|
65
|
+
ctrl.enqueue(chunk)
|
|
66
|
+
} else {
|
|
67
|
+
ctrl.error(new Error('Response too large'))
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
})
|
|
54
71
|
}
|
|
55
72
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
export
|
|
59
|
-
|
|
73
|
+
|
|
74
|
+
const LINE_BREAK = /\r?\n/g
|
|
75
|
+
export function padLines(input: string, pad: string) {
|
|
76
|
+
if (!input) return input
|
|
77
|
+
return pad + input.replace(LINE_BREAK, `$&${pad}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param [onCancellationError] - Callback that will trigger to asynchronously
|
|
82
|
+
* handle any error that occurs while cancelling the response body. Providing
|
|
83
|
+
* this will speed up the process and avoid potential deadlocks. Defaults to
|
|
84
|
+
* awaiting the cancellation operation. use `"log"` to log the error.
|
|
85
|
+
* @see {@link https://undici.nodejs.org/#/?id=garbage-collection}
|
|
86
|
+
* @note awaiting this function's result, when no `onCancellationError` is
|
|
87
|
+
* provided, might result in a dead lock. Indeed, if the response was cloned(),
|
|
88
|
+
* the response.body.cancel() method will not resolve until the other response's
|
|
89
|
+
* body is consumed/cancelled.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* // Make sure response was not cloned, or that every cloned response was
|
|
94
|
+
* // consumed/cancelled before awaiting this function's result.
|
|
95
|
+
* await cancelBody(response)
|
|
96
|
+
* ```
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* await cancelBody(response, (err) => {
|
|
100
|
+
* // No biggie, let's just log the error
|
|
101
|
+
* console.warn('Failed to cancel response body', err)
|
|
102
|
+
* })
|
|
103
|
+
* ```
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* // Will generate an "unhandledRejection" if an error occurs while cancelling
|
|
107
|
+
* // the response body. This will likely crash the process.
|
|
108
|
+
* await cancelBody(response, (err) => { throw err })
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export async function cancelBody(
|
|
112
|
+
body: Body,
|
|
113
|
+
onCancellationError?: 'log' | ((err: unknown) => void),
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
if (
|
|
116
|
+
body.body &&
|
|
117
|
+
!body.bodyUsed &&
|
|
118
|
+
!body.body.locked &&
|
|
119
|
+
// Support for alternative fetch implementations
|
|
120
|
+
typeof body.body.cancel === 'function'
|
|
121
|
+
) {
|
|
122
|
+
if (typeof onCancellationError === 'function') {
|
|
123
|
+
void body.body.cancel().catch(onCancellationError)
|
|
124
|
+
} else if (onCancellationError === 'log') {
|
|
125
|
+
void body.body.cancel().catch(logCancellationError)
|
|
126
|
+
} else {
|
|
127
|
+
await body.body.cancel()
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function logCancellationError(err: unknown): void {
|
|
133
|
+
console.warn('Failed to cancel response body', err)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function stringifyMessage(input: Body & { headers: Headers }) {
|
|
137
|
+
try {
|
|
138
|
+
const headers = stringifyHeaders(input.headers)
|
|
139
|
+
const payload = await stringifyBody(input)
|
|
140
|
+
return headers && payload ? `${headers}\n${payload}` : headers || payload
|
|
141
|
+
} finally {
|
|
142
|
+
void cancelBody(input, 'log')
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function stringifyHeaders(headers: Headers) {
|
|
147
|
+
return Array.from(headers)
|
|
148
|
+
.map(([name, value]) => `${name}: ${value}`)
|
|
149
|
+
.join('\n')
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function stringifyBody(body: Body) {
|
|
153
|
+
try {
|
|
154
|
+
const blob = await body.blob()
|
|
155
|
+
if (blob.type?.startsWith('text/')) {
|
|
156
|
+
const text = await blob.text()
|
|
157
|
+
return JSON.stringify(text)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (/application\/(?:\w+\+)?json/.test(blob.type)) {
|
|
161
|
+
const text = await blob.text()
|
|
162
|
+
return text.includes('\n') ? JSON.stringify(JSON.parse(text)) : text
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return `[Body size: ${blob.size}, type: ${JSON.stringify(blob.type)} ]`
|
|
166
|
+
} catch {
|
|
167
|
+
return '[Body could not be read]'
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const extractUrl = (input: Request | string | URL) =>
|
|
172
|
+
typeof input === 'string'
|
|
173
|
+
? new URL(input)
|
|
174
|
+
: input instanceof URL
|
|
175
|
+
? input
|
|
176
|
+
: new URL(input.url)
|