@whatwg-node/server 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +46 -0
- package/index.js +219 -0
- package/index.mjs +215 -0
- package/package.json +37 -0
- package/utils.d.ts +20 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { RequestListener, ServerResponse } from 'node:http';
|
|
3
|
+
import { NodeRequest } from './utils';
|
|
4
|
+
import { fetch } from '@whatwg-node/fetch';
|
|
5
|
+
export interface CreateServerAdapterOptions<TServerContext, TBaseObject> {
|
|
6
|
+
/**
|
|
7
|
+
* WHATWG Fetch spec compliant `Request` constructor.
|
|
8
|
+
*/
|
|
9
|
+
Request?: typeof Request;
|
|
10
|
+
/**
|
|
11
|
+
* An async function that takes `Request` and the server context and returns a `Response`.
|
|
12
|
+
* If you use `requestListener`, the server context is `{ req: IncomingMessage, res: ServerResponse }`.
|
|
13
|
+
*/
|
|
14
|
+
handleRequest: (request: Request, serverContext: TServerContext) => Promise<Response>;
|
|
15
|
+
/**
|
|
16
|
+
* If you extend a server object with this, you can pass the original object and it will be extended with the required methods and functionalities.
|
|
17
|
+
*/
|
|
18
|
+
baseObject?: TBaseObject;
|
|
19
|
+
}
|
|
20
|
+
export interface ServerAdapterObject<TServerContext> extends EventListenerObject {
|
|
21
|
+
/**
|
|
22
|
+
* A basic request listener that takes a `Request` with the server context and returns a `Response`.
|
|
23
|
+
*/
|
|
24
|
+
handleRequest: (request: Request, serverContext: TServerContext) => Promise<Response>;
|
|
25
|
+
/**
|
|
26
|
+
* WHATWG Fetch spec compliant `fetch` function that can be used for testing purposes.
|
|
27
|
+
*/
|
|
28
|
+
fetch: typeof fetch;
|
|
29
|
+
/**
|
|
30
|
+
* This function takes Node's request object and returns a WHATWG Fetch spec compliant `Response` object.
|
|
31
|
+
**/
|
|
32
|
+
handleNodeRequest(nodeRequest: NodeRequest, serverContext: TServerContext): Promise<Response>;
|
|
33
|
+
/**
|
|
34
|
+
* A request listener function that can be used with any Node server variation.
|
|
35
|
+
*/
|
|
36
|
+
requestListener: RequestListener;
|
|
37
|
+
/**
|
|
38
|
+
* Proxy to requestListener to mimic Node middlewares
|
|
39
|
+
*/
|
|
40
|
+
handle: RequestListener;
|
|
41
|
+
}
|
|
42
|
+
export declare type ServerAdapter<TServerContext, TBaseObject> = TBaseObject & RequestListener & ServerAdapterObject<TServerContext>;
|
|
43
|
+
export declare function createServerAdapter<TServerContext = {
|
|
44
|
+
req: NodeRequest;
|
|
45
|
+
res: ServerResponse;
|
|
46
|
+
}, TBaseObject = unknown>({ Request: RequestCtor, handleRequest, baseObject, }: CreateServerAdapterOptions<TServerContext, TBaseObject>): ServerAdapter<TServerContext, TBaseObject>;
|
package/index.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const fetch = require('@whatwg-node/fetch');
|
|
6
|
+
|
|
7
|
+
function isAsyncIterable(body) {
|
|
8
|
+
return body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function';
|
|
9
|
+
}
|
|
10
|
+
function buildFullUrl(nodeRequest) {
|
|
11
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
12
|
+
const hostname = nodeRequest.hostname ||
|
|
13
|
+
((_e = (_d = (_c = (_b = (_a = nodeRequest.socket) === null || _a === void 0 ? void 0 : _a.localAddress) === null || _b === void 0 ? void 0 : _b.split('ffff')) === null || _c === void 0 ? void 0 : _c.join('')) === null || _d === void 0 ? void 0 : _d.split(':')) === null || _e === void 0 ? void 0 : _e.join('')) ||
|
|
14
|
+
((_g = (_f = nodeRequest.headers) === null || _f === void 0 ? void 0 : _f.host) === null || _g === void 0 ? void 0 : _g.split(':')[0]) ||
|
|
15
|
+
'localhost';
|
|
16
|
+
const port = ((_h = nodeRequest.socket) === null || _h === void 0 ? void 0 : _h.localPort) || 80;
|
|
17
|
+
const protocol = nodeRequest.protocol || 'http';
|
|
18
|
+
const endpoint = nodeRequest.url || '/graphql';
|
|
19
|
+
return `${protocol}://${hostname}:${port}${endpoint}`;
|
|
20
|
+
}
|
|
21
|
+
function configureSocket(rawRequest) {
|
|
22
|
+
var _a, _b, _c, _d, _e, _f;
|
|
23
|
+
(_b = (_a = rawRequest === null || rawRequest === void 0 ? void 0 : rawRequest.socket) === null || _a === void 0 ? void 0 : _a.setTimeout) === null || _b === void 0 ? void 0 : _b.call(_a, 0);
|
|
24
|
+
(_d = (_c = rawRequest === null || rawRequest === void 0 ? void 0 : rawRequest.socket) === null || _c === void 0 ? void 0 : _c.setNoDelay) === null || _d === void 0 ? void 0 : _d.call(_c, true);
|
|
25
|
+
(_f = (_e = rawRequest === null || rawRequest === void 0 ? void 0 : rawRequest.socket) === null || _e === void 0 ? void 0 : _e.setKeepAlive) === null || _f === void 0 ? void 0 : _f.call(_e, true);
|
|
26
|
+
}
|
|
27
|
+
function isRequestBody(body) {
|
|
28
|
+
const stringTag = body[Symbol.toStringTag];
|
|
29
|
+
if (typeof body === 'string' ||
|
|
30
|
+
stringTag === 'Uint8Array' ||
|
|
31
|
+
stringTag === 'Blob' ||
|
|
32
|
+
stringTag === 'FormData' ||
|
|
33
|
+
stringTag === 'URLSearchParams' ||
|
|
34
|
+
isAsyncIterable(body)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function normalizeNodeRequest(nodeRequest, RequestCtor) {
|
|
40
|
+
var _a;
|
|
41
|
+
const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest;
|
|
42
|
+
configureSocket(rawRequest);
|
|
43
|
+
const fullUrl = buildFullUrl(rawRequest);
|
|
44
|
+
if (nodeRequest.query) {
|
|
45
|
+
const urlObj = new URL(fullUrl);
|
|
46
|
+
for (const queryName in nodeRequest.query) {
|
|
47
|
+
const queryValue = nodeRequest.query[queryName];
|
|
48
|
+
urlObj.searchParams.set(queryName, queryValue);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const baseRequestInit = {
|
|
52
|
+
method: nodeRequest.method,
|
|
53
|
+
headers: nodeRequest.headers,
|
|
54
|
+
};
|
|
55
|
+
if (nodeRequest.method === 'GET' || nodeRequest.method === 'HEAD') {
|
|
56
|
+
return new RequestCtor(fullUrl, baseRequestInit);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Some Node server frameworks like Serverless Express sends a dummy object with body but as a Buffer not string
|
|
60
|
+
* so we do those checks to see is there something we can use directly as BodyInit
|
|
61
|
+
* because the presence of body means the request stream is already consumed and,
|
|
62
|
+
* rawRequest cannot be used as BodyInit/ReadableStream by Fetch API in this case.
|
|
63
|
+
*/
|
|
64
|
+
const maybeParsedBody = nodeRequest.body;
|
|
65
|
+
if (maybeParsedBody != null && Object.keys(maybeParsedBody).length > 0) {
|
|
66
|
+
if (isRequestBody(maybeParsedBody)) {
|
|
67
|
+
return new RequestCtor(fullUrl, {
|
|
68
|
+
...baseRequestInit,
|
|
69
|
+
body: maybeParsedBody,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const request = new RequestCtor(fullUrl, {
|
|
73
|
+
...baseRequestInit,
|
|
74
|
+
});
|
|
75
|
+
if (!((_a = request.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('json'))) {
|
|
76
|
+
request.headers.set('content-type', 'application/json');
|
|
77
|
+
}
|
|
78
|
+
return new Proxy(request, {
|
|
79
|
+
get: (target, prop, receiver) => {
|
|
80
|
+
switch (prop) {
|
|
81
|
+
case 'json':
|
|
82
|
+
return async () => maybeParsedBody;
|
|
83
|
+
default:
|
|
84
|
+
return Reflect.get(target, prop, receiver);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return new RequestCtor(fullUrl, {
|
|
90
|
+
headers: nodeRequest.headers,
|
|
91
|
+
method: nodeRequest.method,
|
|
92
|
+
body: rawRequest,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function isReadable(stream) {
|
|
96
|
+
return stream.read != null;
|
|
97
|
+
}
|
|
98
|
+
function isServerResponse(stream) {
|
|
99
|
+
// Check all used functions are defined
|
|
100
|
+
return stream.setHeader != null && stream.end != null && stream.once != null && stream.write != null;
|
|
101
|
+
}
|
|
102
|
+
async function sendNodeResponse({ headers, status, statusText, body }, serverResponse) {
|
|
103
|
+
headers.forEach((value, name) => {
|
|
104
|
+
serverResponse.setHeader(name, value);
|
|
105
|
+
});
|
|
106
|
+
serverResponse.statusCode = status;
|
|
107
|
+
serverResponse.statusMessage = statusText;
|
|
108
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
109
|
+
return new Promise(async (resolve) => {
|
|
110
|
+
if (body == null) {
|
|
111
|
+
serverResponse.end(resolve);
|
|
112
|
+
}
|
|
113
|
+
else if (body[Symbol.toStringTag] === 'Uint8Array') {
|
|
114
|
+
serverResponse.end(body, resolve);
|
|
115
|
+
}
|
|
116
|
+
else if (isReadable(body)) {
|
|
117
|
+
serverResponse.once('close', () => {
|
|
118
|
+
body.destroy();
|
|
119
|
+
});
|
|
120
|
+
body.pipe(serverResponse);
|
|
121
|
+
}
|
|
122
|
+
else if (isAsyncIterable(body)) {
|
|
123
|
+
for await (const chunk of body) {
|
|
124
|
+
if (!serverResponse.write(chunk)) {
|
|
125
|
+
resolve();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
serverResponse.end(resolve);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function createServerAdapter({ Request: RequestCtor = fetch.Request, handleRequest, baseObject, }) {
|
|
135
|
+
function fetchFn(...[input, init]) {
|
|
136
|
+
let request;
|
|
137
|
+
if (typeof input === 'string' || input instanceof URL) {
|
|
138
|
+
request = new RequestCtor(input, init);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
request = input;
|
|
142
|
+
}
|
|
143
|
+
return handleRequest(request, init);
|
|
144
|
+
}
|
|
145
|
+
function handleNodeRequest(nodeRequest, serverContext) {
|
|
146
|
+
const request = normalizeNodeRequest(nodeRequest, RequestCtor);
|
|
147
|
+
return handleRequest(request, serverContext);
|
|
148
|
+
}
|
|
149
|
+
async function requestListener(nodeRequest, serverResponse) {
|
|
150
|
+
const response = await handleNodeRequest(nodeRequest, { req: nodeRequest, res: serverResponse });
|
|
151
|
+
return sendNodeResponse(response, serverResponse);
|
|
152
|
+
}
|
|
153
|
+
function handleEvent(event) {
|
|
154
|
+
if (!event.respondWith || !event.request) {
|
|
155
|
+
throw new TypeError(`Expected FetchEvent, got ${event}`);
|
|
156
|
+
}
|
|
157
|
+
const response$ = handleRequest(event.request, event);
|
|
158
|
+
event.respondWith(response$);
|
|
159
|
+
}
|
|
160
|
+
const adapterObj = {
|
|
161
|
+
handleRequest,
|
|
162
|
+
fetch: fetchFn,
|
|
163
|
+
handleNodeRequest,
|
|
164
|
+
requestListener,
|
|
165
|
+
handleEvent,
|
|
166
|
+
handle: requestListener,
|
|
167
|
+
};
|
|
168
|
+
function genericRequestHandler(input, ctx) {
|
|
169
|
+
// If it is a Node request
|
|
170
|
+
if (isReadable(input) && ctx != null && isServerResponse(ctx)) {
|
|
171
|
+
return requestListener(input, ctx);
|
|
172
|
+
}
|
|
173
|
+
// Is input a container object over Request?
|
|
174
|
+
if (input.request) {
|
|
175
|
+
// Is it FetchEvent?
|
|
176
|
+
if (input.respondWith) {
|
|
177
|
+
return handleEvent(input);
|
|
178
|
+
}
|
|
179
|
+
// In this input is also the context
|
|
180
|
+
return handleRequest(input.request, input);
|
|
181
|
+
}
|
|
182
|
+
// Or is it Request itself?
|
|
183
|
+
// Then ctx is present and it is the context
|
|
184
|
+
return handleRequest(input, ctx);
|
|
185
|
+
}
|
|
186
|
+
return new Proxy(genericRequestHandler, {
|
|
187
|
+
// It should have all the attributes of the handler function and the server instance
|
|
188
|
+
has: (_, prop) => {
|
|
189
|
+
return (baseObject && prop in baseObject) || prop in adapterObj || prop in genericRequestHandler;
|
|
190
|
+
},
|
|
191
|
+
get: (_, prop) => {
|
|
192
|
+
if (baseObject) {
|
|
193
|
+
if (prop in baseObject) {
|
|
194
|
+
if (baseObject[prop].bind) {
|
|
195
|
+
return baseObject[prop].bind(baseObject);
|
|
196
|
+
}
|
|
197
|
+
return baseObject[prop];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (adapterObj[prop]) {
|
|
201
|
+
if (adapterObj[prop].bind) {
|
|
202
|
+
return adapterObj[prop].bind(adapterObj);
|
|
203
|
+
}
|
|
204
|
+
return adapterObj[prop];
|
|
205
|
+
}
|
|
206
|
+
if (genericRequestHandler[prop]) {
|
|
207
|
+
if (genericRequestHandler[prop].bind) {
|
|
208
|
+
return genericRequestHandler[prop].bind(genericRequestHandler);
|
|
209
|
+
}
|
|
210
|
+
return genericRequestHandler[prop];
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
apply(_, __, [input, ctx]) {
|
|
214
|
+
return genericRequestHandler(input, ctx);
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
exports.createServerAdapter = createServerAdapter;
|
package/index.mjs
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { Request } from '@whatwg-node/fetch';
|
|
2
|
+
|
|
3
|
+
function isAsyncIterable(body) {
|
|
4
|
+
return body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function';
|
|
5
|
+
}
|
|
6
|
+
function buildFullUrl(nodeRequest) {
|
|
7
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
8
|
+
const hostname = nodeRequest.hostname ||
|
|
9
|
+
((_e = (_d = (_c = (_b = (_a = nodeRequest.socket) === null || _a === void 0 ? void 0 : _a.localAddress) === null || _b === void 0 ? void 0 : _b.split('ffff')) === null || _c === void 0 ? void 0 : _c.join('')) === null || _d === void 0 ? void 0 : _d.split(':')) === null || _e === void 0 ? void 0 : _e.join('')) ||
|
|
10
|
+
((_g = (_f = nodeRequest.headers) === null || _f === void 0 ? void 0 : _f.host) === null || _g === void 0 ? void 0 : _g.split(':')[0]) ||
|
|
11
|
+
'localhost';
|
|
12
|
+
const port = ((_h = nodeRequest.socket) === null || _h === void 0 ? void 0 : _h.localPort) || 80;
|
|
13
|
+
const protocol = nodeRequest.protocol || 'http';
|
|
14
|
+
const endpoint = nodeRequest.url || '/graphql';
|
|
15
|
+
return `${protocol}://${hostname}:${port}${endpoint}`;
|
|
16
|
+
}
|
|
17
|
+
function configureSocket(rawRequest) {
|
|
18
|
+
var _a, _b, _c, _d, _e, _f;
|
|
19
|
+
(_b = (_a = rawRequest === null || rawRequest === void 0 ? void 0 : rawRequest.socket) === null || _a === void 0 ? void 0 : _a.setTimeout) === null || _b === void 0 ? void 0 : _b.call(_a, 0);
|
|
20
|
+
(_d = (_c = rawRequest === null || rawRequest === void 0 ? void 0 : rawRequest.socket) === null || _c === void 0 ? void 0 : _c.setNoDelay) === null || _d === void 0 ? void 0 : _d.call(_c, true);
|
|
21
|
+
(_f = (_e = rawRequest === null || rawRequest === void 0 ? void 0 : rawRequest.socket) === null || _e === void 0 ? void 0 : _e.setKeepAlive) === null || _f === void 0 ? void 0 : _f.call(_e, true);
|
|
22
|
+
}
|
|
23
|
+
function isRequestBody(body) {
|
|
24
|
+
const stringTag = body[Symbol.toStringTag];
|
|
25
|
+
if (typeof body === 'string' ||
|
|
26
|
+
stringTag === 'Uint8Array' ||
|
|
27
|
+
stringTag === 'Blob' ||
|
|
28
|
+
stringTag === 'FormData' ||
|
|
29
|
+
stringTag === 'URLSearchParams' ||
|
|
30
|
+
isAsyncIterable(body)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
function normalizeNodeRequest(nodeRequest, RequestCtor) {
|
|
36
|
+
var _a;
|
|
37
|
+
const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest;
|
|
38
|
+
configureSocket(rawRequest);
|
|
39
|
+
const fullUrl = buildFullUrl(rawRequest);
|
|
40
|
+
if (nodeRequest.query) {
|
|
41
|
+
const urlObj = new URL(fullUrl);
|
|
42
|
+
for (const queryName in nodeRequest.query) {
|
|
43
|
+
const queryValue = nodeRequest.query[queryName];
|
|
44
|
+
urlObj.searchParams.set(queryName, queryValue);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const baseRequestInit = {
|
|
48
|
+
method: nodeRequest.method,
|
|
49
|
+
headers: nodeRequest.headers,
|
|
50
|
+
};
|
|
51
|
+
if (nodeRequest.method === 'GET' || nodeRequest.method === 'HEAD') {
|
|
52
|
+
return new RequestCtor(fullUrl, baseRequestInit);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Some Node server frameworks like Serverless Express sends a dummy object with body but as a Buffer not string
|
|
56
|
+
* so we do those checks to see is there something we can use directly as BodyInit
|
|
57
|
+
* because the presence of body means the request stream is already consumed and,
|
|
58
|
+
* rawRequest cannot be used as BodyInit/ReadableStream by Fetch API in this case.
|
|
59
|
+
*/
|
|
60
|
+
const maybeParsedBody = nodeRequest.body;
|
|
61
|
+
if (maybeParsedBody != null && Object.keys(maybeParsedBody).length > 0) {
|
|
62
|
+
if (isRequestBody(maybeParsedBody)) {
|
|
63
|
+
return new RequestCtor(fullUrl, {
|
|
64
|
+
...baseRequestInit,
|
|
65
|
+
body: maybeParsedBody,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const request = new RequestCtor(fullUrl, {
|
|
69
|
+
...baseRequestInit,
|
|
70
|
+
});
|
|
71
|
+
if (!((_a = request.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('json'))) {
|
|
72
|
+
request.headers.set('content-type', 'application/json');
|
|
73
|
+
}
|
|
74
|
+
return new Proxy(request, {
|
|
75
|
+
get: (target, prop, receiver) => {
|
|
76
|
+
switch (prop) {
|
|
77
|
+
case 'json':
|
|
78
|
+
return async () => maybeParsedBody;
|
|
79
|
+
default:
|
|
80
|
+
return Reflect.get(target, prop, receiver);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return new RequestCtor(fullUrl, {
|
|
86
|
+
headers: nodeRequest.headers,
|
|
87
|
+
method: nodeRequest.method,
|
|
88
|
+
body: rawRequest,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function isReadable(stream) {
|
|
92
|
+
return stream.read != null;
|
|
93
|
+
}
|
|
94
|
+
function isServerResponse(stream) {
|
|
95
|
+
// Check all used functions are defined
|
|
96
|
+
return stream.setHeader != null && stream.end != null && stream.once != null && stream.write != null;
|
|
97
|
+
}
|
|
98
|
+
async function sendNodeResponse({ headers, status, statusText, body }, serverResponse) {
|
|
99
|
+
headers.forEach((value, name) => {
|
|
100
|
+
serverResponse.setHeader(name, value);
|
|
101
|
+
});
|
|
102
|
+
serverResponse.statusCode = status;
|
|
103
|
+
serverResponse.statusMessage = statusText;
|
|
104
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
105
|
+
return new Promise(async (resolve) => {
|
|
106
|
+
if (body == null) {
|
|
107
|
+
serverResponse.end(resolve);
|
|
108
|
+
}
|
|
109
|
+
else if (body[Symbol.toStringTag] === 'Uint8Array') {
|
|
110
|
+
serverResponse.end(body, resolve);
|
|
111
|
+
}
|
|
112
|
+
else if (isReadable(body)) {
|
|
113
|
+
serverResponse.once('close', () => {
|
|
114
|
+
body.destroy();
|
|
115
|
+
});
|
|
116
|
+
body.pipe(serverResponse);
|
|
117
|
+
}
|
|
118
|
+
else if (isAsyncIterable(body)) {
|
|
119
|
+
for await (const chunk of body) {
|
|
120
|
+
if (!serverResponse.write(chunk)) {
|
|
121
|
+
resolve();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
serverResponse.end(resolve);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createServerAdapter({ Request: RequestCtor = Request, handleRequest, baseObject, }) {
|
|
131
|
+
function fetchFn(...[input, init]) {
|
|
132
|
+
let request;
|
|
133
|
+
if (typeof input === 'string' || input instanceof URL) {
|
|
134
|
+
request = new RequestCtor(input, init);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
request = input;
|
|
138
|
+
}
|
|
139
|
+
return handleRequest(request, init);
|
|
140
|
+
}
|
|
141
|
+
function handleNodeRequest(nodeRequest, serverContext) {
|
|
142
|
+
const request = normalizeNodeRequest(nodeRequest, RequestCtor);
|
|
143
|
+
return handleRequest(request, serverContext);
|
|
144
|
+
}
|
|
145
|
+
async function requestListener(nodeRequest, serverResponse) {
|
|
146
|
+
const response = await handleNodeRequest(nodeRequest, { req: nodeRequest, res: serverResponse });
|
|
147
|
+
return sendNodeResponse(response, serverResponse);
|
|
148
|
+
}
|
|
149
|
+
function handleEvent(event) {
|
|
150
|
+
if (!event.respondWith || !event.request) {
|
|
151
|
+
throw new TypeError(`Expected FetchEvent, got ${event}`);
|
|
152
|
+
}
|
|
153
|
+
const response$ = handleRequest(event.request, event);
|
|
154
|
+
event.respondWith(response$);
|
|
155
|
+
}
|
|
156
|
+
const adapterObj = {
|
|
157
|
+
handleRequest,
|
|
158
|
+
fetch: fetchFn,
|
|
159
|
+
handleNodeRequest,
|
|
160
|
+
requestListener,
|
|
161
|
+
handleEvent,
|
|
162
|
+
handle: requestListener,
|
|
163
|
+
};
|
|
164
|
+
function genericRequestHandler(input, ctx) {
|
|
165
|
+
// If it is a Node request
|
|
166
|
+
if (isReadable(input) && ctx != null && isServerResponse(ctx)) {
|
|
167
|
+
return requestListener(input, ctx);
|
|
168
|
+
}
|
|
169
|
+
// Is input a container object over Request?
|
|
170
|
+
if (input.request) {
|
|
171
|
+
// Is it FetchEvent?
|
|
172
|
+
if (input.respondWith) {
|
|
173
|
+
return handleEvent(input);
|
|
174
|
+
}
|
|
175
|
+
// In this input is also the context
|
|
176
|
+
return handleRequest(input.request, input);
|
|
177
|
+
}
|
|
178
|
+
// Or is it Request itself?
|
|
179
|
+
// Then ctx is present and it is the context
|
|
180
|
+
return handleRequest(input, ctx);
|
|
181
|
+
}
|
|
182
|
+
return new Proxy(genericRequestHandler, {
|
|
183
|
+
// It should have all the attributes of the handler function and the server instance
|
|
184
|
+
has: (_, prop) => {
|
|
185
|
+
return (baseObject && prop in baseObject) || prop in adapterObj || prop in genericRequestHandler;
|
|
186
|
+
},
|
|
187
|
+
get: (_, prop) => {
|
|
188
|
+
if (baseObject) {
|
|
189
|
+
if (prop in baseObject) {
|
|
190
|
+
if (baseObject[prop].bind) {
|
|
191
|
+
return baseObject[prop].bind(baseObject);
|
|
192
|
+
}
|
|
193
|
+
return baseObject[prop];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (adapterObj[prop]) {
|
|
197
|
+
if (adapterObj[prop].bind) {
|
|
198
|
+
return adapterObj[prop].bind(adapterObj);
|
|
199
|
+
}
|
|
200
|
+
return adapterObj[prop];
|
|
201
|
+
}
|
|
202
|
+
if (genericRequestHandler[prop]) {
|
|
203
|
+
if (genericRequestHandler[prop].bind) {
|
|
204
|
+
return genericRequestHandler[prop].bind(genericRequestHandler);
|
|
205
|
+
}
|
|
206
|
+
return genericRequestHandler[prop];
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
apply(_, __, [input, ctx]) {
|
|
210
|
+
return genericRequestHandler(input, ctx);
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export { createServerAdapter };
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@whatwg-node/server",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Fetch API compliant HTTP Server adapter",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"peerDependencies": {
|
|
7
|
+
"@types/node": "^18.0.6"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@whatwg-node/fetch": "^0.0.1",
|
|
11
|
+
"tslib": "^2.3.1"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "ardatan/whatwg-node",
|
|
16
|
+
"directory": "packages/whatwg-server"
|
|
17
|
+
},
|
|
18
|
+
"author": "Arda TANRIKULU <ardatanrikulu@gmail.com>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"main": "index.js",
|
|
21
|
+
"module": "index.mjs",
|
|
22
|
+
"typings": "index.d.ts",
|
|
23
|
+
"typescript": {
|
|
24
|
+
"definition": "index.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"require": "./index.js",
|
|
29
|
+
"import": "./index.mjs"
|
|
30
|
+
},
|
|
31
|
+
"./*": {
|
|
32
|
+
"require": "./*.js",
|
|
33
|
+
"import": "./*.mjs"
|
|
34
|
+
},
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/utils.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
import type { Socket } from 'node:net';
|
|
4
|
+
import type { Readable } from 'node:stream';
|
|
5
|
+
export interface NodeRequest {
|
|
6
|
+
protocol?: string;
|
|
7
|
+
hostname?: string;
|
|
8
|
+
body?: any;
|
|
9
|
+
url?: string;
|
|
10
|
+
method?: string;
|
|
11
|
+
headers: any;
|
|
12
|
+
req?: IncomingMessage;
|
|
13
|
+
raw?: IncomingMessage;
|
|
14
|
+
socket?: Socket;
|
|
15
|
+
query?: any;
|
|
16
|
+
}
|
|
17
|
+
export declare function normalizeNodeRequest(nodeRequest: NodeRequest, RequestCtor: typeof Request): Request;
|
|
18
|
+
export declare function isReadable(stream: any): stream is Readable;
|
|
19
|
+
export declare function isServerResponse(stream: any): stream is ServerResponse;
|
|
20
|
+
export declare function sendNodeResponse({ headers, status, statusText, body }: Response, serverResponse: ServerResponse): Promise<void>;
|