@torpor/build 1.0.0 → 1.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/dist/{flattenHeaders-C_YYLdOq.mjs → Server-BnjLJleZ.mjs} +2 -12
- package/dist/Server-BnjLJleZ.mjs.map +1 -0
- package/dist/{Server-C_IKav_e.d.mts → Server-Cler2w5O.d.mts} +3 -48
- package/dist/Server-Cler2w5O.d.mts.map +1 -0
- package/dist/ServerEvent-CTx3Q-hk.d.mts +48 -0
- package/dist/ServerEvent-CTx3Q-hk.d.mts.map +1 -0
- package/dist/{Site-CMa153FA.d.mts → Site-CE0_o5Ci.d.mts} +3 -2
- package/dist/Site-CE0_o5Ci.d.mts.map +1 -0
- package/dist/bin/index.js +1 -1
- package/dist/clientEntry.d.mts +1 -0
- package/dist/clientEntry.mjs +59 -0
- package/dist/clientEntry.mjs.map +1 -0
- package/dist/clientEntryDev.d.mts +1 -0
- package/dist/clientEntryDev.mjs +7 -0
- package/dist/clientEntryDev.mjs.map +1 -0
- package/dist/dev.d.mts +3 -0
- package/dist/dev.mjs +3 -0
- package/dist/endpoint-EULRGCKA.mjs +108 -0
- package/dist/endpoint-EULRGCKA.mjs.map +1 -0
- package/dist/flattenHeaders-DM5qsSqO.mjs +13 -0
- package/dist/flattenHeaders-DM5qsSqO.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/load-BntzvmwL.mjs +228 -0
- package/dist/load-BntzvmwL.mjs.map +1 -0
- package/dist/nav.mjs +1 -225
- package/dist/nav.mjs.map +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/run.d.mts +1 -1
- package/dist/run.d.mts.map +1 -1
- package/dist/run.mjs +1 -1
- package/dist/{runOpenApi-BUi1RH1l.mjs → runOpenApi-DlOgF2Il.mjs} +54 -16
- package/dist/runOpenApi-DlOgF2Il.mjs.map +1 -0
- package/dist/server/Server.d.mts +2 -0
- package/dist/server/Server.mjs +2 -0
- package/dist/server.d.mts +2 -1
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +2 -1
- package/dist/server.mjs.map +1 -1
- package/dist/serverEntry-1qY3wZig.mjs +351 -0
- package/dist/serverEntry-1qY3wZig.mjs.map +1 -0
- package/dist/serverEntry-BAO8m03V.d.mts +6 -0
- package/dist/serverEntry-BAO8m03V.d.mts.map +1 -0
- package/dist/serverEntry.d.mts +2 -0
- package/dist/serverEntry.mjs +2 -0
- package/dist/test.d.mts +2 -2
- package/dist/test.mjs +2 -104
- package/dist/test.mjs.map +1 -1
- package/package.json +9 -8
- package/dist/Server-C_IKav_e.d.mts.map +0 -1
- package/dist/Site-CMa153FA.d.mts.map +0 -1
- package/dist/flattenHeaders-C_YYLdOq.mjs.map +0 -1
- package/dist/runOpenApi-BUi1RH1l.mjs.map +0 -1
- package/src/bin/index.ts +0 -22
- package/src/dev.ts +0 -10
- package/src/form/formDataToRecord.ts +0 -23
- package/src/form/readForm.ts +0 -71
- package/src/form.ts +0 -4
- package/src/index.ts +0 -82
- package/src/nav/api.test-d.ts +0 -79
- package/src/nav/api.ts +0 -104
- package/src/nav/formSubmit.ts +0 -70
- package/src/nav/load.ts +0 -9
- package/src/nav/loadData.ts +0 -134
- package/src/nav/navigate.ts +0 -189
- package/src/nav/reload.ts +0 -9
- package/src/nav/route.ts +0 -38
- package/src/nav.ts +0 -6
- package/src/openapi/docsHtml.ts +0 -25
- package/src/openapi/document.ts +0 -154
- package/src/openapi/plugin.ts +0 -78
- package/src/openapi/types.ts +0 -82
- package/src/openapi.ts +0 -14
- package/src/response/TypedResponse.ts +0 -17
- package/src/response/badRequest.ts +0 -31
- package/src/response/created.ts +0 -31
- package/src/response/forbidden.ts +0 -21
- package/src/response/found.ts +0 -30
- package/src/response/methodNotAllowed.ts +0 -18
- package/src/response/movedPermanently.ts +0 -25
- package/src/response/notFound.ts +0 -24
- package/src/response/notModified.ts +0 -34
- package/src/response/ok.ts +0 -38
- package/src/response/permanentRedirect.ts +0 -30
- package/src/response/response.ts +0 -22
- package/src/response/seeOther.ts +0 -19
- package/src/response/serverError.ts +0 -24
- package/src/response/temporaryRedirect.ts +0 -31
- package/src/response/transfer.ts +0 -14
- package/src/response/unauthorized.ts +0 -21
- package/src/response/unprocessable.ts +0 -30
- package/src/response.ts +0 -37
- package/src/run/depCache.ts +0 -175
- package/src/run/devPlugin.ts +0 -128
- package/src/run/prepareTemplate.ts +0 -42
- package/src/run/run.ts +0 -86
- package/src/run/runBuild.ts +0 -128
- package/src/run/runDev.ts +0 -177
- package/src/run/runOpenApi.ts +0 -52
- package/src/run/runPreview.ts +0 -84
- package/src/run/staleTorpCopies.ts +0 -100
- package/src/run.ts +0 -7
- package/src/schema.ts +0 -7
- package/src/server/CookieHelper.ts +0 -46
- package/src/server/HeaderHelper.ts +0 -25
- package/src/server/Server.ts +0 -168
- package/src/server/ServerEvent.ts +0 -62
- package/src/server/connect/connectMiddleware.ts +0 -85
- package/src/server/connect/flattenHeaders.ts +0 -22
- package/src/server/connect/nodeMessageToNodeResponse.ts +0 -89
- package/src/server/connect/requestToNodeMessage.ts +0 -29
- package/src/server/contentType.ts +0 -85
- package/src/server/types/HttpMethod.ts +0 -12
- package/src/server/types/MiddlewareFunction.ts +0 -22
- package/src/server/types/ServerFunction.ts +0 -7
- package/src/server.ts +0 -7
- package/src/site/Router.ts +0 -175
- package/src/site/Site.ts +0 -388
- package/src/site/checkLayoutSlots.ts +0 -114
- package/src/site/checkRoutes.ts +0 -435
- package/src/site/clientEntry.ts +0 -121
- package/src/site/clientEntryDev.ts +0 -3
- package/src/site/defaultAdapter.ts +0 -11
- package/src/site/layoutSlots.ts +0 -53
- package/src/site/manifest.ts +0 -173
- package/src/site/serverEntry.ts +0 -648
- package/src/state/$page.ts +0 -25
- package/src/state/$serverPage.ts +0 -22
- package/src/state/client.ts +0 -15
- package/src/state.ts +0 -3
- package/src/test/runTest.ts +0 -538
- package/src/test.ts +0 -3
- package/src/types/Adapter.ts +0 -20
- package/src/types/ClientState.ts +0 -8
- package/src/types/InternalState.ts +0 -7
- package/src/types/Jsonify.ts +0 -20
- package/src/types/LayoutHandler.ts +0 -5
- package/src/types/LayoutPath.ts +0 -10
- package/src/types/ManifestRoute.ts +0 -6
- package/src/types/PageData.test-d.ts +0 -136
- package/src/types/PageData.ts +0 -35
- package/src/types/PageEndPoint.ts +0 -55
- package/src/types/PageForm.test-d.ts +0 -78
- package/src/types/PageForm.ts +0 -29
- package/src/types/PageLoadEvent.ts +0 -26
- package/src/types/PageLoadReturn.ts +0 -13
- package/src/types/PageProps.ts +0 -14
- package/src/types/PageServerAction.ts +0 -13
- package/src/types/PageServerEndPoint.test-d.ts +0 -105
- package/src/types/PageServerEndPoint.ts +0 -110
- package/src/types/PageServerLoad.ts +0 -15
- package/src/types/PageState.ts +0 -8
- package/src/types/ParseRouteParams.test-d.ts +0 -84
- package/src/types/ParseRouteParams.ts +0 -64
- package/src/types/Route.ts +0 -30
- package/src/types/RouteHandler.ts +0 -23
- package/src/types/RouteLayoutHandler.ts +0 -5
- package/src/types/RouteMatchResult.ts +0 -7
- package/src/types/RouteType.ts +0 -19
- package/src/types/ServerEndPoint.test-d.ts +0 -77
- package/src/types/ServerEndPoint.ts +0 -169
- package/src/types/ServerHook.ts +0 -18
- package/src/types/ServerLoadEvent.ts +0 -82
- package/src/types/ServerRequest.ts +0 -15
- package/src/types/SitePlugin.ts +0 -24
- package/src/types/StandardSchema.ts +0 -109
- package/src/utils/pathToRegex.ts +0 -17
- package/src/utils/pathTrie.ts +0 -182
- package/src/utils/searchParamsToRecord.ts +0 -18
- package/src/utils/torporPackages.ts +0 -150
- package/src/utils/tsconfigAliases.ts +0 -90
- package/src/validation/ValidationError.ts +0 -18
- package/src/validation/endpoint.ts +0 -83
- package/src/validation/validate.ts +0 -26
- package/src/vite-env.d.ts +0 -2
package/src/run/runPreview.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { configDotenv } from "dotenv";
|
|
2
|
-
import { existsSync, promises as fs } from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { serverError } from "../response.ts";
|
|
5
|
-
import Server from "../server/Server.ts";
|
|
6
|
-
import contentType from "../server/contentType.ts";
|
|
7
|
-
import Site from "../site/Site";
|
|
8
|
-
import prepareTemplate from "./prepareTemplate.ts";
|
|
9
|
-
import runBuild from "./runBuild";
|
|
10
|
-
|
|
11
|
-
export default async function runPreview(site: Site): Promise<void> {
|
|
12
|
-
// Build the site
|
|
13
|
-
await runBuild(site);
|
|
14
|
-
|
|
15
|
-
const distFolder = path.resolve(site.root, "dist");
|
|
16
|
-
const clientFolder = path.join(distFolder, "client");
|
|
17
|
-
const serverFolder = path.join(distFolder, "server");
|
|
18
|
-
|
|
19
|
-
// Read site.html if present, and find the client script file in /assets.
|
|
20
|
-
// An endpoints-only site has no site.html, and no template is needed
|
|
21
|
-
let template: string | undefined;
|
|
22
|
-
const siteHtml = path.join(clientFolder, "site.html");
|
|
23
|
-
if (existsSync(siteHtml)) {
|
|
24
|
-
let clientScript = (await fs.readdir(path.join(clientFolder, "assets"))).find((f) =>
|
|
25
|
-
f.startsWith("clientEntry-"),
|
|
26
|
-
);
|
|
27
|
-
if (!clientScript) {
|
|
28
|
-
throw new Error("clientEntry.js not found");
|
|
29
|
-
}
|
|
30
|
-
clientScript = `/assets/${clientScript}`;
|
|
31
|
-
|
|
32
|
-
template = await fs.readFile(siteHtml, "utf-8");
|
|
33
|
-
|
|
34
|
-
// Prepare site.html so that we can just splice components into it
|
|
35
|
-
template = prepareTemplate(template, clientScript);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Configure the server
|
|
39
|
-
const server = new Server();
|
|
40
|
-
|
|
41
|
-
// Serve dist/client/assets statically
|
|
42
|
-
server.add("/assets/*", async (ev) => {
|
|
43
|
-
try {
|
|
44
|
-
const url = new URL(ev.request.url);
|
|
45
|
-
const file = path.join(site.root, "dist", "client", url.pathname);
|
|
46
|
-
if (existsSync(file)) {
|
|
47
|
-
const body = await fs.readFile(file);
|
|
48
|
-
return new Response(body, {
|
|
49
|
-
status: 200,
|
|
50
|
-
headers: {
|
|
51
|
-
"Content-Type": contentType(path.extname(file)),
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
} catch (e: any) {
|
|
56
|
-
return serverError(e);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Every request (GET, POST, etc) goes through loadEndPoint
|
|
61
|
-
const serverScript = path.resolve(serverFolder, "serverEntry.js");
|
|
62
|
-
server.add("*", async (ev) => {
|
|
63
|
-
try {
|
|
64
|
-
// Load the server entry
|
|
65
|
-
const { load } = await import(serverScript);
|
|
66
|
-
|
|
67
|
-
// Render the app HTML (or fetch server data etc) via serverEntry's
|
|
68
|
-
// exported `load` function
|
|
69
|
-
return await load(ev, template);
|
|
70
|
-
} catch (e: any) {
|
|
71
|
-
return serverError(e);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Load environment variables from a `.env` file, with defaults if not set
|
|
76
|
-
configDotenv();
|
|
77
|
-
|
|
78
|
-
process.env.PROTOCOL ??= "http:";
|
|
79
|
-
process.env.HOST ??= "localhost";
|
|
80
|
-
process.env.PORT ??= "7059";
|
|
81
|
-
|
|
82
|
-
// Serve the site
|
|
83
|
-
site.adapter.serve(server, site);
|
|
84
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import type { PackageJson } from "../utils/torporPackages";
|
|
4
|
-
import { findWorkspaceRoot, workspaceDependencyDirs } from "./depCache";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Workspace packages that ship copies of their .torp components (e.g.
|
|
8
|
-
* @torpor/ui copies its src .torp files into dist/ during its build) can go
|
|
9
|
-
* stale: a site resolves the package to its dist folder, so edits to src
|
|
10
|
-
* silently keep serving the OLD components until the package is rebuilt.
|
|
11
|
-
* The symptom is confusing -- e.g. a TypeError about a renamed slot prop
|
|
12
|
-
* after a component's API changed.
|
|
13
|
-
*
|
|
14
|
-
* reportStaleTorpCopies runs at `tb --dev` startup: for every workspace
|
|
15
|
-
* dependency with both src and dist folders, it compares the .torp files and
|
|
16
|
-
* reports anything missing from dist, differing from src, or left over in
|
|
17
|
-
* dist without a src file.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Reports stale dist .torp copies in the site's workspace dependencies.
|
|
22
|
-
* Never throws -- a broken check must not break dev startup.
|
|
23
|
-
*/
|
|
24
|
-
export function reportStaleTorpCopies(siteRoot: string, log?: (message: string) => void): void {
|
|
25
|
-
try {
|
|
26
|
-
const workspaceRoot = findWorkspaceRoot(siteRoot);
|
|
27
|
-
if (!workspaceRoot) return;
|
|
28
|
-
|
|
29
|
-
for (const dir of workspaceDependencyDirs(siteRoot, workspaceRoot)) {
|
|
30
|
-
const srcFolder = path.join(dir, "src");
|
|
31
|
-
const distFolder = path.join(dir, "dist");
|
|
32
|
-
// Only packages that ship dist copies of src .torp files are
|
|
33
|
-
// checked; packages without the folders (or with neither .torp
|
|
34
|
-
// set) are skipped
|
|
35
|
-
if (!existsSync(srcFolder) || !existsSync(distFolder)) continue;
|
|
36
|
-
const src = collectTorpFiles(srcFolder);
|
|
37
|
-
const dist = collectTorpFiles(distFolder);
|
|
38
|
-
if (src.size === 0 && dist.size === 0) continue;
|
|
39
|
-
|
|
40
|
-
const problems: string[] = [];
|
|
41
|
-
for (const [rel, content] of src) {
|
|
42
|
-
const distContent = dist.get(rel);
|
|
43
|
-
if (distContent === undefined) {
|
|
44
|
-
problems.push(`${rel} (not copied to dist)`);
|
|
45
|
-
} else if (distContent !== content) {
|
|
46
|
-
problems.push(`${rel} (differs from src)`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
for (const rel of dist.keys()) {
|
|
50
|
-
if (!src.has(rel)) {
|
|
51
|
-
problems.push(`${rel} (left over in dist, no src file)`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (problems.length === 0) continue;
|
|
55
|
-
|
|
56
|
-
const shown = problems
|
|
57
|
-
.slice(0, 5)
|
|
58
|
-
.map((p) => ` ${p}`)
|
|
59
|
-
.join("\n");
|
|
60
|
-
const more = problems.length > 5 ? `\n ... and ${problems.length - 5} more` : "";
|
|
61
|
-
log?.(
|
|
62
|
-
`${packageLabel(dir, workspaceRoot)}'s dist .torp copies are stale, so the site is ` +
|
|
63
|
-
`serving OLD components:\n${shown}${more}\n` +
|
|
64
|
-
`Rebuild the package (e.g. \`pnpm build\` in ` +
|
|
65
|
-
`${path.relative(workspaceRoot, dir) || "."}) and restart \`tb --dev\`.`,
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
} catch (e) {
|
|
69
|
-
if (process.env.TORPOR_DEBUG_CACHE) throw e;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function collectTorpFiles(folder: string): Map<string, string> {
|
|
74
|
-
const files = new Map<string, string>();
|
|
75
|
-
collect(folder, folder, files);
|
|
76
|
-
return files;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function collect(folder: string, root: string, files: Map<string, string>): void {
|
|
80
|
-
for (const entry of readdirSync(folder, { withFileTypes: true })) {
|
|
81
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
82
|
-
const full = path.join(folder, entry.name);
|
|
83
|
-
if (entry.isDirectory()) {
|
|
84
|
-
collect(full, root, files);
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (!entry.isFile() || !entry.name.endsWith(".torp")) continue;
|
|
88
|
-
files.set(path.relative(root, full).replaceAll("\\", "/"), readFileSync(full, "utf8"));
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function packageLabel(dir: string, workspaceRoot: string): string {
|
|
93
|
-
try {
|
|
94
|
-
const pkg: PackageJson = JSON.parse(readFileSync(path.join(dir, "package.json"), "utf8"));
|
|
95
|
-
if (pkg.name) return pkg.name;
|
|
96
|
-
} catch {
|
|
97
|
-
// Fall through to the relative path
|
|
98
|
-
}
|
|
99
|
-
return path.relative(workspaceRoot, dir) || dir;
|
|
100
|
-
}
|
package/src/run.ts
DELETED
package/src/schema.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { type SerializeOptions, parseCookie, stringifySetCookie } from "cookie";
|
|
2
|
-
|
|
3
|
-
export default class CookieHelper {
|
|
4
|
-
request: Request;
|
|
5
|
-
cookies: Map<string, string> = new Map();
|
|
6
|
-
|
|
7
|
-
constructor(request: Request) {
|
|
8
|
-
this.request = request;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
get(name: string): string | undefined {
|
|
12
|
-
// If it's been set, return the value from the map
|
|
13
|
-
if (this.cookies.has(name)) {
|
|
14
|
-
// The stored value is a serialized Set-Cookie header; pull the
|
|
15
|
-
// "name=value" prefix off and re-parse to get just the value
|
|
16
|
-
return parseCookie(this.cookies.get(name)!)[name];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Otherwise, return the value from the `cookie` header
|
|
20
|
-
const header = this.request.headers.get("Cookie");
|
|
21
|
-
return parseCookie(header ?? "")[name];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
set(name: string, value: string, options?: SerializeOptions): void {
|
|
25
|
-
// Set a cookie header with some default values for security
|
|
26
|
-
options ??= {};
|
|
27
|
-
options.httpOnly ??= true;
|
|
28
|
-
options.secure ??= true;
|
|
29
|
-
options.sameSite ??= "lax";
|
|
30
|
-
|
|
31
|
-
this.cookies.set(name, stringifySetCookie({ name, value, ...options }));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
delete(name: string, options?: SerializeOptions): void {
|
|
35
|
-
// Set a cookie header with an empty value and an immediate expiry date.
|
|
36
|
-
// Apply the same secure defaults as `set` so a previously-set Secure
|
|
37
|
-
// cookie is actually overridden by the deletion.
|
|
38
|
-
options ??= {};
|
|
39
|
-
options.httpOnly ??= true;
|
|
40
|
-
options.secure ??= true;
|
|
41
|
-
options.sameSite ??= "lax";
|
|
42
|
-
options.expires = new Date(0);
|
|
43
|
-
|
|
44
|
-
this.cookies.set(name, stringifySetCookie({ name, value: "", ...options }));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export default class HeaderHelper {
|
|
2
|
-
request: Request;
|
|
3
|
-
headers: Map<string, string> = new Map();
|
|
4
|
-
|
|
5
|
-
constructor(request: Request) {
|
|
6
|
-
this.request = request;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
get(name: string): string | undefined {
|
|
10
|
-
// If it's been set, return the updated value
|
|
11
|
-
if (this.headers.has(name)) {
|
|
12
|
-
return this.headers.get(name);
|
|
13
|
-
}
|
|
14
|
-
// Otherwise, return the value from the header
|
|
15
|
-
return this.request.headers.get(name) ?? undefined;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
set(name: string, value: string): void {
|
|
19
|
-
this.headers.set(name, value);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
delete(name: string): void {
|
|
23
|
-
this.headers.delete(name);
|
|
24
|
-
}
|
|
25
|
-
}
|
package/src/server/Server.ts
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import PathTrie from "../utils/pathTrie";
|
|
2
|
-
import ServerEvent from "./ServerEvent";
|
|
3
|
-
import type MiddlewareFunction from "./types/MiddlewareFunction";
|
|
4
|
-
import type ServerFunction from "./types/ServerFunction";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* A Fetch API Request/Response server.
|
|
8
|
-
*/
|
|
9
|
-
export default class Server {
|
|
10
|
-
// TODO: We don't use any of the routes, just add("*") in runDev/runBuild...
|
|
11
|
-
routes: RouteHandler[] = [];
|
|
12
|
-
// Match index for the routes above
|
|
13
|
-
#trie = new PathTrie<RouteHandler>();
|
|
14
|
-
// TODO: We only use the Vite middleware here
|
|
15
|
-
middleware: MiddlewareFunction[] = [];
|
|
16
|
-
|
|
17
|
-
constructor() {
|
|
18
|
-
this.routes = [];
|
|
19
|
-
this.middleware = [];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* The standard Fetch API method that should work in any runtime (including
|
|
24
|
-
* Node with polyfills).
|
|
25
|
-
* @returns
|
|
26
|
-
*/
|
|
27
|
-
fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
28
|
-
// Use the request as-is when we were given one, so that the adapter
|
|
29
|
-
// doesn't pay for constructing it twice
|
|
30
|
-
const request = input instanceof Request && !init ? input : new Request(input, init);
|
|
31
|
-
const url = new URL(request.url);
|
|
32
|
-
const match = this.match(url.pathname);
|
|
33
|
-
let ev = new ServerEvent(request, match?.params, url);
|
|
34
|
-
|
|
35
|
-
if (this.middleware.length) {
|
|
36
|
-
// TODO: Get middleware that applies to this route only?
|
|
37
|
-
let entered = 0;
|
|
38
|
-
let response: Response | undefined = undefined;
|
|
39
|
-
let error: unknown = undefined;
|
|
40
|
-
try {
|
|
41
|
-
for (; entered < this.middleware.length && !response; entered++) {
|
|
42
|
-
// A middleware returns a Response to short-circuit the request
|
|
43
|
-
const result = await this.middleware[entered].enter?.(ev);
|
|
44
|
-
if (result) {
|
|
45
|
-
response = result;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (!response && match) {
|
|
49
|
-
const result = await match.fn(ev);
|
|
50
|
-
if (result) {
|
|
51
|
-
response = result;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
} catch (e) {
|
|
55
|
-
// Exits can handle the error by returning a Response; if none
|
|
56
|
-
// does, it is rethrown below
|
|
57
|
-
error = e;
|
|
58
|
-
ev.error = e;
|
|
59
|
-
} finally {
|
|
60
|
-
if (response) {
|
|
61
|
-
ev.response = response;
|
|
62
|
-
}
|
|
63
|
-
// Run exit hooks in reverse order for the middleware that were
|
|
64
|
-
// entered. An exit hook can return a Response to replace the
|
|
65
|
-
// response, or to handle ev.error
|
|
66
|
-
while (entered > 0) {
|
|
67
|
-
entered--;
|
|
68
|
-
const result = await this.middleware[entered].exit?.(ev);
|
|
69
|
-
if (result) {
|
|
70
|
-
response = result;
|
|
71
|
-
ev.response = result;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (error && !response) {
|
|
76
|
-
throw error;
|
|
77
|
-
}
|
|
78
|
-
} else if (match) {
|
|
79
|
-
ev.response = await match.fn(ev);
|
|
80
|
-
} else {
|
|
81
|
-
return new Response(null, { status: 404 });
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// TODO: Should I be returning 200 if there's no response? Because it's not an error??
|
|
85
|
-
ev.response ??= new Response(null, { status: 200 });
|
|
86
|
-
ev.addHeaders();
|
|
87
|
-
|
|
88
|
-
return ev.response;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Adds a server function at a route path.
|
|
93
|
-
* @param path The route path pattern.
|
|
94
|
-
* @param fn The function to call when the pattern is matched.
|
|
95
|
-
* @returns
|
|
96
|
-
*/
|
|
97
|
-
add(path: string, fn: ServerFunction): Server {
|
|
98
|
-
let handler = new RouteHandler(path, fn);
|
|
99
|
-
this.routes.push(handler);
|
|
100
|
-
this.#trie.insert(path, handler);
|
|
101
|
-
this.#sortRoutes();
|
|
102
|
-
return this;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
match(path: string): Match | undefined {
|
|
106
|
-
const found = this.#trie.match(path);
|
|
107
|
-
if (found) {
|
|
108
|
-
return {
|
|
109
|
-
fn: found.value.fn,
|
|
110
|
-
params: found.params,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
console.log(`Server path not found: ${path}`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// TODO:
|
|
117
|
-
//use(path: string, ...fn: MiddlewareFunction[])
|
|
118
|
-
use(...fn: MiddlewareFunction[]): Server {
|
|
119
|
-
this.middleware = this.middleware.concat(fn);
|
|
120
|
-
return this;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
#sortRoutes() {
|
|
124
|
-
this.routes = this.routes.sort((a, b) => {
|
|
125
|
-
// Sort [param]s after paths
|
|
126
|
-
// There might be a quicker/easier way to do this
|
|
127
|
-
for (let i = 0; i < Math.min(a.path.length, b.path.length); i++) {
|
|
128
|
-
if (a.path[i] === "[" && b.path[i] !== "[") {
|
|
129
|
-
return 1;
|
|
130
|
-
} else if (b.path[i] === "[" && a.path[i] !== "[") {
|
|
131
|
-
return -1;
|
|
132
|
-
} else if (a.path[i] === b.path[i]) {
|
|
133
|
-
// Keep going...
|
|
134
|
-
} else {
|
|
135
|
-
return a.path[i].localeCompare(b.path[i]);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return a.path.length - b.path.length;
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
class RouteHandler {
|
|
144
|
-
path: string;
|
|
145
|
-
fn: ServerFunction;
|
|
146
|
-
|
|
147
|
-
constructor(path: string, fn: ServerFunction) {
|
|
148
|
-
this.path = path;
|
|
149
|
-
this.fn = fn;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
//class MiddlewareHandler {
|
|
154
|
-
// path: string;
|
|
155
|
-
// regex: RegExp;
|
|
156
|
-
// fn: MiddlewareFunction;
|
|
157
|
-
//
|
|
158
|
-
// constructor(path: string, fn: MiddlewareFunction) {
|
|
159
|
-
// this.path = path;
|
|
160
|
-
// this.regex = pathToRegex(path);
|
|
161
|
-
// this.fn = fn;
|
|
162
|
-
// }
|
|
163
|
-
//}
|
|
164
|
-
|
|
165
|
-
interface Match {
|
|
166
|
-
fn: ServerFunction;
|
|
167
|
-
params?: Record<string, string>;
|
|
168
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import CookieHelper from "./CookieHelper";
|
|
2
|
-
import HeaderHelper from "./HeaderHelper";
|
|
3
|
-
|
|
4
|
-
export default class ServerEvent {
|
|
5
|
-
request: Request;
|
|
6
|
-
response: Response | undefined | void;
|
|
7
|
-
/**
|
|
8
|
-
* Set when the route handler (or a middleware enter hook) threw an
|
|
9
|
-
* error. Exit hooks can return a Response to handle it; if none does,
|
|
10
|
-
* the error is rethrown to the caller.
|
|
11
|
-
*/
|
|
12
|
-
error: unknown;
|
|
13
|
-
// TODO: Should we cast types??
|
|
14
|
-
params?: Record<string, string>;
|
|
15
|
-
|
|
16
|
-
adapter: any;
|
|
17
|
-
|
|
18
|
-
cookies: CookieHelper;
|
|
19
|
-
headers: HeaderHelper;
|
|
20
|
-
|
|
21
|
-
// Parsed once per request; Server.fetch passes the URL it already needed
|
|
22
|
-
// for matching so it isn't parsed twice
|
|
23
|
-
#url: URL | undefined;
|
|
24
|
-
|
|
25
|
-
/** The request URL, parsed lazily. */
|
|
26
|
-
get url(): URL {
|
|
27
|
-
return (this.#url ??= new URL(this.request.url));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
constructor(request: Request, params?: Record<string, string>, url?: URL) {
|
|
31
|
-
this.request = request;
|
|
32
|
-
this.params = params;
|
|
33
|
-
this.#url = url;
|
|
34
|
-
this.cookies = new CookieHelper(request);
|
|
35
|
-
this.headers = new HeaderHelper(request);
|
|
36
|
-
|
|
37
|
-
// Adapters may have added the `adapter` property to globalThis
|
|
38
|
-
// @ts-ignore
|
|
39
|
-
this.adapter = globalThis.adapter;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
addHeaders(): void {
|
|
43
|
-
if (!this.response) {
|
|
44
|
-
throw new Error("Response not created yet");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
for (let cookie of this.cookies.cookies.values()) {
|
|
48
|
-
this.response.headers.append("set-cookie", cookie);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
for (let header of this.headers.headers.entries()) {
|
|
52
|
-
this.response.headers.append(header[0], header[1]);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Reads the request body as JSON.
|
|
58
|
-
*/
|
|
59
|
-
json(): Promise<unknown> {
|
|
60
|
-
return this.request.json();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
-
import { Readable } from "node:stream";
|
|
3
|
-
import type MiddlewareFunction from "../types/MiddlewareFunction";
|
|
4
|
-
import flattenHeaders from "./flattenHeaders";
|
|
5
|
-
import nodeMessageToNodeResponse from "./nodeMessageToNodeResponse";
|
|
6
|
-
import requestToNodeMessage from "./requestToNodeMessage";
|
|
7
|
-
|
|
8
|
-
// From https://github.com/vikejs/vike-node/blob/main/packages/vike-node/src/runtime/adapters/connectToWeb.ts
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Converts a Connect-style middleware to a torpor middleware.
|
|
12
|
-
*
|
|
13
|
-
* If the Connect handler writes a response, it is returned from the `enter`
|
|
14
|
-
* hook and the middleware chain is short-circuited. If the handler calls
|
|
15
|
-
* `next()`, the rest of the chain runs after this middleware completes.
|
|
16
|
-
*
|
|
17
|
-
* @param handler - The Connect-style middleware function to be converted.
|
|
18
|
-
* @returns A middleware whose `enter` hook handles the web request.
|
|
19
|
-
*/
|
|
20
|
-
export default function connectMiddleware(
|
|
21
|
-
handler: ConnectMiddleware | ConnectMiddlewareBoolean,
|
|
22
|
-
): MiddlewareFunction {
|
|
23
|
-
return {
|
|
24
|
-
enter: (ev) =>
|
|
25
|
-
new Promise<Response | void>((resolve, reject) => {
|
|
26
|
-
const req = requestToNodeMessage(ev.request);
|
|
27
|
-
const { res, onReadable } = nodeMessageToNodeResponse(req);
|
|
28
|
-
|
|
29
|
-
onReadable(({ readable, headers, status }) => {
|
|
30
|
-
const responseBody: ReadableStream | null = statusCodesWithoutBody.includes(status)
|
|
31
|
-
? null
|
|
32
|
-
: (Readable.toWeb(readable) as unknown as ReadableStream);
|
|
33
|
-
resolve(
|
|
34
|
-
new Response(responseBody, {
|
|
35
|
-
status,
|
|
36
|
-
headers: flattenHeaders(headers),
|
|
37
|
-
}),
|
|
38
|
-
);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const cnext = (error?: unknown) => {
|
|
42
|
-
if (error) {
|
|
43
|
-
// eslint-disable-next-line no-base-to-string
|
|
44
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
45
|
-
} else {
|
|
46
|
-
// The Connect handler is passing the request on
|
|
47
|
-
resolve();
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
Promise.resolve(handler(req, res, cnext))
|
|
52
|
-
.then((handled) => {
|
|
53
|
-
if (handled === false) {
|
|
54
|
-
res.destroy();
|
|
55
|
-
// Returning false signals that the request was handled,
|
|
56
|
-
// so return an empty response to stop the chain
|
|
57
|
-
resolve(new Response(null, { status: 200 }));
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
.catch((e: unknown) => {
|
|
61
|
-
cnext(e);
|
|
62
|
-
});
|
|
63
|
-
}),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const statusCodesWithoutBody = [
|
|
68
|
-
100, // Continue
|
|
69
|
-
101, // Switching Protocols
|
|
70
|
-
102, // Processing (WebDAV)
|
|
71
|
-
103, // Early Hints
|
|
72
|
-
204, // No Content
|
|
73
|
-
205, // Reset Content
|
|
74
|
-
304, // Not Modified
|
|
75
|
-
];
|
|
76
|
-
|
|
77
|
-
type NextFunction = (err?: unknown) => void;
|
|
78
|
-
type ConnectMiddleware<
|
|
79
|
-
PlatformRequest extends IncomingMessage = IncomingMessage,
|
|
80
|
-
PlatformResponse extends ServerResponse = ServerResponse,
|
|
81
|
-
> = (req: PlatformRequest, res: PlatformResponse, next: NextFunction) => void | Promise<void>;
|
|
82
|
-
type ConnectMiddlewareBoolean<
|
|
83
|
-
PlatformRequest extends IncomingMessage = IncomingMessage,
|
|
84
|
-
PlatformResponse extends ServerResponse = ServerResponse,
|
|
85
|
-
> = (req: PlatformRequest, res: PlatformResponse, next: NextFunction) => boolean | Promise<boolean>;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { IncomingHttpHeaders } from "node:http";
|
|
2
|
-
import { OutgoingHttpHeaders } from "node:http2";
|
|
3
|
-
|
|
4
|
-
export default function flattenHeaders(
|
|
5
|
-
headers: IncomingHttpHeaders | OutgoingHttpHeaders,
|
|
6
|
-
): HeadersInit {
|
|
7
|
-
const flatHeaders = new Headers();
|
|
8
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
9
|
-
if (value === undefined || value === null) {
|
|
10
|
-
continue;
|
|
11
|
-
} else if (Array.isArray(value)) {
|
|
12
|
-
for (const v of value) {
|
|
13
|
-
if (v !== undefined && v !== null) {
|
|
14
|
-
flatHeaders.append(key, String(v));
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
} else {
|
|
18
|
-
flatHeaders.set(key, String(value));
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return flatHeaders;
|
|
22
|
-
}
|