make-fetch 3.1.2 → 3.2.0
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/index.js +118 -8
- package/package.json +9 -3
- package/test.js +33 -0
- package/tsconfig.json +15 -0
package/index.js
CHANGED
|
@@ -1,29 +1,71 @@
|
|
|
1
|
-
|
|
1
|
+
/** @typedef {ConstructorParameters<typeof globalThis.Response>[0] | AsyncIterable<string>} Body */
|
|
2
|
+
/** @typedef {Response|ResponseInit & {body?: Body}} ResponseLike */
|
|
3
|
+
/** @typedef {(request: Request) => ResponseLike|Promise<ResponseLike> } Handler */
|
|
4
|
+
/** @typedef {(error: Error, request: Request) => ResponseLike } ErrorHandler */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {object} Route
|
|
8
|
+
* @property {string} protocol
|
|
9
|
+
* @property {string} method
|
|
10
|
+
* @property {string} hostname
|
|
11
|
+
* @property {string[]} segments
|
|
12
|
+
* @property {Handler} handler
|
|
13
|
+
*/
|
|
2
14
|
|
|
15
|
+
/** @typedef {'pathname' | 'hostname' | 'protocol' | 'method'} MatchProperty */
|
|
16
|
+
|
|
17
|
+
/** @type {MatchProperty[]} */
|
|
3
18
|
const MATCH_ORDER = ['method', 'protocol', 'hostname', 'pathname']
|
|
4
19
|
|
|
20
|
+
export const WILDCARD = '*'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {Handler} handler
|
|
24
|
+
* @param {object} [options]
|
|
25
|
+
* @param {typeof globalThis.Request} [options.Request]
|
|
26
|
+
* @param {typeof globalThis.Response} [options.Response]
|
|
27
|
+
* @returns {typeof fetch}
|
|
28
|
+
*/
|
|
5
29
|
export function makeFetch (handler, {
|
|
6
30
|
Request = globalThis.Request,
|
|
7
31
|
Response = globalThis.Response
|
|
8
32
|
} = {}) {
|
|
9
|
-
return
|
|
10
|
-
|
|
33
|
+
return fetch
|
|
34
|
+
|
|
35
|
+
/** @type {typeof globalThis.fetch} */
|
|
36
|
+
async function fetch (...requestOptions) {
|
|
37
|
+
const isAlreadyRequest = requestOptions[0] instanceof Request
|
|
38
|
+
const request = isAlreadyRequest ? /** @type {Request} */(requestOptions[0]) : new Request(...requestOptions)
|
|
11
39
|
|
|
12
40
|
const { body = null, ...responseOptions } = await handler(request)
|
|
13
41
|
|
|
42
|
+
// @ts-ignore You can use an AsyncIterable of strings for body
|
|
14
43
|
const response = new Response(body, responseOptions)
|
|
15
44
|
|
|
16
45
|
return response
|
|
17
46
|
}
|
|
18
47
|
}
|
|
19
48
|
|
|
49
|
+
/**
|
|
50
|
+
*
|
|
51
|
+
* @param {object} [options]
|
|
52
|
+
* @param {Handler} [options.onNotFound]
|
|
53
|
+
* @param {ErrorHandler} [options.onError]
|
|
54
|
+
* @returns
|
|
55
|
+
*/
|
|
20
56
|
export function makeRoutedFetch ({
|
|
21
57
|
onNotFound = DEFAULT_NOT_FOUND,
|
|
22
58
|
onError = DEFAULT_ON_ERROR
|
|
23
59
|
} = {}) {
|
|
24
60
|
const router = new Router()
|
|
25
61
|
|
|
26
|
-
const fetch = makeFetch(
|
|
62
|
+
const fetch = makeFetch(handler)
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {Request} request
|
|
66
|
+
* @returns {Promise<ResponseLike>}
|
|
67
|
+
*/
|
|
68
|
+
async function handler (request) {
|
|
27
69
|
const route = router.route(request)
|
|
28
70
|
if (!route) {
|
|
29
71
|
return onNotFound(request)
|
|
@@ -32,9 +74,10 @@ export function makeRoutedFetch ({
|
|
|
32
74
|
const response = await route.handler(request)
|
|
33
75
|
return response
|
|
34
76
|
} catch (e) {
|
|
35
|
-
|
|
77
|
+
// Typescript is annoying
|
|
78
|
+
return await onError(/** @type {Error} */ (e), request)
|
|
36
79
|
}
|
|
37
|
-
}
|
|
80
|
+
}
|
|
38
81
|
|
|
39
82
|
return { fetch, router }
|
|
40
83
|
}
|
|
@@ -43,6 +86,7 @@ export function DEFAULT_NOT_FOUND () {
|
|
|
43
86
|
return { status: 404, statusText: 'Invalid URL' }
|
|
44
87
|
}
|
|
45
88
|
|
|
89
|
+
/** @type {ErrorHandler} */
|
|
46
90
|
export function DEFAULT_ON_ERROR (e) {
|
|
47
91
|
return {
|
|
48
92
|
status: 500,
|
|
@@ -55,37 +99,86 @@ export function DEFAULT_ON_ERROR (e) {
|
|
|
55
99
|
|
|
56
100
|
export class Router {
|
|
57
101
|
constructor () {
|
|
58
|
-
this.routes = []
|
|
102
|
+
this.routes = /** @type {Route[]} */ ([])
|
|
59
103
|
}
|
|
60
104
|
|
|
105
|
+
/**
|
|
106
|
+
*
|
|
107
|
+
* @param {string} url
|
|
108
|
+
* @param {Handler} handler
|
|
109
|
+
* @returns {Router}
|
|
110
|
+
*/
|
|
61
111
|
get (url, handler) {
|
|
62
112
|
return this.add('GET', url, handler)
|
|
63
113
|
}
|
|
64
114
|
|
|
115
|
+
/**
|
|
116
|
+
*
|
|
117
|
+
* @param {string} url
|
|
118
|
+
* @param {Handler} handler
|
|
119
|
+
* @returns {Router}
|
|
120
|
+
*/
|
|
65
121
|
head (url, handler) {
|
|
66
122
|
return this.add('HEAD', url, handler)
|
|
67
123
|
}
|
|
68
124
|
|
|
125
|
+
/**
|
|
126
|
+
*
|
|
127
|
+
* @param {string} url
|
|
128
|
+
* @param {Handler} handler
|
|
129
|
+
* @returns {Router}
|
|
130
|
+
*/
|
|
69
131
|
post (url, handler) {
|
|
70
132
|
return this.add('POST', url, handler)
|
|
71
133
|
}
|
|
72
134
|
|
|
135
|
+
/**
|
|
136
|
+
*
|
|
137
|
+
* @param {string} url
|
|
138
|
+
* @param {Handler} handler
|
|
139
|
+
* @returns {Router}
|
|
140
|
+
*/
|
|
73
141
|
put (url, handler) {
|
|
74
142
|
return this.add('PUT', url, handler)
|
|
75
143
|
}
|
|
76
144
|
|
|
145
|
+
/**
|
|
146
|
+
*
|
|
147
|
+
* @param {string} url
|
|
148
|
+
* @param {Handler} handler
|
|
149
|
+
* @returns {Router}
|
|
150
|
+
*/
|
|
77
151
|
delete (url, handler) {
|
|
78
152
|
return this.add('DELETE', url, handler)
|
|
79
153
|
}
|
|
80
154
|
|
|
155
|
+
/**
|
|
156
|
+
*
|
|
157
|
+
* @param {string} url
|
|
158
|
+
* @param {Handler} handler
|
|
159
|
+
* @returns {Router}
|
|
160
|
+
*/
|
|
81
161
|
patch (url, handler) {
|
|
82
162
|
return this.add('PATCH', url, handler)
|
|
83
163
|
}
|
|
84
164
|
|
|
165
|
+
/**
|
|
166
|
+
*
|
|
167
|
+
* @param {string} url
|
|
168
|
+
* @param {Handler} handler
|
|
169
|
+
* @returns {Router}
|
|
170
|
+
*/
|
|
85
171
|
any (url, handler) {
|
|
86
172
|
return this.add(WILDCARD, url, handler)
|
|
87
173
|
}
|
|
88
174
|
|
|
175
|
+
/**
|
|
176
|
+
*
|
|
177
|
+
* @param {string} method
|
|
178
|
+
* @param {string} url
|
|
179
|
+
* @param {Handler} handler
|
|
180
|
+
* @returns {Router}
|
|
181
|
+
*/
|
|
89
182
|
add (method, url, handler) {
|
|
90
183
|
const parsed = new URL(url)
|
|
91
184
|
const { hostname, protocol, pathname } = parsed
|
|
@@ -103,6 +196,11 @@ export class Router {
|
|
|
103
196
|
return this
|
|
104
197
|
}
|
|
105
198
|
|
|
199
|
+
/**
|
|
200
|
+
*
|
|
201
|
+
* @param {Request} request
|
|
202
|
+
* @returns {Route?}
|
|
203
|
+
*/
|
|
106
204
|
route (request) {
|
|
107
205
|
for (const route of this.routes) {
|
|
108
206
|
let hasFail = false
|
|
@@ -119,6 +217,13 @@ export class Router {
|
|
|
119
217
|
}
|
|
120
218
|
}
|
|
121
219
|
|
|
220
|
+
/**
|
|
221
|
+
*
|
|
222
|
+
* @param {Request} request
|
|
223
|
+
* @param {Route} route
|
|
224
|
+
* @param {MatchProperty} property
|
|
225
|
+
* @returns
|
|
226
|
+
*/
|
|
122
227
|
function matches (request, route, property) {
|
|
123
228
|
if (property === 'pathname') {
|
|
124
229
|
const routeSegments = route.segments
|
|
@@ -150,7 +255,7 @@ function matches (request, route, property) {
|
|
|
150
255
|
return areEqual(route.hostname, new URL(request.url).hostname)
|
|
151
256
|
} else if (property === 'protocol') {
|
|
152
257
|
return areEqual(route.protocol, new URL(request.url).protocol)
|
|
153
|
-
} else if(property === 'method') {
|
|
258
|
+
} else if (property === 'method') {
|
|
154
259
|
return areEqual(route.method, request.method.toUpperCase())
|
|
155
260
|
} else {
|
|
156
261
|
const routeProperty = route[property]
|
|
@@ -159,6 +264,11 @@ function matches (request, route, property) {
|
|
|
159
264
|
}
|
|
160
265
|
}
|
|
161
266
|
|
|
267
|
+
/**
|
|
268
|
+
* @param {string} routeProperty
|
|
269
|
+
* @param {string} requestProperty
|
|
270
|
+
* @returns
|
|
271
|
+
*/
|
|
162
272
|
function areEqual (routeProperty, requestProperty) {
|
|
163
273
|
if (routeProperty === '*') return true
|
|
164
274
|
return routeProperty === requestProperty
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "make-fetch",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Implement your own `fetch()` with node.js streams",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "node test",
|
|
9
|
-
"lint": "standard --fix"
|
|
9
|
+
"lint": "standard --fix && tsc --noEmit",
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"preversion": "npm run lint && npm run build && npm run test"
|
|
10
12
|
},
|
|
11
13
|
"repository": {
|
|
12
14
|
"type": "git",
|
|
@@ -25,7 +27,11 @@
|
|
|
25
27
|
},
|
|
26
28
|
"homepage": "https://github.com/RangerMauve/make-fetch#readme",
|
|
27
29
|
"devDependencies": {
|
|
30
|
+
"@tsconfig/node20": "^20.1.8",
|
|
31
|
+
"@types/node": "^25.0.3",
|
|
32
|
+
"@types/tape": "^5.8.1",
|
|
28
33
|
"standard": "^17.0.0",
|
|
29
|
-
"tape": "^5.6.1"
|
|
34
|
+
"tape": "^5.6.1",
|
|
35
|
+
"typescript": "^5.9.3"
|
|
30
36
|
}
|
|
31
37
|
}
|
package/test.js
CHANGED
|
@@ -31,6 +31,35 @@ test('Basic makeFetch test', async (t) => {
|
|
|
31
31
|
t.end()
|
|
32
32
|
})
|
|
33
33
|
|
|
34
|
+
test('Basic makeFetch test with Request object', async (t) => {
|
|
35
|
+
const fetch = makeFetch(({ url }) => {
|
|
36
|
+
return {
|
|
37
|
+
status: 200,
|
|
38
|
+
statusText: 'OK',
|
|
39
|
+
headers: {
|
|
40
|
+
url
|
|
41
|
+
},
|
|
42
|
+
body: intoAsyncIterable(url)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const toFetch = new Request('example://hostname/pathname')
|
|
47
|
+
|
|
48
|
+
const response = await fetch(toFetch)
|
|
49
|
+
|
|
50
|
+
t.ok(response.ok, 'response was OK')
|
|
51
|
+
|
|
52
|
+
const body = await response.text()
|
|
53
|
+
|
|
54
|
+
t.equal(body, toFetch.url, 'got expected response body')
|
|
55
|
+
|
|
56
|
+
t.equal(response.headers.get('url'), toFetch.url, 'got expected response headers')
|
|
57
|
+
|
|
58
|
+
t.equal(response.statusText, 'OK', 'got expected status text')
|
|
59
|
+
|
|
60
|
+
t.end()
|
|
61
|
+
})
|
|
62
|
+
|
|
34
63
|
test('Basic router tests', async (t) => {
|
|
35
64
|
const { fetch, router } = makeRoutedFetch()
|
|
36
65
|
|
|
@@ -48,6 +77,10 @@ test('Basic router tests', async (t) => {
|
|
|
48
77
|
t.equal(body, toFetch, 'got expected body')
|
|
49
78
|
})
|
|
50
79
|
|
|
80
|
+
/**
|
|
81
|
+
* @param {string} data
|
|
82
|
+
* @returns {AsyncIterable<string>}
|
|
83
|
+
*/
|
|
51
84
|
async function * intoAsyncIterable (data) {
|
|
52
85
|
yield data
|
|
53
86
|
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2022",
|
|
4
|
+
"module": "nodenext",
|
|
5
|
+
"checkJs": true,
|
|
6
|
+
"allowJs": true,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"emitDeclarationOnly": true,
|
|
9
|
+
"moduleResolution": "nodenext",
|
|
10
|
+
"outDir": "dist",
|
|
11
|
+
},
|
|
12
|
+
"extends": "@tsconfig/node20/tsconfig.json",
|
|
13
|
+
"files": ["./index.js","./test.js"],
|
|
14
|
+
"include": ["./index.js","./test.js"]
|
|
15
|
+
}
|