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 CHANGED
@@ -1,29 +1,71 @@
1
- export const WILDCARD = '*'
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 async function fetch (...requestOptions) {
10
- const request = new Request(...requestOptions)
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(async (request) => {
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
- return await onError(e, request)
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.1.2",
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
+ }