@whatwg-node/server
Advanced tools
Comparing version 0.4.18-alpha-20221220143426-2744af4 to 0.5.0-alpha-20221220144402-dce2fb1
@@ -1,55 +0,5 @@ | ||
/// <reference types="node" /> | ||
import type { RequestListener, ServerResponse } from 'node:http'; | ||
import { NodeRequest } from './utils'; | ||
import { FetchEvent } from './types'; | ||
export interface ServerAdapterBaseObject<TServerContext, THandleRequest extends ServerAdapterRequestHandler<TServerContext> = ServerAdapterRequestHandler<TServerContext>> { | ||
/** | ||
* An async function that takes `Request` and the server context and returns a `Response`. | ||
* If you use `requestListener`, the server context is `{ req: IncomingMessage, res: ServerResponse }`. | ||
*/ | ||
handle: THandleRequest; | ||
} | ||
export interface ServerAdapterObject<TServerContext, TBaseObject extends ServerAdapterBaseObject<TServerContext, ServerAdapterRequestHandler<TServerContext>>> extends EventListenerObject { | ||
/** | ||
* A basic request listener that takes a `Request` with the server context and returns a `Response`. | ||
*/ | ||
handleRequest: TBaseObject['handle']; | ||
/** | ||
* WHATWG Fetch spec compliant `fetch` function that can be used for testing purposes. | ||
*/ | ||
fetch(request: Request, ctx: TServerContext): Promise<Response> | Response; | ||
fetch(request: Request, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
fetch(urlStr: string, ctx: TServerContext): Promise<Response> | Response; | ||
fetch(urlStr: string, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
fetch(urlStr: string, init: RequestInit, ctx: TServerContext): Promise<Response> | Response; | ||
fetch(urlStr: string, init: RequestInit, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
fetch(url: URL, ctx: TServerContext): Promise<Response> | Response; | ||
fetch(url: URL, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
fetch(url: URL, init: RequestInit, ctx: TServerContext): Promise<Response> | Response; | ||
fetch(url: URL, init: RequestInit, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
/** | ||
* This function takes Node's request object and returns a WHATWG Fetch spec compliant `Response` object. | ||
**/ | ||
handleNodeRequest(nodeRequest: NodeRequest, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
/** | ||
* A request listener function that can be used with any Node server variation. | ||
*/ | ||
requestListener: RequestListener; | ||
handle(req: NodeRequest, res: ServerResponse, ...ctx: Partial<TServerContext>[]): Promise<void>; | ||
handle(request: Request, ...ctx: Partial<TServerContext>[]): Promise<Response>; | ||
handle(request: Request, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
handle(fetchEvent: FetchEvent & Partial<TServerContext>, ...ctx: Partial<TServerContext>[]): void; | ||
handle(container: { | ||
request: Request; | ||
} & Partial<TServerContext>, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
} | ||
export type ServerAdapter<TServerContext, TBaseObject extends ServerAdapterBaseObject<TServerContext>> = TBaseObject & ServerAdapterObject<TServerContext, TBaseObject>['handle'] & ServerAdapterObject<TServerContext, TBaseObject>; | ||
export type ServerAdapterRequestHandler<TServerContext> = (request: Request, ctx: TServerContext) => Promise<Response> | Response; | ||
export type DefaultServerAdapterContext = { | ||
req: NodeRequest; | ||
res: ServerResponse; | ||
waitUntil(promise: Promise<unknown>): void; | ||
}; | ||
declare function createServerAdapter<TServerContext = DefaultServerAdapterContext, THandleRequest extends ServerAdapterRequestHandler<TServerContext> = ServerAdapterRequestHandler<TServerContext>>(serverAdapterRequestHandler: THandleRequest, RequestCtor?: typeof Request): ServerAdapter<TServerContext, ServerAdapterBaseObject<TServerContext, THandleRequest>>; | ||
declare function createServerAdapter<TServerContext, TBaseObject extends ServerAdapterBaseObject<TServerContext>>(serverAdapterBaseObject: TBaseObject, RequestCtor?: typeof Request): ServerAdapter<TServerContext, TBaseObject>; | ||
export { createServerAdapter }; | ||
export * from './createServerAdapter'; | ||
export * from './types'; | ||
export * from './utils'; | ||
export * from './middlewares/withCORS'; | ||
export * from './middlewares/withErrorHandling'; |
170
index.js
@@ -129,2 +129,5 @@ 'use strict'; | ||
} | ||
function isReadableStream(stream) { | ||
return stream != null && stream.getReader != null; | ||
} | ||
function isFetchEvent(event) { | ||
@@ -330,2 +333,169 @@ return event != null && event.request != null && event.respondWith != null; | ||
function getCORSHeadersByRequestAndOptions(request, corsOptions) { | ||
var _a, _b; | ||
const headers = {}; | ||
if (corsOptions === false) { | ||
return headers; | ||
} | ||
// If defined origins have '*' or undefined by any means, we should allow all origins | ||
if (corsOptions.origin == null || corsOptions.origin.length === 0 || corsOptions.origin.includes('*')) { | ||
const currentOrigin = request.headers.get('origin'); | ||
// If origin is available in the headers, use it | ||
if (currentOrigin != null) { | ||
headers['Access-Control-Allow-Origin'] = currentOrigin; | ||
// Vary by origin because there are multiple origins | ||
headers['Vary'] = 'Origin'; | ||
} | ||
else { | ||
headers['Access-Control-Allow-Origin'] = '*'; | ||
} | ||
} | ||
else if (typeof corsOptions.origin === 'string') { | ||
// If there is one specific origin is specified, use it directly | ||
headers['Access-Control-Allow-Origin'] = corsOptions.origin; | ||
} | ||
else if (Array.isArray(corsOptions.origin)) { | ||
// If there is only one origin defined in the array, consider it as a single one | ||
if (corsOptions.origin.length === 1) { | ||
headers['Access-Control-Allow-Origin'] = corsOptions.origin[0]; | ||
} | ||
else { | ||
const currentOrigin = request.headers.get('origin'); | ||
if (currentOrigin != null && corsOptions.origin.includes(currentOrigin)) { | ||
// If origin is available in the headers, use it | ||
headers['Access-Control-Allow-Origin'] = currentOrigin; | ||
// Vary by origin because there are multiple origins | ||
headers['Vary'] = 'Origin'; | ||
} | ||
else { | ||
// There is no origin found in the headers, so we should return null | ||
headers['Access-Control-Allow-Origin'] = 'null'; | ||
} | ||
} | ||
} | ||
if ((_a = corsOptions.methods) === null || _a === void 0 ? void 0 : _a.length) { | ||
headers['Access-Control-Allow-Methods'] = corsOptions.methods.join(', '); | ||
} | ||
else { | ||
const requestMethod = request.headers.get('access-control-request-method'); | ||
if (requestMethod) { | ||
headers['Access-Control-Allow-Methods'] = requestMethod; | ||
} | ||
} | ||
if ((_b = corsOptions.allowedHeaders) === null || _b === void 0 ? void 0 : _b.length) { | ||
headers['Access-Control-Allow-Headers'] = corsOptions.allowedHeaders.join(', '); | ||
} | ||
else { | ||
const requestHeaders = request.headers.get('access-control-request-headers'); | ||
if (requestHeaders) { | ||
headers['Access-Control-Allow-Headers'] = requestHeaders; | ||
if (headers['Vary']) { | ||
headers['Vary'] += ', Access-Control-Request-Headers'; | ||
} | ||
headers['Vary'] = 'Access-Control-Request-Headers'; | ||
} | ||
} | ||
if (corsOptions.credentials != null) { | ||
if (corsOptions.credentials === true) { | ||
headers['Access-Control-Allow-Credentials'] = 'true'; | ||
} | ||
} | ||
else if (headers['Access-Control-Allow-Origin'] !== '*') { | ||
headers['Access-Control-Allow-Credentials'] = 'true'; | ||
} | ||
if (corsOptions.exposedHeaders) { | ||
headers['Access-Control-Expose-Headers'] = corsOptions.exposedHeaders.join(', '); | ||
} | ||
if (corsOptions.maxAge) { | ||
headers['Access-Control-Max-Age'] = corsOptions.maxAge.toString(); | ||
} | ||
return headers; | ||
} | ||
async function getCORSResponseHeaders(request, corsOptionsFactory, serverContext) { | ||
const corsOptions = await corsOptionsFactory(request, serverContext); | ||
return getCORSHeadersByRequestAndOptions(request, corsOptions); | ||
} | ||
function withCORS(obj, options) { | ||
let corsOptionsFactory = () => ({}); | ||
if (options != null) { | ||
if (typeof options === 'function') { | ||
corsOptionsFactory = options; | ||
} | ||
else if (typeof options === 'object') { | ||
const corsOptions = { | ||
...options, | ||
}; | ||
corsOptionsFactory = () => corsOptions; | ||
} | ||
else if (options === false) { | ||
corsOptionsFactory = () => false; | ||
} | ||
} | ||
async function handleWithCORS(request, serverContext) { | ||
let response; | ||
if (request.method.toUpperCase() === 'OPTIONS') { | ||
response = new fetch.Response(null, { | ||
status: 204, | ||
}); | ||
} | ||
else { | ||
response = await obj.handle(request, serverContext); | ||
} | ||
if (response != null) { | ||
const headers = await getCORSResponseHeaders(request, corsOptionsFactory, serverContext); | ||
for (const headerName in headers) { | ||
response.headers.set(headerName, headers[headerName]); | ||
} | ||
return response; | ||
} | ||
} | ||
return new Proxy(obj, { | ||
get(_, prop, receiver) { | ||
if (prop === 'handle') { | ||
return handleWithCORS; | ||
} | ||
return Reflect.get(obj, prop, receiver); | ||
}, | ||
}); | ||
} | ||
const defaultErrorHandler = function defaultErrorHandler(e) { | ||
return new fetch.Response(e.stack || e.message || e.toString(), { | ||
status: e.statusCode || e.status || 500, | ||
statusText: e.statusText || 'Internal Server Error', | ||
}); | ||
}; | ||
function withErrorHandling(obj, onError = defaultErrorHandler) { | ||
async function handleWithErrorHandling(request, ctx) { | ||
try { | ||
const res = await obj.handle(request, ctx); | ||
return res; | ||
} | ||
catch (e) { | ||
return onError(e, request, ctx); | ||
} | ||
} | ||
return new Proxy(obj, { | ||
get(obj, prop, receiver) { | ||
if (prop === 'handle') { | ||
return handleWithErrorHandling; | ||
} | ||
return Reflect.get(obj, prop, receiver); | ||
}, | ||
}); | ||
} | ||
exports.createServerAdapter = createServerAdapter; | ||
exports.defaultErrorHandler = defaultErrorHandler; | ||
exports.getCORSHeadersByRequestAndOptions = getCORSHeadersByRequestAndOptions; | ||
exports.isAsyncIterable = isAsyncIterable; | ||
exports.isFetchEvent = isFetchEvent; | ||
exports.isNodeRequest = isNodeRequest; | ||
exports.isReadable = isReadable; | ||
exports.isReadableStream = isReadableStream; | ||
exports.isRequestInit = isRequestInit; | ||
exports.isServerResponse = isServerResponse; | ||
exports.normalizeNodeRequest = normalizeNodeRequest; | ||
exports.sendNodeResponse = sendNodeResponse; | ||
exports.withCORS = withCORS; | ||
exports.withErrorHandling = withErrorHandling; |
{ | ||
"name": "@whatwg-node/server", | ||
"version": "0.4.18-alpha-20221220143426-2744af4", | ||
"version": "0.5.0-alpha-20221220144402-dce2fb1", | ||
"description": "Fetch API compliant HTTP Server adapter", | ||
@@ -5,0 +5,0 @@ "sideEffects": false, |
@@ -0,1 +1,4 @@ | ||
/// <reference types="node" /> | ||
import type { RequestListener, ServerResponse } from 'node:http'; | ||
import type { NodeRequest } from './utils'; | ||
export interface FetchEvent extends Event { | ||
@@ -6,1 +9,48 @@ waitUntil(f: Promise<any>): void; | ||
} | ||
export interface ServerAdapterBaseObject<TServerContext, THandleRequest extends ServerAdapterRequestHandler<TServerContext> = ServerAdapterRequestHandler<TServerContext>> { | ||
/** | ||
* An async function that takes `Request` and the server context and returns a `Response`. | ||
* If you use `requestListener`, the server context is `{ req: IncomingMessage, res: ServerResponse }`. | ||
*/ | ||
handle: THandleRequest; | ||
} | ||
export interface ServerAdapterObject<TServerContext, TBaseObject extends ServerAdapterBaseObject<TServerContext, ServerAdapterRequestHandler<TServerContext>>> extends EventListenerObject { | ||
/** | ||
* A basic request listener that takes a `Request` with the server context and returns a `Response`. | ||
*/ | ||
handleRequest: TBaseObject['handle']; | ||
/** | ||
* WHATWG Fetch spec compliant `fetch` function that can be used for testing purposes. | ||
*/ | ||
fetch(request: Request, ctx: TServerContext): Promise<Response> | Response; | ||
fetch(request: Request, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
fetch(urlStr: string, ctx: TServerContext): Promise<Response> | Response; | ||
fetch(urlStr: string, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
fetch(urlStr: string, init: RequestInit, ctx: TServerContext): Promise<Response> | Response; | ||
fetch(urlStr: string, init: RequestInit, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
fetch(url: URL, ctx: TServerContext): Promise<Response> | Response; | ||
fetch(url: URL, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
fetch(url: URL, init: RequestInit, ctx: TServerContext): Promise<Response> | Response; | ||
fetch(url: URL, init: RequestInit, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
/** | ||
* This function takes Node's request object and returns a WHATWG Fetch spec compliant `Response` object. | ||
**/ | ||
handleNodeRequest(nodeRequest: NodeRequest, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
/** | ||
* A request listener function that can be used with any Node server variation. | ||
*/ | ||
requestListener: RequestListener; | ||
handle(req: NodeRequest, res: ServerResponse, ...ctx: Partial<TServerContext>[]): Promise<void>; | ||
handle(request: Request, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
handle(fetchEvent: FetchEvent & Partial<TServerContext>, ...ctx: Partial<TServerContext>[]): void; | ||
handle(container: { | ||
request: Request; | ||
} & Partial<TServerContext>, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response; | ||
} | ||
export type ServerAdapter<TServerContext, TBaseObject extends ServerAdapterBaseObject<TServerContext>> = TBaseObject & ServerAdapterObject<TServerContext, TBaseObject>['handle'] & ServerAdapterObject<TServerContext, TBaseObject>; | ||
export type ServerAdapterRequestHandler<TServerContext> = (request: Request, ctx: TServerContext) => Promise<Response> | Response; | ||
export type DefaultServerAdapterContext = { | ||
req: NodeRequest; | ||
res: ServerResponse; | ||
waitUntil(promise: Promise<unknown>): void; | ||
}; |
@@ -8,2 +8,3 @@ /// <reference types="node" /> | ||
import { FetchEvent } from './types'; | ||
export declare function isAsyncIterable(body: any): body is AsyncIterable<any>; | ||
export interface NodeRequest { | ||
@@ -10,0 +11,0 @@ protocol?: string; |
Sorry, the diff of this file is not supported yet
56264
10
1080
3