@torpor/adapter-node 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/LICENSE.md +5 -0
- package/README.md +9 -0
- package/createFetchHandler.ts +29 -0
- package/dist/index.cjs +156 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +129 -0
- package/dist/index.js.map +1 -0
- package/dist/metafile-cjs.json +1 -0
- package/dist/metafile-esm.json +1 -0
- package/package.json +32 -0
- package/src/adapter.ts +17 -0
- package/src/constants.ts +1 -0
- package/src/createFetchHandler.ts +29 -0
- package/src/createNodeServer.ts +8 -0
- package/src/index.ts +3 -0
- package/src/nodeMessageToRequest.ts +43 -0
- package/src/responseToNodeResponse.ts +44 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +10 -0
- package/vitest.config.ts +3 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Copyright 2024 <OWNER>
|
|
2
|
+
|
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
4
|
+
|
|
5
|
+
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { Http2ServerRequest, Http2ServerResponse } from "node:http2";
|
|
3
|
+
import nodeMessageToRequest from "./nodeMessageToRequest";
|
|
4
|
+
import responseToNodeResponse from "./responseToNodeResponse";
|
|
5
|
+
import { INTERNAL_BODY } from "./src/constants";
|
|
6
|
+
|
|
7
|
+
// From https://gist.github.com/marvinhagemeister/cc236ec97235ce0305ae9d48a24a607d
|
|
8
|
+
// Referenced from https://marvinh.dev/blog/modern-way-to-write-javascript-servers/
|
|
9
|
+
|
|
10
|
+
const GlobalResponse = Response;
|
|
11
|
+
globalThis.Response = class Response extends GlobalResponse {
|
|
12
|
+
[INTERNAL_BODY]: BodyInit | null | undefined = null;
|
|
13
|
+
|
|
14
|
+
constructor(body?: BodyInit | null, init?: ResponseInit) {
|
|
15
|
+
super(body, init);
|
|
16
|
+
this[INTERNAL_BODY] = body;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function createFetchHandler(handler: (req: Request) => Response | Promise<Response>) {
|
|
21
|
+
return async (
|
|
22
|
+
incoming: IncomingMessage | Http2ServerRequest,
|
|
23
|
+
outgoing: ServerResponse | Http2ServerResponse,
|
|
24
|
+
) => {
|
|
25
|
+
const req = await nodeMessageToRequest(incoming);
|
|
26
|
+
const res = await handler(req);
|
|
27
|
+
return await responseToNodeResponse(res, outgoing);
|
|
28
|
+
};
|
|
29
|
+
}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
node: () => adapter_default
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/createNodeServer.ts
|
|
28
|
+
var import_node_http = require("http");
|
|
29
|
+
|
|
30
|
+
// src/constants.ts
|
|
31
|
+
var INTERNAL_BODY = Symbol("internal_body");
|
|
32
|
+
|
|
33
|
+
// src/nodeMessageToRequest.ts
|
|
34
|
+
var import_node_stream = require("stream");
|
|
35
|
+
|
|
36
|
+
// ../../build/src/server/connect/flattenHeaders.ts
|
|
37
|
+
function flattenHeaders(headers) {
|
|
38
|
+
const flatHeaders = new Headers();
|
|
39
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
40
|
+
if (value === void 0 || value === null) {
|
|
41
|
+
continue;
|
|
42
|
+
} else if (Array.isArray(value)) {
|
|
43
|
+
for (const v of value) {
|
|
44
|
+
if (v !== void 0 && v !== null) {
|
|
45
|
+
value.forEach((v2) => flatHeaders.append(key, v2));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
flatHeaders.set(key, String(value));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return flatHeaders;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/nodeMessageToRequest.ts
|
|
56
|
+
async function nodeMessageToRequest(req) {
|
|
57
|
+
let path = req.url;
|
|
58
|
+
if (path === void 0) {
|
|
59
|
+
throw new Error(`Empty request URL`);
|
|
60
|
+
}
|
|
61
|
+
if (!path.startsWith("/")) {
|
|
62
|
+
path = "/" + path;
|
|
63
|
+
}
|
|
64
|
+
const { host } = req.headers;
|
|
65
|
+
if (host == void 0) {
|
|
66
|
+
throw new Error(`Missing "Host" header`);
|
|
67
|
+
}
|
|
68
|
+
const protocol = "encrypted" in req.socket && req.socket.encrypted ? "https" : "http";
|
|
69
|
+
const url = `${protocol}://${host}${path}`;
|
|
70
|
+
const headers = flattenHeaders(req.headers);
|
|
71
|
+
const body = req.method !== "GET" && req.method !== "HEAD" ? import_node_stream.Readable.toWeb(req) : null;
|
|
72
|
+
return new Request(url, {
|
|
73
|
+
method: req.method,
|
|
74
|
+
headers,
|
|
75
|
+
body,
|
|
76
|
+
// @ts-ignore this is needed for using a Readable for some reason
|
|
77
|
+
duplex: "half"
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/responseToNodeResponse.ts
|
|
82
|
+
var import_types = require("util/types");
|
|
83
|
+
async function responseToNodeResponse(res, outgoing) {
|
|
84
|
+
outgoing.statusCode = res.status;
|
|
85
|
+
outgoing.statusMessage = res.statusText;
|
|
86
|
+
res.headers.forEach((value, key) => {
|
|
87
|
+
outgoing.setHeader(key, value);
|
|
88
|
+
});
|
|
89
|
+
const body = res[INTERNAL_BODY];
|
|
90
|
+
if (body === null || body === void 0) {
|
|
91
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
92
|
+
outgoing.end();
|
|
93
|
+
} else if (typeof body === "string" || body instanceof Uint8Array) {
|
|
94
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
95
|
+
outgoing.end(body);
|
|
96
|
+
} else if (body instanceof Blob) {
|
|
97
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
98
|
+
outgoing.end(new Uint8Array(await body.arrayBuffer()));
|
|
99
|
+
} else if (body instanceof ArrayBuffer) {
|
|
100
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
101
|
+
outgoing.end(new Uint8Array(body));
|
|
102
|
+
} else if ((0, import_types.isArrayBufferView)(body)) {
|
|
103
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
104
|
+
outgoing.end(new Uint8Array(body.buffer));
|
|
105
|
+
} else if (body instanceof FormData) {
|
|
106
|
+
outgoing.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
|
|
107
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
108
|
+
outgoing.end();
|
|
109
|
+
} else {
|
|
110
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
111
|
+
outgoing.end();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/createFetchHandler.ts
|
|
116
|
+
var GlobalResponse = Response;
|
|
117
|
+
globalThis.Response = class Response2 extends GlobalResponse {
|
|
118
|
+
[INTERNAL_BODY] = null;
|
|
119
|
+
constructor(body, init) {
|
|
120
|
+
super(body, init);
|
|
121
|
+
this[INTERNAL_BODY] = body;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
function createFetchHandler(handler) {
|
|
125
|
+
return async (incoming, outgoing) => {
|
|
126
|
+
const req = await nodeMessageToRequest(incoming);
|
|
127
|
+
const res = await handler(req);
|
|
128
|
+
return await responseToNodeResponse(res, outgoing);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/createNodeServer.ts
|
|
133
|
+
function createNodeServer(handler) {
|
|
134
|
+
return (0, import_node_http.createServer)(createFetchHandler(handler));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/adapter.ts
|
|
138
|
+
var adapter_default = {
|
|
139
|
+
serve: (server) => {
|
|
140
|
+
process.env.PROTOCOL ??= "http:";
|
|
141
|
+
process.env.HOST ??= "localhost";
|
|
142
|
+
process.env.PORT ??= "7059";
|
|
143
|
+
console.log(`
|
|
144
|
+
Connecting to ${process.env.HOST}:${process.env.PORT}`);
|
|
145
|
+
const devServer = createNodeServer(server.fetch);
|
|
146
|
+
devServer.listen(parseInt(process.env.PORT), process.env.HOST, () => {
|
|
147
|
+
console.log(`Listening on ${process.env.HOST}:${process.env.PORT}
|
|
148
|
+
`);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
153
|
+
0 && (module.exports = {
|
|
154
|
+
node
|
|
155
|
+
});
|
|
156
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/createNodeServer.ts","../src/constants.ts","../src/nodeMessageToRequest.ts","../../../build/src/server/connect/flattenHeaders.ts","../src/responseToNodeResponse.ts","../src/createFetchHandler.ts","../src/adapter.ts"],"sourcesContent":["import node from \"./adapter\";\n\nexport { node };\n","import { createServer } from \"node:http\";\nimport { createFetchHandler } from \"./createFetchHandler\";\n\nexport default function createNodeServer(handler: (req: Request) => Response | Promise<Response>) {\n\t// Create a server, polyfill it so that it can handle standard fetch methods,\n\t// and pass our app fetch method to the handler\n\treturn createServer(createFetchHandler(handler));\n}\n","export const INTERNAL_BODY = Symbol(\"internal_body\");\n","import { type IncomingMessage } from \"node:http\";\nimport { Http2ServerRequest } from \"node:http2\";\nimport { Readable } from \"node:stream\";\nimport flattenHeaders from \"../../../build/src/server/connect/flattenHeaders\";\n\n// From https://stackoverflow.com/a/78849544\n// and https://gist.github.com/marvinhagemeister/cc236ec97235ce0305ae9d48a24a607d\n\nexport default async function nodeMessageToRequest(\n\treq: IncomingMessage | Http2ServerRequest,\n): Promise<Request> {\n\t// Build the full url\n\tlet path = req.url;\n\tif (path === undefined) {\n\t\tthrow new Error(`Empty request URL`);\n\t}\n\tif (!path.startsWith(\"/\")) {\n\t\tpath = \"/\" + path;\n\t}\n\tconst { host } = req.headers;\n\tif (host == undefined) {\n\t\tthrow new Error(`Missing \"Host\" header`);\n\t}\n\tconst protocol = \"encrypted\" in req.socket && req.socket.encrypted ? \"https\" : \"http\";\n\tconst url = `${protocol}://${host}${path}`;\n\n\t// Flatten the headers\n\tconst headers = flattenHeaders(req.headers);\n\n\t// Convert body to Buffer if applicable\n\tconst body =\n\t\treq.method !== \"GET\" && req.method !== \"HEAD\"\n\t\t\t? (Readable.toWeb(req) as ReadableStream) // await readableToBuffer(req)\n\t\t\t: null;\n\n\treturn new Request(url, {\n\t\tmethod: req.method,\n\t\theaders: headers,\n\t\tbody: body,\n\t\t// @ts-ignore this is needed for using a Readable for some reason\n\t\tduplex: \"half\",\n\t});\n}\n","import { IncomingHttpHeaders } from \"node:http\";\nimport { OutgoingHttpHeaders } from \"node:http2\";\n\nexport default function flattenHeaders(\n\theaders: IncomingHttpHeaders | OutgoingHttpHeaders,\n): HeadersInit {\n\tconst flatHeaders = new Headers();\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (value === undefined || value === null) {\n\t\t\tcontinue;\n\t\t} else if (Array.isArray(value)) {\n\t\t\tfor (const v of value) {\n\t\t\t\tif (v !== undefined && v !== null) {\n\t\t\t\t\tvalue.forEach((v) => flatHeaders.append(key, v));\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tflatHeaders.set(key, String(value));\n\t\t}\n\t}\n\treturn flatHeaders;\n\n\t/*\n const flatHeaders: [string, string][] = [];\n\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (value === undefined || value === null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const v of value) {\n\t\t\t\tif (v != null) {\n\t\t\t\t\tflatHeaders.push([key, String(v)]);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tflatHeaders.push([key, String(value)]);\n\t\t}\n\t}\n\n\treturn flatHeaders;\n */\n}\n","import { ServerResponse } from \"node:http\";\nimport { Http2ServerResponse } from \"node:http2\";\nimport { isArrayBufferView } from \"node:util/types\";\nimport { INTERNAL_BODY } from \"./constants\";\n\nexport default async function responseToNodeResponse(\n\tres: Response,\n\toutgoing: ServerResponse | Http2ServerResponse,\n) {\n\toutgoing.statusCode = res.status;\n\toutgoing.statusMessage = res.statusText;\n\n\tres.headers.forEach((value, key) => {\n\t\toutgoing.setHeader(key, value);\n\t});\n\n\tconst body = (res as any)[INTERNAL_BODY];\n\n\tif (body === null || body === undefined) {\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end();\n\t} else if (typeof body === \"string\" || body instanceof Uint8Array) {\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end(body);\n\t} else if (body instanceof Blob) {\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end(new Uint8Array(await body.arrayBuffer()));\n\t} else if (body instanceof ArrayBuffer) {\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end(new Uint8Array(body));\n\t} else if (isArrayBufferView(body)) {\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end(new Uint8Array(body.buffer));\n\t} else if (body instanceof FormData) {\n\t\t// TODO\n\t\toutgoing.setHeader(\"Content-Type\", \"application/x-www-form-urlencoded;charset=UTF-8\");\n\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end();\n\t} else {\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end();\n\t}\n}\n","import { type IncomingMessage, ServerResponse } from \"node:http\";\nimport { Http2ServerRequest, Http2ServerResponse } from \"node:http2\";\nimport { INTERNAL_BODY } from \"./constants\";\nimport nodeMessageToRequest from \"./nodeMessageToRequest\";\nimport responseToNodeResponse from \"./responseToNodeResponse\";\n\n// From https://gist.github.com/marvinhagemeister/cc236ec97235ce0305ae9d48a24a607d\n// Referenced from https://marvinh.dev/blog/modern-way-to-write-javascript-servers/\n\nconst GlobalResponse = Response;\nglobalThis.Response = class Response extends GlobalResponse {\n\t[INTERNAL_BODY]: BodyInit | null | undefined = null;\n\n\tconstructor(body?: BodyInit | null, init?: ResponseInit) {\n\t\tsuper(body, init);\n\t\tthis[INTERNAL_BODY] = body;\n\t}\n};\n\nexport function createFetchHandler(handler: (req: Request) => Response | Promise<Response>) {\n\treturn async (\n\t\tincoming: IncomingMessage | Http2ServerRequest,\n\t\toutgoing: ServerResponse | Http2ServerResponse,\n\t) => {\n\t\tconst req = await nodeMessageToRequest(incoming);\n\t\tconst res = await handler(req);\n\t\treturn await responseToNodeResponse(res, outgoing);\n\t};\n}\n","import { type Adapter, Server } from \"@torpor/build\";\nimport createNodeServer from \"./createNodeServer\";\n\nexport default {\n\tserve: (server: Server) => {\n\t\tprocess.env.PROTOCOL ??= \"http:\";\n\t\tprocess.env.HOST ??= \"localhost\";\n\t\tprocess.env.PORT ??= \"7059\";\n\n\t\t// Create the Node server and start listening\n\t\tconsole.log(`\\nConnecting to ${process.env.HOST}:${process.env.PORT}`);\n\t\tconst devServer = createNodeServer(server.fetch);\n\t\tdevServer.listen(parseInt(process.env.PORT), process.env.HOST, () => {\n\t\t\tconsole.log(`Listening on ${process.env.HOST}:${process.env.PORT}\\n`);\n\t\t});\n\t},\n} satisfies Adapter;\n"],"mappings":"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","names":["v","Response"]}
|
package/dist/index.d.cts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// src/createNodeServer.ts
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
|
|
4
|
+
// src/constants.ts
|
|
5
|
+
var INTERNAL_BODY = Symbol("internal_body");
|
|
6
|
+
|
|
7
|
+
// src/nodeMessageToRequest.ts
|
|
8
|
+
import { Readable } from "node:stream";
|
|
9
|
+
|
|
10
|
+
// ../../build/src/server/connect/flattenHeaders.ts
|
|
11
|
+
function flattenHeaders(headers) {
|
|
12
|
+
const flatHeaders = new Headers();
|
|
13
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
14
|
+
if (value === void 0 || value === null) {
|
|
15
|
+
continue;
|
|
16
|
+
} else if (Array.isArray(value)) {
|
|
17
|
+
for (const v of value) {
|
|
18
|
+
if (v !== void 0 && v !== null) {
|
|
19
|
+
value.forEach((v2) => flatHeaders.append(key, v2));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
flatHeaders.set(key, String(value));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return flatHeaders;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/nodeMessageToRequest.ts
|
|
30
|
+
async function nodeMessageToRequest(req) {
|
|
31
|
+
let path = req.url;
|
|
32
|
+
if (path === void 0) {
|
|
33
|
+
throw new Error(`Empty request URL`);
|
|
34
|
+
}
|
|
35
|
+
if (!path.startsWith("/")) {
|
|
36
|
+
path = "/" + path;
|
|
37
|
+
}
|
|
38
|
+
const { host } = req.headers;
|
|
39
|
+
if (host == void 0) {
|
|
40
|
+
throw new Error(`Missing "Host" header`);
|
|
41
|
+
}
|
|
42
|
+
const protocol = "encrypted" in req.socket && req.socket.encrypted ? "https" : "http";
|
|
43
|
+
const url = `${protocol}://${host}${path}`;
|
|
44
|
+
const headers = flattenHeaders(req.headers);
|
|
45
|
+
const body = req.method !== "GET" && req.method !== "HEAD" ? Readable.toWeb(req) : null;
|
|
46
|
+
return new Request(url, {
|
|
47
|
+
method: req.method,
|
|
48
|
+
headers,
|
|
49
|
+
body,
|
|
50
|
+
// @ts-ignore this is needed for using a Readable for some reason
|
|
51
|
+
duplex: "half"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/responseToNodeResponse.ts
|
|
56
|
+
import { isArrayBufferView } from "node:util/types";
|
|
57
|
+
async function responseToNodeResponse(res, outgoing) {
|
|
58
|
+
outgoing.statusCode = res.status;
|
|
59
|
+
outgoing.statusMessage = res.statusText;
|
|
60
|
+
res.headers.forEach((value, key) => {
|
|
61
|
+
outgoing.setHeader(key, value);
|
|
62
|
+
});
|
|
63
|
+
const body = res[INTERNAL_BODY];
|
|
64
|
+
if (body === null || body === void 0) {
|
|
65
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
66
|
+
outgoing.end();
|
|
67
|
+
} else if (typeof body === "string" || body instanceof Uint8Array) {
|
|
68
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
69
|
+
outgoing.end(body);
|
|
70
|
+
} else if (body instanceof Blob) {
|
|
71
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
72
|
+
outgoing.end(new Uint8Array(await body.arrayBuffer()));
|
|
73
|
+
} else if (body instanceof ArrayBuffer) {
|
|
74
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
75
|
+
outgoing.end(new Uint8Array(body));
|
|
76
|
+
} else if (isArrayBufferView(body)) {
|
|
77
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
78
|
+
outgoing.end(new Uint8Array(body.buffer));
|
|
79
|
+
} else if (body instanceof FormData) {
|
|
80
|
+
outgoing.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
|
|
81
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
82
|
+
outgoing.end();
|
|
83
|
+
} else {
|
|
84
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
85
|
+
outgoing.end();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/createFetchHandler.ts
|
|
90
|
+
var GlobalResponse = Response;
|
|
91
|
+
globalThis.Response = class Response2 extends GlobalResponse {
|
|
92
|
+
[INTERNAL_BODY] = null;
|
|
93
|
+
constructor(body, init) {
|
|
94
|
+
super(body, init);
|
|
95
|
+
this[INTERNAL_BODY] = body;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
function createFetchHandler(handler) {
|
|
99
|
+
return async (incoming, outgoing) => {
|
|
100
|
+
const req = await nodeMessageToRequest(incoming);
|
|
101
|
+
const res = await handler(req);
|
|
102
|
+
return await responseToNodeResponse(res, outgoing);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/createNodeServer.ts
|
|
107
|
+
function createNodeServer(handler) {
|
|
108
|
+
return createServer(createFetchHandler(handler));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/adapter.ts
|
|
112
|
+
var adapter_default = {
|
|
113
|
+
serve: (server) => {
|
|
114
|
+
process.env.PROTOCOL ??= "http:";
|
|
115
|
+
process.env.HOST ??= "localhost";
|
|
116
|
+
process.env.PORT ??= "7059";
|
|
117
|
+
console.log(`
|
|
118
|
+
Connecting to ${process.env.HOST}:${process.env.PORT}`);
|
|
119
|
+
const devServer = createNodeServer(server.fetch);
|
|
120
|
+
devServer.listen(parseInt(process.env.PORT), process.env.HOST, () => {
|
|
121
|
+
console.log(`Listening on ${process.env.HOST}:${process.env.PORT}
|
|
122
|
+
`);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
export {
|
|
127
|
+
adapter_default as node
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/createNodeServer.ts","../src/constants.ts","../src/nodeMessageToRequest.ts","../../../build/src/server/connect/flattenHeaders.ts","../src/responseToNodeResponse.ts","../src/createFetchHandler.ts","../src/adapter.ts"],"sourcesContent":["import { createServer } from \"node:http\";\nimport { createFetchHandler } from \"./createFetchHandler\";\n\nexport default function createNodeServer(handler: (req: Request) => Response | Promise<Response>) {\n\t// Create a server, polyfill it so that it can handle standard fetch methods,\n\t// and pass our app fetch method to the handler\n\treturn createServer(createFetchHandler(handler));\n}\n","export const INTERNAL_BODY = Symbol(\"internal_body\");\n","import { type IncomingMessage } from \"node:http\";\nimport { Http2ServerRequest } from \"node:http2\";\nimport { Readable } from \"node:stream\";\nimport flattenHeaders from \"../../../build/src/server/connect/flattenHeaders\";\n\n// From https://stackoverflow.com/a/78849544\n// and https://gist.github.com/marvinhagemeister/cc236ec97235ce0305ae9d48a24a607d\n\nexport default async function nodeMessageToRequest(\n\treq: IncomingMessage | Http2ServerRequest,\n): Promise<Request> {\n\t// Build the full url\n\tlet path = req.url;\n\tif (path === undefined) {\n\t\tthrow new Error(`Empty request URL`);\n\t}\n\tif (!path.startsWith(\"/\")) {\n\t\tpath = \"/\" + path;\n\t}\n\tconst { host } = req.headers;\n\tif (host == undefined) {\n\t\tthrow new Error(`Missing \"Host\" header`);\n\t}\n\tconst protocol = \"encrypted\" in req.socket && req.socket.encrypted ? \"https\" : \"http\";\n\tconst url = `${protocol}://${host}${path}`;\n\n\t// Flatten the headers\n\tconst headers = flattenHeaders(req.headers);\n\n\t// Convert body to Buffer if applicable\n\tconst body =\n\t\treq.method !== \"GET\" && req.method !== \"HEAD\"\n\t\t\t? (Readable.toWeb(req) as ReadableStream) // await readableToBuffer(req)\n\t\t\t: null;\n\n\treturn new Request(url, {\n\t\tmethod: req.method,\n\t\theaders: headers,\n\t\tbody: body,\n\t\t// @ts-ignore this is needed for using a Readable for some reason\n\t\tduplex: \"half\",\n\t});\n}\n","import { IncomingHttpHeaders } from \"node:http\";\nimport { OutgoingHttpHeaders } from \"node:http2\";\n\nexport default function flattenHeaders(\n\theaders: IncomingHttpHeaders | OutgoingHttpHeaders,\n): HeadersInit {\n\tconst flatHeaders = new Headers();\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (value === undefined || value === null) {\n\t\t\tcontinue;\n\t\t} else if (Array.isArray(value)) {\n\t\t\tfor (const v of value) {\n\t\t\t\tif (v !== undefined && v !== null) {\n\t\t\t\t\tvalue.forEach((v) => flatHeaders.append(key, v));\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tflatHeaders.set(key, String(value));\n\t\t}\n\t}\n\treturn flatHeaders;\n\n\t/*\n const flatHeaders: [string, string][] = [];\n\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (value === undefined || value === null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const v of value) {\n\t\t\t\tif (v != null) {\n\t\t\t\t\tflatHeaders.push([key, String(v)]);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tflatHeaders.push([key, String(value)]);\n\t\t}\n\t}\n\n\treturn flatHeaders;\n */\n}\n","import { ServerResponse } from \"node:http\";\nimport { Http2ServerResponse } from \"node:http2\";\nimport { isArrayBufferView } from \"node:util/types\";\nimport { INTERNAL_BODY } from \"./constants\";\n\nexport default async function responseToNodeResponse(\n\tres: Response,\n\toutgoing: ServerResponse | Http2ServerResponse,\n) {\n\toutgoing.statusCode = res.status;\n\toutgoing.statusMessage = res.statusText;\n\n\tres.headers.forEach((value, key) => {\n\t\toutgoing.setHeader(key, value);\n\t});\n\n\tconst body = (res as any)[INTERNAL_BODY];\n\n\tif (body === null || body === undefined) {\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end();\n\t} else if (typeof body === \"string\" || body instanceof Uint8Array) {\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end(body);\n\t} else if (body instanceof Blob) {\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end(new Uint8Array(await body.arrayBuffer()));\n\t} else if (body instanceof ArrayBuffer) {\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end(new Uint8Array(body));\n\t} else if (isArrayBufferView(body)) {\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end(new Uint8Array(body.buffer));\n\t} else if (body instanceof FormData) {\n\t\t// TODO\n\t\toutgoing.setHeader(\"Content-Type\", \"application/x-www-form-urlencoded;charset=UTF-8\");\n\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end();\n\t} else {\n\t\toutgoing.writeHead(res.status, res.statusText);\n\t\toutgoing.end();\n\t}\n}\n","import { type IncomingMessage, ServerResponse } from \"node:http\";\nimport { Http2ServerRequest, Http2ServerResponse } from \"node:http2\";\nimport { INTERNAL_BODY } from \"./constants\";\nimport nodeMessageToRequest from \"./nodeMessageToRequest\";\nimport responseToNodeResponse from \"./responseToNodeResponse\";\n\n// From https://gist.github.com/marvinhagemeister/cc236ec97235ce0305ae9d48a24a607d\n// Referenced from https://marvinh.dev/blog/modern-way-to-write-javascript-servers/\n\nconst GlobalResponse = Response;\nglobalThis.Response = class Response extends GlobalResponse {\n\t[INTERNAL_BODY]: BodyInit | null | undefined = null;\n\n\tconstructor(body?: BodyInit | null, init?: ResponseInit) {\n\t\tsuper(body, init);\n\t\tthis[INTERNAL_BODY] = body;\n\t}\n};\n\nexport function createFetchHandler(handler: (req: Request) => Response | Promise<Response>) {\n\treturn async (\n\t\tincoming: IncomingMessage | Http2ServerRequest,\n\t\toutgoing: ServerResponse | Http2ServerResponse,\n\t) => {\n\t\tconst req = await nodeMessageToRequest(incoming);\n\t\tconst res = await handler(req);\n\t\treturn await responseToNodeResponse(res, outgoing);\n\t};\n}\n","import { type Adapter, Server } from \"@torpor/build\";\nimport createNodeServer from \"./createNodeServer\";\n\nexport default {\n\tserve: (server: Server) => {\n\t\tprocess.env.PROTOCOL ??= \"http:\";\n\t\tprocess.env.HOST ??= \"localhost\";\n\t\tprocess.env.PORT ??= \"7059\";\n\n\t\t// Create the Node server and start listening\n\t\tconsole.log(`\\nConnecting to ${process.env.HOST}:${process.env.PORT}`);\n\t\tconst devServer = createNodeServer(server.fetch);\n\t\tdevServer.listen(parseInt(process.env.PORT), process.env.HOST, () => {\n\t\t\tconsole.log(`Listening on ${process.env.HOST}:${process.env.PORT}\\n`);\n\t\t});\n\t},\n} satisfies Adapter;\n"],"mappings":"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","names":["v","Response"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"inputs":{"src/constants.ts":{"bytes":54,"imports":[],"format":"esm"},"../../build/src/server/connect/flattenHeaders.ts":{"bytes":1017,"imports":[{"path":"node:http","kind":"import-statement","external":true},{"path":"node:http2","kind":"import-statement","external":true}],"format":"esm"},"src/nodeMessageToRequest.ts":{"bytes":1309,"imports":[{"path":"node:http","kind":"import-statement","external":true},{"path":"node:http2","kind":"import-statement","external":true},{"path":"stream","kind":"import-statement","external":true},{"path":"../../build/src/server/connect/flattenHeaders.ts","kind":"import-statement","original":"../../../build/src/server/connect/flattenHeaders"}],"format":"esm"},"src/responseToNodeResponse.ts":{"bytes":1471,"imports":[{"path":"node:http","kind":"import-statement","external":true},{"path":"node:http2","kind":"import-statement","external":true},{"path":"util/types","kind":"import-statement","external":true},{"path":"src/constants.ts","kind":"import-statement","original":"./constants"}],"format":"esm"},"src/createFetchHandler.ts":{"bytes":1104,"imports":[{"path":"node:http","kind":"import-statement","external":true},{"path":"node:http2","kind":"import-statement","external":true},{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"src/nodeMessageToRequest.ts","kind":"import-statement","original":"./nodeMessageToRequest"},{"path":"src/responseToNodeResponse.ts","kind":"import-statement","original":"./responseToNodeResponse"}],"format":"esm"},"src/createNodeServer.ts":{"bytes":383,"imports":[{"path":"http","kind":"import-statement","external":true},{"path":"src/createFetchHandler.ts","kind":"import-statement","original":"./createFetchHandler"}],"format":"esm"},"src/adapter.ts":{"bytes":609,"imports":[{"path":"@torpor/build","kind":"import-statement","external":true},{"path":"src/createNodeServer.ts","kind":"import-statement","original":"./createNodeServer"}],"format":"esm"},"src/index.ts":{"bytes":48,"imports":[{"path":"src/adapter.ts","kind":"import-statement","original":"./adapter"}],"format":"esm"}},"outputs":{"dist/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":9552},"dist/index.cjs":{"imports":[{"path":"http","kind":"require-call","external":true},{"path":"stream","kind":"require-call","external":true},{"path":"util/types","kind":"require-call","external":true}],"exports":[],"entryPoint":"src/index.ts","inputs":{"src/index.ts":{"bytesInOutput":130},"src/createNodeServer.ts":{"bytesInOutput":153},"src/constants.ts":{"bytesInOutput":45},"src/nodeMessageToRequest.ts":{"bytesInOutput":808},"../../build/src/server/connect/flattenHeaders.ts":{"bytesInOutput":472},"src/responseToNodeResponse.ts":{"bytesInOutput":1289},"src/createFetchHandler.ts":{"bytesInOutput":443},"src/adapter.ts":{"bytesInOutput":451}},"bytes":4982}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"inputs":{"src/constants.ts":{"bytes":54,"imports":[],"format":"esm"},"../../build/src/server/connect/flattenHeaders.ts":{"bytes":1017,"imports":[{"path":"node:http","kind":"import-statement","external":true},{"path":"node:http2","kind":"import-statement","external":true}],"format":"esm"},"src/nodeMessageToRequest.ts":{"bytes":1309,"imports":[{"path":"node:http","kind":"import-statement","external":true},{"path":"node:http2","kind":"import-statement","external":true},{"path":"node:stream","kind":"import-statement","external":true},{"path":"../../build/src/server/connect/flattenHeaders.ts","kind":"import-statement","original":"../../../build/src/server/connect/flattenHeaders"}],"format":"esm"},"src/responseToNodeResponse.ts":{"bytes":1471,"imports":[{"path":"node:http","kind":"import-statement","external":true},{"path":"node:http2","kind":"import-statement","external":true},{"path":"node:util/types","kind":"import-statement","external":true},{"path":"src/constants.ts","kind":"import-statement","original":"./constants"}],"format":"esm"},"src/createFetchHandler.ts":{"bytes":1104,"imports":[{"path":"node:http","kind":"import-statement","external":true},{"path":"node:http2","kind":"import-statement","external":true},{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"src/nodeMessageToRequest.ts","kind":"import-statement","original":"./nodeMessageToRequest"},{"path":"src/responseToNodeResponse.ts","kind":"import-statement","original":"./responseToNodeResponse"}],"format":"esm"},"src/createNodeServer.ts":{"bytes":383,"imports":[{"path":"node:http","kind":"import-statement","external":true},{"path":"src/createFetchHandler.ts","kind":"import-statement","original":"./createFetchHandler"}],"format":"esm"},"src/adapter.ts":{"bytes":609,"imports":[{"path":"@torpor/build","kind":"import-statement","external":true},{"path":"src/createNodeServer.ts","kind":"import-statement","original":"./createNodeServer"}],"format":"esm"},"src/index.ts":{"bytes":48,"imports":[{"path":"src/adapter.ts","kind":"import-statement","original":"./adapter"}],"format":"esm"}},"outputs":{"dist/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":9443},"dist/index.js":{"imports":[{"path":"node:http","kind":"import-statement","external":true},{"path":"node:stream","kind":"import-statement","external":true},{"path":"node:util/types","kind":"import-statement","external":true}],"exports":["node"],"entryPoint":"src/index.ts","inputs":{"src/createNodeServer.ts":{"bytesInOutput":133},"src/constants.ts":{"bytesInOutput":45},"src/nodeMessageToRequest.ts":{"bytesInOutput":785},"../../build/src/server/connect/flattenHeaders.ts":{"bytesInOutput":472},"src/responseToNodeResponse.ts":{"bytesInOutput":1282},"src/createFetchHandler.ts":{"bytesInOutput":443},"src/adapter.ts":{"bytesInOutput":451},"src/index.ts":{"bytesInOutput":0}},"bytes":3925}}}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@torpor/adapter-node",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A @torpor/build adapter for running on Node",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"vite": "^6.2.4",
|
|
17
|
+
"@torpor/build": "^0.1.3"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
|
21
|
+
"tsup": "^8.4.0",
|
|
22
|
+
"tsx": "^4.19.3",
|
|
23
|
+
"typescript": "^5.8.2",
|
|
24
|
+
"vitest": "^3.1.1"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"build:watch": "tsup --watch",
|
|
29
|
+
"check": "tsc --noEmit",
|
|
30
|
+
"test": "vitest"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/adapter.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Adapter, Server } from "@torpor/build";
|
|
2
|
+
import createNodeServer from "./createNodeServer";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
serve: (server: Server) => {
|
|
6
|
+
process.env.PROTOCOL ??= "http:";
|
|
7
|
+
process.env.HOST ??= "localhost";
|
|
8
|
+
process.env.PORT ??= "7059";
|
|
9
|
+
|
|
10
|
+
// Create the Node server and start listening
|
|
11
|
+
console.log(`\nConnecting to ${process.env.HOST}:${process.env.PORT}`);
|
|
12
|
+
const devServer = createNodeServer(server.fetch);
|
|
13
|
+
devServer.listen(parseInt(process.env.PORT), process.env.HOST, () => {
|
|
14
|
+
console.log(`Listening on ${process.env.HOST}:${process.env.PORT}\n`);
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
} satisfies Adapter;
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const INTERNAL_BODY = Symbol("internal_body");
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { Http2ServerRequest, Http2ServerResponse } from "node:http2";
|
|
3
|
+
import { INTERNAL_BODY } from "./constants";
|
|
4
|
+
import nodeMessageToRequest from "./nodeMessageToRequest";
|
|
5
|
+
import responseToNodeResponse from "./responseToNodeResponse";
|
|
6
|
+
|
|
7
|
+
// From https://gist.github.com/marvinhagemeister/cc236ec97235ce0305ae9d48a24a607d
|
|
8
|
+
// Referenced from https://marvinh.dev/blog/modern-way-to-write-javascript-servers/
|
|
9
|
+
|
|
10
|
+
const GlobalResponse = Response;
|
|
11
|
+
globalThis.Response = class Response extends GlobalResponse {
|
|
12
|
+
[INTERNAL_BODY]: BodyInit | null | undefined = null;
|
|
13
|
+
|
|
14
|
+
constructor(body?: BodyInit | null, init?: ResponseInit) {
|
|
15
|
+
super(body, init);
|
|
16
|
+
this[INTERNAL_BODY] = body;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function createFetchHandler(handler: (req: Request) => Response | Promise<Response>) {
|
|
21
|
+
return async (
|
|
22
|
+
incoming: IncomingMessage | Http2ServerRequest,
|
|
23
|
+
outgoing: ServerResponse | Http2ServerResponse,
|
|
24
|
+
) => {
|
|
25
|
+
const req = await nodeMessageToRequest(incoming);
|
|
26
|
+
const res = await handler(req);
|
|
27
|
+
return await responseToNodeResponse(res, outgoing);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { createFetchHandler } from "./createFetchHandler";
|
|
3
|
+
|
|
4
|
+
export default function createNodeServer(handler: (req: Request) => Response | Promise<Response>) {
|
|
5
|
+
// Create a server, polyfill it so that it can handle standard fetch methods,
|
|
6
|
+
// and pass our app fetch method to the handler
|
|
7
|
+
return createServer(createFetchHandler(handler));
|
|
8
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type IncomingMessage } from "node:http";
|
|
2
|
+
import { Http2ServerRequest } from "node:http2";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
import flattenHeaders from "../../../build/src/server/connect/flattenHeaders";
|
|
5
|
+
|
|
6
|
+
// From https://stackoverflow.com/a/78849544
|
|
7
|
+
// and https://gist.github.com/marvinhagemeister/cc236ec97235ce0305ae9d48a24a607d
|
|
8
|
+
|
|
9
|
+
export default async function nodeMessageToRequest(
|
|
10
|
+
req: IncomingMessage | Http2ServerRequest,
|
|
11
|
+
): Promise<Request> {
|
|
12
|
+
// Build the full url
|
|
13
|
+
let path = req.url;
|
|
14
|
+
if (path === undefined) {
|
|
15
|
+
throw new Error(`Empty request URL`);
|
|
16
|
+
}
|
|
17
|
+
if (!path.startsWith("/")) {
|
|
18
|
+
path = "/" + path;
|
|
19
|
+
}
|
|
20
|
+
const { host } = req.headers;
|
|
21
|
+
if (host == undefined) {
|
|
22
|
+
throw new Error(`Missing "Host" header`);
|
|
23
|
+
}
|
|
24
|
+
const protocol = "encrypted" in req.socket && req.socket.encrypted ? "https" : "http";
|
|
25
|
+
const url = `${protocol}://${host}${path}`;
|
|
26
|
+
|
|
27
|
+
// Flatten the headers
|
|
28
|
+
const headers = flattenHeaders(req.headers);
|
|
29
|
+
|
|
30
|
+
// Convert body to Buffer if applicable
|
|
31
|
+
const body =
|
|
32
|
+
req.method !== "GET" && req.method !== "HEAD"
|
|
33
|
+
? (Readable.toWeb(req) as ReadableStream) // await readableToBuffer(req)
|
|
34
|
+
: null;
|
|
35
|
+
|
|
36
|
+
return new Request(url, {
|
|
37
|
+
method: req.method,
|
|
38
|
+
headers: headers,
|
|
39
|
+
body: body,
|
|
40
|
+
// @ts-ignore this is needed for using a Readable for some reason
|
|
41
|
+
duplex: "half",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ServerResponse } from "node:http";
|
|
2
|
+
import { Http2ServerResponse } from "node:http2";
|
|
3
|
+
import { isArrayBufferView } from "node:util/types";
|
|
4
|
+
import { INTERNAL_BODY } from "./constants";
|
|
5
|
+
|
|
6
|
+
export default async function responseToNodeResponse(
|
|
7
|
+
res: Response,
|
|
8
|
+
outgoing: ServerResponse | Http2ServerResponse,
|
|
9
|
+
) {
|
|
10
|
+
outgoing.statusCode = res.status;
|
|
11
|
+
outgoing.statusMessage = res.statusText;
|
|
12
|
+
|
|
13
|
+
res.headers.forEach((value, key) => {
|
|
14
|
+
outgoing.setHeader(key, value);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const body = (res as any)[INTERNAL_BODY];
|
|
18
|
+
|
|
19
|
+
if (body === null || body === undefined) {
|
|
20
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
21
|
+
outgoing.end();
|
|
22
|
+
} else if (typeof body === "string" || body instanceof Uint8Array) {
|
|
23
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
24
|
+
outgoing.end(body);
|
|
25
|
+
} else if (body instanceof Blob) {
|
|
26
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
27
|
+
outgoing.end(new Uint8Array(await body.arrayBuffer()));
|
|
28
|
+
} else if (body instanceof ArrayBuffer) {
|
|
29
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
30
|
+
outgoing.end(new Uint8Array(body));
|
|
31
|
+
} else if (isArrayBufferView(body)) {
|
|
32
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
33
|
+
outgoing.end(new Uint8Array(body.buffer));
|
|
34
|
+
} else if (body instanceof FormData) {
|
|
35
|
+
// TODO
|
|
36
|
+
outgoing.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
|
|
37
|
+
|
|
38
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
39
|
+
outgoing.end();
|
|
40
|
+
} else {
|
|
41
|
+
outgoing.writeHead(res.status, res.statusText);
|
|
42
|
+
outgoing.end();
|
|
43
|
+
}
|
|
44
|
+
}
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
package/vitest.config.ts
ADDED