Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/adapters/_node/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type {
FetchHandler,
NodeHttpHandler,
NodeServerRequest,
NodeServerResponse,
ServerRequest,
} from "../../types.ts";
import { fetchNodeHandler } from "../node.ts";
import { NodeRequest } from "./request.ts";
import { sendNodeResponse } from "./send.ts";

type AdapterMeta = {
__nodeHandler?: NodeHttpHandler;
__fetchHandler?: FetchHandler;
};

/**
* Converts a Fetch API handler to a Node.js HTTP handler.
*/
export function toNodeHandler(
handler: FetchHandler & AdapterMeta,
): NodeHttpHandler & AdapterMeta {
if (handler.__nodeHandler) {
return handler.__nodeHandler;
}

function convertedNodeHandler(
nodeReq: NodeServerRequest,
nodeRes: NodeServerResponse,
) {
const request = new NodeRequest({ req: nodeReq, res: nodeRes });
const res = handler(request);
return res instanceof Promise
? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes))
: sendNodeResponse(nodeRes, res);
}

(convertedNodeHandler as AdapterMeta).__fetchHandler = handler;
assignFnName(convertedNodeHandler, handler, " (converted to Node handler)");

return convertedNodeHandler;
}

/**
* Converts a Node.js HTTP handler into a Fetch API handler.
*
* @experimental Behavior might be unstable.
*/
export function toFetchHandler(
handler: NodeHttpHandler & AdapterMeta,
): FetchHandler & AdapterMeta {
if (handler.__fetchHandler) {
return handler.__fetchHandler;
}

function convertedNodeHandler(req: ServerRequest): Promise<Response> {
return fetchNodeHandler(handler as NodeHttpHandler, req);
}

(convertedNodeHandler as AdapterMeta).__nodeHandler =
handler as NodeHttpHandler;
assignFnName(convertedNodeHandler, handler, " (converted to Web handler)");

return convertedNodeHandler;
}

// --- utils ---

type Fn = (...args: any[]) => any;
function assignFnName(target: Fn, source: Fn, suffix: string) {
if (source.name) {
try {
Object.defineProperty(target, "name", {
value: `${source.name}${suffix}`,
});
} catch {
/* safe to ignore */
}
}
}
21 changes: 1 addition & 20 deletions src/adapters/_node/web/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import type {
ServerRequest,
NodeHttpHandler,
FetchHandler,
} from "../../../types.ts";
import type { ServerRequest, NodeHttpHandler } from "../../../types.ts";

import { WebIncomingMessage } from "./incoming.ts";
import { WebRequestSocket } from "./socket.ts";
Expand Down Expand Up @@ -51,18 +47,3 @@ export async function fetchNodeHandler(
});
}
}

/**
* Converts a Node.js HTTP handler into a Fetch API handler.
*
* @experimental Behavior might be unstable.
*/
export function toWebHandler(
handler: NodeHttpHandler | FetchHandler,
): FetchHandler {
if (handler.length === 1) {
return handler as FetchHandler;
}
return (req: ServerRequest) =>
fetchNodeHandler(handler as NodeHttpHandler, req);
}
24 changes: 4 additions & 20 deletions src/adapters/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import type NodeHttp from "node:http";
import type NodeHttps from "node:https";
import type NodeHttp2 from "node:http2";
import type {
FetchHandler,
NodeHttpHandler,
NodeServerRequest,
NodeServerResponse,
Server,
Expand All @@ -28,32 +26,18 @@ import type {
} from "../types.ts";

export { FastURL } from "../_url.ts";

export { NodeRequest } from "./_node/request.ts";
export { NodeRequestHeaders, NodeResponseHeaders } from "./_node/headers.ts";
export {
NodeResponse,
NodeResponse as FastResponse,
} from "./_node/response.ts";

export { NodeResponse } from "./_node/response.ts";
export { NodeResponse as FastResponse } from "./_node/response.ts";
export { sendNodeResponse } from "./_node/send.ts";

export { fetchNodeHandler, toWebHandler } from "./_node/web/fetch.ts";
export { fetchNodeHandler } from "./_node/web/fetch.ts";
export { toNodeHandler, toFetchHandler } from "./_node/adapter.ts";

export function serve(options: ServerOptions): Server {
return new NodeServer(options);
}

export function toNodeHandler(fetchHandler: FetchHandler): NodeHttpHandler {
return (nodeReq, nodeRes) => {
const request = new NodeRequest({ req: nodeReq, res: nodeRes });
const res = fetchHandler(request);
return res instanceof Promise
? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes))
: sendNodeResponse(nodeRes, res);
};
}

// https://nodejs.org/api/http.html
// https://nodejs.org/api/https.html
// https://nodejs.org/api/http2.html
Expand Down
62 changes: 60 additions & 2 deletions test/node-to-web.test.ts → test/node-adapters.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import type { NodeHttpHandler } from "../src/types.ts";
import { fetchNodeHandler, serve } from "../src/adapters/node.ts";
import type {
NodeHttpHandler,
NodeServerRequest,
NodeServerResponse,
} from "../src/types.ts";
import {
fetchNodeHandler,
serve,
toNodeHandler,
toFetchHandler,
} from "../src/adapters/node.ts";

import express from "express";
import fastify from "fastify";
Expand Down Expand Up @@ -114,3 +123,52 @@ describe("fetchNodeHandler", () => {
});
}
});

describe("adapters", () => {
function simpleNodeHandler(req: NodeServerRequest, res: NodeServerResponse) {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("ok");
}

function simpleWebHandler(): Response {
return new Response("ok", { status: 200 });
}

test("toFetchHandler", async () => {
const webHandler = toFetchHandler(simpleNodeHandler);
expect(webHandler.__nodeHandler).toBe(simpleNodeHandler);
expect(webHandler.name).toBe(
"simpleNodeHandler (converted to Web handler)",
);
const res = await webHandler(new Request("http://localhost/"));
expect(res.status).toBe(200);
expect(await res.text()).toBe("ok");
});

test("toNodeHandler", async () => {
const nodeHandler = toNodeHandler(simpleWebHandler);
expect(nodeHandler.__fetchHandler).toBe(simpleWebHandler);
expect(nodeHandler.name).toBe(
"simpleWebHandler (converted to Node handler)",
);

const res = await fetchNodeHandler(
nodeHandler,
new Request("http://localhost/"),
);
expect(res.status).toBe(200);
expect(await res.text()).toBe("ok");
});

test("toFetchHandler(toNodeHandler())", async () => {
expect(toFetchHandler(toNodeHandler(simpleWebHandler))).toBe(
simpleWebHandler,
);
});

test("toNodeHandler(toFetchHandler())", async () => {
expect(toNodeHandler(toFetchHandler(simpleNodeHandler))).toBe(
simpleNodeHandler,
);
});
});