@torpor/build 0.0.5 → 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.
Files changed (106) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/dist/Server-CB99REgs.d.cts +74 -0
  4. package/dist/Server-CB99REgs.d.ts +74 -0
  5. package/dist/Site-CgoeDt8k.d.cts +29 -0
  6. package/dist/Site-D95nwrAg.d.ts +29 -0
  7. package/dist/adapters/cloudflare/index.cjs +125 -0
  8. package/dist/adapters/cloudflare/index.cjs.map +1 -0
  9. package/dist/adapters/cloudflare/index.d.cts +13 -0
  10. package/dist/adapters/cloudflare/index.d.ts +13 -0
  11. package/dist/adapters/cloudflare/index.js +80 -0
  12. package/dist/adapters/cloudflare/index.js.map +1 -0
  13. package/dist/adapters/node/index.cjs +152 -0
  14. package/dist/adapters/node/index.cjs.map +1 -0
  15. package/dist/adapters/node/index.d.cts +8 -0
  16. package/dist/adapters/node/index.d.ts +8 -0
  17. package/dist/adapters/node/index.js +8 -0
  18. package/dist/adapters/node/index.js.map +1 -0
  19. package/dist/bin/index.cjs +541 -93
  20. package/dist/bin/index.cjs.map +1 -1
  21. package/dist/bin/index.js +511 -22
  22. package/dist/bin/index.js.map +1 -1
  23. package/dist/chunk-5X4T3PZN.js +23 -0
  24. package/dist/chunk-5X4T3PZN.js.map +1 -0
  25. package/dist/chunk-ALHFYD7U.js +113 -0
  26. package/dist/chunk-ALHFYD7U.js.map +1 -0
  27. package/dist/chunk-V4XTDQEV.js +19 -0
  28. package/dist/chunk-V4XTDQEV.js.map +1 -0
  29. package/dist/chunk-VAGHGWM5.js +110 -0
  30. package/dist/chunk-VAGHGWM5.js.map +1 -0
  31. package/dist/index.cjs +200 -97
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +33 -36
  34. package/dist/index.d.ts +33 -36
  35. package/dist/index.js +85 -17
  36. package/dist/index.js.map +1 -1
  37. package/dist/metafile-cjs.json +1 -1
  38. package/dist/metafile-esm.json +1 -1
  39. package/dist/response.js +17 -91
  40. package/dist/response.js.map +1 -1
  41. package/package.json +26 -9
  42. package/src/adapters/cloudflare/_worker.ts +38 -0
  43. package/src/adapters/cloudflare/index.ts +98 -0
  44. package/src/adapters/node/constants.ts +1 -0
  45. package/src/adapters/node/createFetchHandler.ts +29 -0
  46. package/src/adapters/node/createMiddlewareHandler.ts +79 -0
  47. package/src/adapters/node/createNodeServer.ts +8 -0
  48. package/src/adapters/node/flattenHeaders.ts +44 -0
  49. package/src/adapters/node/index.ts +18 -0
  50. package/src/adapters/node/nodeMessageToNodeResponse.ts +89 -0
  51. package/src/adapters/node/nodeMessageToRequest.ts +43 -0
  52. package/src/adapters/node/readableToBuffer.ts +16 -0
  53. package/src/adapters/node/requestToNodeMessage.ts +26 -0
  54. package/src/adapters/node/responseToNodeResponse.ts +44 -0
  55. package/src/bin/index.ts +43 -36
  56. package/src/index.ts +6 -14
  57. package/src/server/CookieHelper.ts +36 -0
  58. package/src/server/HeaderHelper.ts +25 -0
  59. package/src/server/Server.ts +119 -0
  60. package/src/server/ServerEvent.ts +33 -0
  61. package/src/server/ServerFunction.ts +5 -0
  62. package/src/server/contentType.ts +85 -0
  63. package/src/site/Router.ts +136 -0
  64. package/src/site/Routerx.ts +67 -0
  65. package/src/site/Site.ts +114 -0
  66. package/src/site/clientEntry.ts +22 -20
  67. package/src/site/manifest.ts +49 -0
  68. package/src/site/pathToRegex.ts +10 -0
  69. package/src/site/prepareTemplate.ts +20 -0
  70. package/src/site/runBuild.ts +92 -0
  71. package/src/site/runDev.ts +84 -0
  72. package/src/site/runPreview.ts +83 -0
  73. package/src/site/serverEntry.ts +113 -200
  74. package/src/types/Adapter.ts +10 -0
  75. package/src/types/HttpMethod.ts +12 -0
  76. package/src/types/LayoutHandler.ts +7 -0
  77. package/src/types/ManifestRoute.ts +8 -0
  78. package/src/types/MiddlewareFunction.ts +8 -0
  79. package/src/types/PageEndPoint.ts +4 -3
  80. package/src/types/{PageEvent.ts → PageLoadEvent.ts} +3 -2
  81. package/src/types/PageServerAction.ts +5 -2
  82. package/src/types/PageServerEndPoint.ts +1 -0
  83. package/src/types/PageServerLoad.ts +5 -2
  84. package/src/types/RouteHandler.ts +6 -5
  85. package/src/types/RouteHandlerCollection.ts +1 -0
  86. package/src/types/RouteLayoutHandler.ts +3 -2
  87. package/src/types/RouteMatchResult.ts +3 -2
  88. package/src/types/RouteType.ts +7 -0
  89. package/src/types/ServerEndPoint.ts +1 -0
  90. package/src/types/ServerHook.ts +3 -2
  91. package/src/types/{ServerEvent.ts → ServerLoadEvent.ts} +13 -6
  92. package/src/types/ServerRequest.ts +5 -2
  93. package/src/types/manifest.d.ts +7 -0
  94. package/test/app.test.ts +47 -0
  95. package/tsconfig.json +1 -1
  96. package/tsup.config.ts +11 -1
  97. package/vitest.config.ts +3 -0
  98. package/dist/chunk-L7GOYKUG.js +0 -93
  99. package/dist/chunk-L7GOYKUG.js.map +0 -1
  100. package/src/site/FileSystemRouter.ts +0 -25
  101. package/src/site/buildRoutePath.ts +0 -25
  102. package/src/site/defineConfig.ts +0 -75
  103. package/src/site/lazyRoute.ts +0 -72
  104. package/src/site/routeHandlers.ts +0 -126
  105. package/src/types/CookieHelper.ts +0 -8
  106. package/src/types/UserConfig.ts +0 -4
