@whatwg-node/server 0.6.0-alpha-20230120130300-bc9d705 → 0.6.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 +98 -31
- package/createServerAdapter.d.ts +3 -8
- package/index.d.ts +2 -3
- package/index.js +74 -123
- package/index.mjs +72 -121
- package/{plugins/useCors.d.ts → middlewares/withCors.d.ts} +3 -3
- package/middlewares/withErrorHandling.d.ts +4 -0
- package/package.json +2 -5
- package/types.d.ts +9 -7
- package/utils.d.ts +1 -1
- package/plugins/cookies/CookieChangeEvent.d.ts +0 -6
- package/plugins/cookies/CookieStore.d.ts +0 -12
- package/plugins/cookies/parse.d.ts +0 -11
- package/plugins/cookies/types.d.ts +0 -37
- package/plugins/cookies/useCookies.d.ts +0 -8
- package/plugins/types.d.ts +0 -21
- package/plugins/useErrorHandling.d.ts +0 -6
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# WHATWG Node Generic Server Adapter
|
|
2
2
|
|
|
3
|
-
`@whatwg-node/server` helps you to create a generic server implementation by using WHATWG Fetch API
|
|
3
|
+
`@whatwg-node/server` helps you to create a generic server implementation by using WHATWG Fetch API
|
|
4
|
+
for Node.js, AWS Lambda, Cloudflare Workers, Deno, Express, Fastify, Koa, Next.js and Sveltekit.
|
|
4
5
|
|
|
5
|
-
Once you create an adapter with `createServerAdapter`, you don't need to install any other platform
|
|
6
|
+
Once you create an adapter with `createServerAdapter`, you don't need to install any other platform
|
|
7
|
+
specific package since the generic adapter will handle it automatically.
|
|
6
8
|
|
|
7
9
|
## How to start
|
|
8
10
|
|
|
@@ -26,8 +28,8 @@ You can use your server adapter with the following integrations:
|
|
|
26
28
|
[Node.js](https://nodejs.org/api/http.html) is the most popular server side JavaScript runtime.
|
|
27
29
|
|
|
28
30
|
```ts
|
|
29
|
-
import myServerAdapter from './myServerAdapter'
|
|
30
31
|
import { createServer } from 'http'
|
|
32
|
+
import myServerAdapter from './myServerAdapter'
|
|
31
33
|
|
|
32
34
|
// You can create your Node server instance by using our adapter
|
|
33
35
|
const nodeServer = createServer(myServerAdapter)
|
|
@@ -37,21 +39,71 @@ nodeServer.listen(4000)
|
|
|
37
39
|
|
|
38
40
|
### AWS Lambda
|
|
39
41
|
|
|
40
|
-
AWS Lambda is a serverless computing platform that makes it easy to build applications that run on
|
|
42
|
+
AWS Lambda is a serverless computing platform that makes it easy to build applications that run on
|
|
43
|
+
the AWS cloud. Our adaoter is platform agnostic so they can fit together easily. In order to reduce
|
|
44
|
+
the boilerplate we prefer to use
|
|
45
|
+
[Serverless Express from Vendia](https://github.com/vendia/serverless-express).
|
|
41
46
|
|
|
42
47
|
```ts
|
|
43
|
-
import
|
|
48
|
+
import { APIGatewayEvent, APIGatewayProxyResult, Context } from 'aws-lambda'
|
|
44
49
|
import type { Handler } from '@aws-cdk/aws-lambda'
|
|
45
|
-
import
|
|
50
|
+
import myServerAdapter from './myServerAdapter'
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
interface ServerContext {
|
|
53
|
+
event: APIGatewayEvent
|
|
54
|
+
lambdaContext: Context
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function handler(
|
|
58
|
+
event: APIGatewayEvent,
|
|
59
|
+
lambdaContext: Context
|
|
60
|
+
): Promise<APIGatewayProxyResult> {
|
|
61
|
+
const url = new URL(event.path, 'http://localhost')
|
|
62
|
+
if (event.queryStringParameters != null) {
|
|
63
|
+
for (const name in event.queryStringParameters) {
|
|
64
|
+
const value = event.queryStringParameters[name]
|
|
65
|
+
if (value != null) {
|
|
66
|
+
url.searchParams.set(name, value)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const response = await myServerAdapter.fetch(
|
|
72
|
+
url,
|
|
73
|
+
{
|
|
74
|
+
// For v1.0 you should use event.httpMethod
|
|
75
|
+
method: event.requestContext.http.method,
|
|
76
|
+
headers: event.headers as HeadersInit,
|
|
77
|
+
body: event.body
|
|
78
|
+
? Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8')
|
|
79
|
+
: undefined
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
event,
|
|
83
|
+
lambdaContext
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
const responseHeaders: Record<string, string> = {}
|
|
88
|
+
|
|
89
|
+
response.headers.forEach((value, name) => {
|
|
90
|
+
responseHeaders[name] = value
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
statusCode: response.status,
|
|
95
|
+
headers: responseHeaders,
|
|
96
|
+
body: await response.text(),
|
|
97
|
+
isBase64Encoded: false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
50
100
|
```
|
|
51
101
|
|
|
52
102
|
### Cloudflare Workers
|
|
53
103
|
|
|
54
|
-
Cloudflare Workers provides a serverless execution environment that allows you to create entirely
|
|
104
|
+
Cloudflare Workers provides a serverless execution environment that allows you to create entirely
|
|
105
|
+
new applications or augment existing ones without configuring or maintaining infrastructure. It uses
|
|
106
|
+
Fetch API already so we can use our adapter as an event listener like below;
|
|
55
107
|
|
|
56
108
|
```ts
|
|
57
109
|
import myServerAdapter from './myServerAdapter'
|
|
@@ -65,18 +117,20 @@ self.addEventListener('fetch', myServerAdapter)
|
|
|
65
117
|
You can use our adapter as a Deno request handler like below;
|
|
66
118
|
|
|
67
119
|
```ts
|
|
68
|
-
import { serve } from 'https://deno.land/std@0.
|
|
120
|
+
import { serve } from 'https://deno.land/std@0.157.0/http/server.ts'
|
|
69
121
|
import myServerAdapter from './myServerAdapter'
|
|
70
122
|
|
|
71
123
|
serve(myServerAdapter, {
|
|
72
|
-
|
|
73
|
-
|
|
124
|
+
onListen({ hostname, port }) {
|
|
125
|
+
console.log(`Listening on http://${hostname}:${port}/graphql`)
|
|
126
|
+
}
|
|
74
127
|
})
|
|
75
128
|
```
|
|
76
129
|
|
|
77
130
|
### Express
|
|
78
131
|
|
|
79
|
-
[Express is the most popular web framework for Node.js.](https://expressjs.com/) It is a minimalist
|
|
132
|
+
[Express is the most popular web framework for Node.js.](https://expressjs.com/) It is a minimalist
|
|
133
|
+
framework that provides a robust set of features to handle HTTP on Node.js applications.
|
|
80
134
|
|
|
81
135
|
You can easily integrate your adapter into your Express application with a few lines of code.
|
|
82
136
|
|
|
@@ -96,14 +150,15 @@ app.listen(4000, () => {
|
|
|
96
150
|
|
|
97
151
|
### Fastify
|
|
98
152
|
|
|
99
|
-
[Fastify is one of the popular HTTP server frameworks for Node.js.](https://www.fastify.io/). You
|
|
153
|
+
[Fastify is one of the popular HTTP server frameworks for Node.js.](https://www.fastify.io/). You
|
|
154
|
+
can use your adapter easily with Fastify.
|
|
100
155
|
|
|
101
156
|
So you can benefit from the powerful plugins of Fastify ecosystem.
|
|
102
157
|
[See the ecosystem](https://www.fastify.io/docs/latest/Guides/Ecosystem/)
|
|
103
158
|
|
|
104
159
|
```ts
|
|
160
|
+
import fastify, { FastifyReply, FastifyRequest } from 'fastify'
|
|
105
161
|
import myServerAdapter from './myServerAdapter'
|
|
106
|
-
import fastify, { FastifyRequest, FastifyReply } from 'fastify'
|
|
107
162
|
|
|
108
163
|
// This is the fastify instance you have created
|
|
109
164
|
const app = fastify({ logger: true })
|
|
@@ -127,7 +182,8 @@ app.route({
|
|
|
127
182
|
|
|
128
183
|
reply.status(response.status)
|
|
129
184
|
|
|
130
|
-
|
|
185
|
+
// Fastify doesn't accept `null` as a response body
|
|
186
|
+
reply.send(response.body || undefined)
|
|
131
187
|
|
|
132
188
|
return reply
|
|
133
189
|
}
|
|
@@ -138,7 +194,9 @@ app.listen(4000)
|
|
|
138
194
|
|
|
139
195
|
### Koa
|
|
140
196
|
|
|
141
|
-
[Koa is another Node.js server framework designed by the team behind Express, which aims to be a smaller, more expressive.](https://koajs.com/)
|
|
197
|
+
[Koa is another Node.js server framework designed by the team behind Express, which aims to be a smaller, more expressive.](https://koajs.com/)
|
|
198
|
+
You can add your adapter to your Koa application with a few lines of code then
|
|
199
|
+
[benefit middlewares written for Koa.](https://github.com/koajs/koa/wiki)
|
|
142
200
|
|
|
143
201
|
```ts
|
|
144
202
|
import Koa from 'koa'
|
|
@@ -157,7 +215,7 @@ app.use(async ctx => {
|
|
|
157
215
|
ctx.append(key, value)
|
|
158
216
|
})
|
|
159
217
|
|
|
160
|
-
ctx.body =
|
|
218
|
+
ctx.body = response.body
|
|
161
219
|
})
|
|
162
220
|
|
|
163
221
|
app.listen(4000, () => {
|
|
@@ -167,13 +225,14 @@ app.listen(4000, () => {
|
|
|
167
225
|
|
|
168
226
|
### Next.js
|
|
169
227
|
|
|
170
|
-
[Next.js](https://nextjs.org/) is a web framework that allows you to build websites very quickly and
|
|
228
|
+
[Next.js](https://nextjs.org/) is a web framework that allows you to build websites very quickly and
|
|
229
|
+
our new server adapter can be integrated with Next.js easily as an API Route.
|
|
171
230
|
|
|
172
231
|
```ts
|
|
173
232
|
// pages/api/myEndpoint.ts
|
|
174
233
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
|
175
|
-
import myServerAdapter from './myServerAdapter'
|
|
176
234
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
235
|
+
import myServerAdapter from './myServerAdapter'
|
|
177
236
|
|
|
178
237
|
export const config = {
|
|
179
238
|
api: {
|
|
@@ -187,7 +246,8 @@ export default myServerAdapter
|
|
|
187
246
|
|
|
188
247
|
### SvelteKit
|
|
189
248
|
|
|
190
|
-
[SvelteKit](https://kit.svelte.dev/) is the fastest way to build svelte apps. It is very simple, and
|
|
249
|
+
[SvelteKit](https://kit.svelte.dev/) is the fastest way to build svelte apps. It is very simple, and
|
|
250
|
+
let you build frontend & backend in a single place
|
|
191
251
|
|
|
192
252
|
```ts
|
|
193
253
|
import myServerAdapter from './myServerAdapter'
|
|
@@ -197,8 +257,8 @@ export { myServerAdapter as get, myServerAdapter as post }
|
|
|
197
257
|
|
|
198
258
|
### Bun
|
|
199
259
|
|
|
200
|
-
[Bun](https://bun.sh/) is a modern JavaScript runtime like Node or Deno, and it supports Fetch API
|
|
201
|
-
So the configuration is really simple like any other JS runtime;
|
|
260
|
+
[Bun](https://bun.sh/) is a modern JavaScript runtime like Node or Deno, and it supports Fetch API
|
|
261
|
+
as a first class citizen. So the configuration is really simple like any other JS runtime;
|
|
202
262
|
|
|
203
263
|
```ts
|
|
204
264
|
import myServerAdapter from './myServerAdapter'
|
|
@@ -212,9 +272,11 @@ console.info(`Server is running on ${server.hostname}`)
|
|
|
212
272
|
|
|
213
273
|
## File Uploads / Multipart Requests
|
|
214
274
|
|
|
215
|
-
Multipart requests are a type of HTTP request that allows you to send blobs together with regular
|
|
275
|
+
Multipart requests are a type of HTTP request that allows you to send blobs together with regular
|
|
276
|
+
text data which has a mime-type `multipart/form-data`.
|
|
216
277
|
|
|
217
|
-
For example, if you send a multipart request from a browser with `FormData`, you can get the same
|
|
278
|
+
For example, if you send a multipart request from a browser with `FormData`, you can get the same
|
|
279
|
+
`FormData` object in your request handler.
|
|
218
280
|
|
|
219
281
|
```ts
|
|
220
282
|
import { createServerAdapter } from '@whatwg-node/server'
|
|
@@ -238,15 +300,21 @@ const myServerAdapter = createServerAdapter(async request => {
|
|
|
238
300
|
})
|
|
239
301
|
```
|
|
240
302
|
|
|
241
|
-
You can learn more about [File API](https://developer.mozilla.org/en-US/docs/Web/API/File) on MDN
|
|
303
|
+
You can learn more about [File API](https://developer.mozilla.org/en-US/docs/Web/API/File) on MDN
|
|
304
|
+
documentation.
|
|
242
305
|
|
|
243
306
|
## Routing and Middlewares
|
|
244
307
|
|
|
245
|
-
We'd recommend to use `@whatwg-node/router` to handle routing and middleware approach. It uses
|
|
308
|
+
We'd recommend to use `@whatwg-node/router` to handle routing and middleware approach. It uses
|
|
309
|
+
`@whatwg-node/server` under the hood.
|
|
310
|
+
|
|
311
|
+
> Learn more about `@whatwg-node/router` [here](../router)
|
|
246
312
|
|
|
247
313
|
### Basic Routing
|
|
248
314
|
|
|
249
315
|
```ts
|
|
316
|
+
// Then use it in any environment
|
|
317
|
+
import { createServer } from 'http'
|
|
250
318
|
import { createRouter, Router } from '@whatwg-node/router'
|
|
251
319
|
|
|
252
320
|
const router = createRouter()
|
|
@@ -266,15 +334,14 @@ router.get('/google', () => Response.redirect('http://www.google.com'))
|
|
|
266
334
|
// 404 for everything else
|
|
267
335
|
router.all('*', () => new Response('Not Found.', { status: 404 }))
|
|
268
336
|
|
|
269
|
-
// Then use it in any environment
|
|
270
|
-
import { createServer } from 'http'
|
|
271
337
|
const httpServer = createServer(router)
|
|
272
338
|
httpServer.listen(4000)
|
|
273
339
|
```
|
|
274
340
|
|
|
275
341
|
### Middlewares to handle CORS, cookies and more
|
|
276
342
|
|
|
277
|
-
This package also provides some utilities for your platform agnostic server implementation. The
|
|
343
|
+
This package also provides some utilities for your platform agnostic server implementation. The
|
|
344
|
+
following example shows how to get the cookies as an object from the request.
|
|
278
345
|
|
|
279
346
|
```ts
|
|
280
347
|
import { withCookies } from '@whatwg-node/server'
|
package/createServerAdapter.d.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
plugins?: ServerAdapterPlugin<TServerContext>[];
|
|
5
|
-
fetchAPI?: Partial<FetchAPI>;
|
|
6
|
-
}
|
|
7
|
-
declare function createServerAdapter<TServerContext = DefaultServerAdapterContext, THandleRequest extends ServerAdapterRequestHandler<TServerContext> = ServerAdapterRequestHandler<TServerContext>>(serverAdapterRequestHandler: THandleRequest, options?: ServerAdapterOptions<TServerContext>): ServerAdapter<TServerContext, ServerAdapterBaseObject<TServerContext, THandleRequest>>;
|
|
8
|
-
declare function createServerAdapter<TServerContext, TBaseObject extends ServerAdapterBaseObject<TServerContext>>(serverAdapterBaseObject: TBaseObject, options?: ServerAdapterOptions<TServerContext>): ServerAdapter<TServerContext, TBaseObject>;
|
|
1
|
+
import { ServerAdapter, ServerAdapterBaseObject, ServerAdapterRequestHandler } from './types';
|
|
2
|
+
declare function createServerAdapter<TServerContext = {}, THandleRequest extends ServerAdapterRequestHandler<TServerContext> = ServerAdapterRequestHandler<TServerContext>>(serverAdapterRequestHandler: THandleRequest, RequestCtor?: typeof Request): ServerAdapter<TServerContext, ServerAdapterBaseObject<TServerContext, THandleRequest>>;
|
|
3
|
+
declare function createServerAdapter<TServerContext, TBaseObject extends ServerAdapterBaseObject<TServerContext>>(serverAdapterBaseObject: TBaseObject, RequestCtor?: typeof Request): ServerAdapter<TServerContext, TBaseObject>;
|
|
9
4
|
export { createServerAdapter };
|
package/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export * from './createServerAdapter';
|
|
2
2
|
export * from './types';
|
|
3
3
|
export * from './utils';
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './plugins/useErrorHandling';
|
|
4
|
+
export * from './middlewares/withCors';
|
|
5
|
+
export * from './middlewares/withErrorHandling';
|
|
7
6
|
export { Response } from '@whatwg-node/fetch';
|
package/index.js
CHANGED
|
@@ -2,32 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const fetch = require('@whatwg-node/fetch');
|
|
6
6
|
|
|
7
7
|
function isAsyncIterable(body) {
|
|
8
|
-
return body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function';
|
|
8
|
+
return (body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function');
|
|
9
9
|
}
|
|
10
10
|
function getPort(nodeRequest) {
|
|
11
11
|
var _a, _b, _c, _d, _e;
|
|
12
12
|
if ((_a = nodeRequest.socket) === null || _a === void 0 ? void 0 : _a.localPort) {
|
|
13
13
|
return (_b = nodeRequest.socket) === null || _b === void 0 ? void 0 : _b.localPort;
|
|
14
14
|
}
|
|
15
|
-
const
|
|
15
|
+
const hostInHeader = ((_c = nodeRequest.headers) === null || _c === void 0 ? void 0 : _c[':authority']) || ((_d = nodeRequest.headers) === null || _d === void 0 ? void 0 : _d.host);
|
|
16
|
+
const portInHeader = (_e = hostInHeader === null || hostInHeader === void 0 ? void 0 : hostInHeader.split(':')) === null || _e === void 0 ? void 0 : _e[1];
|
|
16
17
|
if (portInHeader) {
|
|
17
18
|
return portInHeader;
|
|
18
19
|
}
|
|
19
20
|
return 80;
|
|
20
21
|
}
|
|
21
22
|
function getHostnameWithPort(nodeRequest) {
|
|
22
|
-
var _a, _b, _c;
|
|
23
|
-
if ((_a = nodeRequest.headers) === null || _a === void 0 ? void 0 : _a
|
|
24
|
-
return (_b = nodeRequest.headers) === null || _b === void 0 ? void 0 : _b
|
|
23
|
+
var _a, _b, _c, _d, _e;
|
|
24
|
+
if ((_a = nodeRequest.headers) === null || _a === void 0 ? void 0 : _a[':authority']) {
|
|
25
|
+
return (_b = nodeRequest.headers) === null || _b === void 0 ? void 0 : _b[':authority'];
|
|
26
|
+
}
|
|
27
|
+
if ((_c = nodeRequest.headers) === null || _c === void 0 ? void 0 : _c.host) {
|
|
28
|
+
return (_d = nodeRequest.headers) === null || _d === void 0 ? void 0 : _d.host;
|
|
25
29
|
}
|
|
26
30
|
const port = getPort(nodeRequest);
|
|
27
31
|
if (nodeRequest.hostname) {
|
|
28
32
|
return nodeRequest.hostname + ':' + port;
|
|
29
33
|
}
|
|
30
|
-
const localIp = (
|
|
34
|
+
const localIp = (_e = nodeRequest.socket) === null || _e === void 0 ? void 0 : _e.localAddress;
|
|
31
35
|
if (localIp && !(localIp === null || localIp === void 0 ? void 0 : localIp.includes('::')) && !(localIp === null || localIp === void 0 ? void 0 : localIp.includes('ffff'))) {
|
|
32
36
|
return `${localIp}:${port}`;
|
|
33
37
|
}
|
|
@@ -111,8 +115,7 @@ function normalizeNodeRequest(nodeRequest, RequestCtor) {
|
|
|
111
115
|
});
|
|
112
116
|
}
|
|
113
117
|
return new RequestCtor(fullUrl, {
|
|
114
|
-
|
|
115
|
-
method: nodeRequest.method,
|
|
118
|
+
...baseRequestInit,
|
|
116
119
|
body: rawRequest,
|
|
117
120
|
});
|
|
118
121
|
}
|
|
@@ -124,7 +127,11 @@ function isNodeRequest(request) {
|
|
|
124
127
|
}
|
|
125
128
|
function isServerResponse(stream) {
|
|
126
129
|
// Check all used functions are defined
|
|
127
|
-
return (stream != null &&
|
|
130
|
+
return (stream != null &&
|
|
131
|
+
stream.setHeader != null &&
|
|
132
|
+
stream.end != null &&
|
|
133
|
+
stream.once != null &&
|
|
134
|
+
stream.write != null);
|
|
128
135
|
}
|
|
129
136
|
function isReadableStream(stream) {
|
|
130
137
|
return stream != null && stream.getReader != null;
|
|
@@ -186,6 +193,7 @@ function isRequestInit(val) {
|
|
|
186
193
|
'window' in val));
|
|
187
194
|
}
|
|
188
195
|
|
|
196
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
189
197
|
async function handleWaitUntils(waitUntilPromises) {
|
|
190
198
|
const waitUntils = await Promise.allSettled(waitUntilPromises);
|
|
191
199
|
waitUntils.forEach(waitUntil => {
|
|
@@ -194,60 +202,17 @@ async function handleWaitUntils(waitUntilPromises) {
|
|
|
194
202
|
}
|
|
195
203
|
});
|
|
196
204
|
}
|
|
197
|
-
function createServerAdapter(serverAdapterBaseObject,
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const onResponseHooks = [];
|
|
206
|
-
for (const plugin of (_a = options === null || options === void 0 ? void 0 : options.plugins) !== null && _a !== void 0 ? _a : []) {
|
|
207
|
-
if (plugin.onRequest) {
|
|
208
|
-
onRequestHooks.push(plugin.onRequest);
|
|
209
|
-
}
|
|
210
|
-
if (plugin.onResponse) {
|
|
211
|
-
onResponseHooks.push(plugin.onResponse);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
async function handleRequest(request, serverContext) {
|
|
215
|
-
const url = new URL(request.url, 'http://localhost');
|
|
216
|
-
let requestHandler = givenHandleRequest;
|
|
217
|
-
let response;
|
|
218
|
-
for (const onRequestHook of onRequestHooks) {
|
|
219
|
-
await onRequestHook({
|
|
220
|
-
request,
|
|
221
|
-
serverContext,
|
|
222
|
-
fetchAPI,
|
|
223
|
-
url,
|
|
224
|
-
requestHandler,
|
|
225
|
-
setRequestHandler(newRequestHandler) {
|
|
226
|
-
requestHandler = newRequestHandler;
|
|
227
|
-
},
|
|
228
|
-
endResponse(newResponse) {
|
|
229
|
-
response = newResponse;
|
|
230
|
-
},
|
|
231
|
-
});
|
|
232
|
-
if (response) {
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
if (!response) {
|
|
237
|
-
response = await requestHandler(request, serverContext);
|
|
238
|
-
}
|
|
239
|
-
for (const onResponseHook of onResponseHooks) {
|
|
240
|
-
await onResponseHook({
|
|
241
|
-
request,
|
|
242
|
-
response,
|
|
243
|
-
serverContext,
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
return response;
|
|
247
|
-
}
|
|
205
|
+
function createServerAdapter(serverAdapterBaseObject,
|
|
206
|
+
/**
|
|
207
|
+
* WHATWG Fetch spec compliant `Request` constructor.
|
|
208
|
+
*/
|
|
209
|
+
RequestCtor = fetch.Request) {
|
|
210
|
+
const handleRequest = typeof serverAdapterBaseObject === 'function'
|
|
211
|
+
? serverAdapterBaseObject
|
|
212
|
+
: serverAdapterBaseObject.handle;
|
|
248
213
|
function handleNodeRequest(nodeRequest, ...ctx) {
|
|
249
214
|
const serverContext = ctx.length > 1 ? completeAssign({}, ...ctx) : ctx[0];
|
|
250
|
-
const request = normalizeNodeRequest(nodeRequest,
|
|
215
|
+
const request = normalizeNodeRequest(nodeRequest, RequestCtor);
|
|
251
216
|
return handleRequest(request, serverContext);
|
|
252
217
|
}
|
|
253
218
|
async function requestListener(nodeRequest, serverResponse, ...ctx) {
|
|
@@ -307,9 +272,9 @@ function createServerAdapter(serverAdapterBaseObject, options) {
|
|
|
307
272
|
if (typeof input === 'string' || input instanceof URL) {
|
|
308
273
|
const [initOrCtx, ...restOfCtx] = maybeCtx;
|
|
309
274
|
if (isRequestInit(initOrCtx)) {
|
|
310
|
-
return handleRequestWithWaitUntil(new
|
|
275
|
+
return handleRequestWithWaitUntil(new RequestCtor(input, initOrCtx), ...restOfCtx);
|
|
311
276
|
}
|
|
312
|
-
return handleRequestWithWaitUntil(new
|
|
277
|
+
return handleRequestWithWaitUntil(new RequestCtor(input), ...maybeCtx);
|
|
313
278
|
}
|
|
314
279
|
return handleRequestWithWaitUntil(input, ...maybeCtx);
|
|
315
280
|
};
|
|
@@ -386,24 +351,27 @@ function createServerAdapter(serverAdapterBaseObject, options) {
|
|
|
386
351
|
// from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#copying_accessors
|
|
387
352
|
function completeAssign(target, ...sources) {
|
|
388
353
|
sources.forEach(source => {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
descriptors
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
354
|
+
if (source != null && typeof source === 'object') {
|
|
355
|
+
// modified Object.keys to Object.getOwnPropertyNames
|
|
356
|
+
// because Object.keys only returns enumerable properties
|
|
357
|
+
const descriptors = Object.getOwnPropertyNames(source).reduce((descriptors, key) => {
|
|
358
|
+
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
|
|
359
|
+
return descriptors;
|
|
360
|
+
}, {});
|
|
361
|
+
// By default, Object.assign copies enumerable Symbols, too
|
|
362
|
+
Object.getOwnPropertySymbols(source).forEach(sym => {
|
|
363
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, sym);
|
|
364
|
+
if (descriptor.enumerable) {
|
|
365
|
+
descriptors[sym] = descriptor;
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
Object.defineProperties(target, descriptors);
|
|
369
|
+
}
|
|
403
370
|
});
|
|
404
371
|
return target;
|
|
405
372
|
}
|
|
406
373
|
|
|
374
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
407
375
|
function getCORSHeadersByRequestAndOptions(request, corsOptions) {
|
|
408
376
|
var _a, _b;
|
|
409
377
|
const headers = {};
|
|
@@ -458,8 +426,7 @@ function getCORSHeadersByRequestAndOptions(request, corsOptions) {
|
|
|
458
426
|
}
|
|
459
427
|
}
|
|
460
428
|
if ((_b = corsOptions.allowedHeaders) === null || _b === void 0 ? void 0 : _b.length) {
|
|
461
|
-
headers['Access-Control-Allow-Headers'] =
|
|
462
|
-
corsOptions.allowedHeaders.join(', ');
|
|
429
|
+
headers['Access-Control-Allow-Headers'] = corsOptions.allowedHeaders.join(', ');
|
|
463
430
|
}
|
|
464
431
|
else {
|
|
465
432
|
const requestHeaders = request.headers.get('access-control-request-headers');
|
|
@@ -480,8 +447,7 @@ function getCORSHeadersByRequestAndOptions(request, corsOptions) {
|
|
|
480
447
|
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
481
448
|
}
|
|
482
449
|
if (corsOptions.exposedHeaders) {
|
|
483
|
-
headers['Access-Control-Expose-Headers'] =
|
|
484
|
-
corsOptions.exposedHeaders.join(', ');
|
|
450
|
+
headers['Access-Control-Expose-Headers'] = corsOptions.exposedHeaders.join(', ');
|
|
485
451
|
}
|
|
486
452
|
if (corsOptions.maxAge) {
|
|
487
453
|
headers['Access-Control-Max-Age'] = corsOptions.maxAge.toString();
|
|
@@ -492,8 +458,7 @@ async function getCORSResponseHeaders(request, corsOptionsFactory, serverContext
|
|
|
492
458
|
const corsOptions = await corsOptionsFactory(request, serverContext);
|
|
493
459
|
return getCORSHeadersByRequestAndOptions(request, corsOptions);
|
|
494
460
|
}
|
|
495
|
-
|
|
496
|
-
function useCORS(options) {
|
|
461
|
+
function withCORS(obj, options, ResponseCtor = fetch.Response) {
|
|
497
462
|
let corsOptionsFactory = () => ({});
|
|
498
463
|
if (options != null) {
|
|
499
464
|
if (typeof options === 'function') {
|
|
@@ -509,32 +474,36 @@ function useCORS(options) {
|
|
|
509
474
|
corsOptionsFactory = () => false;
|
|
510
475
|
}
|
|
511
476
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
});
|
|
524
|
-
endResponse(response);
|
|
525
|
-
}
|
|
526
|
-
},
|
|
527
|
-
async onResponse({ request, serverContext, response }) {
|
|
528
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
477
|
+
async function handleWithCORS(request, serverContext) {
|
|
478
|
+
let response;
|
|
479
|
+
if (request.method.toUpperCase() === 'OPTIONS') {
|
|
480
|
+
response = new ResponseCtor(null, {
|
|
481
|
+
status: 204,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
response = await obj.handle(request, serverContext);
|
|
486
|
+
}
|
|
487
|
+
if (response != null) {
|
|
529
488
|
const headers = await getCORSResponseHeaders(request, corsOptionsFactory, serverContext);
|
|
530
489
|
for (const headerName in headers) {
|
|
531
490
|
response.headers.set(headerName, headers[headerName]);
|
|
532
491
|
}
|
|
492
|
+
return response;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return new Proxy(obj, {
|
|
496
|
+
get(_, prop, receiver) {
|
|
497
|
+
if (prop === 'handle') {
|
|
498
|
+
return handleWithCORS;
|
|
499
|
+
}
|
|
500
|
+
return Reflect.get(obj, prop, receiver);
|
|
533
501
|
},
|
|
534
|
-
};
|
|
502
|
+
});
|
|
535
503
|
}
|
|
536
504
|
|
|
537
|
-
|
|
505
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
506
|
+
function createDefaultErrorHandler(ResponseCtor = fetch.Response) {
|
|
538
507
|
return function defaultErrorHandler(e) {
|
|
539
508
|
return new ResponseCtor(e.stack || e.message || e.toString(), {
|
|
540
509
|
status: e.statusCode || e.status || 500,
|
|
@@ -542,23 +511,6 @@ function createDefaultErrorHandler(ResponseCtor = DefaultFetchAPI.Response) {
|
|
|
542
511
|
});
|
|
543
512
|
};
|
|
544
513
|
}
|
|
545
|
-
function useErrorHandling(onError) {
|
|
546
|
-
return {
|
|
547
|
-
onRequest({ requestHandler, setRequestHandler, fetchAPI }) {
|
|
548
|
-
const errorHandler = onError || createDefaultErrorHandler(fetchAPI.Response);
|
|
549
|
-
setRequestHandler(async function handlerWithErrorHandling(request, serverContext) {
|
|
550
|
-
try {
|
|
551
|
-
const response = await requestHandler(request, serverContext);
|
|
552
|
-
return response;
|
|
553
|
-
}
|
|
554
|
-
catch (e) {
|
|
555
|
-
const response = await errorHandler(e, request, serverContext);
|
|
556
|
-
return response;
|
|
557
|
-
}
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
514
|
function withErrorHandling(obj, onError = createDefaultErrorHandler()) {
|
|
563
515
|
async function handleWithErrorHandling(request, ctx) {
|
|
564
516
|
try {
|
|
@@ -582,7 +534,7 @@ function withErrorHandling(obj, onError = createDefaultErrorHandler()) {
|
|
|
582
534
|
Object.defineProperty(exports, 'Response', {
|
|
583
535
|
enumerable: true,
|
|
584
536
|
get: function () {
|
|
585
|
-
return
|
|
537
|
+
return fetch.Response;
|
|
586
538
|
}
|
|
587
539
|
});
|
|
588
540
|
exports.createDefaultErrorHandler = createDefaultErrorHandler;
|
|
@@ -597,6 +549,5 @@ exports.isRequestInit = isRequestInit;
|
|
|
597
549
|
exports.isServerResponse = isServerResponse;
|
|
598
550
|
exports.normalizeNodeRequest = normalizeNodeRequest;
|
|
599
551
|
exports.sendNodeResponse = sendNodeResponse;
|
|
600
|
-
exports.
|
|
601
|
-
exports.useErrorHandling = useErrorHandling;
|
|
552
|
+
exports.withCORS = withCORS;
|
|
602
553
|
exports.withErrorHandling = withErrorHandling;
|
package/index.mjs
CHANGED
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Response } from '@whatwg-node/fetch';
|
|
1
|
+
import { Request, Response } from '@whatwg-node/fetch';
|
|
3
2
|
export { Response } from '@whatwg-node/fetch';
|
|
4
3
|
|
|
5
4
|
function isAsyncIterable(body) {
|
|
6
|
-
return body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function';
|
|
5
|
+
return (body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function');
|
|
7
6
|
}
|
|
8
7
|
function getPort(nodeRequest) {
|
|
9
8
|
var _a, _b, _c, _d, _e;
|
|
10
9
|
if ((_a = nodeRequest.socket) === null || _a === void 0 ? void 0 : _a.localPort) {
|
|
11
10
|
return (_b = nodeRequest.socket) === null || _b === void 0 ? void 0 : _b.localPort;
|
|
12
11
|
}
|
|
13
|
-
const
|
|
12
|
+
const hostInHeader = ((_c = nodeRequest.headers) === null || _c === void 0 ? void 0 : _c[':authority']) || ((_d = nodeRequest.headers) === null || _d === void 0 ? void 0 : _d.host);
|
|
13
|
+
const portInHeader = (_e = hostInHeader === null || hostInHeader === void 0 ? void 0 : hostInHeader.split(':')) === null || _e === void 0 ? void 0 : _e[1];
|
|
14
14
|
if (portInHeader) {
|
|
15
15
|
return portInHeader;
|
|
16
16
|
}
|
|
17
17
|
return 80;
|
|
18
18
|
}
|
|
19
19
|
function getHostnameWithPort(nodeRequest) {
|
|
20
|
-
var _a, _b, _c;
|
|
21
|
-
if ((_a = nodeRequest.headers) === null || _a === void 0 ? void 0 : _a
|
|
22
|
-
return (_b = nodeRequest.headers) === null || _b === void 0 ? void 0 : _b
|
|
20
|
+
var _a, _b, _c, _d, _e;
|
|
21
|
+
if ((_a = nodeRequest.headers) === null || _a === void 0 ? void 0 : _a[':authority']) {
|
|
22
|
+
return (_b = nodeRequest.headers) === null || _b === void 0 ? void 0 : _b[':authority'];
|
|
23
|
+
}
|
|
24
|
+
if ((_c = nodeRequest.headers) === null || _c === void 0 ? void 0 : _c.host) {
|
|
25
|
+
return (_d = nodeRequest.headers) === null || _d === void 0 ? void 0 : _d.host;
|
|
23
26
|
}
|
|
24
27
|
const port = getPort(nodeRequest);
|
|
25
28
|
if (nodeRequest.hostname) {
|
|
26
29
|
return nodeRequest.hostname + ':' + port;
|
|
27
30
|
}
|
|
28
|
-
const localIp = (
|
|
31
|
+
const localIp = (_e = nodeRequest.socket) === null || _e === void 0 ? void 0 : _e.localAddress;
|
|
29
32
|
if (localIp && !(localIp === null || localIp === void 0 ? void 0 : localIp.includes('::')) && !(localIp === null || localIp === void 0 ? void 0 : localIp.includes('ffff'))) {
|
|
30
33
|
return `${localIp}:${port}`;
|
|
31
34
|
}
|
|
@@ -109,8 +112,7 @@ function normalizeNodeRequest(nodeRequest, RequestCtor) {
|
|
|
109
112
|
});
|
|
110
113
|
}
|
|
111
114
|
return new RequestCtor(fullUrl, {
|
|
112
|
-
|
|
113
|
-
method: nodeRequest.method,
|
|
115
|
+
...baseRequestInit,
|
|
114
116
|
body: rawRequest,
|
|
115
117
|
});
|
|
116
118
|
}
|
|
@@ -122,7 +124,11 @@ function isNodeRequest(request) {
|
|
|
122
124
|
}
|
|
123
125
|
function isServerResponse(stream) {
|
|
124
126
|
// Check all used functions are defined
|
|
125
|
-
return (stream != null &&
|
|
127
|
+
return (stream != null &&
|
|
128
|
+
stream.setHeader != null &&
|
|
129
|
+
stream.end != null &&
|
|
130
|
+
stream.once != null &&
|
|
131
|
+
stream.write != null);
|
|
126
132
|
}
|
|
127
133
|
function isReadableStream(stream) {
|
|
128
134
|
return stream != null && stream.getReader != null;
|
|
@@ -184,6 +190,7 @@ function isRequestInit(val) {
|
|
|
184
190
|
'window' in val));
|
|
185
191
|
}
|
|
186
192
|
|
|
193
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
187
194
|
async function handleWaitUntils(waitUntilPromises) {
|
|
188
195
|
const waitUntils = await Promise.allSettled(waitUntilPromises);
|
|
189
196
|
waitUntils.forEach(waitUntil => {
|
|
@@ -192,60 +199,17 @@ async function handleWaitUntils(waitUntilPromises) {
|
|
|
192
199
|
}
|
|
193
200
|
});
|
|
194
201
|
}
|
|
195
|
-
function createServerAdapter(serverAdapterBaseObject,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const onResponseHooks = [];
|
|
204
|
-
for (const plugin of (_a = options === null || options === void 0 ? void 0 : options.plugins) !== null && _a !== void 0 ? _a : []) {
|
|
205
|
-
if (plugin.onRequest) {
|
|
206
|
-
onRequestHooks.push(plugin.onRequest);
|
|
207
|
-
}
|
|
208
|
-
if (plugin.onResponse) {
|
|
209
|
-
onResponseHooks.push(plugin.onResponse);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
async function handleRequest(request, serverContext) {
|
|
213
|
-
const url = new URL(request.url, 'http://localhost');
|
|
214
|
-
let requestHandler = givenHandleRequest;
|
|
215
|
-
let response;
|
|
216
|
-
for (const onRequestHook of onRequestHooks) {
|
|
217
|
-
await onRequestHook({
|
|
218
|
-
request,
|
|
219
|
-
serverContext,
|
|
220
|
-
fetchAPI,
|
|
221
|
-
url,
|
|
222
|
-
requestHandler,
|
|
223
|
-
setRequestHandler(newRequestHandler) {
|
|
224
|
-
requestHandler = newRequestHandler;
|
|
225
|
-
},
|
|
226
|
-
endResponse(newResponse) {
|
|
227
|
-
response = newResponse;
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
if (response) {
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
if (!response) {
|
|
235
|
-
response = await requestHandler(request, serverContext);
|
|
236
|
-
}
|
|
237
|
-
for (const onResponseHook of onResponseHooks) {
|
|
238
|
-
await onResponseHook({
|
|
239
|
-
request,
|
|
240
|
-
response,
|
|
241
|
-
serverContext,
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
return response;
|
|
245
|
-
}
|
|
202
|
+
function createServerAdapter(serverAdapterBaseObject,
|
|
203
|
+
/**
|
|
204
|
+
* WHATWG Fetch spec compliant `Request` constructor.
|
|
205
|
+
*/
|
|
206
|
+
RequestCtor = Request) {
|
|
207
|
+
const handleRequest = typeof serverAdapterBaseObject === 'function'
|
|
208
|
+
? serverAdapterBaseObject
|
|
209
|
+
: serverAdapterBaseObject.handle;
|
|
246
210
|
function handleNodeRequest(nodeRequest, ...ctx) {
|
|
247
211
|
const serverContext = ctx.length > 1 ? completeAssign({}, ...ctx) : ctx[0];
|
|
248
|
-
const request = normalizeNodeRequest(nodeRequest,
|
|
212
|
+
const request = normalizeNodeRequest(nodeRequest, RequestCtor);
|
|
249
213
|
return handleRequest(request, serverContext);
|
|
250
214
|
}
|
|
251
215
|
async function requestListener(nodeRequest, serverResponse, ...ctx) {
|
|
@@ -305,9 +269,9 @@ function createServerAdapter(serverAdapterBaseObject, options) {
|
|
|
305
269
|
if (typeof input === 'string' || input instanceof URL) {
|
|
306
270
|
const [initOrCtx, ...restOfCtx] = maybeCtx;
|
|
307
271
|
if (isRequestInit(initOrCtx)) {
|
|
308
|
-
return handleRequestWithWaitUntil(new
|
|
272
|
+
return handleRequestWithWaitUntil(new RequestCtor(input, initOrCtx), ...restOfCtx);
|
|
309
273
|
}
|
|
310
|
-
return handleRequestWithWaitUntil(new
|
|
274
|
+
return handleRequestWithWaitUntil(new RequestCtor(input), ...maybeCtx);
|
|
311
275
|
}
|
|
312
276
|
return handleRequestWithWaitUntil(input, ...maybeCtx);
|
|
313
277
|
};
|
|
@@ -384,24 +348,27 @@ function createServerAdapter(serverAdapterBaseObject, options) {
|
|
|
384
348
|
// from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#copying_accessors
|
|
385
349
|
function completeAssign(target, ...sources) {
|
|
386
350
|
sources.forEach(source => {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
descriptors
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
351
|
+
if (source != null && typeof source === 'object') {
|
|
352
|
+
// modified Object.keys to Object.getOwnPropertyNames
|
|
353
|
+
// because Object.keys only returns enumerable properties
|
|
354
|
+
const descriptors = Object.getOwnPropertyNames(source).reduce((descriptors, key) => {
|
|
355
|
+
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
|
|
356
|
+
return descriptors;
|
|
357
|
+
}, {});
|
|
358
|
+
// By default, Object.assign copies enumerable Symbols, too
|
|
359
|
+
Object.getOwnPropertySymbols(source).forEach(sym => {
|
|
360
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, sym);
|
|
361
|
+
if (descriptor.enumerable) {
|
|
362
|
+
descriptors[sym] = descriptor;
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
Object.defineProperties(target, descriptors);
|
|
366
|
+
}
|
|
401
367
|
});
|
|
402
368
|
return target;
|
|
403
369
|
}
|
|
404
370
|
|
|
371
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
405
372
|
function getCORSHeadersByRequestAndOptions(request, corsOptions) {
|
|
406
373
|
var _a, _b;
|
|
407
374
|
const headers = {};
|
|
@@ -456,8 +423,7 @@ function getCORSHeadersByRequestAndOptions(request, corsOptions) {
|
|
|
456
423
|
}
|
|
457
424
|
}
|
|
458
425
|
if ((_b = corsOptions.allowedHeaders) === null || _b === void 0 ? void 0 : _b.length) {
|
|
459
|
-
headers['Access-Control-Allow-Headers'] =
|
|
460
|
-
corsOptions.allowedHeaders.join(', ');
|
|
426
|
+
headers['Access-Control-Allow-Headers'] = corsOptions.allowedHeaders.join(', ');
|
|
461
427
|
}
|
|
462
428
|
else {
|
|
463
429
|
const requestHeaders = request.headers.get('access-control-request-headers');
|
|
@@ -478,8 +444,7 @@ function getCORSHeadersByRequestAndOptions(request, corsOptions) {
|
|
|
478
444
|
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
479
445
|
}
|
|
480
446
|
if (corsOptions.exposedHeaders) {
|
|
481
|
-
headers['Access-Control-Expose-Headers'] =
|
|
482
|
-
corsOptions.exposedHeaders.join(', ');
|
|
447
|
+
headers['Access-Control-Expose-Headers'] = corsOptions.exposedHeaders.join(', ');
|
|
483
448
|
}
|
|
484
449
|
if (corsOptions.maxAge) {
|
|
485
450
|
headers['Access-Control-Max-Age'] = corsOptions.maxAge.toString();
|
|
@@ -490,8 +455,7 @@ async function getCORSResponseHeaders(request, corsOptionsFactory, serverContext
|
|
|
490
455
|
const corsOptions = await corsOptionsFactory(request, serverContext);
|
|
491
456
|
return getCORSHeadersByRequestAndOptions(request, corsOptions);
|
|
492
457
|
}
|
|
493
|
-
|
|
494
|
-
function useCORS(options) {
|
|
458
|
+
function withCORS(obj, options, ResponseCtor = Response) {
|
|
495
459
|
let corsOptionsFactory = () => ({});
|
|
496
460
|
if (options != null) {
|
|
497
461
|
if (typeof options === 'function') {
|
|
@@ -507,31 +471,35 @@ function useCORS(options) {
|
|
|
507
471
|
corsOptionsFactory = () => false;
|
|
508
472
|
}
|
|
509
473
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
});
|
|
522
|
-
endResponse(response);
|
|
523
|
-
}
|
|
524
|
-
},
|
|
525
|
-
async onResponse({ request, serverContext, response }) {
|
|
526
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
474
|
+
async function handleWithCORS(request, serverContext) {
|
|
475
|
+
let response;
|
|
476
|
+
if (request.method.toUpperCase() === 'OPTIONS') {
|
|
477
|
+
response = new ResponseCtor(null, {
|
|
478
|
+
status: 204,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
response = await obj.handle(request, serverContext);
|
|
483
|
+
}
|
|
484
|
+
if (response != null) {
|
|
527
485
|
const headers = await getCORSResponseHeaders(request, corsOptionsFactory, serverContext);
|
|
528
486
|
for (const headerName in headers) {
|
|
529
487
|
response.headers.set(headerName, headers[headerName]);
|
|
530
488
|
}
|
|
489
|
+
return response;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return new Proxy(obj, {
|
|
493
|
+
get(_, prop, receiver) {
|
|
494
|
+
if (prop === 'handle') {
|
|
495
|
+
return handleWithCORS;
|
|
496
|
+
}
|
|
497
|
+
return Reflect.get(obj, prop, receiver);
|
|
531
498
|
},
|
|
532
|
-
};
|
|
499
|
+
});
|
|
533
500
|
}
|
|
534
501
|
|
|
502
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
535
503
|
function createDefaultErrorHandler(ResponseCtor = Response) {
|
|
536
504
|
return function defaultErrorHandler(e) {
|
|
537
505
|
return new ResponseCtor(e.stack || e.message || e.toString(), {
|
|
@@ -540,23 +508,6 @@ function createDefaultErrorHandler(ResponseCtor = Response) {
|
|
|
540
508
|
});
|
|
541
509
|
};
|
|
542
510
|
}
|
|
543
|
-
function useErrorHandling(onError) {
|
|
544
|
-
return {
|
|
545
|
-
onRequest({ requestHandler, setRequestHandler, fetchAPI }) {
|
|
546
|
-
const errorHandler = onError || createDefaultErrorHandler(fetchAPI.Response);
|
|
547
|
-
setRequestHandler(async function handlerWithErrorHandling(request, serverContext) {
|
|
548
|
-
try {
|
|
549
|
-
const response = await requestHandler(request, serverContext);
|
|
550
|
-
return response;
|
|
551
|
-
}
|
|
552
|
-
catch (e) {
|
|
553
|
-
const response = await errorHandler(e, request, serverContext);
|
|
554
|
-
return response;
|
|
555
|
-
}
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
511
|
function withErrorHandling(obj, onError = createDefaultErrorHandler()) {
|
|
561
512
|
async function handleWithErrorHandling(request, ctx) {
|
|
562
513
|
try {
|
|
@@ -577,4 +528,4 @@ function withErrorHandling(obj, onError = createDefaultErrorHandler()) {
|
|
|
577
528
|
});
|
|
578
529
|
}
|
|
579
530
|
|
|
580
|
-
export { createDefaultErrorHandler, createServerAdapter, getCORSHeadersByRequestAndOptions, isAsyncIterable, isFetchEvent, isNodeRequest, isReadable, isReadableStream, isRequestInit, isServerResponse, normalizeNodeRequest, sendNodeResponse,
|
|
531
|
+
export { createDefaultErrorHandler, createServerAdapter, getCORSHeadersByRequestAndOptions, isAsyncIterable, isFetchEvent, isNodeRequest, isReadable, isReadableStream, isRequestInit, isServerResponse, normalizeNodeRequest, sendNodeResponse, withCORS, withErrorHandling };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ServerAdapterBaseObject } from '../types';
|
|
2
2
|
export type CORSOptions = {
|
|
3
3
|
origin?: string[] | string;
|
|
4
4
|
methods?: string[];
|
|
@@ -7,7 +7,7 @@ export type CORSOptions = {
|
|
|
7
7
|
credentials?: boolean;
|
|
8
8
|
maxAge?: number;
|
|
9
9
|
} | false;
|
|
10
|
-
export type
|
|
10
|
+
export type WithCORSOptions<TServerContext> = CORSOptionsFactory<TServerContext> | CORSOptions | boolean;
|
|
11
11
|
export type CORSOptionsFactory<TServerContext> = (request: Request, ...args: {} extends TServerContext ? [serverContext?: TServerContext | undefined] : [serverContext: TServerContext]) => Promise<CORSOptions> | CORSOptions;
|
|
12
12
|
export declare function getCORSHeadersByRequestAndOptions(request: Request, corsOptions: CORSOptions): Record<string, string>;
|
|
13
|
-
export declare function
|
|
13
|
+
export declare function withCORS<TServerContext = {}, TBaseObject extends ServerAdapterBaseObject<TServerContext> = ServerAdapterBaseObject<TServerContext>>(obj: TBaseObject, options: WithCORSOptions<TServerContext>, ResponseCtor?: typeof Response): TBaseObject;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ServerAdapterBaseObject } from '../types';
|
|
2
|
+
export declare function createDefaultErrorHandler<TServerContext = {}>(ResponseCtor?: typeof Response): ErrorHandler<TServerContext>;
|
|
3
|
+
export type ErrorHandler<TServerContext> = (e: any, request: Request, ctx: TServerContext) => Response | Promise<Response>;
|
|
4
|
+
export declare function withErrorHandling<TServerContext = {}, TBaseObject extends ServerAdapterBaseObject<TServerContext> = ServerAdapterBaseObject<TServerContext>>(obj: TBaseObject, onError?: ErrorHandler<TServerContext>): TBaseObject;
|
package/package.json
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whatwg-node/server",
|
|
3
|
-
"version": "0.6.0
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Fetch API compliant HTTP Server adapter",
|
|
5
5
|
"sideEffects": false,
|
|
6
|
-
"peerDependencies": {
|
|
7
|
-
"@types/node": "^18.0.6"
|
|
8
|
-
},
|
|
9
6
|
"dependencies": {
|
|
10
|
-
"@whatwg-node/fetch": "0.6.
|
|
7
|
+
"@whatwg-node/fetch": "0.6.6",
|
|
11
8
|
"tslib": "^2.3.1"
|
|
12
9
|
},
|
|
13
10
|
"repository": {
|
package/types.d.ts
CHANGED
|
@@ -13,11 +13,11 @@ export interface ServerAdapterBaseObject<TServerContext, THandleRequest extends
|
|
|
13
13
|
*/
|
|
14
14
|
handle: THandleRequest;
|
|
15
15
|
}
|
|
16
|
-
export interface ServerAdapterObject<TServerContext
|
|
16
|
+
export interface ServerAdapterObject<TServerContext, TBaseObject extends ServerAdapterBaseObject<TServerContext, ServerAdapterRequestHandler<TServerContext>>> extends EventListenerObject {
|
|
17
17
|
/**
|
|
18
18
|
* A basic request listener that takes a `Request` with the server context and returns a `Response`.
|
|
19
19
|
*/
|
|
20
|
-
handleRequest:
|
|
20
|
+
handleRequest: TBaseObject['handle'];
|
|
21
21
|
/**
|
|
22
22
|
* WHATWG Fetch spec compliant `fetch` function that can be used for testing purposes.
|
|
23
23
|
*/
|
|
@@ -46,11 +46,13 @@ export interface ServerAdapterObject<TServerContext> extends EventListenerObject
|
|
|
46
46
|
request: Request;
|
|
47
47
|
} & Partial<TServerContext>, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response;
|
|
48
48
|
}
|
|
49
|
-
export type ServerAdapter<TServerContext, TBaseObject extends ServerAdapterBaseObject<TServerContext>> = TBaseObject & ServerAdapterObject<TServerContext>['handle'] & ServerAdapterObject<TServerContext>;
|
|
50
|
-
export type ServerAdapterRequestHandler<TServerContext> = (request: Request, ctx: TServerContext) => Promise<Response> | Response;
|
|
51
|
-
export type
|
|
49
|
+
export type ServerAdapter<TServerContext, TBaseObject extends ServerAdapterBaseObject<TServerContext>> = TBaseObject & ServerAdapterObject<TServerContext, TBaseObject>['handle'] & ServerAdapterObject<TServerContext, TBaseObject>;
|
|
50
|
+
export type ServerAdapterRequestHandler<TServerContext> = (request: Request, ctx: ServerAdapterContext<TServerContext>) => Promise<Response> | Response;
|
|
51
|
+
export type ServerAdapterNodeContext = {
|
|
52
52
|
req: NodeRequest;
|
|
53
53
|
res: NodeResponse;
|
|
54
|
-
waitUntil(promise: Promise<void> | void): void;
|
|
55
54
|
};
|
|
56
|
-
export type
|
|
55
|
+
export type WaitUntilFn = (promise: Promise<void> | void) => void;
|
|
56
|
+
export type ServerAdapterContext<TServerContext> = TServerContext & {
|
|
57
|
+
waitUntil: WaitUntilFn;
|
|
58
|
+
};
|
package/utils.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
/// <reference types="node" />
|
|
4
4
|
/// <reference types="node" />
|
|
5
|
-
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
5
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
6
|
import type { Http2ServerRequest, Http2ServerResponse } from 'node:http2';
|
|
7
7
|
import type { Socket } from 'node:net';
|
|
8
8
|
import type { Readable } from 'node:stream';
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { CookieChangeEvent } from "./CookieChangeEvent";
|
|
2
|
-
import { CookieStoreGetOptions, Cookie, CookieListItem, CookieStoreDeleteOptions } from "./types";
|
|
3
|
-
export declare class CookieStore extends EventTarget {
|
|
4
|
-
cookieString: string;
|
|
5
|
-
onchange?: (event: CookieChangeEvent) => void;
|
|
6
|
-
get [Symbol.toStringTag](): 'CookieStore';
|
|
7
|
-
constructor(cookieString: string);
|
|
8
|
-
get(init?: CookieStoreGetOptions['name'] | CookieStoreGetOptions): Promise<Cookie | undefined>;
|
|
9
|
-
set(init: CookieListItem | string, possibleValue?: string): Promise<void>;
|
|
10
|
-
getAll(init?: CookieStoreGetOptions['name'] | CookieStoreGetOptions): Promise<Cookie[]>;
|
|
11
|
-
delete(init: CookieStoreDeleteOptions['name'] | CookieStoreDeleteOptions): Promise<void>;
|
|
12
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Cookie } from './types';
|
|
2
|
-
export interface ParseOptions {
|
|
3
|
-
decode?: boolean;
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* Parse a cookie header.
|
|
7
|
-
*
|
|
8
|
-
* Parse the given cookie header string into an object
|
|
9
|
-
* The object has the various cookies as keys(names) => values
|
|
10
|
-
*/
|
|
11
|
-
export declare function parse(str: string, options?: ParseOptions): Cookie[];
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
export interface Cookie {
|
|
2
|
-
domain?: string;
|
|
3
|
-
expires?: number;
|
|
4
|
-
name: string;
|
|
5
|
-
path?: string;
|
|
6
|
-
secure?: boolean;
|
|
7
|
-
sameSite?: CookieSameSite;
|
|
8
|
-
value: string;
|
|
9
|
-
}
|
|
10
|
-
export interface CookieStoreDeleteOptions {
|
|
11
|
-
name: string;
|
|
12
|
-
domain?: string;
|
|
13
|
-
path?: string;
|
|
14
|
-
}
|
|
15
|
-
export interface CookieStoreGetOptions {
|
|
16
|
-
name?: string;
|
|
17
|
-
url?: string;
|
|
18
|
-
}
|
|
19
|
-
export declare enum CookieSameSite {
|
|
20
|
-
strict = "strict",
|
|
21
|
-
lax = "lax",
|
|
22
|
-
none = "none"
|
|
23
|
-
}
|
|
24
|
-
export interface CookieListItem {
|
|
25
|
-
name?: string;
|
|
26
|
-
value?: string;
|
|
27
|
-
domain: string | null;
|
|
28
|
-
path?: string;
|
|
29
|
-
expires: Date | number | null;
|
|
30
|
-
secure?: boolean;
|
|
31
|
-
sameSite?: CookieSameSite;
|
|
32
|
-
}
|
|
33
|
-
export type CookieList = CookieListItem[];
|
|
34
|
-
export interface CookieChangeEventInit extends EventInit {
|
|
35
|
-
changed: CookieList;
|
|
36
|
-
deleted: CookieList;
|
|
37
|
-
}
|
package/plugins/types.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { DefaultServerAdapterContext, FetchAPI, ServerAdapterRequestHandler } from "../types";
|
|
2
|
-
export interface ServerAdapterPlugin<TServerContext = DefaultServerAdapterContext> {
|
|
3
|
-
onRequest?: OnRequestHook<TServerContext>;
|
|
4
|
-
onResponse?: OnResponseHook<TServerContext>;
|
|
5
|
-
}
|
|
6
|
-
export type OnRequestHook<TServerContext> = (payload: OnRequestEventPayload<TServerContext>) => Promise<void> | void;
|
|
7
|
-
export interface OnRequestEventPayload<TServerContext> {
|
|
8
|
-
request: Request;
|
|
9
|
-
serverContext: TServerContext | undefined;
|
|
10
|
-
fetchAPI: FetchAPI;
|
|
11
|
-
requestHandler: ServerAdapterRequestHandler<TServerContext>;
|
|
12
|
-
setRequestHandler(newRequestHandler: ServerAdapterRequestHandler<TServerContext>): void;
|
|
13
|
-
endResponse(response: Response): void;
|
|
14
|
-
url: URL;
|
|
15
|
-
}
|
|
16
|
-
export type OnResponseHook<TServerContext> = (payload: OnResponseEventPayload<TServerContext>) => Promise<void> | void;
|
|
17
|
-
export interface OnResponseEventPayload<TServerContext> {
|
|
18
|
-
request: Request;
|
|
19
|
-
serverContext: TServerContext | undefined;
|
|
20
|
-
response: Response;
|
|
21
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { DefaultServerAdapterContext, ServerAdapterBaseObject } from '../types';
|
|
2
|
-
import { ServerAdapterPlugin } from './types';
|
|
3
|
-
export declare function createDefaultErrorHandler<TServerContext = DefaultServerAdapterContext>(ResponseCtor?: typeof Response): ErrorHandler<TServerContext>;
|
|
4
|
-
export type ErrorHandler<TServerContext> = (e: any, request: Request, ctx: TServerContext) => Response | Promise<Response>;
|
|
5
|
-
export declare function useErrorHandling<TServerContext>(onError?: ErrorHandler<TServerContext>): ServerAdapterPlugin<TServerContext>;
|
|
6
|
-
export declare function withErrorHandling<TServerContext = DefaultServerAdapterContext, TBaseObject extends ServerAdapterBaseObject<TServerContext> = ServerAdapterBaseObject<TServerContext>>(obj: TBaseObject, onError?: ErrorHandler<TServerContext>): TBaseObject;
|