tachyon-dom 0.1.0
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 +21 -0
- package/README.md +339 -0
- package/dist/adapters/lambda.d.ts +65 -0
- package/dist/adapters/lambda.js +211 -0
- package/dist/adapters/node.d.ts +25 -0
- package/dist/adapters/node.js +360 -0
- package/dist/adapters/workers.d.ts +149 -0
- package/dist/adapters/workers.js +272 -0
- package/dist/adapters.d.ts +3 -0
- package/dist/adapters.js +3 -0
- package/dist/app.d.ts +98 -0
- package/dist/app.js +306 -0
- package/dist/cli.d.ts +55 -0
- package/dist/cli.js +711 -0
- package/dist/compiler/expression.d.ts +55 -0
- package/dist/compiler/expression.js +858 -0
- package/dist/compiler/index.d.ts +8 -0
- package/dist/compiler/index.js +50 -0
- package/dist/compiler/ir.d.ts +4 -0
- package/dist/compiler/ir.js +255 -0
- package/dist/compiler/parser.d.ts +3 -0
- package/dist/compiler/parser.js +236 -0
- package/dist/compiler/sfc.d.ts +33 -0
- package/dist/compiler/sfc.js +439 -0
- package/dist/compiler/targets/client.d.ts +3 -0
- package/dist/compiler/targets/client.js +568 -0
- package/dist/compiler/targets/server.d.ts +7 -0
- package/dist/compiler/targets/server.js +396 -0
- package/dist/compiler/targets/stream.d.ts +2 -0
- package/dist/compiler/targets/stream.js +204 -0
- package/dist/compiler/types.d.ts +185 -0
- package/dist/compiler/types.js +1 -0
- package/dist/compiler/utils.d.ts +40 -0
- package/dist/compiler/utils.js +185 -0
- package/dist/compiler/whitespace.d.ts +2 -0
- package/dist/compiler/whitespace.js +66 -0
- package/dist/compiler.d.ts +1 -0
- package/dist/compiler.js +1 -0
- package/dist/constant-time.d.ts +1 -0
- package/dist/constant-time.js +26 -0
- package/dist/cookies.d.ts +44 -0
- package/dist/cookies.js +199 -0
- package/dist/diagnostics.d.ts +22 -0
- package/dist/diagnostics.js +45 -0
- package/dist/env.d.ts +27 -0
- package/dist/env.js +37 -0
- package/dist/html-escape.d.ts +2 -0
- package/dist/html-escape.js +20 -0
- package/dist/html-whitespace.d.ts +6 -0
- package/dist/html-whitespace.js +83 -0
- package/dist/i18n.d.ts +19 -0
- package/dist/i18n.js +93 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +18 -0
- package/dist/language-server.d.ts +17 -0
- package/dist/language-server.js +79 -0
- package/dist/package-integrity.d.ts +9 -0
- package/dist/package-integrity.js +64 -0
- package/dist/result.d.ts +13 -0
- package/dist/result.js +4 -0
- package/dist/router-node.d.ts +2 -0
- package/dist/router-node.js +12 -0
- package/dist/router.d.ts +324 -0
- package/dist/router.js +1024 -0
- package/dist/runtime/attr.d.ts +3 -0
- package/dist/runtime/attr.js +66 -0
- package/dist/runtime/class.d.ts +2 -0
- package/dist/runtime/class.js +10 -0
- package/dist/runtime/conditional.d.ts +78 -0
- package/dist/runtime/conditional.js +140 -0
- package/dist/runtime/enhancement.d.ts +11 -0
- package/dist/runtime/enhancement.js +70 -0
- package/dist/runtime/error-boundary.d.ts +8 -0
- package/dist/runtime/error-boundary.js +27 -0
- package/dist/runtime/event.d.ts +1 -0
- package/dist/runtime/event.js +11 -0
- package/dist/runtime/form.d.ts +41 -0
- package/dist/runtime/form.js +167 -0
- package/dist/runtime/fragment.d.ts +6 -0
- package/dist/runtime/fragment.js +18 -0
- package/dist/runtime/html.d.ts +8 -0
- package/dist/runtime/html.js +6 -0
- package/dist/runtime/hydrate.d.ts +68 -0
- package/dist/runtime/hydrate.js +205 -0
- package/dist/runtime/keyed-rows.d.ts +61 -0
- package/dist/runtime/keyed-rows.js +192 -0
- package/dist/runtime/list.d.ts +84 -0
- package/dist/runtime/list.js +424 -0
- package/dist/runtime/portal.d.ts +6 -0
- package/dist/runtime/portal.js +12 -0
- package/dist/runtime/router.d.ts +102 -0
- package/dist/runtime/router.js +649 -0
- package/dist/runtime/signal.d.ts +30 -0
- package/dist/runtime/signal.js +275 -0
- package/dist/runtime/store.d.ts +1 -0
- package/dist/runtime/store.js +1 -0
- package/dist/runtime/stream-client.d.ts +8 -0
- package/dist/runtime/stream-client.js +33 -0
- package/dist/runtime/text.d.ts +2 -0
- package/dist/runtime/text.js +10 -0
- package/dist/runtime/virtual-list.d.ts +15 -0
- package/dist/runtime/virtual-list.js +89 -0
- package/dist/security.d.ts +27 -0
- package/dist/security.js +173 -0
- package/dist/server/form-action.d.ts +52 -0
- package/dist/server/form-action.js +102 -0
- package/dist/server/html.d.ts +19 -0
- package/dist/server/html.js +101 -0
- package/dist/server/stream.d.ts +4 -0
- package/dist/server/stream.js +72 -0
- package/dist/source-map.d.ts +17 -0
- package/dist/source-map.js +18 -0
- package/dist/tachyon-html.d.ts +110 -0
- package/dist/testing.d.ts +14 -0
- package/dist/testing.js +36 -0
- package/dist/typed.d.ts +9 -0
- package/dist/typed.js +4 -0
- package/dist/vite.d.ts +84 -0
- package/dist/vite.js +426 -0
- package/package.json +604 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { type AdapterFetchHandler, type WorkersFetchHandlerOptions, type RouteAdapterHandlerOptions } from "./workers.js";
|
|
3
|
+
export type NodeHandlerOptions = RouteAdapterHandlerOptions & {
|
|
4
|
+
staticAssets?: StaticAssetOptions;
|
|
5
|
+
origin?: string | ((request: IncomingMessage) => string);
|
|
6
|
+
trustedHosts?: readonly string[];
|
|
7
|
+
trustProxy?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type NodeFetchHandlerOptions = Omit<WorkersFetchHandlerOptions, "fetch"> & {
|
|
10
|
+
fetch: AdapterFetchHandler;
|
|
11
|
+
staticAssets?: StaticAssetOptions;
|
|
12
|
+
origin?: string | ((request: IncomingMessage) => string);
|
|
13
|
+
trustedHosts?: readonly string[];
|
|
14
|
+
trustProxy?: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type StaticAssetOptions = {
|
|
17
|
+
rootDir: string;
|
|
18
|
+
basePath?: string;
|
|
19
|
+
headers?: HeadersInit;
|
|
20
|
+
fallthroughOnNotFound?: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare const createStaticAssetHandler: (options: StaticAssetOptions) => ((request: Request) => Promise<Response | undefined>);
|
|
23
|
+
export declare const writeNodeResponse: (webResponse: Response, response: ServerResponse) => Promise<void>;
|
|
24
|
+
export declare const createNodeHandler: (options: NodeHandlerOptions) => (request: IncomingMessage, response: ServerResponse) => Promise<void>;
|
|
25
|
+
export declare const createNodeFetchHandler: (options: NodeFetchHandlerOptions) => ((request: IncomingMessage, response: ServerResponse) => Promise<void>);
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
import { createWorkersFetchHandler, createWorkersHandler, } from "./workers.js";
|
|
5
|
+
const mergeHeaders = (base, extra) => {
|
|
6
|
+
const headers = new Headers(base);
|
|
7
|
+
extra?.forEach((value, key) => headers.set(key, value));
|
|
8
|
+
return headers;
|
|
9
|
+
};
|
|
10
|
+
const contentTypeFor = (file) => {
|
|
11
|
+
if (file.endsWith(".css")) {
|
|
12
|
+
return "text/css; charset=utf-8";
|
|
13
|
+
}
|
|
14
|
+
if (file.endsWith(".js") || file.endsWith(".mjs")) {
|
|
15
|
+
return "text/javascript; charset=utf-8";
|
|
16
|
+
}
|
|
17
|
+
if (file.endsWith(".json")) {
|
|
18
|
+
return "application/json; charset=utf-8";
|
|
19
|
+
}
|
|
20
|
+
if (file.endsWith(".svg")) {
|
|
21
|
+
return "image/svg+xml";
|
|
22
|
+
}
|
|
23
|
+
if (file.endsWith(".html")) {
|
|
24
|
+
return "text/html; charset=utf-8";
|
|
25
|
+
}
|
|
26
|
+
if (file.endsWith(".wasm")) {
|
|
27
|
+
return "application/wasm";
|
|
28
|
+
}
|
|
29
|
+
return "application/octet-stream";
|
|
30
|
+
};
|
|
31
|
+
const findStaticRoute = (routes, method, url) => routes?.find((route) => {
|
|
32
|
+
const methods = route.methods ?? ["GET", "HEAD"];
|
|
33
|
+
const path = route.path.includes("?") ? `${url.pathname}${url.search}` : url.search ? "" : url.pathname;
|
|
34
|
+
return methods.includes(method) && route.path === path;
|
|
35
|
+
});
|
|
36
|
+
const byteLength = (value) => new TextEncoder().encode(value).byteLength;
|
|
37
|
+
const headersForStaticRoute = (route, extra) => {
|
|
38
|
+
const headers = mergeHeaders(new Headers(route.headers), extra);
|
|
39
|
+
if (!headers.has("content-type")) {
|
|
40
|
+
headers.set("content-type", "text/html; charset=utf-8");
|
|
41
|
+
}
|
|
42
|
+
if (!headers.has("content-length")) {
|
|
43
|
+
headers.set("content-length", String(byteLength(route.body)));
|
|
44
|
+
}
|
|
45
|
+
return headers;
|
|
46
|
+
};
|
|
47
|
+
const setNodeHeaders = (response, headers) => {
|
|
48
|
+
const setCookies = headers.getSetCookie();
|
|
49
|
+
headers.forEach((value, key) => {
|
|
50
|
+
if (key.toLowerCase() !== "set-cookie") {
|
|
51
|
+
response.setHeader(key, value);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
if (setCookies.length === 1) {
|
|
55
|
+
response.setHeader("set-cookie", setCookies[0] ?? "");
|
|
56
|
+
}
|
|
57
|
+
else if (setCookies.length > 1) {
|
|
58
|
+
response.setHeader("set-cookie", setCookies);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const writeNodeStaticRoute = (route, request, response, securityHeaders) => {
|
|
62
|
+
response.statusCode = route.status ?? 200;
|
|
63
|
+
setNodeHeaders(response, headersForStaticRoute(route, securityHeaders));
|
|
64
|
+
response.end(request.method === "HEAD" ? undefined : route.body);
|
|
65
|
+
};
|
|
66
|
+
const withSecurityHeaders = (response, securityHeaders) => {
|
|
67
|
+
if (!securityHeaders) {
|
|
68
|
+
return response;
|
|
69
|
+
}
|
|
70
|
+
return new Response(response.body, {
|
|
71
|
+
status: response.status,
|
|
72
|
+
statusText: response.statusText,
|
|
73
|
+
headers: mergeHeaders(response.headers, securityHeaders),
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
const isFileSystemNotFound = (error) => {
|
|
77
|
+
const code = error.code;
|
|
78
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
79
|
+
};
|
|
80
|
+
const rawPathnameFromRequestUrl = (requestUrl) => {
|
|
81
|
+
const raw = requestUrl ?? "/";
|
|
82
|
+
const queryStart = raw.search(/[?#]/);
|
|
83
|
+
return queryStart === -1 ? raw : raw.slice(0, queryStart);
|
|
84
|
+
};
|
|
85
|
+
const rawPathStartsWithBase = (rawPathname, basePath) => {
|
|
86
|
+
if (basePath === "/") {
|
|
87
|
+
return rawPathname.startsWith("/");
|
|
88
|
+
}
|
|
89
|
+
return rawPathname === basePath || rawPathname.startsWith(`${basePath}/`) || rawPathname.startsWith(`${basePath}\\`);
|
|
90
|
+
};
|
|
91
|
+
const staticAssetTraversalResponse = (requestUrl, options) => {
|
|
92
|
+
const basePath = options.basePath ?? "/";
|
|
93
|
+
const rawPathname = rawPathnameFromRequestUrl(requestUrl);
|
|
94
|
+
try {
|
|
95
|
+
const decodedPathname = decodeURIComponent(rawPathname);
|
|
96
|
+
if (!rawPathStartsWithBase(rawPathname, basePath) && !rawPathStartsWithBase(decodedPathname, basePath)) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
const relativePath = decodedPathname.slice(basePath.length).replace(/^\/+/, "");
|
|
100
|
+
return relativePath.split(/[\\/]/).includes("..") ? new Response("Forbidden", { status: 403 }) : undefined;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
export const createStaticAssetHandler = (options) => async (request) => {
|
|
107
|
+
const basePath = options.basePath ?? "/";
|
|
108
|
+
const url = new URL(request.url);
|
|
109
|
+
if (basePath !== "/" && url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
113
|
+
if (options.fallthroughOnNotFound) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
return new Response("Method Not Allowed", { status: 405, headers: { allow: "GET, HEAD" } });
|
|
117
|
+
}
|
|
118
|
+
let relativePath;
|
|
119
|
+
try {
|
|
120
|
+
relativePath = decodeURIComponent(url.pathname.slice(basePath.length)).replace(/^\/+/, "");
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return new Response("Not Found", { status: 404 });
|
|
124
|
+
}
|
|
125
|
+
if (!relativePath) {
|
|
126
|
+
return options.fallthroughOnNotFound ? undefined : new Response("Forbidden", { status: 403 });
|
|
127
|
+
}
|
|
128
|
+
if (relativePath.split(/[\\/]/).some((segment) => segment === ".." || segment.startsWith("."))) {
|
|
129
|
+
return new Response("Forbidden", { status: 403 });
|
|
130
|
+
}
|
|
131
|
+
const root = path.resolve(options.rootDir);
|
|
132
|
+
const file = path.resolve(root, relativePath);
|
|
133
|
+
if (!file.startsWith(`${root}${path.sep}`)) {
|
|
134
|
+
return new Response("Forbidden", { status: 403 });
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const canonicalRoot = await realpath(root);
|
|
138
|
+
const canonicalFile = await realpath(file);
|
|
139
|
+
if (!canonicalFile.startsWith(`${canonicalRoot}${path.sep}`)) {
|
|
140
|
+
return new Response("Forbidden", { status: 403 });
|
|
141
|
+
}
|
|
142
|
+
const canonicalRelativePath = path.relative(canonicalRoot, canonicalFile);
|
|
143
|
+
if (canonicalRelativePath.split(path.sep).some((segment) => segment.startsWith("."))) {
|
|
144
|
+
return new Response("Forbidden", { status: 403 });
|
|
145
|
+
}
|
|
146
|
+
const info = await stat(canonicalFile);
|
|
147
|
+
if (!info.isFile()) {
|
|
148
|
+
return options.fallthroughOnNotFound ? undefined : new Response("Not Found", { status: 404 });
|
|
149
|
+
}
|
|
150
|
+
const headers = new Headers(options.headers);
|
|
151
|
+
if (!headers.has("content-type")) {
|
|
152
|
+
headers.set("content-type", contentTypeFor(canonicalFile));
|
|
153
|
+
}
|
|
154
|
+
headers.set("content-length", String(info.size));
|
|
155
|
+
return new Response(request.method === "HEAD" ? null : await readFile(canonicalFile), { status: 200, headers });
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (options.fallthroughOnNotFound && isFileSystemNotFound(error)) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
return new Response("Not Found", { status: 404 });
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
export const writeNodeResponse = async (webResponse, response) => {
|
|
165
|
+
response.statusCode = webResponse.status;
|
|
166
|
+
setNodeHeaders(response, webResponse.headers);
|
|
167
|
+
const writable = response;
|
|
168
|
+
if (!webResponse.body || typeof writable.write !== "function") {
|
|
169
|
+
response.end(await webResponse.text());
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (typeof response.flushHeaders === "function") {
|
|
173
|
+
response.flushHeaders();
|
|
174
|
+
}
|
|
175
|
+
const reader = webResponse.body.getReader();
|
|
176
|
+
let responseClosed = false;
|
|
177
|
+
let finished = false;
|
|
178
|
+
let cancelPromise;
|
|
179
|
+
const onCloseOrError = () => {
|
|
180
|
+
if (finished || response.writableEnded) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
responseClosed = true;
|
|
184
|
+
cancelPromise = reader.cancel().catch(() => undefined);
|
|
185
|
+
};
|
|
186
|
+
if (typeof response.once === "function") {
|
|
187
|
+
response.once("close", onCloseOrError);
|
|
188
|
+
response.once("error", onCloseOrError);
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
while (!responseClosed) {
|
|
192
|
+
const result = await reader.read();
|
|
193
|
+
if (responseClosed) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (result.done) {
|
|
197
|
+
finished = true;
|
|
198
|
+
response.end();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (writable.write(Buffer.from(result.value)) === false && typeof response.once === "function") {
|
|
202
|
+
await new Promise((resolve) => {
|
|
203
|
+
const resume = () => {
|
|
204
|
+
response.off?.("drain", resume);
|
|
205
|
+
response.off?.("close", resume);
|
|
206
|
+
response.off?.("error", resume);
|
|
207
|
+
resolve();
|
|
208
|
+
};
|
|
209
|
+
response.once("drain", resume);
|
|
210
|
+
response.once("close", resume);
|
|
211
|
+
response.once("error", resume);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
if (typeof response.off === "function") {
|
|
218
|
+
response.off("close", onCloseOrError);
|
|
219
|
+
response.off("error", onCloseOrError);
|
|
220
|
+
}
|
|
221
|
+
await cancelPromise;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
const requestBody = (request) => {
|
|
225
|
+
if (request.method === "GET" || request.method === "HEAD") {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
return Readable.toWeb(request);
|
|
229
|
+
};
|
|
230
|
+
const firstHeaderValue = (value) => Array.isArray(value) ? value[0] : value;
|
|
231
|
+
const normalizeOrigin = (origin) => {
|
|
232
|
+
const candidate = origin.includes("://") ? origin : `https://${origin}`;
|
|
233
|
+
return new URL(candidate).origin;
|
|
234
|
+
};
|
|
235
|
+
const hostName = (host) => host.toLowerCase().replace(/:\d+$/, "");
|
|
236
|
+
const isTrustedHost = (host, trustedHosts) => {
|
|
237
|
+
if (!trustedHosts || trustedHosts.length === 0) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
const normalized = host.toLowerCase();
|
|
241
|
+
const normalizedName = hostName(normalized);
|
|
242
|
+
return trustedHosts.some((trusted) => {
|
|
243
|
+
const candidate = trusted.toLowerCase();
|
|
244
|
+
return normalized === candidate || normalizedName === candidate;
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
const badRequestResponse = (message) => new Response(message, { status: 400 });
|
|
248
|
+
const requestUrl = (request, options = {}) => {
|
|
249
|
+
const host = firstHeaderValue(request.headers.host) ?? "localhost";
|
|
250
|
+
const hasTrustedHosts = options.trustedHosts !== undefined && options.trustedHosts.length > 0;
|
|
251
|
+
const trustedHost = isTrustedHost(host, options.trustedHosts) ? host : "localhost";
|
|
252
|
+
if (hasTrustedHosts && trustedHost === "localhost") {
|
|
253
|
+
return badRequestResponse("Untrusted Host header");
|
|
254
|
+
}
|
|
255
|
+
const protocol = options.trustProxy
|
|
256
|
+
? (firstHeaderValue(request.headers["x-forwarded-proto"]) ?? "http").split(",")[0]?.trim() || "http"
|
|
257
|
+
: "http";
|
|
258
|
+
const origin = options.origin
|
|
259
|
+
? normalizeOrigin(typeof options.origin === "function" ? options.origin(request) : options.origin)
|
|
260
|
+
: `${protocol}://${trustedHost}`;
|
|
261
|
+
return `${origin}${request.url ?? "/"}`;
|
|
262
|
+
};
|
|
263
|
+
const requestAbortSignal = (request, response) => {
|
|
264
|
+
const controller = new AbortController();
|
|
265
|
+
const abort = () => {
|
|
266
|
+
if (!controller.signal.aborted) {
|
|
267
|
+
controller.abort();
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
request.once("aborted", abort);
|
|
271
|
+
request.once("close", () => {
|
|
272
|
+
if (request.aborted) {
|
|
273
|
+
abort();
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
if (typeof response?.once === "function") {
|
|
277
|
+
response.once("close", () => {
|
|
278
|
+
if (!response.writableEnded) {
|
|
279
|
+
abort();
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return controller.signal;
|
|
284
|
+
};
|
|
285
|
+
const webRequestFor = (request, options = {}, response) => {
|
|
286
|
+
const url = requestUrl(request, options);
|
|
287
|
+
if (url instanceof Response) {
|
|
288
|
+
return url;
|
|
289
|
+
}
|
|
290
|
+
const body = requestBody(request);
|
|
291
|
+
const init = {
|
|
292
|
+
method: request.method ?? "GET",
|
|
293
|
+
headers: request.headers,
|
|
294
|
+
signal: requestAbortSignal(request, response),
|
|
295
|
+
...(body ? { body, duplex: "half" } : {}),
|
|
296
|
+
};
|
|
297
|
+
return new Request(url, init);
|
|
298
|
+
};
|
|
299
|
+
const nodeObservability = (observability) => observability ? { ...observability, adapter: observability.adapter ?? "node" } : undefined;
|
|
300
|
+
export const createNodeHandler = (options) => async (request, response) => {
|
|
301
|
+
const url = requestUrl(request, options);
|
|
302
|
+
if (url instanceof Response) {
|
|
303
|
+
await writeNodeResponse(url, response);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const staticRoute = findStaticRoute(options.staticRoutes, request.method ?? "GET", new URL(url));
|
|
307
|
+
if (staticRoute) {
|
|
308
|
+
writeNodeStaticRoute(staticRoute, request, response, options.securityHeaders);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const webRequest = webRequestFor(request, options, response);
|
|
312
|
+
if (webRequest instanceof Response) {
|
|
313
|
+
await writeNodeResponse(webRequest, response);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (options.staticAssets) {
|
|
317
|
+
const traversal = staticAssetTraversalResponse(request.url, options.staticAssets);
|
|
318
|
+
if (traversal) {
|
|
319
|
+
await writeNodeResponse(withSecurityHeaders(traversal, options.securityHeaders), response);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const asset = await createStaticAssetHandler(options.staticAssets)(webRequest);
|
|
323
|
+
if (asset) {
|
|
324
|
+
await writeNodeResponse(withSecurityHeaders(asset, options.securityHeaders), response);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const webResponse = await createWorkersHandler({
|
|
329
|
+
...options,
|
|
330
|
+
observability: nodeObservability(options.observability),
|
|
331
|
+
}).fetch(webRequest);
|
|
332
|
+
await writeNodeResponse(webResponse, response);
|
|
333
|
+
};
|
|
334
|
+
export const createNodeFetchHandler = (options) => {
|
|
335
|
+
const staticAssetHandler = options.staticAssets ? createStaticAssetHandler(options.staticAssets) : undefined;
|
|
336
|
+
const fetchHandler = createWorkersFetchHandler({
|
|
337
|
+
...options,
|
|
338
|
+
observability: nodeObservability(options.observability),
|
|
339
|
+
fetch: async (request) => {
|
|
340
|
+
const asset = await staticAssetHandler?.(request);
|
|
341
|
+
return asset ?? options.fetch(request);
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
return async (request, response) => {
|
|
345
|
+
const webRequest = webRequestFor(request, options, response);
|
|
346
|
+
if (webRequest instanceof Response) {
|
|
347
|
+
await writeNodeResponse(webRequest, response);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (options.staticAssets) {
|
|
351
|
+
const traversal = staticAssetTraversalResponse(request.url, options.staticAssets);
|
|
352
|
+
if (traversal) {
|
|
353
|
+
await writeNodeResponse(withSecurityHeaders(traversal, options.securityHeaders), response);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const webResponse = await fetchHandler.fetch(webRequest);
|
|
358
|
+
await writeNodeResponse(webResponse, response);
|
|
359
|
+
};
|
|
360
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { type MatchedRoute, type RouteDefinition, type RouteContext, type RouteCachePolicy, type RouteEnvironment, type RouteHeadDescriptor, type RouteResource, type RouteRenderOptions } from "../router.js";
|
|
2
|
+
export type WorkersAssetsBinding = {
|
|
3
|
+
fetch: (request: Request) => Response | Promise<Response>;
|
|
4
|
+
};
|
|
5
|
+
export type AdapterFetchHandler = (request: Request) => Response | Promise<Response>;
|
|
6
|
+
export type AdapterObservabilityMetadata = Record<string, string | number | boolean | undefined>;
|
|
7
|
+
export type AdapterRequestStartEvent = {
|
|
8
|
+
type: "request_start";
|
|
9
|
+
request: Request;
|
|
10
|
+
method: string;
|
|
11
|
+
path: string;
|
|
12
|
+
adapter: string;
|
|
13
|
+
runtime?: string;
|
|
14
|
+
metadata?: AdapterObservabilityMetadata;
|
|
15
|
+
startedAt: number;
|
|
16
|
+
};
|
|
17
|
+
export type AdapterRouteMatchedEvent = {
|
|
18
|
+
type: "route_matched";
|
|
19
|
+
request: Request;
|
|
20
|
+
method: string;
|
|
21
|
+
path: string;
|
|
22
|
+
adapter: string;
|
|
23
|
+
runtime?: string;
|
|
24
|
+
metadata?: AdapterObservabilityMetadata;
|
|
25
|
+
routeId: string;
|
|
26
|
+
routePattern: string;
|
|
27
|
+
matchedRoute: MatchedRoute;
|
|
28
|
+
startedAt: number;
|
|
29
|
+
};
|
|
30
|
+
export type AdapterResponseEvent = {
|
|
31
|
+
type: "response";
|
|
32
|
+
request: Request;
|
|
33
|
+
method: string;
|
|
34
|
+
path: string;
|
|
35
|
+
adapter: string;
|
|
36
|
+
runtime?: string;
|
|
37
|
+
metadata?: AdapterObservabilityMetadata;
|
|
38
|
+
status: number;
|
|
39
|
+
routeId?: string;
|
|
40
|
+
routePattern?: string;
|
|
41
|
+
failedBeforeRouteDispatch: boolean;
|
|
42
|
+
durationMs: number;
|
|
43
|
+
startedAt: number;
|
|
44
|
+
};
|
|
45
|
+
export type AdapterErrorEvent = {
|
|
46
|
+
type: "error";
|
|
47
|
+
request: Request;
|
|
48
|
+
method: string;
|
|
49
|
+
path: string;
|
|
50
|
+
adapter: string;
|
|
51
|
+
runtime?: string;
|
|
52
|
+
metadata?: AdapterObservabilityMetadata;
|
|
53
|
+
error: unknown;
|
|
54
|
+
routeId?: string;
|
|
55
|
+
routePattern?: string;
|
|
56
|
+
failedBeforeRouteDispatch: boolean;
|
|
57
|
+
durationMs: number;
|
|
58
|
+
startedAt: number;
|
|
59
|
+
};
|
|
60
|
+
export type AdapterObservabilityHooks = {
|
|
61
|
+
adapter?: string;
|
|
62
|
+
runtime?: string;
|
|
63
|
+
metadata?: AdapterObservabilityMetadata;
|
|
64
|
+
onRequestStart?: (event: AdapterRequestStartEvent) => void | Promise<void>;
|
|
65
|
+
onRouteMatched?: (event: AdapterRouteMatchedEvent) => void | Promise<void>;
|
|
66
|
+
onResponse?: (event: AdapterResponseEvent) => void | Promise<void>;
|
|
67
|
+
onError?: (event: AdapterErrorEvent) => void | Promise<void>;
|
|
68
|
+
};
|
|
69
|
+
export type WorkersAssetOptions<Env> = {
|
|
70
|
+
binding?: WorkersAssetsBinding;
|
|
71
|
+
bindingName?: keyof Env & string;
|
|
72
|
+
basePath?: string;
|
|
73
|
+
headers?: HeadersInit;
|
|
74
|
+
fallthroughStatuses?: readonly number[];
|
|
75
|
+
};
|
|
76
|
+
export type WorkersRouteContext<Env, Data = unknown, ActionResult = unknown> = Omit<RouteContext<Data, ActionResult>, "route"> & {
|
|
77
|
+
bindings: Env;
|
|
78
|
+
route: WorkersRouteDefinition<Env, Data, ActionResult>;
|
|
79
|
+
};
|
|
80
|
+
export type WorkersRouteDefinition<Env, Data = unknown, ActionResult = unknown> = Omit<RouteDefinition<Data, ActionResult>, "action" | "cache" | "children" | "head" | "headers" | "loader" | "render" | "resources" | "stream"> & {
|
|
81
|
+
loader?: (context: Omit<WorkersRouteContext<Env, Data, ActionResult>, "data" | "outlet">) => Data | Promise<Data>;
|
|
82
|
+
action?: (context: Omit<WorkersRouteContext<Env, Data, ActionResult>, "data" | "outlet">) => ActionResult | Promise<ActionResult>;
|
|
83
|
+
head?: (context: WorkersRouteContext<Env, Data, ActionResult>) => RouteHeadDescriptor | Promise<RouteHeadDescriptor>;
|
|
84
|
+
resources?: readonly RouteResource[] | ((context: WorkersRouteContext<Env, Data, ActionResult>) => readonly RouteResource[]);
|
|
85
|
+
headers?: HeadersInit | ((context: WorkersRouteContext<Env, Data, ActionResult>) => HeadersInit | Promise<HeadersInit>);
|
|
86
|
+
cache?: RouteCachePolicy | ((context: WorkersRouteContext<Env, Data, ActionResult>) => RouteCachePolicy | Promise<RouteCachePolicy>);
|
|
87
|
+
render: (context: WorkersRouteContext<Env, Data, ActionResult>) => string | Promise<string>;
|
|
88
|
+
stream?: (context: WorkersRouteContext<Env, Data, ActionResult>) => AsyncIterable<string>;
|
|
89
|
+
children?: WorkersRouteDefinition<Env>[];
|
|
90
|
+
};
|
|
91
|
+
export type WorkersRouteMiddleware<Env> = (context: {
|
|
92
|
+
request: Request;
|
|
93
|
+
url: URL;
|
|
94
|
+
env: RouteEnvironment;
|
|
95
|
+
bindings: Env;
|
|
96
|
+
}) => ReturnType<NonNullable<RouteRenderOptions["middleware"]>[number]>;
|
|
97
|
+
export type WorkersCsrfOptions<Env> = {
|
|
98
|
+
verify: (context: {
|
|
99
|
+
request: Request;
|
|
100
|
+
url: URL;
|
|
101
|
+
env: RouteEnvironment;
|
|
102
|
+
bindings: Env;
|
|
103
|
+
}) => boolean | Promise<boolean>;
|
|
104
|
+
};
|
|
105
|
+
export type WorkersHandlerOptions<Env = Record<string, unknown>> = Omit<RouteRenderOptions, "csrf" | "middleware"> & {
|
|
106
|
+
routes: readonly WorkersRouteDefinition<Env, any, any>[];
|
|
107
|
+
middleware?: readonly WorkersRouteMiddleware<Env>[];
|
|
108
|
+
csrf?: WorkersCsrfOptions<Env>;
|
|
109
|
+
securityHeaders?: Headers;
|
|
110
|
+
streaming?: boolean;
|
|
111
|
+
staticRoutes?: readonly StaticRouteDefinition[];
|
|
112
|
+
assets?: WorkersAssetOptions<Env>;
|
|
113
|
+
observability?: AdapterObservabilityHooks | undefined;
|
|
114
|
+
};
|
|
115
|
+
export type RouteAdapterHandlerOptions = RouteRenderOptions & {
|
|
116
|
+
routes: readonly RouteDefinition[];
|
|
117
|
+
securityHeaders?: Headers;
|
|
118
|
+
streaming?: boolean;
|
|
119
|
+
staticRoutes?: readonly StaticRouteDefinition[];
|
|
120
|
+
assets?: WorkersAssetOptions<Record<string, unknown>>;
|
|
121
|
+
observability?: AdapterObservabilityHooks | undefined;
|
|
122
|
+
};
|
|
123
|
+
export type WorkersFetchHandlerOptions<Env = Record<string, unknown>> = {
|
|
124
|
+
fetch: AdapterFetchHandler;
|
|
125
|
+
securityHeaders?: Headers;
|
|
126
|
+
staticRoutes?: readonly StaticRouteDefinition[];
|
|
127
|
+
assets?: WorkersAssetOptions<Env>;
|
|
128
|
+
observability?: AdapterObservabilityHooks | undefined;
|
|
129
|
+
};
|
|
130
|
+
export type StaticRouteDefinition = {
|
|
131
|
+
path: string;
|
|
132
|
+
body: string;
|
|
133
|
+
status?: number;
|
|
134
|
+
headers?: HeadersInit;
|
|
135
|
+
methods?: readonly string[];
|
|
136
|
+
};
|
|
137
|
+
export declare const defineStaticRoute: (route: StaticRouteDefinition) => StaticRouteDefinition;
|
|
138
|
+
export declare const workersStreamFromChunks: (chunks: AsyncIterable<string>) => ReadableStream<Uint8Array>;
|
|
139
|
+
type WorkersHandler<Env> = {
|
|
140
|
+
fetch: (request: Request, ...args: [Env] extends [never] ? [env?: undefined] : [env: Env]) => Promise<Response>;
|
|
141
|
+
};
|
|
142
|
+
export declare function createWorkersHandler<Env = never>(options: WorkersHandlerOptions<Env>): WorkersHandler<Env>;
|
|
143
|
+
export declare function createWorkersHandler(options: RouteAdapterHandlerOptions): {
|
|
144
|
+
fetch: (request: Request, env?: unknown) => Promise<Response>;
|
|
145
|
+
};
|
|
146
|
+
export declare const createWorkersFetchHandler: <Env = Record<string, unknown>>(options: WorkersFetchHandlerOptions<Env>) => {
|
|
147
|
+
fetch: (request: Request, env?: Env) => Promise<Response>;
|
|
148
|
+
};
|
|
149
|
+
export {};
|