make-fetch 2.3.4 → 3.0.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/README.md +65 -6
- package/index.js +123 -98
- package/package.json +3 -9
- package/test.js +38 -24
- package/index.d.ts +0 -74
- package/tsconfig.json +0 -11
package/README.md
CHANGED
|
@@ -7,8 +7,10 @@ Implement your own `fetch()` with node.js streams
|
|
|
7
7
|
npm i --save make-fetch
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
+
Basic example:
|
|
11
|
+
|
|
10
12
|
```javascript
|
|
11
|
-
|
|
13
|
+
import { makeFetch } from 'make-fetch'
|
|
12
14
|
const fetch = makeFetch(async (request) => {
|
|
13
15
|
const {
|
|
14
16
|
url, // String representing request URL
|
|
@@ -20,11 +22,11 @@ const fetch = makeFetch(async (request) => {
|
|
|
20
22
|
} = request
|
|
21
23
|
|
|
22
24
|
return {
|
|
23
|
-
|
|
25
|
+
status: 200, // Should specify the status code to send back
|
|
24
26
|
headers: { // Optional object mapping response header titles to values
|
|
25
27
|
"something": "whatever"
|
|
26
28
|
},
|
|
27
|
-
|
|
29
|
+
body: asyncIterator // Required async iterable for the response body, can be empty
|
|
28
30
|
}
|
|
29
31
|
})
|
|
30
32
|
|
|
@@ -32,7 +34,64 @@ const response = await fetch('myscheme://whatever/foobar')
|
|
|
32
34
|
console.log(await response.text())
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
Routed example:
|
|
38
|
+
|
|
39
|
+
```JavaScript
|
|
40
|
+
|
|
41
|
+
import {makeRoutedFetch} from "make-fetch"
|
|
42
|
+
|
|
43
|
+
const {fetch, router} = makeRoutedFetch()
|
|
44
|
+
|
|
45
|
+
router.get('example://somehost/**', (request) => {
|
|
46
|
+
return {
|
|
47
|
+
body: "hello world",
|
|
48
|
+
headers: {example: "Whatever"}
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// You can have wildcards in the protocol, hostname,
|
|
53
|
+
// or individual segments in the pathname
|
|
54
|
+
router.post('*://*/foo/*/bar/, () => {
|
|
55
|
+
return {body: 'Goodbye world'}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Match first handler
|
|
59
|
+
fetch('example://somehost/example/path/here')
|
|
60
|
+
|
|
61
|
+
// Match second handler
|
|
62
|
+
fetch('whatever://something/foo/whatever/bar/')
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### API:
|
|
67
|
+
|
|
68
|
+
`makeFetch(async (Request) => ResponseOptions) => fetch()`
|
|
69
|
+
|
|
70
|
+
The main API is based on the handler which takes a standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object, and must return options for constructing a response based on the [Response](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request) constructor.
|
|
71
|
+
|
|
72
|
+
Instead of having a separate parameter for the body and the response options, the fetch handler should return both in one object.
|
|
73
|
+
|
|
74
|
+
This will then return a standard [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) API which takes request info, and returns responses.
|
|
75
|
+
|
|
76
|
+
`makeRoutedFetch({onNotFound}) => {router: Router, fetch}`
|
|
77
|
+
|
|
78
|
+
If you want to have an easier way of routing different methods/hostnames/paths, you can use a routed make-fetch which can make it easier to register handlers for different routes.
|
|
79
|
+
This will creat a Router, and a `fetch()` instance for that router.
|
|
80
|
+
Handlers you add on the router will be useful to match URLs+methods from the fetch request and will use the matched handler to generate the response.
|
|
81
|
+
You can optionally supply a `onNotFound` handler to be invoked if no other routes match.
|
|
82
|
+
|
|
83
|
+
`router.add(method, urlPattern, handler) => Router`
|
|
84
|
+
|
|
85
|
+
You can add routes for specific methods, and use URL patterns.
|
|
86
|
+
Then you can pass in the same basic handler as in makeFetch.
|
|
87
|
+
You can chain multiple add requests since the router returns itself when adding a route.
|
|
88
|
+
|
|
89
|
+
`router.get/head/put/post/delete(urlPattern, handler) => Router`
|
|
90
|
+
|
|
91
|
+
You can also use shorthands for methods with a similar API.
|
|
92
|
+
|
|
93
|
+
`router.any(urlPattern, handler)`
|
|
94
|
+
|
|
95
|
+
You can register handlers for any method.
|
|
36
96
|
|
|
37
|
-
|
|
38
|
-
- Eventually ReadableStream will become async iterable so you'll be able to iterate either normally
|
|
97
|
+
For example `router.any('*://*/**', handler)` will register a handler that will be invoked on any method/protocol scheme/hostname/path.
|
package/index.js
CHANGED
|
@@ -1,122 +1,147 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
})
|
|
14
|
-
} else if (resource instanceof URL) {
|
|
15
|
-
return fetch(resource.href, init)
|
|
16
|
-
}
|
|
1
|
+
export const WILDCARD = '*'
|
|
2
|
+
|
|
3
|
+
const MATCH_ORDER = ['method', 'protocol', 'hostname', 'pathname']
|
|
4
|
+
|
|
5
|
+
export function makeFetch (handler, {
|
|
6
|
+
Request = globalThis.Request,
|
|
7
|
+
Response = globalThis.Response
|
|
8
|
+
} = {}) {
|
|
9
|
+
return async function fetch (...requestOptions) {
|
|
10
|
+
const request = new Request(...requestOptions)
|
|
11
|
+
|
|
12
|
+
const { body = null, ...responseOptions } = await handler(request)
|
|
17
13
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
headers: rawHeaders,
|
|
22
|
-
method: rawMethod,
|
|
23
|
-
body: rawBody,
|
|
24
|
-
referrer,
|
|
25
|
-
signal
|
|
26
|
-
} = resource
|
|
27
|
-
|
|
28
|
-
const headers = rawHeaders ? headersToObject(rawHeaders) : {}
|
|
29
|
-
const method = (rawMethod || 'GET').toUpperCase()
|
|
30
|
-
const body = rawBody ? bodyToIterator(rawBody, session) : null
|
|
31
|
-
|
|
32
|
-
const {
|
|
33
|
-
statusCode,
|
|
34
|
-
statusText: rawStatusText,
|
|
35
|
-
headers: rawResponseHeaders,
|
|
36
|
-
data
|
|
37
|
-
} = await handler({
|
|
38
|
-
url,
|
|
39
|
-
headers,
|
|
40
|
-
method,
|
|
41
|
-
body,
|
|
42
|
-
referrer,
|
|
43
|
-
signal
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const responseHeaders = new Headers(rawResponseHeaders || {})
|
|
47
|
-
const statusText = rawStatusText || getStatus(statusCode)
|
|
48
|
-
|
|
49
|
-
return new FakeResponse(statusCode, statusText, responseHeaders, data, url)
|
|
14
|
+
const response = new Response(body, responseOptions)
|
|
15
|
+
|
|
16
|
+
return response
|
|
50
17
|
}
|
|
51
18
|
}
|
|
52
19
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
20
|
+
export function makeRoutedFetch ({
|
|
21
|
+
onNotFound = DEFAULT_NOT_FOUND
|
|
22
|
+
} = {}) {
|
|
23
|
+
const router = new Router()
|
|
24
|
+
|
|
25
|
+
const fetch = makeFetch(async (request) => {
|
|
26
|
+
const route = router.route(request)
|
|
27
|
+
if (!route) {
|
|
28
|
+
return onNotFound(request)
|
|
29
|
+
}
|
|
30
|
+
return route.handler(request)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return { fetch, router }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function DEFAULT_NOT_FOUND () {
|
|
37
|
+
return { status: 404, statusText: 'Invalid URL' }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class Router {
|
|
41
|
+
constructor () {
|
|
42
|
+
this.routes = []
|
|
60
43
|
}
|
|
61
44
|
|
|
62
|
-
get
|
|
63
|
-
return this.
|
|
45
|
+
get (url, handler) {
|
|
46
|
+
return this.add('GET', url, handler)
|
|
64
47
|
}
|
|
65
48
|
|
|
66
|
-
|
|
67
|
-
return
|
|
49
|
+
head (url, handler) {
|
|
50
|
+
return this.add('HEAD', url, handler)
|
|
68
51
|
}
|
|
69
52
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const { byteOffset, length } = buffer
|
|
73
|
-
const end = byteOffset + length
|
|
74
|
-
return buffer.buffer.slice(buffer.byteOffset, end)
|
|
53
|
+
post (url, handler) {
|
|
54
|
+
return this.add('POST', url, handler)
|
|
75
55
|
}
|
|
76
56
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return buffer.toString('utf-8')
|
|
57
|
+
put (url, handler) {
|
|
58
|
+
return this.add('PUT', url, handler)
|
|
80
59
|
}
|
|
81
60
|
|
|
82
|
-
|
|
83
|
-
return
|
|
61
|
+
delete (url, handler) {
|
|
62
|
+
return this.add('DELETE', url, handler)
|
|
84
63
|
}
|
|
85
|
-
}
|
|
86
64
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
65
|
+
patch (url, handler) {
|
|
66
|
+
return this.add('PATCH', url, handler)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
any (url, handler) {
|
|
70
|
+
return this.add(WILDCARD, url, handler)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
add (method, url, handler) {
|
|
74
|
+
const parsed = new URL(url)
|
|
75
|
+
const { hostname, protocol, pathname } = parsed
|
|
76
|
+
const segments = pathname.slice(1).split('/')
|
|
77
|
+
|
|
78
|
+
const route = {
|
|
79
|
+
protocol,
|
|
80
|
+
method: method.toUpperCase(),
|
|
81
|
+
hostname,
|
|
82
|
+
segments,
|
|
83
|
+
handler
|
|
99
84
|
}
|
|
100
|
-
})()
|
|
101
|
-
return readable
|
|
102
|
-
}
|
|
103
85
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
86
|
+
this.routes.push(route)
|
|
87
|
+
return this
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
route (request) {
|
|
91
|
+
for (const route of this.routes) {
|
|
92
|
+
let hasFail = false
|
|
93
|
+
for (const property of MATCH_ORDER) {
|
|
94
|
+
if (!matches(request, route, property)) {
|
|
95
|
+
hasFail = true
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!hasFail) {
|
|
99
|
+
return route
|
|
100
|
+
}
|
|
110
101
|
}
|
|
111
|
-
return
|
|
112
|
-
}
|
|
102
|
+
return null
|
|
103
|
+
}
|
|
113
104
|
}
|
|
114
105
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
106
|
+
function matches (request, route, property) {
|
|
107
|
+
if (property === 'pathname') {
|
|
108
|
+
const routeSegments = route.segments
|
|
109
|
+
const { pathname } = new URL(request.url)
|
|
110
|
+
const requestSegments = pathname.slice(1).split('/')
|
|
111
|
+
|
|
112
|
+
let i = 0
|
|
113
|
+
while (true) {
|
|
114
|
+
const routeLast = i === (routeSegments.length - 1)
|
|
115
|
+
const requestLast = i === (requestSegments.length - 1)
|
|
116
|
+
|
|
117
|
+
const routeSegment = routeSegments[i]
|
|
118
|
+
const requestSegment = requestSegments[i]
|
|
119
|
+
const routeWild = routeSegments === WILDCARD
|
|
120
|
+
const matches = routeWild || (routeSegment === requestSegment)
|
|
121
|
+
|
|
122
|
+
if (routeLast) {
|
|
123
|
+
if (routeSegment === (WILDCARD + WILDCARD)) return true
|
|
124
|
+
if (requestLast) return matches
|
|
125
|
+
return false
|
|
126
|
+
} else if (requestLast) {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!matches) return false
|
|
131
|
+
i++
|
|
132
|
+
}
|
|
133
|
+
} if (property === 'hostname') {
|
|
134
|
+
return areEqual(route.hostname, new URL(request.url).hostname)
|
|
135
|
+
} else if (property === 'protocol') {
|
|
136
|
+
return areEqual(route.protocol, new URL(request.url).protocol)
|
|
137
|
+
} else {
|
|
138
|
+
const routeProperty = route[property]
|
|
139
|
+
const requestProperty = request[property]
|
|
140
|
+
return areEqual(routeProperty, requestProperty)
|
|
119
141
|
}
|
|
142
|
+
}
|
|
120
143
|
|
|
121
|
-
|
|
144
|
+
function areEqual (routeProperty, requestProperty) {
|
|
145
|
+
if (routeProperty === '*') return true
|
|
146
|
+
return routeProperty === requestProperty
|
|
122
147
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "make-fetch",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Implement your own `fetch()` with node.js streams",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"test": "node test",
|
|
8
9
|
"lint": "standard --fix"
|
|
@@ -23,15 +24,8 @@
|
|
|
23
24
|
"url": "https://github.com/RangerMauve/make-fetch/issues"
|
|
24
25
|
},
|
|
25
26
|
"homepage": "https://github.com/RangerMauve/make-fetch#readme",
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"concat-stream": "^2.0.0",
|
|
28
|
-
"fetch-headers": "^2.0.0",
|
|
29
|
-
"fetch-request-body-to-async-iterator": "^1.0.3",
|
|
30
|
-
"statuses": "^2.0.0",
|
|
31
|
-
"web-streams-polyfill": "^3.0.0"
|
|
32
|
-
},
|
|
33
27
|
"devDependencies": {
|
|
34
28
|
"standard": "^17.0.0",
|
|
35
|
-
"tape": "^5.
|
|
29
|
+
"tape": "^5.6.1"
|
|
36
30
|
}
|
|
37
31
|
}
|
package/test.js
CHANGED
|
@@ -1,37 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
import test from 'tape'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { makeFetch, makeRoutedFetch } from './index.js'
|
|
4
4
|
|
|
5
|
-
test('
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
test('Basic makeFetch test', async (t) => {
|
|
6
|
+
const fetch = makeFetch(({ url }) => {
|
|
7
|
+
return {
|
|
8
|
+
status: 200,
|
|
9
|
+
statusText: 'OK',
|
|
10
|
+
headers: {
|
|
11
|
+
url
|
|
12
|
+
},
|
|
13
|
+
body: intoAsyncIterable(url)
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const toFetch = 'example://hostname/pathname'
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
const response = await fetch(toFetch)
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
t.ok(response.ok, 'response was OK')
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
const body = await response.text()
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
t.equal(body, toFetch, 'got expected response body')
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
t.equal(response.headers.get('url'), toFetch, 'got expected response headers')
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
t.equal(response.statusText, 'OK', 'got expected status text')
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
t.end()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('Basic router tests', async (t) => {
|
|
35
|
+
const { fetch, router } = makeRoutedFetch()
|
|
36
|
+
|
|
37
|
+
router.get('ipfs://localhost/ipfs/**', ({ url }) => {
|
|
38
|
+
return { body: url }
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const toFetch = 'ipfs://localhost/ipfs/cidwould/gohere'
|
|
42
|
+
const response = await fetch(toFetch)
|
|
43
|
+
|
|
44
|
+
t.ok(response.ok, 'response was OK')
|
|
45
|
+
|
|
46
|
+
const body = await response.text()
|
|
47
|
+
|
|
48
|
+
t.equal(body, toFetch, 'got expected body')
|
|
35
49
|
})
|
|
36
50
|
|
|
37
51
|
async function * intoAsyncIterable (data) {
|
package/index.d.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
declare module 'make-fetch' {
|
|
2
|
-
export interface NormalizedRequest {
|
|
3
|
-
url: string
|
|
4
|
-
headers: RawHeaders
|
|
5
|
-
method: string
|
|
6
|
-
body: AsyncIterableIterator<Uint8Array>
|
|
7
|
-
referrer?: string
|
|
8
|
-
|
|
9
|
-
// Hard to add types for. 😂
|
|
10
|
-
signal?: any
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface HandlerResponse {
|
|
14
|
-
statusCode?: number
|
|
15
|
-
statusText?: string
|
|
16
|
-
headers?: RawHeaders
|
|
17
|
-
data?: AsyncIterableIterator<Uint8Array | string>
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface RawHeaders {
|
|
21
|
-
[name: string]: string | undefined
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// TODO: Support actual fetch Headers
|
|
25
|
-
export interface Headers {
|
|
26
|
-
append(name: string, value: string) : void
|
|
27
|
-
delete(name: string) : void
|
|
28
|
-
entries() : IterableIterator<[string, string]>
|
|
29
|
-
get(name: string): string | undefined
|
|
30
|
-
has(name: string) : boolean
|
|
31
|
-
keys() : IterableIterator<string>
|
|
32
|
-
set(name: string, value: string) : void
|
|
33
|
-
values() : IterableIterator<string>
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface Request {
|
|
37
|
-
// This is kind of a pain to document
|
|
38
|
-
session?: any
|
|
39
|
-
signal?: any
|
|
40
|
-
|
|
41
|
-
url?: string
|
|
42
|
-
headers?: Headers | RawHeaders
|
|
43
|
-
method?: string,
|
|
44
|
-
body?: string | Uint8Array | AsyncIterableIterator<Uint8Array|string>,
|
|
45
|
-
referrer?: string,
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface Body {
|
|
49
|
-
// TODO: Fill this in
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface Response {
|
|
53
|
-
url: string
|
|
54
|
-
headers: Headers
|
|
55
|
-
status: number
|
|
56
|
-
statusText: string
|
|
57
|
-
ok: boolean
|
|
58
|
-
useFinalURL: true
|
|
59
|
-
body: Body
|
|
60
|
-
arrayBuffer() : Promise<ArrayBuffer>
|
|
61
|
-
text(): Promise<string>
|
|
62
|
-
json(): Promise<any>
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface Fetch {
|
|
66
|
-
(request: string | Request, info? : Request): Promise<Response>
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface FetchHandler {
|
|
70
|
-
(request: NormalizedRequest): Promise<HandlerResponse>
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export default function makeFetch(handler: FetchHandler) : Fetch
|
|
74
|
-
}
|