package/src/bin/index.ts CHANGED
@@ -1,42 +1,49 @@
1
1
  #! /usr/bin/env node
2
- //import { existsSync, promises as fs } from "node:fs";
3
2
  import fs from "node:fs";
4
- import path from "path";
5
- import defineConfig from "../site/defineConfig";
6
- import type UserConfig from "../types/UserConfig";
3
+ import path from "node:path";
4
+ import { transformWithEsbuild } from "vite";
5
+ import Site from "../site/Site";
6
+ import runBuild from "../site/runBuild";
7
+ import runDev from "../site/runDev";
7
8
 
8
- // Check for a torpor.config.js/ts file in the working directory
9
- // TODO: Make sure it's valid, try/catch, etc
10
- // TODO: There has GOT to be an easier way
11
- let options: UserConfig | undefined;
12
- const workingDir = process.cwd();
13
- const jsConfigFile = path.join(workingDir, "torpor.config.js");
14
- const tsConfigFile = path.join(workingDir, "torpor.config.ts");
15
- let source: string | undefined;
16
- if (fs.existsSync(jsConfigFile)) {
17
- //const configFile = path.relative(".", tsConfigFile);
18
- //options = await import(configFile);
19
- source = fs.readFileSync(jsConfigFile, "utf8");
20
- source = source
21
- .replace(
22
- /import\s+\{\s+defineConfig\s+\}\s+from\s+["']@torpor\/build["']/,
23
- "const defineConfig = (x) => x",
24
- )
25
- .replace("export default", "");
26
- } else if (fs.existsSync(tsConfigFile)) {
27
- console.log("TS config file is not supported");
28
- }
9
+ run();
29
10
 
30
- if (source) {
31
- options = eval(source);
32
- }
11
+ async function run() {
12
+ // Look for and load a site.config.js/ts file in the working directory
13
+ const workingDir = process.cwd();
14
+ let configFile = "";
15
+ let deleteConfigFile = false;
16
+ const jsConfigFile = path.join(workingDir, "site.config.js");
17
+ const tsConfigFile = path.join(workingDir, "site.config.ts");
18
+ if (fs.existsSync(jsConfigFile)) {
19
+ // If it's JS, we can just import it straight up
20
+ configFile = jsConfigFile;
21
+ } else if (fs.existsSync(tsConfigFile)) {
22
+ // If it's TS, we need to convert it to JS
23
+ // TODO: Put the generated file somewhere better (dist?)
24
+ configFile = path.join(workingDir, "site.config.temp.js");
25
+ let source = (
26
+ await transformWithEsbuild(fs.readFileSync(tsConfigFile, "utf-8"), tsConfigFile, {
27
+ loader: "ts",
28
+ })
29
+ ).code;
30
+ fs.writeFileSync(configFile, source);
31
+ deleteConfigFile = true;
32
+ } else {
33
+ throw new Error("site.config file not found");
34
+ }
33
35
 
34
- defineConfig(options);
36
+ const site = (await import(configFile)).default as Site;
37
+ if (!site || !site.root || !site.routes) {
38
+ throw new Error("Invalid site in config file");
39
+ }
40
+ if (deleteConfigFile) {
41
+ fs.rmSync(configFile);
42
+ }
35
43
 
36
- // This already gets done in vinxi.createApp so we can't do it twice
37
- //const app = defineConfig(options);
38
- //if (process.argv.includes("--dev")) {
39
- // await app.dev();
40
- //} else if (process.argv.includes("--build")) {
41
- // await app.build();
42
- //}
44
+ if (process.argv.includes("--dev")) {
45
+ await runDev(site);
46
+ } else if (process.argv.includes("--build")) {
47
+ await runBuild(site);
48
+ }
49
+ }
package/src/index.ts CHANGED
@@ -1,24 +1,17 @@
1
- import buildRoutePath from "./site/buildRoutePath";
2
- import defineConfig from "./site/defineConfig";
3
- import type CookieHelper from "./types/CookieHelper";
1
+ import Site from "./site/Site";
4
2
  import type PageEndPoint from "./types/PageEndPoint";
5
- import type PageEvent from "./types/PageEvent";
3
+ import type PageLoadEvent from "./types/PageLoadEvent";
6
4
  import type PageServerAction from "./types/PageServerAction";
7
5
  import type PageServerEndPoint from "./types/PageServerEndPoint";
8
6
  import type PageServerLoad from "./types/PageServerLoad";
9
7
  import type ServerEndPoint from "./types/ServerEndPoint";
10
- import type ServerEvent from "./types/ServerEvent";
11
8
  import type ServerHook from "./types/ServerHook";
9
+ import type ServerLoadEvent from "./types/ServerLoadEvent";
12
10
  import type ServerRequest from "./types/ServerRequest";
13
- import type UserConfig from "./types/UserConfig";
14
11
 
15
- // NOTE: Do not export anything that relies on vinxi/vite from here, or it will
16
- // cause problems when they are re-imported when routes are lazily loaded
17
-
18
- export { defineConfig, buildRoutePath };
12
+ export { Site };
19
13
 
20
14
  export type {
21
- UserConfig,
22
15
  PageEndPoint,
23
16
  PageServerEndPoint,
24
17
  PageServerLoad,
@@ -26,7 +19,6 @@ export type {
26
19
  ServerEndPoint,
27
20
  ServerRequest,
28
21
  ServerHook,
29
- PageEvent,
30
- ServerEvent,
31
- CookieHelper,
22
+ PageLoadEvent,
23
+ ServerLoadEvent,
32
24
  };
@@ -0,0 +1,36 @@
1
+ import { SerializeOptions, parse, serialize } 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 updated value
13
+ if (this.cookies.has(name)) {
14
+ return this.cookies.get(name);
15
+ }
16
+ // Otherwise, return the cookie value from the `Cookie` header
17
+ const header = this.request.headers.get("cookie");
18
+ return parse(header ?? "")[name];
19
+ }
20
+
21
+ set(name: string, value: string, options?: SerializeOptions) {
22
+ // Set a cookie header with some default values for security
23
+ options ??= {};
24
+ options.httpOnly ??= true;
25
+ options.secure ??= true;
26
+ options.sameSite ??= "lax";
27
+ this.cookies.set(name, serialize(name, value, options));
28
+ }
29
+
30
+ delete(name: string, options?: SerializeOptions) {
31
+ // Set a cookie header with an immediate expiry date
32
+ options ??= {};
33
+ options.expires = new Date(0);
34
+ this.cookies.set(name, serialize(name, "", options));
35
+ }
36
+ }
@@ -0,0 +1,25 @@
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) {
19
+ this.headers.set(name, value);
20
+ }
21
+
22
+ delete(name: string) {
23
+ this.headers.delete(name);
24
+ }
25
+ }
@@ -0,0 +1,119 @@
1
+ import pathToRegex from "../site/pathToRegex";
2
+ import type MiddlewareFunction from "../types/MiddlewareFunction";
3
+ import ServerEvent from "./ServerEvent";
4
+ import type ServerFunction from "./ServerFunction";
5
+
6
+ const noop = () => {};
7
+
8
+ /**
9
+ * A Fetch API Request/Response server.
10
+ */
11
+ export default class Server {
12
+ // TODO: We don't use any of the routes, just add("*") in runDev/runBuild...
13
+ routes: 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
+ const request = new Request(input, init);
29
+ const url = new URL(request.url);
30
+ const match = this.match(url.pathname);
31
+ let ev = new ServerEvent(request, match?.params);
32
+
33
+ if (this.middleware.length) {
34
+ // TODO: Get middleware that applies to this route only?
35
+ await this.middleware[0](ev, buildNext(this.middleware, 0));
36
+ function buildNext(middleware: MiddlewareFunction[], i: number): () => void | Promise<void> {
37
+ if (ev.response) {
38
+ return noop;
39
+ }
40
+
41
+ if (i < middleware.length - 1) {
42
+ let next = middleware[i + 1];
43
+ return async () => next(ev, buildNext(middleware, i + 1));
44
+ } else {
45
+ return match
46
+ ? async () => {
47
+ ev.response = await match.fn(ev);
48
+ }
49
+ : noop;
50
+ }
51
+ }
52
+ } else if (match) {
53
+ ev.response = await match.fn(ev);
54
+ } else {
55
+ return new Response(null, { status: 404 });
56
+ }
57
+
58
+ // TODO: Should I be returning 200 if there's no response? Because it's not an error??
59
+ ev.response ??= new Response(null, { status: 200 });
60
+ ev.addHeaders();
61
+
62
+ return ev.response;
63
+ };
64
+
65
+ /**
66
+ * Adds a server function at a route path.
67
+ * @param path The route path pattern.
68
+ * @param fn The function to call when the pattern is matched.
69
+ * @returns
70
+ */
71
+ add(path: string, fn: ServerFunction): Server {
72
+ this.routes.push(new RouteHandler(path, fn));
73
+ return this;
74
+ }
75
+
76
+ match(path: string) {
77
+ for (let handler of this.routes) {
78
+ let match = path.match(handler.regex);
79
+ if (match) {
80
+ return {
81
+ fn: handler.fn,
82
+ params: match.groups,
83
+ };
84
+ }
85
+ }
86
+ console.log(`Server path not found: ${path}`);
87
+ }
88
+
89
+ // TODO:
90
+ //use(path: string, ...fn: MiddlewareFunction[])
91
+ use(...fn: MiddlewareFunction[]): Server {
92
+ this.middleware = this.middleware.concat(fn);
93
+ return this;
94
+ }
95
+ }
96
+
97
+ class RouteHandler {
98
+ path: string;
99
+ regex: RegExp;
100
+ fn: ServerFunction;
101
+
102
+ constructor(path: string, fn: ServerFunction) {
103
+ this.path = path;
104
+ this.regex = pathToRegex(path);
105
+ this.fn = fn;
106
+ }
107
+ }
108
+
109
+ //class MiddlewareHandler {
110
+ // path: string;
111
+ // regex: RegExp;
112
+ // fn: MiddlewareFunction;
113
+ //
114
+ // constructor(path: string, fn: MiddlewareFunction) {
115
+ // this.path = path;
116
+ // this.regex = pathToRegex(path);
117
+ // this.fn = fn;
118
+ // }
119
+ //}
@@ -0,0 +1,33 @@
1
+ import CookieHelper from "./CookieHelper";
2
+ import HeaderHelper from "./HeaderHelper";
3
+
4
+ export default class ServerEvent {
5
+ request: Request;
6
+ response?: Response;
7
+ // TODO: Should we cast types??
8
+ params?: { [key: string]: string };
9
+
10
+ cookies: CookieHelper;
11
+ headers: HeaderHelper;
12
+
13
+ constructor(request: Request, params?: { [key: string]: string }) {
14
+ this.request = request;
15
+ this.params = params;
16
+ this.cookies = new CookieHelper(request);
17
+ this.headers = new HeaderHelper(request);
18
+ }
19
+
20
+ addHeaders() {
21
+ if (!this.response) {
22
+ throw new Error("Response not created yet");
23
+ }
24
+
25
+ for (let cookie of this.cookies.cookies.values()) {
26
+ this.response.headers.append("set-cookie", cookie);
27
+ }
28
+
29
+ for (let header of this.headers.headers.entries()) {
30
+ this.response.headers.append(header[0], header[1]);
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,5 @@
1
+ import ServerEvent from "./ServerEvent";
2
+
3
+ type ServerFunction = (e: ServerEvent) => Response | Promise<Response | undefined> | undefined;
4
+
5
+ export default ServerFunction;
@@ -0,0 +1,85 @@
1
+ // From https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types
2
+
3
+ export default function contentType(extension: string) {
4
+ return types[extension] || "application/octet-stream";
5
+ }
6
+
7
+ const types: Record<string, string> = {
8
+ ".aac": "audio/aac",
9
+ ".abw": "application/x-abiword",
10
+ ".apng": "image/apng",
11
+ ".arc": "application/x-freearc",
12
+ ".avif": "image/avif",
13
+ ".avi": "video/x-msvideo",
14
+ ".azw": "application/vnd.amazon.ebook",
15
+ ".bin": "application/octet-stream",
16
+ ".bmp": "image/bmp",
17
+ ".bz": "application/x-bzip",
18
+ ".bz2": "application/x-bzip2",
19
+ ".cda": "application/x-cdf",
20
+ ".csh": "application/x-csh",
21
+ ".css": "text/css",
22
+ ".csv": "text/csv",
23
+ ".doc": "application/msword",
24
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
25
+ ".eot": "application/vnd.ms-fontobject",
26
+ ".epub": "application/epub+zip",
27
+ ".gz": "application/gzip",
28
+ ".gif": "image/gif",
29
+ ".htm": "text/html",
30
+ ".html": "text/html",
31
+ ".ico": "image/vnd.microsoft.icon",
32
+ ".ics": "text/calendar",
33
+ ".jar": "application/java-archive",
34
+ ".jpeg": "image/jpeg",
35
+ ".jpg": "image/jpeg",
36
+ ".js": "text/javascript",
37
+ ".json": "application/json",
38
+ ".jsonld": "application/ld+json",
39
+ ".mid": "audio/midi",
40
+ ".midi": "audio/midi",
41
+ ".mjs": "text/javascript",
42
+ ".mp3": "audio/mpeg",
43
+ ".mp4": "video/mp4",
44
+ ".mpeg": "video/mpeg",
45
+ ".mpkg": "application/vnd.apple.installer+xml",
46
+ ".odp": "application/vnd.oasis.opendocument.presentation",
47
+ ".ods": "application/vnd.oasis.opendocument.spreadsheet",
48
+ ".odt": "application/vnd.oasis.opendocument.text",
49
+ ".oga": "audio/ogg",
50
+ ".ogv": "video/ogg",
51
+ ".ogx": "application/ogg",
52
+ ".opus": "audio/ogg",
53
+ ".otf": "font/otf",
54
+ ".png": "image/png",
55
+ ".pdf": "application/pdf",
56
+ ".php": "application/x-httpd-php",
57
+ ".ppt": "application/vnd.ms-powerpoint",
58
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
59
+ ".rar": "application/vnd.rar",
60
+ ".rtf": "application/rtf",
61
+ ".sh": "application/x-sh",
62
+ ".svg": "image/svg+xml",
63
+ ".tar": "application/x-tar",
64
+ ".tif": "image/tiff",
65
+ ".tiff": "image/tiff",
66
+ ".ts": "video/mp2t",
67
+ ".ttf": "font/ttf",
68
+ ".txt": "text/plain",
69
+ ".vsd": "application/vnd.visio",
70
+ ".wav": "audio/wav",
71
+ ".weba": "audio/webm",
72
+ ".webm": "video/webm",
73
+ ".webp": "image/webp",
74
+ ".woff": "font/woff",
75
+ ".woff2": "font/woff2",
76
+ ".xhtml": "application/xhtml+xml",
77
+ ".xls": "application/vnd.ms-excel",
78
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
79
+ ".xml": "application/xml",
80
+ ".xul": "application/vnd.mozilla.xul+xml",
81
+ ".zip": "application/zip",
82
+ ".3gp": "video/3gpp",
83
+ ".3g2": "video/3gpp2",
84
+ ".7z": "application/x-7z-compressed",
85
+ };
@@ -0,0 +1,136 @@
1
+ import type LayoutHandler from "../types/LayoutHandler";
2
+ import type ManifestRoute from "../types/ManifestRoute";
3
+ import type RouteHandler from "../types/RouteHandler";
4
+ import pathToRegex from "./pathToRegex";
5
+
6
+ export default class Router {
7
+ routes: Route[] = [];
8
+ //pages = new Map<string, PageEndPoint>();
9
+
10
+ constructor() {}
11
+
12
+ //add(path: string, file: string) {
13
+ // // TODO: Call the specific addX method based on the file path
14
+ // this.addPage(route, file);
15
+ //}
16
+
17
+ addPages(routes: ManifestRoute[]) {
18
+ for (let r of routes) {
19
+ this.routes.push(
20
+ new Route(r.path, {
21
+ path: r.path,
22
+ type: r.type,
23
+ endPoint: r.endPoint,
24
+ }),
25
+ );
26
+ }
27
+ return this;
28
+ }
29
+
30
+ // TODO: Allow calling with the endpoint itself, so that you can setup an app with no scaffold
31
+ addPage(path: string, type: number, endPoint: () => Promise<any>) {
32
+ //this.server.get(route, (ev) => this.loadPage(ev, file));
33
+ this.routes.push(
34
+ new Route(path, {
35
+ path,
36
+ type,
37
+ endPoint,
38
+ }),
39
+ );
40
+ return this;
41
+ }
42
+
43
+ /*
44
+ loadPage(ev: ServerEvent, file: string): Response {
45
+ let page = this.pages.get(file);
46
+ if (!page) {
47
+ page = {};
48
+ this.pages.set(file, page);
49
+ }
50
+ }
51
+ */
52
+
53
+ match(path: string, query: URLSearchParams) {
54
+ for (let route of this.routes) {
55
+ let match = path.match(route.regex);
56
+ if (match) {
57
+ // Lazy load server endpoints and layouts
58
+ if (!route.handler.loaded) {
59
+ this.loadHandler(route.handler, route.path);
60
+ }
61
+
62
+ return {
63
+ handler: route.handler,
64
+ params: match.groups,
65
+ query,
66
+ };
67
+ }
68
+ }
69
+
70
+ console.log(`not found: '${path}'`);
71
+ }
72
+
73
+ loadHandler(handler: RouteHandler, path: string) {
74
+ ////handler.endPoint = import(handler.path);
75
+ handler.layouts = this.findLayouts(path);
76
+ handler.serverEndPoint = this.findServer(path);
77
+ handler.serverHook = this.findServerHook(path);
78
+ }
79
+
80
+ findLayouts(path: string): LayoutHandler[] | undefined {
81
+ let layouts: LayoutHandler[] = [];
82
+ let parts = path
83
+ // Strip the trailing`~/server` (so we don't look for `~/server/_layout`
84
+ // which will never exist)
85
+ .replace(/\/~server$/, "")
86
+ // The path will always start with / so splitting e.g. `/first/second`
87
+ // will result in ['', 'first', 'second'] and checks for `/_layout`,
88
+ // `/first/_layout` and `/second/_layout`
89
+ .split("/");
90
+ let basePath = "";
91
+ for (let i = 0; i < parts.length; i++) {
92
+ if (i > 0) {
93
+ basePath += "/" + parts[i];
94
+ }
95
+ const layoutPath = basePath + "/_layout";
96
+ const layoutRoute = this.routes.find((r) => r.path === layoutPath);
97
+ if (layoutRoute) {
98
+ layouts.push({
99
+ path: layoutPath,
100
+ endPoint: layoutRoute.handler.endPoint,
101
+ serverEndPoint: this.findServer(layoutPath),
102
+ });
103
+ }
104
+ }
105
+ return layouts.length ? layouts : undefined;
106
+ }
107
+
108
+ findServer(path: string): (() => Promise<any>) | undefined {
109
+ const serverPath = path.replace(/\/$/, "") + "/~server";
110
+ const serverRoute = this.routes.find((r) => r.path === serverPath);
111
+ return serverRoute && serverRoute.handler.endPoint;
112
+ }
113
+
114
+ findServerHook(path: string): (() => Promise<any>) | undefined {
115
+ // TODO: Should this be a collection, like layouts?
116
+ let serverHookPath = "/_hook/~server";
117
+ // HACK: Generalise this
118
+ if (path.startsWith("/api/")) {
119
+ serverHookPath = "/api" + serverHookPath;
120
+ }
121
+ const serverHookRoute = this.routes.find((r) => r.path === serverHookPath);
122
+ return serverHookRoute && serverHookRoute.handler.endPoint;
123
+ }
124
+ }
125
+
126
+ class Route {
127
+ path: string;
128
+ regex: RegExp;
129
+ handler: RouteHandler;
130
+
131
+ constructor(path: string, handler: RouteHandler) {
132
+ this.path = path;
133
+ this.regex = pathToRegex(path);
134
+ this.handler = handler;
135
+ }
136
+ }
@@ -0,0 +1,67 @@
1
+ import ServerFunction from "../server/ServerFunction";
2
+ import type HttpMethod from "../types/HttpMethod";
3
+ import type MiddlewareFunction from "../types/MiddlewareFunction";
4
+ import pathToRegex from "./pathToRegex";
5
+
6
+ // This is not actually used anywhere, for now at least
7
+
8
+ export default class Router {
9
+ methods = new Map<HttpMethod, RouteHandler[]>();
10
+ middleware: MiddlewareHandler[] = [];
11
+
12
+ constructor() {
13
+ // Create all method handlers up front so we don't have to worry about
14
+ // checking if they exist. Using a non-standard method will just error
15
+ this.methods.set("GET", []);
16
+ this.methods.set("HEAD", []);
17
+ this.methods.set("PATCH", []);
18
+ this.methods.set("POST", []);
19
+ this.methods.set("PUT", []);
20
+ this.methods.set("DELETE", []);
21
+ this.methods.set("OPTIONS", []);
22
+ this.methods.set("CONNECT", []);
23
+ this.methods.set("TRACE", []);
24
+ }
25
+
26
+ add(method: HttpMethod, path: string, fn: ServerFunction): Router {
27
+ this.methods.get(method)!.push(new RouteHandler(path, fn));
28
+ return this;
29
+ }
30
+
31
+ match(method: HttpMethod, path: string) {
32
+ for (let handler of this.methods.get(method)!) {
33
+ let match = path.match(handler.regex);
34
+ if (match) {
35
+ console.log("found", handler);
36
+ return {
37
+ fn: handler.fn,
38
+ params: match.groups,
39
+ };
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ class RouteHandler {
46
+ path: string;
47
+ regex: RegExp;
48
+ fn: ServerFunction;
49
+
50
+ constructor(path: string, fn: ServerFunction) {
51
+ this.path = path;
52
+ this.regex = pathToRegex(path);
53
+ this.fn = fn;
54
+ }
55
+ }
56
+
57
+ class MiddlewareHandler {
58
+ path: string;
59
+ regex: RegExp;
60
+ fn: MiddlewareFunction;
61
+
62
+ constructor(path: string, fn: MiddlewareFunction) {
63
+ this.path = path;
64
+ this.regex = pathToRegex(path);
65
+ this.fn = fn;
66
+ }
67
+ }