@marko/run 0.0.1-beta4 → 0.0.1-beta5

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/README.md CHANGED
@@ -12,12 +12,14 @@
12
12
  </a>
13
13
  </div>
14
14
 
15
- `@marko/serve` is a Vite plugin for [Marko](https://markojs.com), with these features:
15
+ `@marko/run` is an application framework for [Marko](https://markojs.com), with these features:
16
16
 
17
- - Encapsulates [`@marko/vite`](https://github.com/marko-js/vite)
17
+ - Vite plugin that encapsulates [`@marko/vite`](https://github.com/marko-js/vite)
18
+ - CLI to simplify build modes
18
19
  - File-based routing with layouts and middleware
19
20
  - Efficient routing using a compiled static trie
20
21
  - [Designed with web standards](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern/URLPattern) to run anywhere
22
+ - TypeScript support
21
23
 
22
24
  ## Installation
23
25
 
@@ -25,6 +27,39 @@
25
27
  npm install @marko/run
26
28
  ```
27
29
 
30
+ ## CLI
31
+
32
+ The package provides a command line tool `marko-run` which can be run using scripts in your package.json or with npx.
33
+
34
+ ### Getting Started / Zero Config
35
+
36
+ `marko-run` makes it easy to get started without little to no config. The package ships with a default Vite config and node-based adapter that means a minimal project start can be:
37
+ 1. Install `@marko/run`
38
+ 2. Create file `src/routes/+page.marko`
39
+ 3. Run `npx marko-run dev`
40
+ 4. Open browser to `http://localhost:3000`
41
+
42
+ ### Commands
43
+
44
+ **`dev`** - Start development server in watch mode
45
+ ```bash
46
+ > npx marko-run dev
47
+ ```
48
+
49
+ **`build`** - Create a production build
50
+ ```bash
51
+ > npx marko-run build
52
+ ```
53
+
54
+ **`preview`** - Create a production build and serve
55
+ ```bash
56
+ > npx marko-run preview
57
+ ```
58
+ or (default command)
59
+ ```bash
60
+ > npx marko-run
61
+ ```
62
+
28
63
  ## Vite Plugin
29
64
 
30
65
  This package’s Vite plugin discovers your route files, generates the routing code, and registers the `@marko/vite` plugin to compile your `.marko` files.
@@ -36,47 +71,66 @@ import marko from "@marko/run/vite"; // Import the Vite plugin
36
71
 
37
72
  export default defineConfig({
38
73
  plugins: [marko()], // Register the Vite plugin
39
- build: {
40
- sourcemap: true, // Generate sourcemaps for all builds
41
- emptyOutDir: false, // Avoid server & client deleting files from each other. TODO: do we have to make the user set this themselves?
42
- }
43
74
  })
44
75
  ```
45
76
 
77
+ ## Adapters
78
+
79
+ *🎗 TODO: provide a quick overview*
80
+
46
81
  ## Runtime
47
82
 
48
- Generally, using one of the adapters is more convenient <!-- TODO: is an adapter the same thing as using the Vite plugin above? -->, but this package also provides a runtime API:
83
+ Generally, when using an adapter, this runtime will be abstracted away.
49
84
 
50
85
  ```ts
51
- import { router, getMatchedRoute } from '@marko/run`;
86
+ import { router, matchRoute, invokeRoute } from '@marko/run/router`;
52
87
  ```
53
88
 
54
89
  ### `router`
55
90
 
56
91
  ```ts
57
- (request: Request) => Promise<Response>;
92
+ interface RequestContext<T> {
93
+ url: URL;
94
+ method: string;
95
+ request: Request;
96
+ platform: T;
97
+ }
98
+
99
+ async function router(context: RequestContext) => Promise<Response | void>;
58
100
  ```
59
101
 
60
- This asynchronous function takes a [WHATWG `Request` object](https://fetch.spec.whatwg.org/#request-class) and returns a [`Response` object](https://fetch.spec.whatwg.org/#response-class) generated by executing any matched route files.
102
+ This asynchronous function takes a context object and returns the [WHATWG `Response` object](https://fetch.spec.whatwg.org/#response-class) from executing any matched route files or undefined if the request was explicitly not handled. If no route matches the requested path, a `404` status code response will be returned. If an error occurs a `500` status code response will be returned.
61
103
 
62
- If no match is found, returns a response with a `404` status code. If an unhandled error occurs, returns a response with a `500` status code.
104
+ The context object contains the following properties
105
+ - `url`: The URL reprensting the requested resource
106
+ - `method`: The HTTP method used
107
+ - `request`: [WHATWG `Request` object](https://fetch.spec.whatwg.org/#request-class)
108
+ - `platform`: An object containing any platform-specific data (eg Node request/response) and will vary between adapters.
63
109
 
64
- ### `getMatchedRoute`
110
+ ### `matchRoute`
65
111
 
66
112
  ```ts
67
- (method: string, url: URL) => {
113
+ interface interface Route {
68
114
  params: Record<string, string>;
69
115
  meta: unknown;
70
- invoke(request: Request): Promise<Response>;
71
- } | null;
116
+ }
117
+
118
+ function matchRoute(method: string, pathname: string) => Route | null;
72
119
  ```
73
120
 
74
- This synchronous function takes an HTTP method + URL, then returns an object representing the best match — or `null` if no match is found.
121
+ This synchronous function takes an HTTP method and path name, then returns an object representing the best match — or `null` if no match is found.
75
122
 
76
123
  - `params` - a `{ key: value }` collection of any path parameters for the route
77
124
  - `meta` - metadata for the route
78
- - `invoke` - an asynchronous function that takes a `Request` and returns the `Response` generated from matched route files.
79
- > **Note**: unlike the top-level `router` function, errors will not be caught.
125
+
126
+ ### `invokeRoute`
127
+
128
+ ```ts
129
+ function invokeRoute(route: Route, context: RequestContext) => Promise<Response | void>;
130
+ ```
131
+ This asynchronous function takes a route object returned by [matchRoute](#matchRoute) and a context object and returns a response in the same way the [router](#router) does.
132
+
133
+
80
134
 
81
135
  ## File-based Routing
82
136
 
@@ -108,7 +108,7 @@ prog.command("dev [entry]").describe("Start development server in watch mode").o
108
108
  const config2 = await getViteConfig(cwd, opts.config);
109
109
  await dev(cmd, config2, opts.port, opts.env);
110
110
  });
111
- prog.command("build [entry]").describe("Build the application (without serving it)").option("-o, --output", "Directory to write built files (default: )").option("--skip-client", "Skip the client-side build").example("build --config vite.config.js").action(async (entry, opts) => {
111
+ prog.command("build [entry]").describe("Build the application (without serving it)").option("-o, --output", "Directory to write built files (default: 'build.outDir' in Vite config)").option("--skip-client", "Skip the client-side build").example("build --config vite.config.js").action(async (entry, opts) => {
112
112
  const config2 = await getViteConfig(cwd, opts.config);
113
113
  await build(entry, config2, opts.ouput, opts["skip-client"], opts.env);
114
114
  });
@@ -1,10 +1,17 @@
1
- import type { HandlerLike, ParamsObject, Route } from "./types";
1
+ import type { HandlerLike, ParamsObject, Route, RouteContext } from "./types";
2
2
  declare global {
3
3
  namespace Marko {
4
+ interface Global {
5
+ context: RouteContext;
6
+ }
7
+ }
8
+ namespace MarkoRun {
4
9
  interface CurrentRoute extends Route {
5
10
  }
11
+ interface CurrentContext extends RouteContext<CurrentRoute> {
12
+ }
6
13
  type Handler<Params extends ParamsObject = {}, Meta = unknown> = HandlerLike<Route<Params, Meta, string>>;
7
14
  function route<Params extends ParamsObject = {}, Meta = unknown>(handler: Handler<Params, Meta>): typeof handler;
8
15
  }
9
16
  }
10
- export type { HandlerLike, InputObject, InvokeRoute, MatchRoute, NextFunction, RequestContext, Route, RouteContext, RouteContextExtensions, RouteHandler, RouteWithHandler, Router, } from "./types";
17
+ export type { HandlerLike, InputObject, InvokeRoute, MatchRoute, NextFunction, PathTemplate, RequestContext, Route, RouteContext, RouteContextExtensions, RouteHandler, RouteWithHandler, Router, ValidateHref, ValidatePath, } from "./types";
@@ -31,8 +31,8 @@ __export(internal_exports, {
31
31
  notMatched: () => notMatched
32
32
  });
33
33
  module.exports = __toCommonJS(internal_exports);
34
- globalThis.Marko ?? (globalThis.Marko = {});
35
- globalThis.Marko.route = (handler) => handler;
34
+ globalThis.MarkoRun ?? (globalThis.MarkoRun = {});
35
+ globalThis.MarkoRun.route = (handler) => handler;
36
36
  var RequestNotHandled = Symbol();
37
37
  var RequestNotMatched = Symbol();
38
38
  function createInput(context) {
@@ -1,6 +1,6 @@
1
1
  // src/runtime/internal.ts
2
- globalThis.Marko ?? (globalThis.Marko = {});
3
- globalThis.Marko.route = (handler) => handler;
2
+ globalThis.MarkoRun ?? (globalThis.MarkoRun = {});
3
+ globalThis.MarkoRun.route = (handler) => handler;
4
4
  var RequestNotHandled = Symbol();
5
5
  var RequestNotMatched = Symbol();
6
6
  function createInput(context) {
@@ -32,4 +32,12 @@ export interface RouteWithHandler<Params extends ParamsObject = {}, Meta = unkno
32
32
  export declare type MatchRoute = (method: string, pathname: string) => RouteWithHandler | null;
33
33
  export declare type Router<T = unknown> = (context: RequestContext<T>) => Promise<Response | void>;
34
34
  export declare type InvokeRoute<T = unknown> = (route: Route | null, context: RequestContext<T>) => Promise<Response | void>;
35
+ declare type Member<T, U> = T extends T ? (U extends T ? T : never) : never;
36
+ declare type Segments<T extends string, Acc extends string[] = []> = T extends "" ? Acc : T extends `${infer Left}/${infer Rest}` ? Segments<Rest, [...Acc, Left]> : [...Acc, T];
37
+ declare type GTE<A extends any[], B extends any[]> = A["length"] extends B["length"] ? 1 : A extends [infer _Ha, ...infer Ta] ? B extends [infer _Hb, ...infer Tb] ? GTE<Ta, Tb> : 1 : 0;
38
+ declare type MatchSegments<A extends string, B extends string> = A extends `${infer P}/${string}*` ? 1 extends GTE<Segments<B>, Segments<P>> ? `${P}/${string}` : never : Segments<B>["length"] extends Segments<A>["length"] ? A : never;
39
+ declare type PathPattern<T extends string> = T extends `${infer Left}/\${${string}}/${infer Rest}` ? PathPattern<`${Left}/${string}/${Rest}`> : T extends `${infer Left}/\${...${string}}` ? PathPattern<`${Left}/${string}*`> : T extends `${infer Left}/\${${string}}` ? PathPattern<`${Left}/${string}`> : T;
40
+ export declare type PathTemplate<Path extends string> = Path extends `${infer Left}/\${${string}}/${infer Rest}` ? PathTemplate<`${Left}/${string}/${Rest}`> : Path extends `${infer Left}/\${${string}}` ? PathTemplate<`${Left}/${string}`> : Path;
41
+ export declare type ValidatePath<Paths extends string, Path extends string> = Paths | (Path extends `/${string}` ? MatchSegments<Member<PathPattern<Paths>, Path>, Path> : Path);
42
+ export declare type ValidateHref<Paths extends string, Href extends string> = Href extends `${infer P}#${infer H}?${infer Q}` ? `${ValidatePath<Paths, P>}#${H}?${Q}` : Href extends `${infer P}?${infer Q}` ? `${ValidatePath<Paths, P>}?${Q}` : Href extends `${infer P}#${infer H}` ? `${ValidatePath<Paths, P>}#${H}` : ValidatePath<Paths, Href>;
35
43
  export {};
@@ -980,6 +980,7 @@ function stripTsExtension(path3) {
980
980
  return path3;
981
981
  }
982
982
  function renderRouteTypeInfo(routes, pathPrefix = ".") {
983
+ var _a, _b;
983
984
  const writer = createStringWriter();
984
985
  writer.writeLines(
985
986
  `/*
@@ -987,13 +988,28 @@ function renderRouteTypeInfo(routes, pathPrefix = ".") {
987
988
  Do NOT manually edit this file or your changes will be lost.
988
989
  */
989
990
  `,
990
- `import type { HandlerLike, Route } from "@marko/run";
991
- `
991
+ `import type { HandlerLike, Route, RouteContext, ValidatePath, ValidateHref } from "@marko/run";
992
+
993
+ declare global {
994
+ namespace MarkoRun {`
992
995
  );
996
+ const pathsWriter = writer.branch("paths");
997
+ writer.write(`
998
+ type GetablePath<T extends string> = ValidatePath<GetPaths, T>;
999
+ type GetableHref<T extends string> = ValidateHref<GetPaths, T>;
1000
+ type PostablePath<T extends string> = ValidatePath<PostPaths, T>;
1001
+ type PostableHref<T extends string> = ValidateHref<PostPaths, T>;
1002
+ }
1003
+ }
1004
+ `);
993
1005
  const routesWriter = writer.branch("types");
1006
+ const serverWriter = writer.branch("server");
994
1007
  const middlewareRouteTypes = /* @__PURE__ */ new Map();
1008
+ const layoutRouteTypes = /* @__PURE__ */ new Map();
1009
+ const getPaths = /* @__PURE__ */ new Set();
1010
+ const postPaths = /* @__PURE__ */ new Set();
995
1011
  for (const route of routes.list) {
996
- const { meta, handler, params, middleware } = route;
1012
+ const { meta, handler, params, middleware, page, layouts } = route;
997
1013
  const routeType = `Route${route.index}`;
998
1014
  const pathType = `\`${route.path.replace(
999
1015
  /\/\$(\$?)([^\/]*)/,
@@ -1001,6 +1017,24 @@ function renderRouteTypeInfo(routes, pathPrefix = ".") {
1001
1017
  )}\``;
1002
1018
  const paramsType = params ? renderParamsInfoType(params) : "{}";
1003
1019
  let metaType = "undefined";
1020
+ if (page || handler) {
1021
+ const isGet = page || ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes("get"));
1022
+ const isPost = (_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes("post");
1023
+ if (isGet || isPost) {
1024
+ const path3 = route.path.replace(
1025
+ /\$(\$?)([^/]+)/g,
1026
+ (_, s, name) => s ? `\${...${name}}` : `\${${name}}`
1027
+ );
1028
+ const splatIndex = path3.indexOf("/${...");
1029
+ if (splatIndex >= 0) {
1030
+ const path22 = path3.slice(0, splatIndex) || "/";
1031
+ isGet && getPaths.add(path22);
1032
+ isPost && postPaths.add(path22);
1033
+ }
1034
+ isGet && getPaths.add(path3);
1035
+ isPost && postPaths.add(path3);
1036
+ }
1037
+ }
1004
1038
  if (meta) {
1005
1039
  metaType = `typeof import('${pathPrefix}/${stripTsExtension(
1006
1040
  meta.relativePath
@@ -1010,7 +1044,23 @@ function renderRouteTypeInfo(routes, pathPrefix = ".") {
1010
1044
  }
1011
1045
  }
1012
1046
  if (handler) {
1013
- writeRouteTypeModule(writer, pathPrefix, handler.relativePath, routeType);
1047
+ writeRouteTypeModule(
1048
+ serverWriter,
1049
+ pathPrefix,
1050
+ handler.relativePath,
1051
+ routeType
1052
+ );
1053
+ }
1054
+ if (page) {
1055
+ writer.writeLines(`
1056
+ declare module '${pathPrefix}/${page.relativePath}' {
1057
+ export interface Input {}
1058
+
1059
+ namespace MarkoRun {
1060
+ type CurrentRoute = ${routeType};
1061
+ type CurrentContext = RouteContext<CurrentRoute>;
1062
+ }
1063
+ }`);
1014
1064
  }
1015
1065
  if (middleware) {
1016
1066
  let i = 0;
@@ -1027,21 +1077,84 @@ function renderRouteTypeInfo(routes, pathPrefix = ".") {
1027
1077
  i++;
1028
1078
  }
1029
1079
  }
1080
+ if (layouts) {
1081
+ for (const layout of layouts) {
1082
+ const existing = layoutRouteTypes.get(layout);
1083
+ if (!existing) {
1084
+ layoutRouteTypes.set(layout, {
1085
+ routeTypes: [routeType]
1086
+ });
1087
+ } else {
1088
+ existing.routeTypes.push(routeType);
1089
+ }
1090
+ }
1091
+ }
1030
1092
  routesWriter.writeLines(
1031
1093
  `interface ${routeType} extends Route<${paramsType}, ${metaType}, ${pathType}> {}`
1032
1094
  );
1033
1095
  }
1096
+ pathsWriter.write(" type GetPaths =");
1097
+ for (const path3 of getPaths) {
1098
+ pathsWriter.write(`
1099
+ | '${path3}'`);
1100
+ }
1101
+ pathsWriter.writeLines(";", "");
1102
+ pathsWriter.write(" type PostPaths =");
1103
+ for (const path3 of postPaths) {
1104
+ pathsWriter.write(`
1105
+ | '${path3}'`);
1106
+ }
1107
+ pathsWriter.writeLines(";");
1108
+ pathsWriter.join();
1034
1109
  for (const [file, { routeTypes }] of middlewareRouteTypes) {
1035
- const routeType = routeTypes.length > 1 ? routeTypes.join(" | ") : routeTypes[0];
1036
- writeRouteTypeModule(writer, pathPrefix, file.relativePath, routeType);
1110
+ writeRouteTypeModule(
1111
+ serverWriter,
1112
+ pathPrefix,
1113
+ file.relativePath,
1114
+ routeTypes.join(" | ")
1115
+ );
1116
+ }
1117
+ for (const [file, { routeTypes }] of layoutRouteTypes) {
1118
+ writer.writeLines(`
1119
+ declare module '${pathPrefix}/${file.relativePath}' {
1120
+ export interface Input {
1121
+ renderBody: Marko.Body;
1122
+ }
1123
+
1124
+ namespace MarkoRun {
1125
+ type CurrentRoute = ${routeTypes.join(" | ")};
1126
+ type CurrentContext = RouteContext<CurrentRoute>;
1127
+ }
1128
+ }`);
1129
+ }
1130
+ for (const route of [routes.special["404"], routes.special["500"]]) {
1131
+ if (route && route.page) {
1132
+ writer.write(`
1133
+ declare module '${pathPrefix}/${route.page.relativePath}' {
1134
+ export interface Input {`);
1135
+ if (route.page.type === RoutableFileTypes.Error) {
1136
+ writer.write(`
1137
+ error: unknown;
1138
+ `);
1139
+ }
1140
+ writer.writeLines(`}
1141
+
1142
+ namespace MarkoRun {
1143
+ type CurrentRoute = Route;
1144
+ type CurrentContext = RouteContext<CurrentRoute>;
1145
+ }
1146
+ }`);
1147
+ }
1037
1148
  }
1149
+ serverWriter.join();
1038
1150
  return writer.end();
1039
1151
  }
1040
1152
  function writeRouteTypeModule(writer, pathPrefix, path3, routeType) {
1041
1153
  writer.writeLines(`
1042
1154
  declare module '${pathPrefix}/${stripTsExtension(path3)}' {
1043
- namespace Marko {
1155
+ namespace MarkoRun {
1044
1156
  type CurrentRoute = ${routeType};
1157
+ type CurrentContext = RouteContext<CurrentRoute>;
1045
1158
  type Handler<_Params = CurrentRoute['params'], _Meta = CurrentRoute['meta']> = HandlerLike<CurrentRoute>;
1046
1159
  function route(handler: Handler): typeof handler;
1047
1160
  function route<_Params = CurrentRoute['params'], _Meta = CurrentRoute['meta']>(handler: Handler): typeof handler;
@@ -1321,13 +1434,14 @@ function markoServe(opts = {}) {
1321
1434
  const startTime = performance.now();
1322
1435
  routes = await buildRoutes(createFSWalker(resolvedRoutesDir), routesDir);
1323
1436
  times.routesBuild = performance.now() - startTime;
1324
- await Promise.all([writeTypesFile(), setVirtualFiles(false)]);
1437
+ await setVirtualFiles(false);
1325
1438
  isStale = false;
1326
1439
  isRendered = false;
1327
1440
  });
1328
1441
  const renderVirtualFiles = single(async () => {
1329
1442
  const startTime = performance.now();
1330
1443
  await setVirtualFiles(true);
1444
+ await writeTypesFile();
1331
1445
  times.routesRender = performance.now() - startTime;
1332
1446
  isRendered = true;
1333
1447
  });
@@ -943,6 +943,7 @@ function stripTsExtension(path3) {
943
943
  return path3;
944
944
  }
945
945
  function renderRouteTypeInfo(routes, pathPrefix = ".") {
946
+ var _a, _b;
946
947
  const writer = createStringWriter();
947
948
  writer.writeLines(
948
949
  `/*
@@ -950,13 +951,28 @@ function renderRouteTypeInfo(routes, pathPrefix = ".") {
950
951
  Do NOT manually edit this file or your changes will be lost.
951
952
  */
952
953
  `,
953
- `import type { HandlerLike, Route } from "@marko/run";
954
- `
954
+ `import type { HandlerLike, Route, RouteContext, ValidatePath, ValidateHref } from "@marko/run";
955
+
956
+ declare global {
957
+ namespace MarkoRun {`
955
958
  );
959
+ const pathsWriter = writer.branch("paths");
960
+ writer.write(`
961
+ type GetablePath<T extends string> = ValidatePath<GetPaths, T>;
962
+ type GetableHref<T extends string> = ValidateHref<GetPaths, T>;
963
+ type PostablePath<T extends string> = ValidatePath<PostPaths, T>;
964
+ type PostableHref<T extends string> = ValidateHref<PostPaths, T>;
965
+ }
966
+ }
967
+ `);
956
968
  const routesWriter = writer.branch("types");
969
+ const serverWriter = writer.branch("server");
957
970
  const middlewareRouteTypes = /* @__PURE__ */ new Map();
971
+ const layoutRouteTypes = /* @__PURE__ */ new Map();
972
+ const getPaths = /* @__PURE__ */ new Set();
973
+ const postPaths = /* @__PURE__ */ new Set();
958
974
  for (const route of routes.list) {
959
- const { meta, handler, params, middleware } = route;
975
+ const { meta, handler, params, middleware, page, layouts } = route;
960
976
  const routeType = `Route${route.index}`;
961
977
  const pathType = `\`${route.path.replace(
962
978
  /\/\$(\$?)([^\/]*)/,
@@ -964,6 +980,24 @@ function renderRouteTypeInfo(routes, pathPrefix = ".") {
964
980
  )}\``;
965
981
  const paramsType = params ? renderParamsInfoType(params) : "{}";
966
982
  let metaType = "undefined";
983
+ if (page || handler) {
984
+ const isGet = page || ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes("get"));
985
+ const isPost = (_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes("post");
986
+ if (isGet || isPost) {
987
+ const path3 = route.path.replace(
988
+ /\$(\$?)([^/]+)/g,
989
+ (_, s, name) => s ? `\${...${name}}` : `\${${name}}`
990
+ );
991
+ const splatIndex = path3.indexOf("/${...");
992
+ if (splatIndex >= 0) {
993
+ const path22 = path3.slice(0, splatIndex) || "/";
994
+ isGet && getPaths.add(path22);
995
+ isPost && postPaths.add(path22);
996
+ }
997
+ isGet && getPaths.add(path3);
998
+ isPost && postPaths.add(path3);
999
+ }
1000
+ }
967
1001
  if (meta) {
968
1002
  metaType = `typeof import('${pathPrefix}/${stripTsExtension(
969
1003
  meta.relativePath
@@ -973,7 +1007,23 @@ function renderRouteTypeInfo(routes, pathPrefix = ".") {
973
1007
  }
974
1008
  }
975
1009
  if (handler) {
976
- writeRouteTypeModule(writer, pathPrefix, handler.relativePath, routeType);
1010
+ writeRouteTypeModule(
1011
+ serverWriter,
1012
+ pathPrefix,
1013
+ handler.relativePath,
1014
+ routeType
1015
+ );
1016
+ }
1017
+ if (page) {
1018
+ writer.writeLines(`
1019
+ declare module '${pathPrefix}/${page.relativePath}' {
1020
+ export interface Input {}
1021
+
1022
+ namespace MarkoRun {
1023
+ type CurrentRoute = ${routeType};
1024
+ type CurrentContext = RouteContext<CurrentRoute>;
1025
+ }
1026
+ }`);
977
1027
  }
978
1028
  if (middleware) {
979
1029
  let i = 0;
@@ -990,21 +1040,84 @@ function renderRouteTypeInfo(routes, pathPrefix = ".") {
990
1040
  i++;
991
1041
  }
992
1042
  }
1043
+ if (layouts) {
1044
+ for (const layout of layouts) {
1045
+ const existing = layoutRouteTypes.get(layout);
1046
+ if (!existing) {
1047
+ layoutRouteTypes.set(layout, {
1048
+ routeTypes: [routeType]
1049
+ });
1050
+ } else {
1051
+ existing.routeTypes.push(routeType);
1052
+ }
1053
+ }
1054
+ }
993
1055
  routesWriter.writeLines(
994
1056
  `interface ${routeType} extends Route<${paramsType}, ${metaType}, ${pathType}> {}`
995
1057
  );
996
1058
  }
1059
+ pathsWriter.write(" type GetPaths =");
1060
+ for (const path3 of getPaths) {
1061
+ pathsWriter.write(`
1062
+ | '${path3}'`);
1063
+ }
1064
+ pathsWriter.writeLines(";", "");
1065
+ pathsWriter.write(" type PostPaths =");
1066
+ for (const path3 of postPaths) {
1067
+ pathsWriter.write(`
1068
+ | '${path3}'`);
1069
+ }
1070
+ pathsWriter.writeLines(";");
1071
+ pathsWriter.join();
997
1072
  for (const [file, { routeTypes }] of middlewareRouteTypes) {
998
- const routeType = routeTypes.length > 1 ? routeTypes.join(" | ") : routeTypes[0];
999
- writeRouteTypeModule(writer, pathPrefix, file.relativePath, routeType);
1073
+ writeRouteTypeModule(
1074
+ serverWriter,
1075
+ pathPrefix,
1076
+ file.relativePath,
1077
+ routeTypes.join(" | ")
1078
+ );
1079
+ }
1080
+ for (const [file, { routeTypes }] of layoutRouteTypes) {
1081
+ writer.writeLines(`
1082
+ declare module '${pathPrefix}/${file.relativePath}' {
1083
+ export interface Input {
1084
+ renderBody: Marko.Body;
1085
+ }
1086
+
1087
+ namespace MarkoRun {
1088
+ type CurrentRoute = ${routeTypes.join(" | ")};
1089
+ type CurrentContext = RouteContext<CurrentRoute>;
1090
+ }
1091
+ }`);
1092
+ }
1093
+ for (const route of [routes.special["404"], routes.special["500"]]) {
1094
+ if (route && route.page) {
1095
+ writer.write(`
1096
+ declare module '${pathPrefix}/${route.page.relativePath}' {
1097
+ export interface Input {`);
1098
+ if (route.page.type === RoutableFileTypes.Error) {
1099
+ writer.write(`
1100
+ error: unknown;
1101
+ `);
1102
+ }
1103
+ writer.writeLines(`}
1104
+
1105
+ namespace MarkoRun {
1106
+ type CurrentRoute = Route;
1107
+ type CurrentContext = RouteContext<CurrentRoute>;
1108
+ }
1109
+ }`);
1110
+ }
1000
1111
  }
1112
+ serverWriter.join();
1001
1113
  return writer.end();
1002
1114
  }
1003
1115
  function writeRouteTypeModule(writer, pathPrefix, path3, routeType) {
1004
1116
  writer.writeLines(`
1005
1117
  declare module '${pathPrefix}/${stripTsExtension(path3)}' {
1006
- namespace Marko {
1118
+ namespace MarkoRun {
1007
1119
  type CurrentRoute = ${routeType};
1120
+ type CurrentContext = RouteContext<CurrentRoute>;
1008
1121
  type Handler<_Params = CurrentRoute['params'], _Meta = CurrentRoute['meta']> = HandlerLike<CurrentRoute>;
1009
1122
  function route(handler: Handler): typeof handler;
1010
1123
  function route<_Params = CurrentRoute['params'], _Meta = CurrentRoute['meta']>(handler: Handler): typeof handler;
@@ -1283,13 +1396,14 @@ function markoServe(opts = {}) {
1283
1396
  const startTime = performance.now();
1284
1397
  routes = await buildRoutes(createFSWalker(resolvedRoutesDir), routesDir);
1285
1398
  times.routesBuild = performance.now() - startTime;
1286
- await Promise.all([writeTypesFile(), setVirtualFiles(false)]);
1399
+ await setVirtualFiles(false);
1287
1400
  isStale = false;
1288
1401
  isRendered = false;
1289
1402
  });
1290
1403
  const renderVirtualFiles = single(async () => {
1291
1404
  const startTime = performance.now();
1292
1405
  await setVirtualFiles(true);
1406
+ await writeTypesFile();
1293
1407
  times.routesRender = performance.now() - startTime;
1294
1408
  isRendered = true;
1295
1409
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marko/run",
3
- "version": "0.0.1-beta4",
3
+ "version": "0.0.1-beta5",
4
4
  "description": "File-based routing for Marko based on Vite",
5
5
  "keywords": [],
6
6
  "author": "Ryan Turnquist <rturnq@gmail.com>",