@whatwg-node/server 0.9.2-alpha-20230702150553-e1b62ad → 0.9.2

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.
@@ -4,10 +4,32 @@ exports.createServerAdapter = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  /* eslint-disable @typescript-eslint/ban-types */
6
6
  const DefaultFetchAPI = tslib_1.__importStar(require("@whatwg-node/fetch"));
7
- const useFetchEvent_js_1 = require("./internal-plugins/useFetchEvent.js");
8
- const useNodeAdapter_js_1 = require("./internal-plugins/useNodeAdapter.js");
9
- const useUWSAdapter_js_1 = require("./internal-plugins/useUWSAdapter.js");
10
7
  const utils_js_1 = require("./utils.js");
8
+ const uwebsockets_js_1 = require("./uwebsockets.js");
9
+ async function handleWaitUntils(waitUntilPromises) {
10
+ const waitUntils = await Promise.allSettled(waitUntilPromises);
11
+ waitUntils.forEach(waitUntil => {
12
+ if (waitUntil.status === 'rejected') {
13
+ console.error(waitUntil.reason);
14
+ }
15
+ });
16
+ }
17
+ // Required for envs like nextjs edge runtime
18
+ function isRequestAccessible(serverContext) {
19
+ try {
20
+ return !!serverContext?.request;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ function addWaitUntil(serverContext, waitUntilPromises) {
27
+ serverContext['waitUntil'] = function (promise) {
28
+ if (promise != null) {
29
+ waitUntilPromises.push(promise);
30
+ }
31
+ };
32
+ }
11
33
  const EMPTY_OBJECT = {};
12
34
  function createServerAdapter(serverAdapterBaseObject, options) {
13
35
  const fetchAPI = {
@@ -17,20 +39,16 @@ function createServerAdapter(serverAdapterBaseObject, options) {
17
39
  const givenHandleRequest = typeof serverAdapterBaseObject === 'function'
18
40
  ? serverAdapterBaseObject
19
41
  : serverAdapterBaseObject.handle;
20
- const onRequestAdaptHooks = [];
21
42
  const onRequestHooks = [];
22
43
  const onResponseHooks = [];
23
- const plugins = options?.plugins ?? [];
24
- plugins.push((0, useUWSAdapter_js_1.useUWSAdapter)(), (0, useNodeAdapter_js_1.useNodeAdapter)(), (0, useFetchEvent_js_1.useFetchEvent)());
25
- for (const plugin of plugins) {
26
- if (plugin.onRequestAdapt) {
27
- onRequestAdaptHooks.push(plugin.onRequestAdapt);
28
- }
29
- if (plugin.onRequest) {
30
- onRequestHooks.push(plugin.onRequest);
31
- }
32
- if (plugin.onResponse) {
33
- onResponseHooks.push(plugin.onResponse);
44
+ if (options?.plugins != null) {
45
+ for (const plugin of options.plugins) {
46
+ if (plugin.onRequest) {
47
+ onRequestHooks.push(plugin.onRequest);
48
+ }
49
+ if (plugin.onResponse) {
50
+ onResponseHooks.push(plugin.onResponse);
51
+ }
34
52
  }
35
53
  }
36
54
  async function handleRequest(request, serverContext) {
@@ -42,16 +60,6 @@ function createServerAdapter(serverAdapterBaseObject, options) {
42
60
  });
43
61
  let requestHandler = givenHandleRequest;
44
62
  let response;
45
- let waitUntilPromises;
46
- if (serverContext['waitUntil'] == null) {
47
- waitUntilPromises = new Set();
48
- serverContext['waitUntil'] = (promise) => {
49
- waitUntilPromises.add(promise);
50
- promise.then(() => {
51
- waitUntilPromises.delete(promise);
52
- });
53
- };
54
- }
55
63
  for (const onRequestHook of onRequestHooks) {
56
64
  await onRequestHook({
57
65
  request,
@@ -73,12 +81,6 @@ function createServerAdapter(serverAdapterBaseObject, options) {
73
81
  if (!response) {
74
82
  response = await requestHandler(request, serverContext);
75
83
  }
76
- if (!response) {
77
- response = new fetchAPI.Response(undefined, {
78
- status: 404,
79
- statusText: 'Not Found',
80
- });
81
- }
82
84
  for (const onResponseHook of onResponseHooks) {
83
85
  await onResponseHook({
84
86
  request,
@@ -86,58 +88,125 @@ function createServerAdapter(serverAdapterBaseObject, options) {
86
88
  serverContext,
87
89
  });
88
90
  }
89
- if (waitUntilPromises?.size) {
90
- const waitUntils = await Promise.allSettled(waitUntilPromises);
91
- waitUntils.forEach(waitUntil => {
92
- if (waitUntil.status === 'rejected') {
93
- console.error(waitUntil.reason);
94
- }
91
+ return response;
92
+ }
93
+ function handleNodeRequest(nodeRequest, ...ctx) {
94
+ const serverContext = ctx.length > 1 ? (0, utils_js_1.completeAssign)(...ctx) : ctx[0] || {};
95
+ const request = (0, utils_js_1.normalizeNodeRequest)(nodeRequest, fetchAPI.Request);
96
+ return handleRequest(request, serverContext);
97
+ }
98
+ async function requestListener(nodeRequest, serverResponse, ...ctx) {
99
+ const waitUntilPromises = [];
100
+ const defaultServerContext = {
101
+ req: nodeRequest,
102
+ res: serverResponse,
103
+ };
104
+ addWaitUntil(defaultServerContext, waitUntilPromises);
105
+ const response = await handleNodeRequest(nodeRequest, defaultServerContext, ...ctx);
106
+ if (response) {
107
+ await (0, utils_js_1.sendNodeResponse)(response, serverResponse, nodeRequest);
108
+ }
109
+ else {
110
+ await new Promise(resolve => {
111
+ serverResponse.statusCode = 404;
112
+ serverResponse.once('end', resolve);
113
+ serverResponse.end();
95
114
  });
96
115
  }
97
- return response;
116
+ if (waitUntilPromises.length > 0) {
117
+ await handleWaitUntils(waitUntilPromises);
118
+ }
119
+ }
120
+ async function handleUWS(res, req, ...ctx) {
121
+ const waitUntilPromises = [];
122
+ const defaultServerContext = {
123
+ res,
124
+ req,
125
+ };
126
+ addWaitUntil(defaultServerContext, waitUntilPromises);
127
+ const serverContext = ctx.length > 0 ? (0, utils_js_1.completeAssign)(defaultServerContext, ...ctx) : defaultServerContext;
128
+ const request = (0, uwebsockets_js_1.getRequestFromUWSRequest)({
129
+ req,
130
+ res,
131
+ fetchAPI,
132
+ });
133
+ const response = await handleRequest(request, serverContext);
134
+ if (!response) {
135
+ res.writeStatus('404 Not Found');
136
+ res.end();
137
+ return;
138
+ }
139
+ return (0, uwebsockets_js_1.sendResponseToUwsOpts)({
140
+ response,
141
+ res,
142
+ });
143
+ }
144
+ function handleEvent(event, ...ctx) {
145
+ if (!event.respondWith || !event.request) {
146
+ throw new TypeError(`Expected FetchEvent, got ${event}`);
147
+ }
148
+ const serverContext = ctx.length > 0 ? Object.assign({}, event, ...ctx) : event;
149
+ const response$ = handleRequest(event.request, serverContext);
150
+ event.respondWith(response$);
151
+ }
152
+ function handleRequestWithWaitUntil(request, ...ctx) {
153
+ const serverContext = (ctx.length > 1 ? (0, utils_js_1.completeAssign)(...ctx) : ctx[0]) || {};
154
+ if (serverContext.waitUntil == null) {
155
+ const waitUntilPromises = [];
156
+ addWaitUntil(serverContext, waitUntilPromises);
157
+ const response$ = handleRequest(request, serverContext);
158
+ if (waitUntilPromises.length > 0) {
159
+ return handleWaitUntils(waitUntilPromises).then(() => response$);
160
+ }
161
+ return response$;
162
+ }
163
+ return handleRequest(request, serverContext);
98
164
  }
99
165
  const fetchFn = (input, ...maybeCtx) => {
100
166
  if (typeof input === 'string' || 'href' in input) {
101
167
  const [initOrCtx, ...restOfCtx] = maybeCtx;
102
168
  if ((0, utils_js_1.isRequestInit)(initOrCtx)) {
103
- const serverContext = restOfCtx.length > 0 ? (0, utils_js_1.completeAssign)(...restOfCtx) : {};
104
- return handleRequest(new fetchAPI.Request(input, initOrCtx), serverContext);
169
+ return handleRequestWithWaitUntil(new fetchAPI.Request(input, initOrCtx), ...restOfCtx);
105
170
  }
106
- const serverContext = maybeCtx.length > 0 ? (0, utils_js_1.completeAssign)(...maybeCtx) : {};
107
- return handleRequest(new fetchAPI.Request(input), serverContext);
171
+ return handleRequestWithWaitUntil(new fetchAPI.Request(input), ...maybeCtx);
108
172
  }
109
- const serverContext = maybeCtx.length > 0 ? (0, utils_js_1.completeAssign)(...maybeCtx) : {};
110
- return handleRequest(input, serverContext);
173
+ return handleRequestWithWaitUntil(input, ...maybeCtx);
111
174
  };
112
- const genericRequestHandler = (...args) => {
113
- let request;
114
- let serverContext;
115
- for (const onRequestAdapt of onRequestAdaptHooks) {
116
- onRequestAdapt({
117
- args,
118
- setRequest(newRequest) {
119
- request = newRequest;
120
- },
121
- setServerContext(newServerContext) {
122
- serverContext = newServerContext;
123
- },
124
- fetchAPI,
125
- });
175
+ const genericRequestHandler = (input, ...maybeCtx) => {
176
+ // If it is a Node request
177
+ const [initOrCtxOrRes, ...restOfCtx] = maybeCtx;
178
+ if ((0, utils_js_1.isNodeRequest)(input)) {
179
+ if (!(0, utils_js_1.isServerResponse)(initOrCtxOrRes)) {
180
+ throw new TypeError(`Expected ServerResponse, got ${initOrCtxOrRes}`);
181
+ }
182
+ return requestListener(input, initOrCtxOrRes, ...restOfCtx);
183
+ }
184
+ if ((0, uwebsockets_js_1.isUWSResponse)(input)) {
185
+ return handleUWS(input, initOrCtxOrRes, ...restOfCtx);
186
+ }
187
+ if ((0, utils_js_1.isServerResponse)(initOrCtxOrRes)) {
188
+ throw new TypeError('Got Node response without Node request');
126
189
  }
127
- if (request) {
128
- if (!serverContext) {
129
- serverContext = {};
190
+ // Is input a container object over Request?
191
+ if (isRequestAccessible(input)) {
192
+ // Is it FetchEvent?
193
+ if ((0, utils_js_1.isFetchEvent)(input)) {
194
+ return handleEvent(input, ...maybeCtx);
130
195
  }
131
- return handleRequest(request, serverContext);
196
+ // In this input is also the context
197
+ return handleRequestWithWaitUntil(input.request, input, ...maybeCtx);
132
198
  }
133
- return fetchFn(...args);
199
+ // Or is it Request itself?
200
+ // Then ctx is present and it is the context
201
+ return fetchFn(input, ...maybeCtx);
134
202
  };
135
203
  const adapterObj = {
136
204
  handleRequest,
137
205
  fetch: fetchFn,
138
- requestListener: genericRequestHandler,
139
- handleNodeRequest: genericRequestHandler,
140
- handleEvent: genericRequestHandler,
206
+ handleNodeRequest,
207
+ requestListener,
208
+ handleEvent,
209
+ handleUWS,
141
210
  handle: genericRequestHandler,
142
211
  };
143
212
  const serverAdapter = new Proxy(genericRequestHandler, {
package/cjs/index.js CHANGED
@@ -8,8 +8,6 @@ tslib_1.__exportStar(require("./utils.js"), exports);
8
8
  tslib_1.__exportStar(require("./plugins/types.js"), exports);
9
9
  tslib_1.__exportStar(require("./plugins/useCors.js"), exports);
10
10
  tslib_1.__exportStar(require("./plugins/useErrorHandling.js"), exports);
11
- tslib_1.__exportStar(require("./internal-plugins/useFetchEvent.js"), exports);
12
- tslib_1.__exportStar(require("./internal-plugins/useNodeAdapter.js"), exports);
13
- tslib_1.__exportStar(require("./internal-plugins/useUWSAdapter.js"), exports);
11
+ tslib_1.__exportStar(require("./uwebsockets.js"), exports);
14
12
  var fetch_1 = require("@whatwg-node/fetch");
15
13
  Object.defineProperty(exports, "Response", { enumerable: true, get: function () { return fetch_1.Response; } });
package/cjs/utils.js CHANGED
@@ -1,14 +1,208 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addWaitUntil = exports.completeAssign = exports.isRequestInit = exports.isReadableStream = exports.isAsyncIterable = void 0;
3
+ exports.completeAssign = exports.isRequestInit = exports.sendNodeResponse = exports.isFetchEvent = exports.isReadableStream = exports.isServerResponse = exports.isNodeRequest = exports.isReadable = exports.normalizeNodeRequest = exports.isAsyncIterable = void 0;
4
+ const fetch_1 = require("@whatwg-node/fetch");
4
5
  function isAsyncIterable(body) {
5
6
  return (body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function');
6
7
  }
7
8
  exports.isAsyncIterable = isAsyncIterable;
9
+ function getPort(nodeRequest) {
10
+ if (nodeRequest.socket?.localPort) {
11
+ return nodeRequest.socket?.localPort;
12
+ }
13
+ const hostInHeader = nodeRequest.headers?.[':authority'] || nodeRequest.headers?.host;
14
+ const portInHeader = hostInHeader?.split(':')?.[1];
15
+ if (portInHeader) {
16
+ return portInHeader;
17
+ }
18
+ return 80;
19
+ }
20
+ function getHostnameWithPort(nodeRequest) {
21
+ if (nodeRequest.headers?.[':authority']) {
22
+ return nodeRequest.headers?.[':authority'];
23
+ }
24
+ if (nodeRequest.headers?.host) {
25
+ return nodeRequest.headers?.host;
26
+ }
27
+ const port = getPort(nodeRequest);
28
+ if (nodeRequest.hostname) {
29
+ return nodeRequest.hostname + ':' + port;
30
+ }
31
+ const localIp = nodeRequest.socket?.localAddress;
32
+ if (localIp && !localIp?.includes('::') && !localIp?.includes('ffff')) {
33
+ return `${localIp}:${port}`;
34
+ }
35
+ return 'localhost';
36
+ }
37
+ function buildFullUrl(nodeRequest) {
38
+ const hostnameWithPort = getHostnameWithPort(nodeRequest);
39
+ const protocol = nodeRequest.protocol || 'http';
40
+ const endpoint = nodeRequest.originalUrl || nodeRequest.url || '/graphql';
41
+ return `${protocol}://${hostnameWithPort}${endpoint}`;
42
+ }
43
+ function isRequestBody(body) {
44
+ const stringTag = body[Symbol.toStringTag];
45
+ if (typeof body === 'string' ||
46
+ stringTag === 'Uint8Array' ||
47
+ stringTag === 'Blob' ||
48
+ stringTag === 'FormData' ||
49
+ stringTag === 'URLSearchParams' ||
50
+ isAsyncIterable(body)) {
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+ function normalizeNodeRequest(nodeRequest, RequestCtor) {
56
+ const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest;
57
+ let fullUrl = buildFullUrl(rawRequest);
58
+ if (nodeRequest.query) {
59
+ const url = new fetch_1.URL(fullUrl);
60
+ for (const key in nodeRequest.query) {
61
+ url.searchParams.set(key, nodeRequest.query[key]);
62
+ }
63
+ fullUrl = url.toString();
64
+ }
65
+ if (nodeRequest.method === 'GET' || nodeRequest.method === 'HEAD') {
66
+ return new RequestCtor(fullUrl, {
67
+ method: nodeRequest.method,
68
+ headers: nodeRequest.headers,
69
+ });
70
+ }
71
+ /**
72
+ * Some Node server frameworks like Serverless Express sends a dummy object with body but as a Buffer not string
73
+ * so we do those checks to see is there something we can use directly as BodyInit
74
+ * because the presence of body means the request stream is already consumed and,
75
+ * rawRequest cannot be used as BodyInit/ReadableStream by Fetch API in this case.
76
+ */
77
+ const maybeParsedBody = nodeRequest.body;
78
+ if (maybeParsedBody != null && Object.keys(maybeParsedBody).length > 0) {
79
+ if (isRequestBody(maybeParsedBody)) {
80
+ return new RequestCtor(fullUrl, {
81
+ method: nodeRequest.method,
82
+ headers: nodeRequest.headers,
83
+ body: maybeParsedBody,
84
+ });
85
+ }
86
+ const request = new RequestCtor(fullUrl, {
87
+ method: nodeRequest.method,
88
+ headers: nodeRequest.headers,
89
+ });
90
+ if (!request.headers.get('content-type')?.includes('json')) {
91
+ request.headers.set('content-type', 'application/json; charset=utf-8');
92
+ }
93
+ return new Proxy(request, {
94
+ get: (target, prop, receiver) => {
95
+ switch (prop) {
96
+ case 'json':
97
+ return async () => maybeParsedBody;
98
+ case 'text':
99
+ return async () => JSON.stringify(maybeParsedBody);
100
+ default:
101
+ return Reflect.get(target, prop, receiver);
102
+ }
103
+ },
104
+ });
105
+ }
106
+ // perf: instead of spreading the object, we can just pass it as is and it performs better
107
+ return new RequestCtor(fullUrl, {
108
+ method: nodeRequest.method,
109
+ headers: nodeRequest.headers,
110
+ body: rawRequest,
111
+ });
112
+ }
113
+ exports.normalizeNodeRequest = normalizeNodeRequest;
114
+ function isReadable(stream) {
115
+ return stream.read != null;
116
+ }
117
+ exports.isReadable = isReadable;
118
+ function isNodeRequest(request) {
119
+ return isReadable(request);
120
+ }
121
+ exports.isNodeRequest = isNodeRequest;
122
+ function isServerResponse(stream) {
123
+ // Check all used functions are defined
124
+ return (stream != null &&
125
+ stream.setHeader != null &&
126
+ stream.end != null &&
127
+ stream.once != null &&
128
+ stream.write != null);
129
+ }
130
+ exports.isServerResponse = isServerResponse;
8
131
  function isReadableStream(stream) {
9
132
  return stream != null && stream.getReader != null;
10
133
  }
11
134
  exports.isReadableStream = isReadableStream;
135
+ function isFetchEvent(event) {
136
+ return event != null && event.request != null && event.respondWith != null;
137
+ }
138
+ exports.isFetchEvent = isFetchEvent;
139
+ function configureSocket(rawRequest) {
140
+ rawRequest?.socket?.setTimeout?.(0);
141
+ rawRequest?.socket?.setNoDelay?.(true);
142
+ rawRequest?.socket?.setKeepAlive?.(true);
143
+ }
144
+ function endResponse(serverResponse) {
145
+ // @ts-expect-error Avoid arguments adaptor trampoline https://v8.dev/blog/adaptor-frame
146
+ serverResponse.end(null, null, null);
147
+ }
148
+ async function sendAsyncIterable(serverResponse, asyncIterable) {
149
+ for await (const chunk of asyncIterable) {
150
+ if (!serverResponse
151
+ // @ts-expect-error http and http2 writes are actually compatible
152
+ .write(chunk)) {
153
+ break;
154
+ }
155
+ }
156
+ endResponse(serverResponse);
157
+ }
158
+ function sendNodeResponse(fetchResponse, serverResponse, nodeRequest) {
159
+ serverResponse.statusCode = fetchResponse.status;
160
+ serverResponse.statusMessage = fetchResponse.statusText;
161
+ fetchResponse.headers.forEach((value, key) => {
162
+ if (key === 'set-cookie') {
163
+ const setCookies = fetchResponse.headers.getSetCookie?.();
164
+ if (setCookies) {
165
+ serverResponse.setHeader('set-cookie', setCookies);
166
+ return;
167
+ }
168
+ }
169
+ serverResponse.setHeader(key, value);
170
+ });
171
+ // Optimizations for node-fetch
172
+ if (fetchResponse.bodyType === 'Buffer' ||
173
+ fetchResponse.bodyType === 'String' ||
174
+ fetchResponse.bodyType === 'Uint8Array') {
175
+ // @ts-expect-error http and http2 writes are actually compatible
176
+ serverResponse.write(fetchResponse.bodyInit);
177
+ endResponse(serverResponse);
178
+ return;
179
+ }
180
+ // Other fetch implementations
181
+ const fetchBody = fetchResponse.body;
182
+ if (fetchBody == null) {
183
+ endResponse(serverResponse);
184
+ return;
185
+ }
186
+ if (fetchBody[Symbol.toStringTag] === 'Uint8Array') {
187
+ serverResponse
188
+ // @ts-expect-error http and http2 writes are actually compatible
189
+ .write(fetchBody);
190
+ endResponse(serverResponse);
191
+ return;
192
+ }
193
+ configureSocket(nodeRequest);
194
+ if (isReadable(fetchBody)) {
195
+ serverResponse.once('close', () => {
196
+ fetchBody.destroy();
197
+ });
198
+ fetchBody.pipe(serverResponse);
199
+ return;
200
+ }
201
+ if (isAsyncIterable(fetchBody)) {
202
+ return sendAsyncIterable(serverResponse, fetchBody);
203
+ }
204
+ }
205
+ exports.sendNodeResponse = sendNodeResponse;
12
206
  function isRequestInit(val) {
13
207
  return (val != null &&
14
208
  typeof val === 'object' &&
@@ -49,11 +243,3 @@ function completeAssign(...args) {
49
243
  return target;
50
244
  }
51
245
  exports.completeAssign = completeAssign;
52
- function addWaitUntil(serverContext, waitUntilPromises) {
53
- serverContext['waitUntil'] = function (promise) {
54
- if (promise != null) {
55
- waitUntilPromises.push(promise);
56
- }
57
- };
58
- }
59
- exports.addWaitUntil = addWaitUntil;
@@ -1,39 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sendResponseToUwsOpts = exports.getRequestFromUWSRequest = exports.isUWSResponse = exports.useUWSAdapter = void 0;
4
- const utils_js_1 = require("../utils.js");
5
- function useUWSAdapter() {
6
- const uwsResponseMap = new WeakMap();
7
- return {
8
- onRequestAdapt({ args: [res, req, ...restOfCtx], setRequest, setServerContext, fetchAPI }) {
9
- if (isUWSResponse(res)) {
10
- const request = getRequestFromUWSRequest({
11
- req: req,
12
- res,
13
- fetchAPI,
14
- });
15
- uwsResponseMap.set(request, res);
16
- setRequest(request);
17
- const defaultServerContext = {
18
- req,
19
- res,
20
- };
21
- const serverContext = restOfCtx.length > 0 ? (0, utils_js_1.completeAssign)(...restOfCtx) : defaultServerContext;
22
- setServerContext(serverContext);
23
- }
24
- },
25
- onResponse({ request, response }) {
26
- const res = uwsResponseMap.get(request);
27
- if (res) {
28
- return sendResponseToUwsOpts({
29
- res,
30
- response,
31
- });
32
- }
33
- },
34
- };
35
- }
36
- exports.useUWSAdapter = useUWSAdapter;
3
+ exports.sendResponseToUwsOpts = exports.getRequestFromUWSRequest = exports.isUWSResponse = void 0;
37
4
  function isUWSResponse(res) {
38
5
  return !!res.onData;
39
6
  }