@whatwg-node/server 0.9.1 → 0.9.2-alpha-20230702143635-7d0af11
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/cjs/createServerAdapter.js +66 -135
- package/cjs/index.js +3 -1
- package/cjs/internal-plugins/useFetchEvent.js +36 -0
- package/cjs/internal-plugins/useNodeAdapter.js +236 -0
- package/cjs/{uwebsockets.js → internal-plugins/useUWSAdapter.js} +34 -1
- package/cjs/utils.js +9 -206
- package/esm/createServerAdapter.js +67 -136
- package/esm/index.js +3 -1
- package/esm/internal-plugins/useFetchEvent.js +31 -0
- package/esm/internal-plugins/useNodeAdapter.js +227 -0
- package/esm/{uwebsockets.js → internal-plugins/useUWSAdapter.js} +32 -0
- package/esm/utils.js +7 -199
- package/package.json +1 -1
- package/typings/index.d.cts +3 -1
- package/typings/index.d.ts +3 -1
- package/typings/internal-plugins/useFetchEvent.d.cts +4 -0
- package/typings/internal-plugins/useFetchEvent.d.ts +4 -0
- package/typings/internal-plugins/useNodeAdapter.d.cts +34 -0
- package/typings/internal-plugins/useNodeAdapter.d.ts +34 -0
- package/typings/{uwebsockets.d.ts → internal-plugins/useUWSAdapter.d.cts} +7 -1
- package/typings/{uwebsockets.d.cts → internal-plugins/useUWSAdapter.d.ts} +7 -1
- package/typings/plugins/types.d.cts +13 -0
- package/typings/plugins/types.d.ts +13 -0
- package/typings/types.d.cts +3 -8
- package/typings/types.d.ts +3 -8
- package/typings/utils.d.cts +1 -29
- package/typings/utils.d.ts +1 -29
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { URL } from '@whatwg-node/fetch';
|
|
2
|
+
import { completeAssign, isAsyncIterable } from '../utils';
|
|
3
|
+
export function useNodeAdapter() {
|
|
4
|
+
const nodeResponseMap = new WeakMap();
|
|
5
|
+
return {
|
|
6
|
+
onRequestAdapt({ args: [req, res, ...restOfCtx], setRequest, setServerContext, fetchAPI }) {
|
|
7
|
+
if (isNodeRequest(req)) {
|
|
8
|
+
const defaultServerContext = {
|
|
9
|
+
req,
|
|
10
|
+
};
|
|
11
|
+
const request = normalizeNodeRequest(req, fetchAPI.Request);
|
|
12
|
+
setRequest(request);
|
|
13
|
+
let ctxParams = restOfCtx;
|
|
14
|
+
if (isServerResponse(res)) {
|
|
15
|
+
defaultServerContext.res = res;
|
|
16
|
+
nodeResponseMap.set(request, res);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
ctxParams = [res, ...restOfCtx];
|
|
20
|
+
}
|
|
21
|
+
const serverContext = ctxParams.length > 0 ? completeAssign(...ctxParams) : defaultServerContext;
|
|
22
|
+
setServerContext(serverContext);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
onResponse({ request, response }) {
|
|
26
|
+
const nodeResponse = nodeResponseMap.get(request);
|
|
27
|
+
if (nodeResponse) {
|
|
28
|
+
return sendNodeResponse(response, nodeResponse);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function isReadable(stream) {
|
|
34
|
+
return stream.read != null;
|
|
35
|
+
}
|
|
36
|
+
export function isNodeRequest(request) {
|
|
37
|
+
return isReadable(request);
|
|
38
|
+
}
|
|
39
|
+
export function isServerResponse(stream) {
|
|
40
|
+
// Check all used functions are defined
|
|
41
|
+
return (stream != null &&
|
|
42
|
+
stream.setHeader != null &&
|
|
43
|
+
stream.end != null &&
|
|
44
|
+
stream.once != null &&
|
|
45
|
+
stream.write != null);
|
|
46
|
+
}
|
|
47
|
+
function getPort(nodeRequest) {
|
|
48
|
+
if (nodeRequest.socket?.localPort) {
|
|
49
|
+
return nodeRequest.socket?.localPort;
|
|
50
|
+
}
|
|
51
|
+
const hostInHeader = nodeRequest.headers?.[':authority'] || nodeRequest.headers?.host;
|
|
52
|
+
const portInHeader = hostInHeader?.split(':')?.[1];
|
|
53
|
+
if (portInHeader) {
|
|
54
|
+
return portInHeader;
|
|
55
|
+
}
|
|
56
|
+
return 80;
|
|
57
|
+
}
|
|
58
|
+
function getHostnameWithPort(nodeRequest) {
|
|
59
|
+
if (nodeRequest.headers?.[':authority']) {
|
|
60
|
+
return nodeRequest.headers?.[':authority'];
|
|
61
|
+
}
|
|
62
|
+
if (nodeRequest.headers?.host) {
|
|
63
|
+
return nodeRequest.headers?.host;
|
|
64
|
+
}
|
|
65
|
+
const port = getPort(nodeRequest);
|
|
66
|
+
if (nodeRequest.hostname) {
|
|
67
|
+
return nodeRequest.hostname + ':' + port;
|
|
68
|
+
}
|
|
69
|
+
const localIp = nodeRequest.socket?.localAddress;
|
|
70
|
+
if (localIp && !localIp?.includes('::') && !localIp?.includes('ffff')) {
|
|
71
|
+
return `${localIp}:${port}`;
|
|
72
|
+
}
|
|
73
|
+
return 'localhost';
|
|
74
|
+
}
|
|
75
|
+
function buildFullUrl(nodeRequest) {
|
|
76
|
+
const hostnameWithPort = getHostnameWithPort(nodeRequest);
|
|
77
|
+
const protocol = nodeRequest.protocol || 'http';
|
|
78
|
+
const endpoint = nodeRequest.originalUrl || nodeRequest.url || '/graphql';
|
|
79
|
+
return `${protocol}://${hostnameWithPort}${endpoint}`;
|
|
80
|
+
}
|
|
81
|
+
function isRequestBody(body) {
|
|
82
|
+
const stringTag = body[Symbol.toStringTag];
|
|
83
|
+
if (typeof body === 'string' ||
|
|
84
|
+
stringTag === 'Uint8Array' ||
|
|
85
|
+
stringTag === 'Blob' ||
|
|
86
|
+
stringTag === 'FormData' ||
|
|
87
|
+
stringTag === 'URLSearchParams' ||
|
|
88
|
+
isAsyncIterable(body)) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
export function normalizeNodeRequest(nodeRequest, RequestCtor) {
|
|
94
|
+
const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest;
|
|
95
|
+
let fullUrl = buildFullUrl(rawRequest);
|
|
96
|
+
if (nodeRequest.query) {
|
|
97
|
+
const url = new URL(fullUrl);
|
|
98
|
+
for (const key in nodeRequest.query) {
|
|
99
|
+
url.searchParams.set(key, nodeRequest.query[key]);
|
|
100
|
+
}
|
|
101
|
+
fullUrl = url.toString();
|
|
102
|
+
}
|
|
103
|
+
if (nodeRequest.method === 'GET' || nodeRequest.method === 'HEAD') {
|
|
104
|
+
return new RequestCtor(fullUrl, {
|
|
105
|
+
method: nodeRequest.method,
|
|
106
|
+
headers: nodeRequest.headers,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Some Node server frameworks like Serverless Express sends a dummy object with body but as a Buffer not string
|
|
111
|
+
* so we do those checks to see is there something we can use directly as BodyInit
|
|
112
|
+
* because the presence of body means the request stream is already consumed and,
|
|
113
|
+
* rawRequest cannot be used as BodyInit/ReadableStream by Fetch API in this case.
|
|
114
|
+
*/
|
|
115
|
+
const maybeParsedBody = nodeRequest.body;
|
|
116
|
+
if (maybeParsedBody != null && Object.keys(maybeParsedBody).length > 0) {
|
|
117
|
+
if (isRequestBody(maybeParsedBody)) {
|
|
118
|
+
return new RequestCtor(fullUrl, {
|
|
119
|
+
method: nodeRequest.method,
|
|
120
|
+
headers: nodeRequest.headers,
|
|
121
|
+
body: maybeParsedBody,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const request = new RequestCtor(fullUrl, {
|
|
125
|
+
method: nodeRequest.method,
|
|
126
|
+
headers: nodeRequest.headers,
|
|
127
|
+
});
|
|
128
|
+
if (!request.headers.get('content-type')?.includes('json')) {
|
|
129
|
+
request.headers.set('content-type', 'application/json; charset=utf-8');
|
|
130
|
+
}
|
|
131
|
+
return new Proxy(request, {
|
|
132
|
+
get: (target, prop, receiver) => {
|
|
133
|
+
switch (prop) {
|
|
134
|
+
case 'json':
|
|
135
|
+
return async () => maybeParsedBody;
|
|
136
|
+
case 'text':
|
|
137
|
+
return async () => JSON.stringify(maybeParsedBody);
|
|
138
|
+
default:
|
|
139
|
+
return Reflect.get(target, prop, receiver);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
// perf: instead of spreading the object, we can just pass it as is and it performs better
|
|
145
|
+
return new RequestCtor(fullUrl, {
|
|
146
|
+
method: nodeRequest.method,
|
|
147
|
+
headers: nodeRequest.headers,
|
|
148
|
+
body: rawRequest,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function configureSocket(rawRequest) {
|
|
152
|
+
rawRequest?.socket?.setTimeout?.(0);
|
|
153
|
+
rawRequest?.socket?.setNoDelay?.(true);
|
|
154
|
+
rawRequest?.socket?.setKeepAlive?.(true);
|
|
155
|
+
}
|
|
156
|
+
function endResponse(serverResponse) {
|
|
157
|
+
// @ts-expect-error Avoid arguments adaptor trampoline https://v8.dev/blog/adaptor-frame
|
|
158
|
+
serverResponse.end(null, null, null);
|
|
159
|
+
}
|
|
160
|
+
function getHeaderPairs(headers) {
|
|
161
|
+
const headerPairs = new Map();
|
|
162
|
+
headers.forEach((value, key) => {
|
|
163
|
+
let headerValues = headerPairs.get(key);
|
|
164
|
+
if (headerValues === undefined) {
|
|
165
|
+
headerValues = [];
|
|
166
|
+
headerPairs.set(key, headerValues);
|
|
167
|
+
}
|
|
168
|
+
if (key === 'set-cookie') {
|
|
169
|
+
const setCookies = headers.getSetCookie?.();
|
|
170
|
+
if (setCookies) {
|
|
171
|
+
setCookies.forEach(setCookie => {
|
|
172
|
+
headerValues.push(setCookie);
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
headerValues.push(value);
|
|
178
|
+
});
|
|
179
|
+
return headerPairs;
|
|
180
|
+
}
|
|
181
|
+
async function sendAsyncIterable(serverResponse, asyncIterable) {
|
|
182
|
+
for await (const chunk of asyncIterable) {
|
|
183
|
+
if (!serverResponse
|
|
184
|
+
// @ts-expect-error http and http2 writes are actually compatible
|
|
185
|
+
.write(chunk)) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
endResponse(serverResponse);
|
|
190
|
+
}
|
|
191
|
+
export function sendNodeResponse(fetchResponse, serverResponse) {
|
|
192
|
+
const headerPairs = getHeaderPairs(fetchResponse.headers);
|
|
193
|
+
serverResponse.writeHead(fetchResponse.status, fetchResponse.statusText, Object.fromEntries(headerPairs.entries()));
|
|
194
|
+
// Optimizations for node-fetch
|
|
195
|
+
if (fetchResponse.bodyType === 'Buffer' ||
|
|
196
|
+
fetchResponse.bodyType === 'String' ||
|
|
197
|
+
fetchResponse.bodyType === 'Uint8Array') {
|
|
198
|
+
// @ts-expect-error http and http2 writes are actually compatible
|
|
199
|
+
serverResponse.write(fetchResponse.bodyInit);
|
|
200
|
+
endResponse(serverResponse);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Other fetch implementations
|
|
204
|
+
const fetchBody = fetchResponse.body;
|
|
205
|
+
if (fetchBody == null) {
|
|
206
|
+
endResponse(serverResponse);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (fetchBody[Symbol.toStringTag] === 'Uint8Array') {
|
|
210
|
+
serverResponse
|
|
211
|
+
// @ts-expect-error http and http2 writes are actually compatible
|
|
212
|
+
.write(fetchBody);
|
|
213
|
+
endResponse(serverResponse);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
configureSocket(serverResponse.req);
|
|
217
|
+
if (isReadable(fetchBody)) {
|
|
218
|
+
serverResponse.once('close', () => {
|
|
219
|
+
fetchBody.destroy();
|
|
220
|
+
});
|
|
221
|
+
fetchBody.pipe(serverResponse);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (isAsyncIterable(fetchBody)) {
|
|
225
|
+
return sendAsyncIterable(serverResponse, fetchBody);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
import { completeAssign } from '../utils.js';
|
|
2
|
+
export function useUWSAdapter() {
|
|
3
|
+
const uwsResponseMap = new WeakMap();
|
|
4
|
+
return {
|
|
5
|
+
onRequestAdapt({ args: [res, req, ...restOfCtx], setRequest, setServerContext, fetchAPI }) {
|
|
6
|
+
if (isUWSResponse(res)) {
|
|
7
|
+
const request = getRequestFromUWSRequest({
|
|
8
|
+
req: req,
|
|
9
|
+
res,
|
|
10
|
+
fetchAPI,
|
|
11
|
+
});
|
|
12
|
+
uwsResponseMap.set(request, res);
|
|
13
|
+
setRequest(request);
|
|
14
|
+
const defaultServerContext = {
|
|
15
|
+
req,
|
|
16
|
+
res,
|
|
17
|
+
};
|
|
18
|
+
const serverContext = restOfCtx.length > 0 ? completeAssign(...restOfCtx) : defaultServerContext;
|
|
19
|
+
setServerContext(serverContext);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
onResponse({ request, response }) {
|
|
23
|
+
const res = uwsResponseMap.get(request);
|
|
24
|
+
if (res) {
|
|
25
|
+
sendResponseToUwsOpts({
|
|
26
|
+
res,
|
|
27
|
+
response,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
1
33
|
export function isUWSResponse(res) {
|
|
2
34
|
return !!res.onData;
|
|
3
35
|
}
|
package/esm/utils.js
CHANGED
|
@@ -1,208 +1,9 @@
|
|
|
1
|
-
import { URL } from '@whatwg-node/fetch';
|
|
2
1
|
export function isAsyncIterable(body) {
|
|
3
2
|
return (body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function');
|
|
4
3
|
}
|
|
5
|
-
function getPort(nodeRequest) {
|
|
6
|
-
if (nodeRequest.socket?.localPort) {
|
|
7
|
-
return nodeRequest.socket?.localPort;
|
|
8
|
-
}
|
|
9
|
-
const hostInHeader = nodeRequest.headers?.[':authority'] || nodeRequest.headers?.host;
|
|
10
|
-
const portInHeader = hostInHeader?.split(':')?.[1];
|
|
11
|
-
if (portInHeader) {
|
|
12
|
-
return portInHeader;
|
|
13
|
-
}
|
|
14
|
-
return 80;
|
|
15
|
-
}
|
|
16
|
-
function getHostnameWithPort(nodeRequest) {
|
|
17
|
-
if (nodeRequest.headers?.[':authority']) {
|
|
18
|
-
return nodeRequest.headers?.[':authority'];
|
|
19
|
-
}
|
|
20
|
-
if (nodeRequest.headers?.host) {
|
|
21
|
-
return nodeRequest.headers?.host;
|
|
22
|
-
}
|
|
23
|
-
const port = getPort(nodeRequest);
|
|
24
|
-
if (nodeRequest.hostname) {
|
|
25
|
-
return nodeRequest.hostname + ':' + port;
|
|
26
|
-
}
|
|
27
|
-
const localIp = nodeRequest.socket?.localAddress;
|
|
28
|
-
if (localIp && !localIp?.includes('::') && !localIp?.includes('ffff')) {
|
|
29
|
-
return `${localIp}:${port}`;
|
|
30
|
-
}
|
|
31
|
-
return 'localhost';
|
|
32
|
-
}
|
|
33
|
-
function buildFullUrl(nodeRequest) {
|
|
34
|
-
const hostnameWithPort = getHostnameWithPort(nodeRequest);
|
|
35
|
-
const protocol = nodeRequest.protocol || 'http';
|
|
36
|
-
const endpoint = nodeRequest.originalUrl || nodeRequest.url || '/graphql';
|
|
37
|
-
return `${protocol}://${hostnameWithPort}${endpoint}`;
|
|
38
|
-
}
|
|
39
|
-
function isRequestBody(body) {
|
|
40
|
-
const stringTag = body[Symbol.toStringTag];
|
|
41
|
-
if (typeof body === 'string' ||
|
|
42
|
-
stringTag === 'Uint8Array' ||
|
|
43
|
-
stringTag === 'Blob' ||
|
|
44
|
-
stringTag === 'FormData' ||
|
|
45
|
-
stringTag === 'URLSearchParams' ||
|
|
46
|
-
isAsyncIterable(body)) {
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
export function normalizeNodeRequest(nodeRequest, RequestCtor) {
|
|
52
|
-
const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest;
|
|
53
|
-
let fullUrl = buildFullUrl(rawRequest);
|
|
54
|
-
if (nodeRequest.query) {
|
|
55
|
-
const url = new URL(fullUrl);
|
|
56
|
-
for (const key in nodeRequest.query) {
|
|
57
|
-
url.searchParams.set(key, nodeRequest.query[key]);
|
|
58
|
-
}
|
|
59
|
-
fullUrl = url.toString();
|
|
60
|
-
}
|
|
61
|
-
if (nodeRequest.method === 'GET' || nodeRequest.method === 'HEAD') {
|
|
62
|
-
return new RequestCtor(fullUrl, {
|
|
63
|
-
method: nodeRequest.method,
|
|
64
|
-
headers: nodeRequest.headers,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Some Node server frameworks like Serverless Express sends a dummy object with body but as a Buffer not string
|
|
69
|
-
* so we do those checks to see is there something we can use directly as BodyInit
|
|
70
|
-
* because the presence of body means the request stream is already consumed and,
|
|
71
|
-
* rawRequest cannot be used as BodyInit/ReadableStream by Fetch API in this case.
|
|
72
|
-
*/
|
|
73
|
-
const maybeParsedBody = nodeRequest.body;
|
|
74
|
-
if (maybeParsedBody != null && Object.keys(maybeParsedBody).length > 0) {
|
|
75
|
-
if (isRequestBody(maybeParsedBody)) {
|
|
76
|
-
return new RequestCtor(fullUrl, {
|
|
77
|
-
method: nodeRequest.method,
|
|
78
|
-
headers: nodeRequest.headers,
|
|
79
|
-
body: maybeParsedBody,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
const request = new RequestCtor(fullUrl, {
|
|
83
|
-
method: nodeRequest.method,
|
|
84
|
-
headers: nodeRequest.headers,
|
|
85
|
-
});
|
|
86
|
-
if (!request.headers.get('content-type')?.includes('json')) {
|
|
87
|
-
request.headers.set('content-type', 'application/json; charset=utf-8');
|
|
88
|
-
}
|
|
89
|
-
return new Proxy(request, {
|
|
90
|
-
get: (target, prop, receiver) => {
|
|
91
|
-
switch (prop) {
|
|
92
|
-
case 'json':
|
|
93
|
-
return async () => maybeParsedBody;
|
|
94
|
-
case 'text':
|
|
95
|
-
return async () => JSON.stringify(maybeParsedBody);
|
|
96
|
-
default:
|
|
97
|
-
return Reflect.get(target, prop, receiver);
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
// perf: instead of spreading the object, we can just pass it as is and it performs better
|
|
103
|
-
return new RequestCtor(fullUrl, {
|
|
104
|
-
method: nodeRequest.method,
|
|
105
|
-
headers: nodeRequest.headers,
|
|
106
|
-
body: rawRequest,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
export function isReadable(stream) {
|
|
110
|
-
return stream.read != null;
|
|
111
|
-
}
|
|
112
|
-
export function isNodeRequest(request) {
|
|
113
|
-
return isReadable(request);
|
|
114
|
-
}
|
|
115
|
-
export function isServerResponse(stream) {
|
|
116
|
-
// Check all used functions are defined
|
|
117
|
-
return (stream != null &&
|
|
118
|
-
stream.setHeader != null &&
|
|
119
|
-
stream.end != null &&
|
|
120
|
-
stream.once != null &&
|
|
121
|
-
stream.write != null);
|
|
122
|
-
}
|
|
123
4
|
export function isReadableStream(stream) {
|
|
124
5
|
return stream != null && stream.getReader != null;
|
|
125
6
|
}
|
|
126
|
-
export function isFetchEvent(event) {
|
|
127
|
-
return event != null && event.request != null && event.respondWith != null;
|
|
128
|
-
}
|
|
129
|
-
function configureSocket(rawRequest) {
|
|
130
|
-
rawRequest?.socket?.setTimeout?.(0);
|
|
131
|
-
rawRequest?.socket?.setNoDelay?.(true);
|
|
132
|
-
rawRequest?.socket?.setKeepAlive?.(true);
|
|
133
|
-
}
|
|
134
|
-
function endResponse(serverResponse) {
|
|
135
|
-
// @ts-expect-error Avoid arguments adaptor trampoline https://v8.dev/blog/adaptor-frame
|
|
136
|
-
serverResponse.end(null, null, null);
|
|
137
|
-
}
|
|
138
|
-
function getHeaderPairs(headers) {
|
|
139
|
-
const headerPairs = new Map();
|
|
140
|
-
headers.forEach((value, key) => {
|
|
141
|
-
let headerValues = headerPairs.get(key);
|
|
142
|
-
if (headerValues === undefined) {
|
|
143
|
-
headerValues = [];
|
|
144
|
-
headerPairs.set(key, headerValues);
|
|
145
|
-
}
|
|
146
|
-
if (key === 'set-cookie') {
|
|
147
|
-
const setCookies = headers.getSetCookie?.();
|
|
148
|
-
if (setCookies) {
|
|
149
|
-
setCookies.forEach(setCookie => {
|
|
150
|
-
headerValues.push(setCookie);
|
|
151
|
-
});
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
headerValues.push(value);
|
|
156
|
-
});
|
|
157
|
-
return headerPairs;
|
|
158
|
-
}
|
|
159
|
-
async function sendAsyncIterable(serverResponse, asyncIterable) {
|
|
160
|
-
for await (const chunk of asyncIterable) {
|
|
161
|
-
if (!serverResponse
|
|
162
|
-
// @ts-expect-error http and http2 writes are actually compatible
|
|
163
|
-
.write(chunk)) {
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
endResponse(serverResponse);
|
|
168
|
-
}
|
|
169
|
-
export function sendNodeResponse(fetchResponse, serverResponse, nodeRequest) {
|
|
170
|
-
const headerPairs = getHeaderPairs(fetchResponse.headers);
|
|
171
|
-
serverResponse.writeHead(fetchResponse.status, fetchResponse.statusText, Object.fromEntries(headerPairs.entries()));
|
|
172
|
-
// Optimizations for node-fetch
|
|
173
|
-
if (fetchResponse.bodyType === 'Buffer' ||
|
|
174
|
-
fetchResponse.bodyType === 'String' ||
|
|
175
|
-
fetchResponse.bodyType === 'Uint8Array') {
|
|
176
|
-
// @ts-expect-error http and http2 writes are actually compatible
|
|
177
|
-
serverResponse.write(fetchResponse.bodyInit);
|
|
178
|
-
endResponse(serverResponse);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
// Other fetch implementations
|
|
182
|
-
const fetchBody = fetchResponse.body;
|
|
183
|
-
if (fetchBody == null) {
|
|
184
|
-
endResponse(serverResponse);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
if (fetchBody[Symbol.toStringTag] === 'Uint8Array') {
|
|
188
|
-
serverResponse
|
|
189
|
-
// @ts-expect-error http and http2 writes are actually compatible
|
|
190
|
-
.write(fetchBody);
|
|
191
|
-
endResponse(serverResponse);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
configureSocket(nodeRequest);
|
|
195
|
-
if (isReadable(fetchBody)) {
|
|
196
|
-
serverResponse.once('close', () => {
|
|
197
|
-
fetchBody.destroy();
|
|
198
|
-
});
|
|
199
|
-
fetchBody.pipe(serverResponse);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
if (isAsyncIterable(fetchBody)) {
|
|
203
|
-
return sendAsyncIterable(serverResponse, fetchBody);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
7
|
export function isRequestInit(val) {
|
|
207
8
|
return (val != null &&
|
|
208
9
|
typeof val === 'object' &&
|
|
@@ -241,3 +42,10 @@ export function completeAssign(...args) {
|
|
|
241
42
|
});
|
|
242
43
|
return target;
|
|
243
44
|
}
|
|
45
|
+
export function addWaitUntil(serverContext, waitUntilPromises) {
|
|
46
|
+
serverContext['waitUntil'] = function (promise) {
|
|
47
|
+
if (promise != null) {
|
|
48
|
+
waitUntilPromises.push(promise);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
package/package.json
CHANGED
package/typings/index.d.cts
CHANGED
|
@@ -4,5 +4,7 @@ export * from './utils.cjs';
|
|
|
4
4
|
export * from './plugins/types.cjs';
|
|
5
5
|
export * from './plugins/useCors.cjs';
|
|
6
6
|
export * from './plugins/useErrorHandling.cjs';
|
|
7
|
-
export * from './
|
|
7
|
+
export * from './internal-plugins/useFetchEvent.cjs';
|
|
8
|
+
export * from './internal-plugins/useNodeAdapter.cjs';
|
|
9
|
+
export * from './internal-plugins/useUWSAdapter.cjs';
|
|
8
10
|
export { Response } from '@whatwg-node/fetch';
|
package/typings/index.d.ts
CHANGED
|
@@ -4,5 +4,7 @@ export * from './utils.js';
|
|
|
4
4
|
export * from './plugins/types.js';
|
|
5
5
|
export * from './plugins/useCors.js';
|
|
6
6
|
export * from './plugins/useErrorHandling.js';
|
|
7
|
-
export * from './
|
|
7
|
+
export * from './internal-plugins/useFetchEvent.js';
|
|
8
|
+
export * from './internal-plugins/useNodeAdapter.js';
|
|
9
|
+
export * from './internal-plugins/useUWSAdapter.js';
|
|
8
10
|
export { Response } from '@whatwg-node/fetch';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
/// <reference types="node" />
|
|
4
|
+
/// <reference types="node" />
|
|
5
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
|
+
import { Http2ServerRequest, Http2ServerResponse } from 'node:http2';
|
|
7
|
+
import { Socket } from 'node:net';
|
|
8
|
+
import type { Readable } from 'node:stream';
|
|
9
|
+
import { ServerAdapterPlugin } from '../plugins/types';
|
|
10
|
+
interface NodeServerContext {
|
|
11
|
+
req: NodeRequest;
|
|
12
|
+
res?: NodeResponse;
|
|
13
|
+
}
|
|
14
|
+
export declare function useNodeAdapter(): ServerAdapterPlugin<NodeServerContext>;
|
|
15
|
+
export interface NodeRequest {
|
|
16
|
+
protocol?: string;
|
|
17
|
+
hostname?: string;
|
|
18
|
+
body?: any;
|
|
19
|
+
url?: string;
|
|
20
|
+
originalUrl?: string;
|
|
21
|
+
method?: string;
|
|
22
|
+
headers?: any;
|
|
23
|
+
req?: IncomingMessage | Http2ServerRequest;
|
|
24
|
+
raw?: IncomingMessage | Http2ServerRequest;
|
|
25
|
+
socket?: Socket;
|
|
26
|
+
query?: any;
|
|
27
|
+
}
|
|
28
|
+
export type NodeResponse = ServerResponse | Http2ServerResponse;
|
|
29
|
+
export declare function isReadable(stream: any): stream is Readable;
|
|
30
|
+
export declare function isNodeRequest(request: any): request is NodeRequest;
|
|
31
|
+
export declare function isServerResponse(stream: any): stream is NodeResponse;
|
|
32
|
+
export declare function normalizeNodeRequest(nodeRequest: NodeRequest, RequestCtor: typeof Request): Request;
|
|
33
|
+
export declare function sendNodeResponse(fetchResponse: Response, serverResponse: NodeResponse): Promise<void> | undefined;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
/// <reference types="node" />
|
|
4
|
+
/// <reference types="node" />
|
|
5
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
|
+
import { Http2ServerRequest, Http2ServerResponse } from 'node:http2';
|
|
7
|
+
import { Socket } from 'node:net';
|
|
8
|
+
import type { Readable } from 'node:stream';
|
|
9
|
+
import { ServerAdapterPlugin } from '../plugins/types';
|
|
10
|
+
interface NodeServerContext {
|
|
11
|
+
req: NodeRequest;
|
|
12
|
+
res?: NodeResponse;
|
|
13
|
+
}
|
|
14
|
+
export declare function useNodeAdapter(): ServerAdapterPlugin<NodeServerContext>;
|
|
15
|
+
export interface NodeRequest {
|
|
16
|
+
protocol?: string;
|
|
17
|
+
hostname?: string;
|
|
18
|
+
body?: any;
|
|
19
|
+
url?: string;
|
|
20
|
+
originalUrl?: string;
|
|
21
|
+
method?: string;
|
|
22
|
+
headers?: any;
|
|
23
|
+
req?: IncomingMessage | Http2ServerRequest;
|
|
24
|
+
raw?: IncomingMessage | Http2ServerRequest;
|
|
25
|
+
socket?: Socket;
|
|
26
|
+
query?: any;
|
|
27
|
+
}
|
|
28
|
+
export type NodeResponse = ServerResponse | Http2ServerResponse;
|
|
29
|
+
export declare function isReadable(stream: any): stream is Readable;
|
|
30
|
+
export declare function isNodeRequest(request: any): request is NodeRequest;
|
|
31
|
+
export declare function isServerResponse(stream: any): stream is NodeResponse;
|
|
32
|
+
export declare function normalizeNodeRequest(nodeRequest: NodeRequest, RequestCtor: typeof Request): Request;
|
|
33
|
+
export declare function sendNodeResponse(fetchResponse: Response, serverResponse: NodeResponse): Promise<void> | undefined;
|
|
34
|
+
export {};
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ServerAdapterPlugin } from '../plugins/types.cjs';
|
|
2
|
+
import type { FetchAPI } from '../types.cjs';
|
|
3
|
+
interface UWSServerContext {
|
|
4
|
+
req: UWSRequest;
|
|
5
|
+
res: UWSResponse;
|
|
6
|
+
}
|
|
7
|
+
export declare function useUWSAdapter(): ServerAdapterPlugin<UWSServerContext>;
|
|
2
8
|
export interface UWSRequest {
|
|
3
9
|
getMethod(): string;
|
|
4
10
|
forEach(callback: (key: string, value: string) => void): void;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ServerAdapterPlugin } from '../plugins/types.js';
|
|
2
|
+
import type { FetchAPI } from '../types.js';
|
|
3
|
+
interface UWSServerContext {
|
|
4
|
+
req: UWSRequest;
|
|
5
|
+
res: UWSResponse;
|
|
6
|
+
}
|
|
7
|
+
export declare function useUWSAdapter(): ServerAdapterPlugin<UWSServerContext>;
|
|
2
8
|
export interface UWSRequest {
|
|
3
9
|
getMethod(): string;
|
|
4
10
|
forEach(callback: (key: string, value: string) => void): void;
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { FetchAPI, ServerAdapterRequestHandler } from '../types.cjs';
|
|
2
2
|
export interface ServerAdapterPlugin<TServerContext = {}> {
|
|
3
|
+
onRequestAdapt?: OnRequestAdapt<TServerContext>;
|
|
3
4
|
onRequest?: OnRequestHook<TServerContext>;
|
|
4
5
|
onResponse?: OnResponseHook<TServerContext>;
|
|
5
6
|
}
|
|
7
|
+
export type OnRequestAdapt<TServerContext> = (payload: OnRequestAdaptEventPayload<TServerContext>) => Promise<void> | void;
|
|
8
|
+
export interface RequestAdapterResult<TServerContext> {
|
|
9
|
+
serverContext: TServerContext;
|
|
10
|
+
request: Request;
|
|
11
|
+
}
|
|
12
|
+
export type RequestAdapter<TServerContext> = () => RequestAdapterResult<TServerContext>;
|
|
13
|
+
export interface OnRequestAdaptEventPayload<TServerContext> {
|
|
14
|
+
args: unknown[];
|
|
15
|
+
setRequest(request: Request): void;
|
|
16
|
+
setServerContext(serverContext: TServerContext): void;
|
|
17
|
+
fetchAPI: FetchAPI;
|
|
18
|
+
}
|
|
6
19
|
export type OnRequestHook<TServerContext> = (payload: OnRequestEventPayload<TServerContext>) => Promise<void> | void;
|
|
7
20
|
export interface OnRequestEventPayload<TServerContext> {
|
|
8
21
|
request: Request;
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { FetchAPI, ServerAdapterRequestHandler } from '../types.js';
|
|
2
2
|
export interface ServerAdapterPlugin<TServerContext = {}> {
|
|
3
|
+
onRequestAdapt?: OnRequestAdapt<TServerContext>;
|
|
3
4
|
onRequest?: OnRequestHook<TServerContext>;
|
|
4
5
|
onResponse?: OnResponseHook<TServerContext>;
|
|
5
6
|
}
|
|
7
|
+
export type OnRequestAdapt<TServerContext> = (payload: OnRequestAdaptEventPayload<TServerContext>) => Promise<void> | void;
|
|
8
|
+
export interface RequestAdapterResult<TServerContext> {
|
|
9
|
+
serverContext: TServerContext;
|
|
10
|
+
request: Request;
|
|
11
|
+
}
|
|
12
|
+
export type RequestAdapter<TServerContext> = () => RequestAdapterResult<TServerContext>;
|
|
13
|
+
export interface OnRequestAdaptEventPayload<TServerContext> {
|
|
14
|
+
args: unknown[];
|
|
15
|
+
setRequest(request: Request): void;
|
|
16
|
+
setServerContext(serverContext: TServerContext): void;
|
|
17
|
+
fetchAPI: FetchAPI;
|
|
18
|
+
}
|
|
6
19
|
export type OnRequestHook<TServerContext> = (payload: OnRequestEventPayload<TServerContext>) => Promise<void> | void;
|
|
7
20
|
export interface OnRequestEventPayload<TServerContext> {
|
|
8
21
|
request: Request;
|