@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/utils.js CHANGED
@@ -1,219 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
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");
3
+ exports.addWaitUntil = exports.completeAssign = exports.isRequestInit = exports.isReadableStream = exports.isAsyncIterable = void 0;
5
4
  function isAsyncIterable(body) {
6
5
  return (body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function');
7
6
  }
8
7
  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;
131
8
  function isReadableStream(stream) {
132
9
  return stream != null && stream.getReader != null;
133
10
  }
134
11
  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
- function getHeaderPairs(headers) {
149
- const headerPairs = new Map();
150
- headers.forEach((value, key) => {
151
- let headerValues = headerPairs.get(key);
152
- if (headerValues === undefined) {
153
- headerValues = [];
154
- headerPairs.set(key, headerValues);
155
- }
156
- if (key === 'set-cookie') {
157
- const setCookies = headers.getSetCookie?.();
158
- if (setCookies) {
159
- setCookies.forEach(setCookie => {
160
- headerValues.push(setCookie);
161
- });
162
- return;
163
- }
164
- }
165
- headerValues.push(value);
166
- });
167
- return headerPairs;
168
- }
169
- async function sendAsyncIterable(serverResponse, asyncIterable) {
170
- for await (const chunk of asyncIterable) {
171
- if (!serverResponse
172
- // @ts-expect-error http and http2 writes are actually compatible
173
- .write(chunk)) {
174
- break;
175
- }
176
- }
177
- endResponse(serverResponse);
178
- }
179
- function sendNodeResponse(fetchResponse, serverResponse, nodeRequest) {
180
- const headerPairs = getHeaderPairs(fetchResponse.headers);
181
- serverResponse.writeHead(fetchResponse.status, fetchResponse.statusText, Object.fromEntries(headerPairs.entries()));
182
- // Optimizations for node-fetch
183
- if (fetchResponse.bodyType === 'Buffer' ||
184
- fetchResponse.bodyType === 'String' ||
185
- fetchResponse.bodyType === 'Uint8Array') {
186
- // @ts-expect-error http and http2 writes are actually compatible
187
- serverResponse.write(fetchResponse.bodyInit);
188
- endResponse(serverResponse);
189
- return;
190
- }
191
- // Other fetch implementations
192
- const fetchBody = fetchResponse.body;
193
- if (fetchBody == null) {
194
- endResponse(serverResponse);
195
- return;
196
- }
197
- if (fetchBody[Symbol.toStringTag] === 'Uint8Array') {
198
- serverResponse
199
- // @ts-expect-error http and http2 writes are actually compatible
200
- .write(fetchBody);
201
- endResponse(serverResponse);
202
- return;
203
- }
204
- configureSocket(nodeRequest);
205
- if (isReadable(fetchBody)) {
206
- serverResponse.once('close', () => {
207
- fetchBody.destroy();
208
- });
209
- fetchBody.pipe(serverResponse);
210
- return;
211
- }
212
- if (isAsyncIterable(fetchBody)) {
213
- return sendAsyncIterable(serverResponse, fetchBody);
214
- }
215
- }
216
- exports.sendNodeResponse = sendNodeResponse;
217
12
  function isRequestInit(val) {
218
13
  return (val != null &&
219
14
  typeof val === 'object' &&
@@ -254,3 +49,11 @@ function completeAssign(...args) {
254
49
  return target;
255
50
  }
256
51
  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,31 +1,9 @@
1
1
  /* eslint-disable @typescript-eslint/ban-types */
2
2
  import * as DefaultFetchAPI from '@whatwg-node/fetch';
3
- import { completeAssign, isFetchEvent, isNodeRequest, isRequestInit, isServerResponse, normalizeNodeRequest, sendNodeResponse, } from './utils.js';
4
- import { getRequestFromUWSRequest, isUWSResponse, sendResponseToUwsOpts, } from './uwebsockets.js';
5
- async function handleWaitUntils(waitUntilPromises) {
6
- const waitUntils = await Promise.allSettled(waitUntilPromises);
7
- waitUntils.forEach(waitUntil => {
8
- if (waitUntil.status === 'rejected') {
9
- console.error(waitUntil.reason);
10
- }
11
- });
12
- }
13
- // Required for envs like nextjs edge runtime
14
- function isRequestAccessible(serverContext) {
15
- try {
16
- return !!serverContext?.request;
17
- }
18
- catch {
19
- return false;
20
- }
21
- }
22
- function addWaitUntil(serverContext, waitUntilPromises) {
23
- serverContext['waitUntil'] = function (promise) {
24
- if (promise != null) {
25
- waitUntilPromises.push(promise);
26
- }
27
- };
28
- }
3
+ import { useFetchEvent } from './internal-plugins/useFetchEvent.js';
4
+ import { useNodeAdapter } from './internal-plugins/useNodeAdapter.js';
5
+ import { useUWSAdapter } from './internal-plugins/useUWSAdapter.js';
6
+ import { completeAssign, isRequestInit } from './utils.js';
29
7
  const EMPTY_OBJECT = {};
30
8
  function createServerAdapter(serverAdapterBaseObject, options) {
31
9
  const fetchAPI = {
@@ -35,16 +13,20 @@ function createServerAdapter(serverAdapterBaseObject, options) {
35
13
  const givenHandleRequest = typeof serverAdapterBaseObject === 'function'
36
14
  ? serverAdapterBaseObject
37
15
  : serverAdapterBaseObject.handle;
16
+ const onRequestAdaptHooks = [];
38
17
  const onRequestHooks = [];
39
18
  const onResponseHooks = [];
40
- if (options?.plugins != null) {
41
- for (const plugin of options.plugins) {
42
- if (plugin.onRequest) {
43
- onRequestHooks.push(plugin.onRequest);
44
- }
45
- if (plugin.onResponse) {
46
- onResponseHooks.push(plugin.onResponse);
47
- }
19
+ const plugins = options?.plugins ?? [];
20
+ plugins.push(useUWSAdapter(), useNodeAdapter(), useFetchEvent());
21
+ for (const plugin of plugins) {
22
+ if (plugin.onRequestAdapt) {
23
+ onRequestAdaptHooks.push(plugin.onRequestAdapt);
24
+ }
25
+ if (plugin.onRequest) {
26
+ onRequestHooks.push(plugin.onRequest);
27
+ }
28
+ if (plugin.onResponse) {
29
+ onResponseHooks.push(plugin.onResponse);
48
30
  }
49
31
  }
50
32
  async function handleRequest(request, serverContext) {
@@ -56,6 +38,16 @@ function createServerAdapter(serverAdapterBaseObject, options) {
56
38
  });
57
39
  let requestHandler = givenHandleRequest;
58
40
  let response;
41
+ let waitUntilPromises;
42
+ if (serverContext['waitUntil'] == null) {
43
+ waitUntilPromises = new Set();
44
+ serverContext['waitUntil'] = (promise) => {
45
+ waitUntilPromises.add(promise);
46
+ promise.then(() => {
47
+ waitUntilPromises.delete(promise);
48
+ });
49
+ };
50
+ }
59
51
  for (const onRequestHook of onRequestHooks) {
60
52
  await onRequestHook({
61
53
  request,
@@ -77,6 +69,12 @@ function createServerAdapter(serverAdapterBaseObject, options) {
77
69
  if (!response) {
78
70
  response = await requestHandler(request, serverContext);
79
71
  }
72
+ if (!response) {
73
+ response = new fetchAPI.Response(undefined, {
74
+ status: 404,
75
+ statusText: 'Not Found',
76
+ });
77
+ }
80
78
  for (const onResponseHook of onResponseHooks) {
81
79
  await onResponseHook({
82
80
  request,
@@ -84,125 +82,58 @@ function createServerAdapter(serverAdapterBaseObject, options) {
84
82
  serverContext,
85
83
  });
86
84
  }
87
- return response;
88
- }
89
- function handleNodeRequest(nodeRequest, ...ctx) {
90
- const serverContext = ctx.length > 1 ? completeAssign(...ctx) : ctx[0] || {};
91
- const request = normalizeNodeRequest(nodeRequest, fetchAPI.Request);
92
- return handleRequest(request, serverContext);
93
- }
94
- async function requestListener(nodeRequest, serverResponse, ...ctx) {
95
- const waitUntilPromises = [];
96
- const defaultServerContext = {
97
- req: nodeRequest,
98
- res: serverResponse,
99
- };
100
- addWaitUntil(defaultServerContext, waitUntilPromises);
101
- const response = await handleNodeRequest(nodeRequest, defaultServerContext, ...ctx);
102
- if (response) {
103
- await sendNodeResponse(response, serverResponse, nodeRequest);
104
- }
105
- else {
106
- await new Promise(resolve => {
107
- serverResponse.statusCode = 404;
108
- serverResponse.once('end', resolve);
109
- serverResponse.end();
85
+ if (waitUntilPromises?.size) {
86
+ const waitUntils = await Promise.allSettled(waitUntilPromises);
87
+ waitUntils.forEach(waitUntil => {
88
+ if (waitUntil.status === 'rejected') {
89
+ console.error(waitUntil.reason);
90
+ }
110
91
  });
111
92
  }
112
- if (waitUntilPromises.length > 0) {
113
- await handleWaitUntils(waitUntilPromises);
114
- }
115
- }
116
- async function handleUWS(res, req, ...ctx) {
117
- const waitUntilPromises = [];
118
- const defaultServerContext = {
119
- res,
120
- req,
121
- };
122
- addWaitUntil(defaultServerContext, waitUntilPromises);
123
- const serverContext = ctx.length > 0 ? completeAssign(defaultServerContext, ...ctx) : defaultServerContext;
124
- const request = getRequestFromUWSRequest({
125
- req,
126
- res,
127
- fetchAPI,
128
- });
129
- const response = await handleRequest(request, serverContext);
130
- if (!response) {
131
- res.writeStatus('404 Not Found');
132
- res.end();
133
- return;
134
- }
135
- return sendResponseToUwsOpts({
136
- response,
137
- res,
138
- });
139
- }
140
- function handleEvent(event, ...ctx) {
141
- if (!event.respondWith || !event.request) {
142
- throw new TypeError(`Expected FetchEvent, got ${event}`);
143
- }
144
- const serverContext = ctx.length > 0 ? Object.assign({}, event, ...ctx) : event;
145
- const response$ = handleRequest(event.request, serverContext);
146
- event.respondWith(response$);
147
- }
148
- function handleRequestWithWaitUntil(request, ...ctx) {
149
- const serverContext = (ctx.length > 1 ? completeAssign(...ctx) : ctx[0]) || {};
150
- if (serverContext.waitUntil == null) {
151
- const waitUntilPromises = [];
152
- addWaitUntil(serverContext, waitUntilPromises);
153
- const response$ = handleRequest(request, serverContext);
154
- if (waitUntilPromises.length > 0) {
155
- return handleWaitUntils(waitUntilPromises).then(() => response$);
156
- }
157
- return response$;
158
- }
159
- return handleRequest(request, serverContext);
93
+ return response;
160
94
  }
161
95
  const fetchFn = (input, ...maybeCtx) => {
162
96
  if (typeof input === 'string' || 'href' in input) {
163
97
  const [initOrCtx, ...restOfCtx] = maybeCtx;
164
98
  if (isRequestInit(initOrCtx)) {
165
- return handleRequestWithWaitUntil(new fetchAPI.Request(input, initOrCtx), ...restOfCtx);
99
+ const serverContext = restOfCtx.length > 0 ? completeAssign(...restOfCtx) : {};
100
+ return handleRequest(new fetchAPI.Request(input, initOrCtx), serverContext);
166
101
  }
167
- return handleRequestWithWaitUntil(new fetchAPI.Request(input), ...maybeCtx);
102
+ const serverContext = maybeCtx.length > 0 ? completeAssign(...maybeCtx) : {};
103
+ return handleRequest(new fetchAPI.Request(input), serverContext);
168
104
  }
169
- return handleRequestWithWaitUntil(input, ...maybeCtx);
105
+ const serverContext = maybeCtx.length > 0 ? completeAssign(...maybeCtx) : {};
106
+ return handleRequest(input, serverContext);
170
107
  };
171
- const genericRequestHandler = (input, ...maybeCtx) => {
172
- // If it is a Node request
173
- const [initOrCtxOrRes, ...restOfCtx] = maybeCtx;
174
- if (isNodeRequest(input)) {
175
- if (!isServerResponse(initOrCtxOrRes)) {
176
- throw new TypeError(`Expected ServerResponse, got ${initOrCtxOrRes}`);
177
- }
178
- return requestListener(input, initOrCtxOrRes, ...restOfCtx);
179
- }
180
- if (isUWSResponse(input)) {
181
- return handleUWS(input, initOrCtxOrRes, ...restOfCtx);
182
- }
183
- if (isServerResponse(initOrCtxOrRes)) {
184
- throw new TypeError('Got Node response without Node request');
108
+ const genericRequestHandler = (...args) => {
109
+ let request;
110
+ let serverContext;
111
+ for (const onRequestAdapt of onRequestAdaptHooks) {
112
+ onRequestAdapt({
113
+ args,
114
+ setRequest(newRequest) {
115
+ request = newRequest;
116
+ },
117
+ setServerContext(newServerContext) {
118
+ serverContext = newServerContext;
119
+ },
120
+ fetchAPI,
121
+ });
185
122
  }
186
- // Is input a container object over Request?
187
- if (isRequestAccessible(input)) {
188
- // Is it FetchEvent?
189
- if (isFetchEvent(input)) {
190
- return handleEvent(input, ...maybeCtx);
123
+ if (request) {
124
+ if (!serverContext) {
125
+ serverContext = {};
191
126
  }
192
- // In this input is also the context
193
- return handleRequestWithWaitUntil(input.request, input, ...maybeCtx);
127
+ return handleRequest(request, serverContext);
194
128
  }
195
- // Or is it Request itself?
196
- // Then ctx is present and it is the context
197
- return fetchFn(input, ...maybeCtx);
129
+ return fetchFn(...args);
198
130
  };
199
131
  const adapterObj = {
200
132
  handleRequest,
201
133
  fetch: fetchFn,
202
- handleNodeRequest,
203
- requestListener,
204
- handleEvent,
205
- handleUWS,
134
+ requestListener: genericRequestHandler,
135
+ handleNodeRequest: genericRequestHandler,
136
+ handleEvent: genericRequestHandler,
206
137
  handle: genericRequestHandler,
207
138
  };
208
139
  const serverAdapter = new Proxy(genericRequestHandler, {
package/esm/index.js 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 './uwebsockets.js';
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,31 @@
1
+ import { completeAssign } from '../utils';
2
+ export function isFetchEvent(event) {
3
+ return event != null && event.request != null && event.respondWith != null;
4
+ }
5
+ // Required for envs like nextjs edge runtime
6
+ function isRequestAccessible(serverContext) {
7
+ try {
8
+ return !!serverContext?.request;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ export function useFetchEvent() {
15
+ const eventMap = new WeakMap();
16
+ return {
17
+ onRequestAdapt({ args: [event, ...restOfCtx], setRequest, setServerContext }) {
18
+ if (isRequestAccessible(event)) {
19
+ setRequest(event.request);
20
+ const serverContext = restOfCtx.length > 0 ? completeAssign(...restOfCtx) : event;
21
+ setServerContext(serverContext);
22
+ }
23
+ },
24
+ onResponse({ request, response }) {
25
+ const event = eventMap.get(request);
26
+ if (isFetchEvent(event)) {
27
+ event.respondWith(response);
28
+ }
29
+ },
30
+ };
31
+ }