@marko/run 0.0.1-beta3 → 0.0.1-beta4

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
@@ -1,31 +1,33 @@
1
- <h1 align="center">
1
+ <div align="center">
2
2
  <!-- Logo -->
3
- <img src="https://user-images.githubusercontent.com/4985201/115444712-ca550500-a1c9-11eb-9897-238ece59129c.png" height="118"/>
4
- <br/>
5
- @marko/run
6
- <br/>
3
+ <h1>
4
+ <img alt="" src="https://user-images.githubusercontent.com/4985201/115444712-ca550500-a1c9-11eb-9897-238ece59129c.png" height="118"/>
5
+ <br/>
6
+ @marko/run
7
+ </h1>
7
8
 
8
9
  <!-- Language -->
9
- <a href="http://typescriptlang.org">
10
+ <a href="https://www.typescriptlang.org">
10
11
  <img src="https://img.shields.io/badge/%3C%2F%3E-typescript-blue.svg" alt="TypeScript"/>
11
12
  </a>
12
- </h1>
13
+ </div>
13
14
 
14
- Vite plugin for Marko with these features
15
- - Encapsulates [@marko/vite](https://github.com/marko-js/vite)
15
+ `@marko/serve` is a Vite plugin for [Marko](https://markojs.com), with these features:
16
+
17
+ - Encapsulates [`@marko/vite`](https://github.com/marko-js/vite)
16
18
  - File-based routing with layouts and middleware
17
19
  - Efficient routing using a compiled static trie
18
- - Designed with web standards to run anywhere
20
+ - [Designed with web standards](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern/URLPattern) to run anywhere
19
21
 
20
- ## Intallation
22
+ ## Installation
21
23
 
22
- ```
24
+ ```sh
23
25
  npm install @marko/run
24
26
  ```
25
27
 
26
28
  ## Vite Plugin
27
29
 
28
- This package provides both a Vite plugin and a runtime import. The Vite plugin is responsible for discovering your route files, generating the routing code and registering the @marko/vite plugin which builds your .marko files.
30
+ This package’s Vite plugin discovers your route files, generates the routing code, and registers the `@marko/vite` plugin to compile your `.marko` files.
29
31
 
30
32
  ```ts
31
33
  // vite.config.ts
@@ -35,27 +37,32 @@ import marko from "@marko/run/vite"; // Import the Vite plugin
35
37
  export default defineConfig({
36
38
  plugins: [marko()], // Register the Vite plugin
37
39
  build: {
38
- sourcemap: true, // Generate sourcemaps for all builds.
39
- emptyOutDir: false, // Avoid server & client deleting files from each other.
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?
40
42
  }
41
43
  })
42
44
  ```
43
45
 
44
46
  ## Runtime
45
47
 
46
- Generally you'll want to use one of the adapters to provide a more convenient experience but this package provides the following runtime interface:
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:
47
49
 
48
50
  ```ts
49
51
  import { router, getMatchedRoute } from '@marko/run`;
50
52
  ```
51
53
 
52
54
  ### `router`
55
+
53
56
  ```ts
54
57
  (request: Request) => Promise<Response>;
55
58
  ```
56
- This asynchronouse function takes a [WHATWG request](https://fetch.spec.whatwg.org/#request-class) object and returns a [response](https://fetch.spec.whatwg.org/#response-class) object generated by executing the corresponding matched route files. If no match is found, a response with a 404 status code is returned. If an unhandled error occurs, a response with a 500 status code will be returned.
59
+
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.
61
+
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.
57
63
 
58
64
  ### `getMatchedRoute`
65
+
59
66
  ```ts
60
67
  (method: string, url: URL) => {
61
68
  params: Record<string, string>;
@@ -63,20 +70,25 @@ This asynchronouse function takes a [WHATWG request](https://fetch.spec.whatwg.o
63
70
  invoke(request: Request): Promise<Response>;
64
71
  } | null;
65
72
  ```
66
- This synchronous function takes a HTTP verb method, and URL and returns an object representing the best match or null if no match is found.
67
- - `params` - a key-value collection of any path parameters for the route
68
- - `meta` - meta data for the route
69
- - `invoke` - an asynchronouse function that takes a WHATWG request and returns the response generated by executing the matched route files. Note: unlike the top-level `router` function errors will not be caught.
73
+
74
+ This synchronous function takes an HTTP method + URL, then returns an object representing the best match — or `null` if no match is found.
75
+
76
+ - `params` - a `{ key: value }` collection of any path parameters for the route
77
+ - `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.
70
80
 
71
81
  ## File-based Routing
72
82
 
73
- ## Nested Routing
83
+ ### Nested Routing
74
84
 
75
- *TODO: provide a quick overview*
85
+ *🎗 TODO: provide a quick overview*
76
86
 
77
87
  ### Routes Directory
78
88
 
79
- By default, the plugin will look for files in the configured _routes directory_. By default it will look in `./src/routes` (relative to the Vite config file) which can be configured:
89
+ The plugin looks for route files in the configured **routes directory**. By default, that’s `./src/routes`, relative to the Vite config file.
90
+
91
+ To change what directory routes are found in:
80
92
 
81
93
  ```ts
82
94
  // vite.config.ts
@@ -86,55 +98,56 @@ import marko from "@marko/run/vite";
86
98
  export default defineConfig({
87
99
  plugins: [marko({
88
100
  routesDir: 'src/pages' // Use `./src/pages` (relative to this file) as the routes directory
89
- })],
90
- //...
101
+ })]
91
102
  })
92
103
  ```
93
104
 
94
105
  ### Routeable Files
95
106
 
96
- In order to allow for co-location of files that should not be served (e.g. tests, stories, assets), the router only recognizes a specific set of file names. The following files will be discovered in any directory within your application's _routes directory_.
107
+ To allow for colocation of files that shouldn’t be served (like tests, assets, etc.), the router only recognizes certain filenames.
108
+
109
+ The following filenames will be discovered in any directory inside your application’s [routes directory](#routes-directory).
97
110
 
98
111
  #### `+page.marko`
99
- These files establish a route at the current directory path which will be served for GET requests with the HTML content of the page. Only one page may exists for any served path.
112
+
113
+ These files establish a route at the current directory path which will be served for `GET` requests with the HTML content of the page. Only one page may exists for any served path.
100
114
 
101
115
  #### `+layout.marko`
102
- These files provide a layout component which will wrap all nested layouts and pages.
103
116
 
104
- <details>
105
- <summary>More Info</summary>
106
-
107
- Layouts are just like any other Marko component with no extra constraints. Each layout receives the request, path params, URL and route meta data as input as well as a renderBody which will be the subsequent layout or page to project.
108
-
109
- ```marko
110
- <div>
111
- <h1>My Products</h1>
112
- <main>
113
- ${input.renderBody} // project the page or layout here
114
- </main>
115
- </div>
116
- ```
117
- </details>
117
+ These files provide a **layout component**, which will wrap all nested layouts and pages.
118
+
119
+ Layouts are like any other Marko component with no extra constraints. Each layout receives the request, path params, URL, and route metadata as input, as well as a `renderBody` which will be the next layout or page to project.
120
+
121
+ ```marko
122
+ <main>
123
+ <h1>My Products</h1>
124
+
125
+ ${input.renderBody} // render the page or layout here
126
+ </main>
127
+ ```
118
128
 
119
129
  #### `+handler.*`
120
- These files establish a route at the current directory path which can handle requests for GET, POST, PUT and DELETE HTTP verbs. Typically these will be .js or .ts files depending on your project. Just like pages, only one handler may exist for any served path. A handler should export functions
130
+
131
+ These files establish a route at the current directory path which can handle requests for `GET`, `POST`, `PUT`, and `DELETE` HTTP methods. <!-- TODO: what about HEAD? -->
132
+
133
+ Typically, these will be `.js` or `.ts` files depending on your project. Like pages, only one handler may exist for any served path. A handler should export functions
121
134
 
122
135
  <details>
123
136
  <summary>More Info</summary>
124
137
 
125
- - Valid exports are: `get`, `post`, `put`, `del`.
126
- - Each method takes a `context` and `next` argument and should return a WHATWG response either synchronously or asynchronously.
127
- - The `context` argument contains the WHATWG request object, path parameters, URL and route meta data.
128
- - The `next` argument will call the page for get requests where applicable or return a 204 response.
138
+ - Valid exports are functions named `get`, `post`, `put`, or `del`.
139
+ - Each export receives a `context` and `next` argument, and should return a WHATWG `Response` either synchronously or asynchronously.
140
+ - The `context` argument contains the WHATWG request object, path parameters, URL, and route metadata.
141
+ - The `next` argument will call the page for get requests where applicable or return a `204` response.
129
142
 
130
- ```ts
143
+ ```js
131
144
  export function post(context, next) {
132
145
  const { request, params, url, meta } = context;
133
- return new Response('updated', { status: 200 });
146
+ return new Response('Successfully updated', { status: 200 });
134
147
  }
135
148
 
136
149
  export function put(context, next) {
137
- return new Response('created', { status: 201 }); // handle the request
150
+ return new Response('Successfully created', { status: 201 }); // handle the request
138
151
  }
139
152
 
140
153
  export function get(context, next) {
@@ -142,29 +155,33 @@ These files establish a route at the current directory path which can handle req
142
155
  }
143
156
 
144
157
  export function del(context, next) {
145
- return new Response('removed', { status: 204 });
158
+ return new Response('Successfully removed', { status: 204 });
146
159
  }
147
160
  ```
148
161
  </details>
149
162
 
150
163
 
151
- #### `+middlware.*`
152
- These files are analagous to layouts for handlers. Middlware get called before handlers and let you perform arbitrary work before and after. Unlike handlers, middleware run for all HTTP verbs.
164
+ #### `+middleware.*`
165
+
166
+ These files are like layouts, but for handlers. Middleware get called before handlers and let you perform arbitrary work before and after.
167
+
168
+ > **Note**: Unlike handlers, middleware run for all HTTP methods.
153
169
 
154
170
  <details>
155
171
  <summary>More Info</summary>
156
172
 
157
- - Expects default export that takes a `context` and `next` argument and should return a WHATWG response either synchronously or asynchronously.
158
- - The `context` argument contains the WHATWG request object, path parameters, URL and route meta data.
159
- - The `next` argument will call the next middleware, handler or page as applicable for the route.
173
+ Expects a `default` export that receives a `context` and `next` argument, and should return a WHATWG response either synchronously or asynchronously.
174
+
175
+ - The `context` argument contains the WHATWG `Request` object, path parameters, URL, and route metadata.
176
+ - The `next` argument will call the next middleware, handler, or page for the route.
160
177
 
161
178
  ```ts
162
179
  export default async function(context, next) {
163
- const requestName = `${ctx.request.method} ${ctx.url.href}`;
180
+ const requestName = `${ctx.request.method} ${ctx.url.href}`; // TODO: could this be just `ctx.url` with a `toString` that then grabs `.href`?
164
181
  let success = true;
165
182
  console.log(`${requestName} request started`)
166
183
  try {
167
- return await next(); // wait for subsequent middleware/handler/page
184
+ return await next(); // Wait for subsequent middleware/handler/page
168
185
  } catch (err) {
169
186
  success = false;
170
187
  throw err;
@@ -176,30 +193,42 @@ These files are analagous to layouts for handlers. Middlware get called before h
176
193
  </details>
177
194
 
178
195
  #### `+meta.*`
179
- These files represent some meta data to attach to the route. This meta data will be automatically imported by the router and provided on the the route context when invoking a route.
196
+
197
+ These files represent metadata to attach to the route. This metadata will be automatically provided on the the route `context` when invoking a route.
180
198
 
181
199
  ### Special Files
182
200
 
183
- In addition to the files above which can be defined in any directory under the _routes directory_, there are some special files which can only be defined within the _routes directory_:
201
+ In addition to the files above which can be defined in any directory under the _routes directory_, there are some special files which can only be defined at the top-level of the _routes directory_. <!-- TODO: do we want to keep this restriction? Having nested 404s would be handy for disambiguating things like “there’s no user with that name” or “that promotion wasn’t found, it may have expired” -->
202
+
203
+ These special pages are subject to a root layout file (`pages/+layout.marko` in the default configuration).
184
204
 
185
205
  #### `+404.marko`
186
- This is a special page wich will be rendered for any request whose `Accept` header includes `text/html` if no other handler or page handled the request. This page will be subject to the root layout file (ie. +layout.marko file in the _routes directory_). Responses with this page will have a `404` status code.
206
+
207
+ This special page responds to any request where:
208
+
209
+ - The `Accept` request header includes `text/html`
210
+ - *And* no other handler or page rendered the request
211
+
212
+ Responses with this page will have a `404` status code.
187
213
 
188
214
  #### `+500.marko`
189
- This is a special page wich will be rendered for any request whose `Accept` header includes `text/html` if an uncaught error occurs while handling the request. This page will be subject to the root layout file (ie. +layout.marko file in the _routes directory_). Responses with this page will have a `500` status code.
190
215
 
216
+ This special page responds to any request where:
217
+
218
+ - The `Accept` request header includes `text/html`
219
+ - *And* an uncaught error occurs while serving the request
220
+
221
+ Responses with this page will have a `500` status code.
191
222
 
192
223
  ### Execution Order
193
224
 
194
- For a matched route the routable files execute in the following order
225
+ For a matched route, the routable files execute in the following order:
226
+
195
227
  1. Middlewares from root-most to leaf-most
196
228
  2. Handler
197
229
  3. Layouts from root-most to leaf-most
198
230
  4. Page
199
231
 
200
- <details>
201
- <summary>Diagram</summary>
202
-
203
232
  ```mermaid
204
233
  sequenceDiagram
205
234
  participant Middleware1
@@ -218,13 +247,12 @@ sequenceDiagram
218
247
  Handler-->>Middleware2: Response
219
248
  Middleware2-->>Middleware1: Response
220
249
  ```
221
- </details>
222
250
 
223
251
  ### Path Structure
224
252
 
225
- Within the _routes directory_ the directory structure will determine the path the route will be served. There are four types of directory names:
253
+ Within the _routes directory_, the directory structure will determine the path the route will be served. There are four types of directory names: static, pathless, dynamic, and catch-all.
226
254
 
227
- - **Static directories** - The most common type of directory. Each static directory will contribute its name as a segment in the route's served path like a traditional file server. This is the default and unless the directory matches the requirements for one of the below types, it will be a static directory.
255
+ 1. **Static directories** - The most common type. Each static directory contributes its name as a segment in the route's served path, like a traditional fileserver. Unless a directory name matches the requirements for one of the below types, it defaults to a static directory.
228
256
 
229
257
  Examples:
230
258
  ```
@@ -233,7 +261,7 @@ Within the _routes directory_ the directory structure will determine the path th
233
261
  /projects
234
262
  ```
235
263
 
236
- - **Pathless directories** - These directories do not contribute their name to the route's served path. Directory names that start with an underscore ('_') as well as directories named 'index' will be a pathless directory.
264
+ 2. **Pathless directories** - These directories do **not** contribute their name to the route's served path. Directory names that start with an underscore (`_`) or directories named `index` will be a pathless directory.
237
265
 
238
266
  Examples:
239
267
  ```
@@ -242,7 +270,7 @@ Within the _routes directory_ the directory structure will determine the path th
242
270
  /index
243
271
  ```
244
272
 
245
- - **Dynamic directories** - These directories introduce a dynamic parameter to the route's served path and will match any value at that segment. Any directory name that starts with a single dollar sign ('\$') will be a dynamic directory and the remaining directory name will be the parameter at runtime. In the case the directory name is '$', the parameter will not exist at runtime but will be matched.
273
+ 3. **Dynamic directories** - These directories introduce a dynamic parameter to the route's served path and will match any value at that segment. Any directory name that starts with a single dollar sign (`$`) will be a dynamic directory, and the remaining directory name will be the parameter at runtime. If the directory name is exactly `$/`, the parameter will not exist at runtime but will be matched.
246
274
 
247
275
  Examples:
248
276
  ```
@@ -251,9 +279,9 @@ Within the _routes directory_ the directory structure will determine the path th
251
279
  /$
252
280
  ```
253
281
 
254
- - **Catcha-all directories** - These directories are similar to dynamic directories and introduce a dynamic parameter but instead of matching a single sement of the path, they match to the end of the path. Any directory that starts with two dollar signs ('\$\$') will be a catch-all directory and the remaining directory name will be the parameter at runtime. In the case of a directory name '$$', the parameter name at runtime will be '*'. Catch-all directories can be used to make not found/404 routes at any level including the root.
282
+ 4. **Catch-all directories** - These directories are similar to dynamic directories and introduce a dynamic parameter, but instead of matching a single path segment, they match to the end of the path. Any directory that starts with two dollar signs (`$$`) will be a catch-all directory, and the remaining directory name will be the parameter at runtime. In the case of a directory named `$$/`, the parameter name at runtime will be `*`. Catch-all directories can be used to make `404` Not Found routes at any level, including the root.
255
283
 
256
- Because catch-all directories always match and consume all the remaining path, you cannot have nested routes within and no further directories will be traversed.
284
+ Because catch-all directories match any path segment and consume the rest of the path, you cannot nest route files in them and no further directories will be traversed.
257
285
 
258
286
  Examples:
259
287
  ```
@@ -1,4 +1,4 @@
1
1
  import { ViteDevServer } from "vite";
2
- import { type NodeMiddleware } from "@marko/run/adapter/middleware";
2
+ import { type NodeMiddleware } from "./middleware";
3
3
  export declare function createViteDevMiddleware<T>(devServer: ViteDevServer, load: (prev: T | undefined) => Promise<T>, factory: (value: T) => NodeMiddleware): NodeMiddleware;
4
4
  export declare function createDevServer(configFile?: string): Promise<import("vite").Connect.Server>;
@@ -36,7 +36,173 @@ var import_url = require("url");
36
36
 
37
37
  // src/adapter/dev-server.ts
38
38
  var import_vite = require("vite");
39
- var import_middleware = __toESM(require("@marko/run/adapter/middleware"), 1);
39
+
40
+ // src/adapter/middleware.ts
41
+ var webStream = __toESM(require("stream/web"), 1);
42
+ var import_crypto = __toESM(require("@hattip/polyfills/crypto"), 1);
43
+ (0, import_crypto.default)();
44
+ for (const key of Object.keys(webStream)) {
45
+ if (!(key in global)) {
46
+ global[key] = webStream[key];
47
+ }
48
+ }
49
+ function getForwardedHeader(req, name) {
50
+ const value = req.headers["x-forwarded-" + name];
51
+ if (value) {
52
+ if (typeof value === "string") {
53
+ const index = value.indexOf(",");
54
+ return index < 0 ? value : value.slice(0, index);
55
+ }
56
+ return value[0];
57
+ }
58
+ }
59
+ function getOrigin(req, protocol, host, trustProxy) {
60
+ var _a;
61
+ protocol ?? (protocol = req.protocol || trustProxy && getForwardedHeader(req, "proto") || ((_a = req.socket) == null ? void 0 : _a.encrypted) && "https" || "http");
62
+ host ?? (host = trustProxy && getForwardedHeader(req, "host") || req.headers.host);
63
+ if (!host) {
64
+ if (process.env.NODE_ENV !== "production") {
65
+ host = "localhost";
66
+ console.warn(
67
+ `Could not automatically determine the origin host, using 'localhost'. Use the 'origin' option or the 'ORIGIN' environment variable to set the origin explicitly.`
68
+ );
69
+ } else {
70
+ throw new Error(
71
+ `Could not automatically determine the origin host. Use the 'origin' option or the 'ORIGIN' environment variable to set the origin explicitly.`
72
+ );
73
+ }
74
+ }
75
+ return `${protocol}://${host}`;
76
+ }
77
+ function createMiddleware(router, options = {}) {
78
+ const { trustProxy = process.env.TRUST_PROXY === "1" } = options;
79
+ let { origin = process.env.ORIGIN } = options;
80
+ let protocol;
81
+ let host;
82
+ if (origin) {
83
+ ({ protocol, host } = new URL(origin));
84
+ protocol = protocol.slice(0, -1);
85
+ }
86
+ return async (req, res, next) => {
87
+ origin ?? (origin = getOrigin(req, protocol, host, trustProxy));
88
+ const url = new URL(req.url, origin);
89
+ const ip = req.ip || trustProxy && getForwardedHeader(req, "for") || req.socket.remoteAddress || "";
90
+ const requestContext = {
91
+ method: req.method,
92
+ url,
93
+ platform: {
94
+ ip,
95
+ request: req,
96
+ response: res,
97
+ setCookie(cookie) {
98
+ res.appendHeader("set-cookie", cookie);
99
+ }
100
+ }
101
+ };
102
+ Object.defineProperty(requestContext, "request", {
103
+ get() {
104
+ const headers = req.headers;
105
+ const body = req.method === "GET" || req.method === "HEAD" ? void 0 : req.socket ? req : new ReadableStream({
106
+ start(controller) {
107
+ req.on("data", (chunk) => controller.enqueue(chunk));
108
+ req.on("end", () => controller.close());
109
+ req.on("error", (err) => controller.error(err));
110
+ }
111
+ });
112
+ const request = new Request(url, {
113
+ method: req.method,
114
+ headers,
115
+ body,
116
+ duplex: "half"
117
+ });
118
+ Object.defineProperty(this, "request", {
119
+ value: request,
120
+ enumerable: true,
121
+ configurable: true
122
+ });
123
+ return request;
124
+ },
125
+ enumerable: true,
126
+ configurable: true
127
+ });
128
+ const response = await router(requestContext);
129
+ if (!response) {
130
+ if (next) {
131
+ next();
132
+ } else {
133
+ res.statusCode = 404;
134
+ res.setHeader("content-length", "0");
135
+ res.end();
136
+ return;
137
+ }
138
+ return;
139
+ }
140
+ res.statusCode = response.status;
141
+ for (const [key, value] of response.headers) {
142
+ if (key === "set-cookie") {
143
+ let sepIndex = value.indexOf(",") + 1;
144
+ if (!sepIndex) {
145
+ res.setHeader(key, value);
146
+ } else {
147
+ let index = 0;
148
+ do {
149
+ res.appendHeader(key, value.slice(index, sepIndex - 1));
150
+ index = sepIndex;
151
+ sepIndex = value.indexOf(",", sepIndex) + 1;
152
+ } while (sepIndex);
153
+ res.appendHeader(key, value.slice(index));
154
+ }
155
+ } else {
156
+ res.setHeader(key, value);
157
+ }
158
+ }
159
+ if (!response.body) {
160
+ if (!response.headers.has("content-length")) {
161
+ res.setHeader("content-length", "0");
162
+ }
163
+ res.end();
164
+ return;
165
+ }
166
+ const reader = response.body.getReader();
167
+ if (res.destroyed) {
168
+ reader.cancel();
169
+ return;
170
+ }
171
+ res.on("close", cancel);
172
+ res.on("error", cancel);
173
+ write();
174
+ function cancel(error) {
175
+ res.off("close", cancel);
176
+ res.off("error", cancel);
177
+ reader.cancel(error).catch(() => {
178
+ });
179
+ error && res.destroy(error);
180
+ }
181
+ async function write() {
182
+ try {
183
+ while (true) {
184
+ const { done, value } = await reader.read();
185
+ if (done) {
186
+ res.end();
187
+ return;
188
+ } else if (!res.write(value)) {
189
+ res.once("drain", write);
190
+ return;
191
+ } else if (res.flush) {
192
+ res.flush();
193
+ }
194
+ }
195
+ } catch (err) {
196
+ const error = err instanceof Error ? err : new Error("Error while writing to node response", {
197
+ cause: err
198
+ });
199
+ cancel(error);
200
+ }
201
+ }
202
+ };
203
+ }
204
+
205
+ // src/adapter/dev-server.ts
40
206
  function createViteDevMiddleware(devServer, load, factory) {
41
207
  let value;
42
208
  let middleware;
@@ -65,7 +231,7 @@ async function createDevServer(configFile) {
65
231
  const middleware = createViteDevMiddleware(
66
232
  devServer,
67
233
  async () => (await devServer.ssrLoadModule("@marko/run/router")).router,
68
- import_middleware.default
234
+ createMiddleware
69
235
  );
70
236
  return devServer.middlewares.use(middleware);
71
237
  }
@@ -1,4 +1,5 @@
1
1
  import type { Adapter } from "../vite";
2
2
  export { createDevServer, createViteDevMiddleware } from "./dev-server";
3
3
  export type { Adapter };
4
+ export type { NodePlatformInfo } from './middleware';
4
5
  export default function adapter(): Adapter;
@@ -4,7 +4,173 @@ import { fileURLToPath } from "url";
4
4
 
5
5
  // src/adapter/dev-server.ts
6
6
  import { createServer } from "vite";
7
- import createMiddleware from "@marko/run/adapter/middleware";
7
+
8
+ // src/adapter/middleware.ts
9
+ import * as webStream from "stream/web";
10
+ import installCrypto from "@hattip/polyfills/crypto";
11
+ installCrypto();
12
+ for (const key of Object.keys(webStream)) {
13
+ if (!(key in global)) {
14
+ global[key] = webStream[key];
15
+ }
16
+ }
17
+ function getForwardedHeader(req, name) {
18
+ const value = req.headers["x-forwarded-" + name];
19
+ if (value) {
20
+ if (typeof value === "string") {
21
+ const index = value.indexOf(",");
22
+ return index < 0 ? value : value.slice(0, index);
23
+ }
24
+ return value[0];
25
+ }
26
+ }
27
+ function getOrigin(req, protocol, host, trustProxy) {
28
+ var _a;
29
+ protocol ?? (protocol = req.protocol || trustProxy && getForwardedHeader(req, "proto") || ((_a = req.socket) == null ? void 0 : _a.encrypted) && "https" || "http");
30
+ host ?? (host = trustProxy && getForwardedHeader(req, "host") || req.headers.host);
31
+ if (!host) {
32
+ if (process.env.NODE_ENV !== "production") {
33
+ host = "localhost";
34
+ console.warn(
35
+ `Could not automatically determine the origin host, using 'localhost'. Use the 'origin' option or the 'ORIGIN' environment variable to set the origin explicitly.`
36
+ );
37
+ } else {
38
+ throw new Error(
39
+ `Could not automatically determine the origin host. Use the 'origin' option or the 'ORIGIN' environment variable to set the origin explicitly.`
40
+ );
41
+ }
42
+ }
43
+ return `${protocol}://${host}`;
44
+ }
45
+ function createMiddleware(router, options = {}) {
46
+ const { trustProxy = process.env.TRUST_PROXY === "1" } = options;
47
+ let { origin = process.env.ORIGIN } = options;
48
+ let protocol;
49
+ let host;
50
+ if (origin) {
51
+ ({ protocol, host } = new URL(origin));
52
+ protocol = protocol.slice(0, -1);
53
+ }
54
+ return async (req, res, next) => {
55
+ origin ?? (origin = getOrigin(req, protocol, host, trustProxy));
56
+ const url = new URL(req.url, origin);
57
+ const ip = req.ip || trustProxy && getForwardedHeader(req, "for") || req.socket.remoteAddress || "";
58
+ const requestContext = {
59
+ method: req.method,
60
+ url,
61
+ platform: {
62
+ ip,
63
+ request: req,
64
+ response: res,
65
+ setCookie(cookie) {
66
+ res.appendHeader("set-cookie", cookie);
67
+ }
68
+ }
69
+ };
70
+ Object.defineProperty(requestContext, "request", {
71
+ get() {
72
+ const headers = req.headers;
73
+ const body = req.method === "GET" || req.method === "HEAD" ? void 0 : req.socket ? req : new ReadableStream({
74
+ start(controller) {
75
+ req.on("data", (chunk) => controller.enqueue(chunk));
76
+ req.on("end", () => controller.close());
77
+ req.on("error", (err) => controller.error(err));
78
+ }
79
+ });
80
+ const request = new Request(url, {
81
+ method: req.method,
82
+ headers,
83
+ body,
84
+ duplex: "half"
85
+ });
86
+ Object.defineProperty(this, "request", {
87
+ value: request,
88
+ enumerable: true,
89
+ configurable: true
90
+ });
91
+ return request;
92
+ },
93
+ enumerable: true,
94
+ configurable: true
95
+ });
96
+ const response = await router(requestContext);
97
+ if (!response) {
98
+ if (next) {
99
+ next();
100
+ } else {
101
+ res.statusCode = 404;
102
+ res.setHeader("content-length", "0");
103
+ res.end();
104
+ return;
105
+ }
106
+ return;
107
+ }
108
+ res.statusCode = response.status;
109
+ for (const [key, value] of response.headers) {
110
+ if (key === "set-cookie") {
111
+ let sepIndex = value.indexOf(",") + 1;
112
+ if (!sepIndex) {
113
+ res.setHeader(key, value);
114
+ } else {
115
+ let index = 0;
116
+ do {
117
+ res.appendHeader(key, value.slice(index, sepIndex - 1));
118
+ index = sepIndex;
119
+ sepIndex = value.indexOf(",", sepIndex) + 1;
120
+ } while (sepIndex);
121
+ res.appendHeader(key, value.slice(index));
122
+ }
123
+ } else {
124
+ res.setHeader(key, value);
125
+ }
126
+ }
127
+ if (!response.body) {
128
+ if (!response.headers.has("content-length")) {
129
+ res.setHeader("content-length", "0");
130
+ }
131
+ res.end();
132
+ return;
133
+ }
134
+ const reader = response.body.getReader();
135
+ if (res.destroyed) {
136
+ reader.cancel();
137
+ return;
138
+ }
139
+ res.on("close", cancel);
140
+ res.on("error", cancel);
141
+ write();
142
+ function cancel(error) {
143
+ res.off("close", cancel);
144
+ res.off("error", cancel);
145
+ reader.cancel(error).catch(() => {
146
+ });
147
+ error && res.destroy(error);
148
+ }
149
+ async function write() {
150
+ try {
151
+ while (true) {
152
+ const { done, value } = await reader.read();
153
+ if (done) {
154
+ res.end();
155
+ return;
156
+ } else if (!res.write(value)) {
157
+ res.once("drain", write);
158
+ return;
159
+ } else if (res.flush) {
160
+ res.flush();
161
+ }
162
+ }
163
+ } catch (err) {
164
+ const error = err instanceof Error ? err : new Error("Error while writing to node response", {
165
+ cause: err
166
+ });
167
+ cancel(error);
168
+ }
169
+ }
170
+ };
171
+ }
172
+
173
+ // src/adapter/dev-server.ts
8
174
  function createViteDevMiddleware(devServer, load, factory) {
9
175
  let value;
10
176
  let middleware;
@@ -79,20 +79,19 @@ function createMiddleware(router, options = {}) {
79
79
  origin ?? (origin = getOrigin(req, protocol, host, trustProxy));
80
80
  const url = new URL(req.url, origin);
81
81
  const ip = req.ip || trustProxy && getForwardedHeader(req, "for") || req.socket.remoteAddress || "";
82
- const platform = {
83
- ip,
84
- request: req,
85
- response: res,
86
- setCookie(cookie) {
87
- res.appendHeader("set-cookie", cookie);
88
- }
89
- };
90
- const context = {
82
+ const requestContext = {
91
83
  method: req.method,
92
84
  url,
93
- platform
85
+ platform: {
86
+ ip,
87
+ request: req,
88
+ response: res,
89
+ setCookie(cookie) {
90
+ res.appendHeader("set-cookie", cookie);
91
+ }
92
+ }
94
93
  };
95
- Object.defineProperty(context, "request", {
94
+ Object.defineProperty(requestContext, "request", {
96
95
  get() {
97
96
  const headers = req.headers;
98
97
  const body = req.method === "GET" || req.method === "HEAD" ? void 0 : req.socket ? req : new ReadableStream({
@@ -118,7 +117,7 @@ function createMiddleware(router, options = {}) {
118
117
  enumerable: true,
119
118
  configurable: true
120
119
  });
121
- const response = await router(context);
120
+ const response = await router(requestContext);
122
121
  if (!response) {
123
122
  if (next) {
124
123
  next();
@@ -1,4 +1,4 @@
1
- import type { Router } from "@marko/run";
1
+ import type { Router } from "../runtime";
2
2
  import type { IncomingMessage, ServerResponse } from "http";
3
3
  declare module "net" {
4
4
  interface Socket {
@@ -14,10 +14,6 @@ declare module "http" {
14
14
  appendHeader(key: string, value: string | string[]): this;
15
15
  }
16
16
  }
17
- declare module "@marko/run" {
18
- interface Platform extends NodePlatformInfo {
19
- }
20
- }
21
17
  export interface NodePlatformInfo {
22
18
  ip: string;
23
19
  request: IncomingMessage;
@@ -48,20 +48,19 @@ function createMiddleware(router, options = {}) {
48
48
  origin ?? (origin = getOrigin(req, protocol, host, trustProxy));
49
49
  const url = new URL(req.url, origin);
50
50
  const ip = req.ip || trustProxy && getForwardedHeader(req, "for") || req.socket.remoteAddress || "";
51
- const platform = {
52
- ip,
53
- request: req,
54
- response: res,
55
- setCookie(cookie) {
56
- res.appendHeader("set-cookie", cookie);
57
- }
58
- };
59
- const context = {
51
+ const requestContext = {
60
52
  method: req.method,
61
53
  url,
62
- platform
54
+ platform: {
55
+ ip,
56
+ request: req,
57
+ response: res,
58
+ setCookie(cookie) {
59
+ res.appendHeader("set-cookie", cookie);
60
+ }
61
+ }
63
62
  };
64
- Object.defineProperty(context, "request", {
63
+ Object.defineProperty(requestContext, "request", {
65
64
  get() {
66
65
  const headers = req.headers;
67
66
  const body = req.method === "GET" || req.method === "HEAD" ? void 0 : req.socket ? req : new ReadableStream({
@@ -87,7 +86,7 @@ function createMiddleware(router, options = {}) {
87
86
  enumerable: true,
88
87
  configurable: true
89
88
  });
90
- const response = await router(context);
89
+ const response = await router(requestContext);
91
90
  if (!response) {
92
91
  if (next) {
93
92
  next();
@@ -8,14 +8,18 @@ import { build as viteBuild, resolveConfig } from "vite";
8
8
  import sade from "sade";
9
9
 
10
10
  // src/vite/utils/config.ts
11
- var KEY = "__MARKO_SERVE_OPTIONS__";
12
- function getMarkoRunOptions(viteConfig) {
13
- return viteConfig[KEY];
11
+ var PluginConfigKey = "__MARKO_RUN_PLUGIN_CONFIG__";
12
+ var AdapterConfigKey = "__MARKO_RUN_ADAPTER_CONFIG__";
13
+ function getConfig(obj, key) {
14
+ return obj[key];
14
15
  }
15
- function setMarkoRunOptions(viteConfig, options) {
16
- viteConfig[KEY] = options;
17
- return viteConfig;
16
+ function setConfig(obj, key, value) {
17
+ obj[key] = value;
18
+ return obj;
18
19
  }
20
+ var getExternalPluginOptions = (viteConfig) => getConfig(viteConfig, PluginConfigKey);
21
+ var setExternalPluginOptions = (viteConfig, value) => setConfig(viteConfig, PluginConfigKey, value);
22
+ var setExternalAdapterOptions = (viteConfig, value) => setConfig(viteConfig, AdapterConfigKey, value);
19
23
 
20
24
  // src/cli/index.ts
21
25
  import { MemoryStore } from "@marko/vite";
@@ -91,18 +95,20 @@ function sleep(ms) {
91
95
  var __dirname = fileURLToPath(new URL(".", import.meta.url));
92
96
  var cwd = process.cwd();
93
97
  var defaultPort = +process.env.PORT || 3e3;
94
- var prog = sade("marko-run").version("0.0.1").option("-c, --config", "Provide path to a Vite config").option("-e, --env", "Provide path to a dotenv file");
95
- prog.command("preview [entry]", "", { default: true }).describe("Start production-like server against built assets").option("-o, --output", "Directory to serve files").option("-p, --port", "Port to use for dev server").option("-f, --file", "Output file to start").action(async (entry, opts) => {
98
+ var defaultConfigFileBases = ["serve.config", "vite.config"];
99
+ var defaultConfigFileExts = [".js", ".cjs", ".mjs", ".ts", ".mts"];
100
+ var prog = sade("marko-run").version("0.0.1").option("-c, --config", `Provide path to a Vite config file (by default looks for a file starting with ${defaultConfigFileBases.join(" or ")} with one of these extensions: ${defaultConfigFileExts.join(", ")})`).option("-e, --env", "Provide path to a dotenv file");
101
+ prog.command("serve [entry]", "", { default: true }).describe("Start a production-like server for already-built app files").option("-o, --output", "Directory to serve files from, and write asset files to if `--build` (default: )").option("-p, --port", "Port the server should listen on (defaults: `$PORT` env variable or 3000)").option("-f, --file", "Output file to start").action(async (entry, opts) => {
96
102
  const config2 = await getViteConfig(cwd, opts.config);
97
103
  await build(entry, config2, opts.output, false, opts.env);
98
104
  await preview(opts.entry, config2, opts.port, opts.output, opts.env);
99
105
  });
100
- prog.command("dev [entry]").describe("Start dev server").option("-p, --port", "Port to use for dev server").example("dev --config vite.config.js").action(async (entry, opts) => {
106
+ prog.command("dev [entry]").describe("Start development server in watch mode").option("-p, --port", "Port the dev server should listen on (defaults: 'preview.port' in config, or `$PORT` env variable, or 3000)").example("dev --config vite.config.js").action(async (entry, opts) => {
101
107
  const cmd = opts._.length ? `${entry} ${opts._.join(" ")}` : entry ? `node ${entry}` : void 0;
102
108
  const config2 = await getViteConfig(cwd, opts.config);
103
109
  await dev(cmd, config2, opts.port, opts.env);
104
110
  });
105
- prog.command("build [entry]").describe("Build the application").option("-o, --output", "Directory to ouput built files").option("--skip-client", "Skip the client 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: )").option("--skip-client", "Skip the client-side build").example("build --config vite.config.js").action(async (entry, opts) => {
106
112
  const config2 = await getViteConfig(cwd, opts.config);
107
113
  await build(entry, config2, opts.ouput, opts["skip-client"], opts.env);
108
114
  });
@@ -117,9 +123,9 @@ async function preview(entry, configFile, port, outDir, envFile) {
117
123
  }
118
124
  const adapter = await resolveAdapter(resolvedConfig);
119
125
  if (!adapter) {
120
- throw new Error("No adapter specified for serve command");
126
+ throw new Error("No adapter specified for 'serve' command");
121
127
  } else if (!adapter.startPreview) {
122
- throw new Error(`Adapter ${adapter.name} does not support serve command`);
128
+ throw new Error(`Adapter ${adapter.name} does not support 'serve' command`);
123
129
  }
124
130
  const dir = path.resolve(cwd, resolvedConfig.build.outDir);
125
131
  const entryFile = entry ? path.join(dir, entry) : await findFileWithExt(dir, "index", [".mjs", ".js"]);
@@ -145,10 +151,10 @@ async function dev(cmd, configFile, port, envFile) {
145
151
  const adapter = await resolveAdapter(resolvedConfig);
146
152
  if (!adapter) {
147
153
  throw new Error(
148
- "No adapter specified for dev command without custom target"
154
+ "No adapter specified for 'dev' command without custom target"
149
155
  );
150
156
  } else if (!adapter.startDev) {
151
- throw new Error(`Adapter ${adapter.name} does not support serve command`);
157
+ throw new Error(`Adapter '${adapter.name}' does not support 'serve' command`);
152
158
  } else {
153
159
  await adapter.startDev(configFile, port, envFile);
154
160
  }
@@ -156,38 +162,39 @@ async function dev(cmd, configFile, port, envFile) {
156
162
  }
157
163
  async function build(entry, configFile, outDir, skipClient = false, envFile) {
158
164
  var _a;
165
+ const resolvedConfig = await resolveConfig(
166
+ { root: cwd, configFile },
167
+ "build"
168
+ );
169
+ const adapter = await resolveAdapter(resolvedConfig);
170
+ if (!adapter) {
171
+ throw new Error("No adapter specified for build command without entry");
172
+ }
159
173
  if (!entry) {
160
- const resolvedConfig = await resolveConfig(
161
- { root: cwd, configFile },
162
- "build"
163
- );
164
- const adapter = await resolveAdapter(resolvedConfig);
165
- if (!adapter) {
166
- throw new Error("No adapter specified for build command without entry");
167
- }
168
174
  entry = await ((_a = adapter.getEntryFile) == null ? void 0 : _a.call(adapter));
169
175
  if (!entry) {
170
176
  throw new Error(
171
- `Adapter ${adapter.name} does not support build command without entry`
177
+ `Adapter '${adapter.name}' does not support building without an entry`
172
178
  );
173
179
  }
174
180
  }
175
181
  if (envFile) {
176
182
  envFile = path.resolve(cwd, envFile);
177
183
  }
178
- const buildConfig = setMarkoRunOptions(
179
- {
180
- root: cwd,
181
- configFile,
182
- build: {
183
- ssr: false,
184
- outDir
185
- }
186
- },
187
- {
188
- store: new MemoryStore()
184
+ let buildConfig = {
185
+ root: cwd,
186
+ configFile,
187
+ build: {
188
+ ssr: false,
189
+ outDir
189
190
  }
190
- );
191
+ };
192
+ buildConfig = setExternalPluginOptions(buildConfig, {
193
+ store: new MemoryStore()
194
+ });
195
+ buildConfig = setExternalAdapterOptions(buildConfig, {
196
+ envFile
197
+ });
191
198
  await viteBuild({
192
199
  ...buildConfig,
193
200
  build: {
@@ -211,7 +218,7 @@ async function build(entry, configFile, outDir, skipClient = false, envFile) {
211
218
  });
212
219
  }
213
220
  }
214
- function findFileWithExt(dir, base, extensions = [".js", ".cjs", ".mjs", ".ts", ".mts"]) {
221
+ function findFileWithExt(dir, base, extensions = defaultConfigFileExts) {
215
222
  for (const ext of extensions) {
216
223
  const filePath = path.join(dir, base + ext);
217
224
  if (fs2.existsSync(filePath)) {
@@ -220,10 +227,11 @@ function findFileWithExt(dir, base, extensions = [".js", ".cjs", ".mjs", ".ts",
220
227
  }
221
228
  return void 0;
222
229
  }
223
- async function getViteConfig(dir, configFile, bases = ["serve.config", "vite.config"]) {
230
+ async function getViteConfig(dir, configFile, bases = defaultConfigFileBases) {
224
231
  if (configFile) {
225
- if (!fs2.existsSync(path.join(dir, configFile))) {
226
- throw new Error(`Unable to load config file '${configFile}' from ${dir}`);
232
+ const configFilePath = path.join(dir, configFile);
233
+ if (!fs2.existsSync(configFilePath)) {
234
+ throw new Error(`No config file found at '${configFilePath}'`);
227
235
  }
228
236
  return configFile;
229
237
  }
@@ -236,9 +244,9 @@ async function getViteConfig(dir, configFile, bases = ["serve.config", "vite.con
236
244
  return path.join(__dirname, "default.config.mjs");
237
245
  }
238
246
  async function resolveAdapter(config2) {
239
- const options = getMarkoRunOptions(config2);
247
+ const options = getExternalPluginOptions(config2);
240
248
  if (!options) {
241
- throw new Error("Unable to resolve Marko Serve options");
249
+ throw new Error("Unable to resolve @marko/serve options");
242
250
  }
243
251
  return options.adapter;
244
252
  }
@@ -26,7 +26,9 @@ __export(router_exports, {
26
26
  });
27
27
  module.exports = __toCommonJS(router_exports);
28
28
  function notImplemented() {
29
- throw new Error("This should have been replaced by the @marko/run plugin at build/dev time");
29
+ throw new Error(
30
+ "This should have been replaced by the @marko/run plugin at build/dev time"
31
+ );
30
32
  }
31
33
  var router = notImplemented;
32
34
  var matchRoute = notImplemented;
@@ -1,4 +1,4 @@
1
- import type { InvokeRoute, MatchRoute, Router } from './types';
2
- export declare const router: Router<import("./types").Platform>;
1
+ import type { MatchRoute, RequestContext, Route } from "./types";
2
+ export declare const router: <T>(context: RequestContext<T>) => Promise<Response | void>;
3
3
  export declare const matchRoute: MatchRoute;
4
- export declare const invokeRoute: InvokeRoute<import("./types").Platform>;
4
+ export declare const invokeRoute: <T>(route: Route | null, context: RequestContext<T>) => Promise<Response | void>;
@@ -1,6 +1,8 @@
1
1
  // src/runtime/router.ts
2
2
  function notImplemented() {
3
- throw new Error("This should have been replaced by the @marko/run plugin at build/dev time");
3
+ throw new Error(
4
+ "This should have been replaced by the @marko/run plugin at build/dev time"
5
+ );
4
6
  }
5
7
  var router = notImplemented;
6
8
  var matchRoute = notImplemented;
@@ -5,11 +5,9 @@ declare type Combine<T> = T extends object ? {
5
5
  } : T;
6
6
  export interface RouteContextExtensions {
7
7
  }
8
- export interface Platform {
9
- }
10
8
  export declare type ParamsObject = Record<string, string>;
11
9
  export declare type InputObject = Record<PropertyKey, any>;
12
- export interface RequestContext<T = Platform> {
10
+ export interface RequestContext<T = unknown> {
13
11
  url: URL;
14
12
  method: string;
15
13
  request: Request;
@@ -32,6 +30,6 @@ export interface RouteWithHandler<Params extends ParamsObject = {}, Meta = unkno
32
30
  handler: RouteHandler<this>;
33
31
  }
34
32
  export declare type MatchRoute = (method: string, pathname: string) => RouteWithHandler | null;
35
- export declare type Router<T = Platform> = (context: RequestContext<T>) => Promise<Response | void>;
36
- export declare type InvokeRoute<T = Platform> = (route: Route | null, context: RequestContext<T>) => Promise<Response | void>;
33
+ export declare type Router<T = unknown> = (context: RequestContext<T>) => Promise<Response | void>;
34
+ export declare type InvokeRoute<T = unknown> = (route: Route | null, context: RequestContext<T>) => Promise<Response | void>;
37
35
  export {};
@@ -1219,14 +1219,18 @@ function prettyPath(path3) {
1219
1219
  }
1220
1220
 
1221
1221
  // src/vite/utils/config.ts
1222
- var KEY = "__MARKO_SERVE_OPTIONS__";
1223
- function getMarkoRunOptions(viteConfig) {
1224
- return viteConfig[KEY];
1222
+ var PluginConfigKey = "__MARKO_RUN_PLUGIN_CONFIG__";
1223
+ var AdapterConfigKey = "__MARKO_RUN_ADAPTER_CONFIG__";
1224
+ function getConfig(obj, key) {
1225
+ return obj[key];
1225
1226
  }
1226
- function setMarkoRunOptions(viteConfig, options) {
1227
- viteConfig[KEY] = options;
1228
- return viteConfig;
1227
+ function setConfig(obj, key, value) {
1228
+ obj[key] = value;
1229
+ return obj;
1229
1230
  }
1231
+ var getExternalPluginOptions = (viteConfig) => getConfig(viteConfig, PluginConfigKey);
1232
+ var setExternalPluginOptions = (viteConfig, value) => setConfig(viteConfig, PluginConfigKey, value);
1233
+ var getExternalAdapterOptions = (viteConfig) => getConfig(viteConfig, AdapterConfigKey);
1230
1234
 
1231
1235
  // src/vite/plugin.ts
1232
1236
  var import_url = require("url");
@@ -1267,10 +1271,8 @@ function markoServe(opts = {}) {
1267
1271
  "{.tsconfig*,tsconfig*.json}"
1268
1272
  ))) {
1269
1273
  const filepath = import_path2.default.join(typesDir, "routes.d.ts");
1270
- const data = renderRouteTypeInfo(
1271
- routes,
1272
- import_path2.default.relative(typesDir, routesDir)
1273
- );
1274
+ const adapterTypeInfo = (adapter == null ? void 0 : adapter.writeTypeInfo) && await (adapter == null ? void 0 : adapter.writeTypeInfo());
1275
+ const data = renderRouteTypeInfo(routes, import_path2.default.relative(typesDir, routesDir)) + adapterTypeInfo;
1274
1276
  if (data !== typesFile || !import_fs2.default.existsSync(filepath)) {
1275
1277
  await ensureDir(typesDir);
1276
1278
  await import_fs2.default.promises.writeFile(filepath, typesFile = data);
@@ -1335,13 +1337,19 @@ function markoServe(opts = {}) {
1335
1337
  enforce: "pre",
1336
1338
  async config(config2, env) {
1337
1339
  var _a, _b, _c;
1338
- const externalPluginOptions = getMarkoRunOptions(config2);
1340
+ const externalPluginOptions = getExternalPluginOptions(config2);
1339
1341
  if (externalPluginOptions) {
1340
1342
  opts = (0, import_vite.mergeConfig)(opts, externalPluginOptions);
1341
1343
  }
1342
- const adapterOptions = await ((_a = adapter == null ? void 0 : adapter.pluginOptions) == null ? void 0 : _a.call(adapter, opts));
1343
- if (adapterOptions) {
1344
- opts = (0, import_vite.mergeConfig)(opts, adapterOptions);
1344
+ if (adapter) {
1345
+ const externalAdapterConfig = getExternalAdapterOptions(config2);
1346
+ if (externalAdapterConfig && adapter.configure) {
1347
+ adapter.configure(externalAdapterConfig);
1348
+ }
1349
+ const adapterOptions = await ((_a = adapter.pluginOptions) == null ? void 0 : _a.call(adapter, opts));
1350
+ if (adapterOptions) {
1351
+ opts = (0, import_vite.mergeConfig)(opts, adapterOptions);
1352
+ }
1345
1353
  }
1346
1354
  root = (0, import_vite.normalizePath)(config2.root || process.cwd());
1347
1355
  store = opts.store || new import_vite2.FileStore(
@@ -1365,7 +1373,7 @@ function markoServe(opts = {}) {
1365
1373
  if (adapterConfig) {
1366
1374
  pluginConfig = (0, import_vite.mergeConfig)(pluginConfig, adapterConfig);
1367
1375
  }
1368
- return setMarkoRunOptions(pluginConfig, opts);
1376
+ return setExternalPluginOptions(pluginConfig, opts);
1369
1377
  },
1370
1378
  configResolved(config2) {
1371
1379
  resolvedConfig = config2;
@@ -1,4 +1,4 @@
1
1
  export { default } from "./plugin";
2
2
  export { getAvailablePort, isPortInUse, loadEnv, parseEnv, spawnServer, } from "./utils/server";
3
3
  export type { SpawnedServer } from "./utils/server";
4
- export type { Adapter, Options, BuiltRoutes, HttpVerb, ParamInfo, Route, RoutableFile, RoutableFileType, } from "./types";
4
+ export type { Adapter, AdapterConfig, Options, BuiltRoutes, HttpVerb, ParamInfo, Route, RoutableFile, RoutableFileType, } from "./types";
@@ -1182,14 +1182,18 @@ function prettyPath(path3) {
1182
1182
  }
1183
1183
 
1184
1184
  // src/vite/utils/config.ts
1185
- var KEY = "__MARKO_SERVE_OPTIONS__";
1186
- function getMarkoRunOptions(viteConfig) {
1187
- return viteConfig[KEY];
1185
+ var PluginConfigKey = "__MARKO_RUN_PLUGIN_CONFIG__";
1186
+ var AdapterConfigKey = "__MARKO_RUN_ADAPTER_CONFIG__";
1187
+ function getConfig(obj, key) {
1188
+ return obj[key];
1188
1189
  }
1189
- function setMarkoRunOptions(viteConfig, options) {
1190
- viteConfig[KEY] = options;
1191
- return viteConfig;
1190
+ function setConfig(obj, key, value) {
1191
+ obj[key] = value;
1192
+ return obj;
1192
1193
  }
1194
+ var getExternalPluginOptions = (viteConfig) => getConfig(viteConfig, PluginConfigKey);
1195
+ var setExternalPluginOptions = (viteConfig, value) => setConfig(viteConfig, PluginConfigKey, value);
1196
+ var getExternalAdapterOptions = (viteConfig) => getConfig(viteConfig, AdapterConfigKey);
1193
1197
 
1194
1198
  // src/vite/plugin.ts
1195
1199
  import { fileURLToPath } from "url";
@@ -1229,10 +1233,8 @@ function markoServe(opts = {}) {
1229
1233
  "{.tsconfig*,tsconfig*.json}"
1230
1234
  ))) {
1231
1235
  const filepath = path2.join(typesDir, "routes.d.ts");
1232
- const data = renderRouteTypeInfo(
1233
- routes,
1234
- path2.relative(typesDir, routesDir)
1235
- );
1236
+ const adapterTypeInfo = (adapter == null ? void 0 : adapter.writeTypeInfo) && await (adapter == null ? void 0 : adapter.writeTypeInfo());
1237
+ const data = renderRouteTypeInfo(routes, path2.relative(typesDir, routesDir)) + adapterTypeInfo;
1236
1238
  if (data !== typesFile || !fs2.existsSync(filepath)) {
1237
1239
  await ensureDir(typesDir);
1238
1240
  await fs2.promises.writeFile(filepath, typesFile = data);
@@ -1297,13 +1299,19 @@ function markoServe(opts = {}) {
1297
1299
  enforce: "pre",
1298
1300
  async config(config2, env) {
1299
1301
  var _a, _b, _c;
1300
- const externalPluginOptions = getMarkoRunOptions(config2);
1302
+ const externalPluginOptions = getExternalPluginOptions(config2);
1301
1303
  if (externalPluginOptions) {
1302
1304
  opts = mergeConfig(opts, externalPluginOptions);
1303
1305
  }
1304
- const adapterOptions = await ((_a = adapter == null ? void 0 : adapter.pluginOptions) == null ? void 0 : _a.call(adapter, opts));
1305
- if (adapterOptions) {
1306
- opts = mergeConfig(opts, adapterOptions);
1306
+ if (adapter) {
1307
+ const externalAdapterConfig = getExternalAdapterOptions(config2);
1308
+ if (externalAdapterConfig && adapter.configure) {
1309
+ adapter.configure(externalAdapterConfig);
1310
+ }
1311
+ const adapterOptions = await ((_a = adapter.pluginOptions) == null ? void 0 : _a.call(adapter, opts));
1312
+ if (adapterOptions) {
1313
+ opts = mergeConfig(opts, adapterOptions);
1314
+ }
1307
1315
  }
1308
1316
  root = normalizePath(config2.root || process.cwd());
1309
1317
  store = opts.store || new FileStore(
@@ -1327,7 +1335,7 @@ function markoServe(opts = {}) {
1327
1335
  if (adapterConfig) {
1328
1336
  pluginConfig = mergeConfig(pluginConfig, adapterConfig);
1329
1337
  }
1330
- return setMarkoRunOptions(pluginConfig, opts);
1338
+ return setExternalPluginOptions(pluginConfig, opts);
1331
1339
  },
1332
1340
  configResolved(config2) {
1333
1341
  resolvedConfig = config2;
@@ -3,14 +3,19 @@ import type { Options as MarkoViteOptions } from "@marko/vite";
3
3
  import type { ResolvedConfig, UserConfig } from "vite";
4
4
  export type { RoutableFileType, HttpVerb };
5
5
  export declare type StartServer = (port?: number) => Promise<void>;
6
+ export interface AdapterConfig {
7
+ [name: PropertyKey]: any;
8
+ }
6
9
  export interface Adapter {
7
10
  readonly name: string;
11
+ configure?(config: AdapterConfig): void;
8
12
  pluginOptions?(options: Options): Promise<Options> | Options | undefined;
9
13
  viteConfig?(config: UserConfig): Promise<UserConfig> | UserConfig | undefined;
10
14
  getEntryFile?(): Promise<string> | string;
11
15
  startDev?(configFile: string, port: number, envFile?: string): Promise<void> | void;
12
16
  startPreview?(dir: string, entry?: string, port?: number, envFile?: string): Promise<void> | void;
13
17
  buildEnd?(config: ResolvedConfig, routes: Route[], builtEntries: string[], sourceEntries: string[]): Promise<void> | void;
18
+ writeTypeInfo?(): Promise<string> | string;
14
19
  }
15
20
  export interface RouterOptions {
16
21
  trailingSlashes: 'Ignore' | 'RedirectWithout' | 'RedirectWith' | 'RewriteWithout' | 'RewriteWith';
@@ -1,3 +1,5 @@
1
- import type { Options } from '../types';
2
- export declare function getMarkoRunOptions<T extends Record<string, any>>(viteConfig: T): Readonly<Options> | undefined;
3
- export declare function setMarkoRunOptions<T extends Record<string, any>>(viteConfig: T, options: Options): T;
1
+ import type { Options, AdapterConfig } from '../types';
2
+ export declare const getExternalPluginOptions: <T>(viteConfig: T) => Readonly<Options> | undefined;
3
+ export declare const setExternalPluginOptions: <T>(viteConfig: T, value: Options) => T;
4
+ export declare const getExternalAdapterOptions: <T>(viteConfig: T) => Readonly<AdapterConfig> | undefined;
5
+ export declare const setExternalAdapterOptions: <T>(viteConfig: T, value: AdapterConfig) => T;
@@ -1,3 +1,3 @@
1
1
  import type { HttpVerb, Route } from "../types";
2
- export declare function getVerbs(route: Route): ("get" | "post" | "put" | "delete")[];
2
+ export declare function getVerbs(route: Route): ("get" | "delete" | "post" | "put")[];
3
3
  export declare function hasVerb(route: Route, verb: HttpVerb): boolean | import("../types").RoutableFile | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marko/run",
3
- "version": "0.0.1-beta3",
3
+ "version": "0.0.1-beta4",
4
4
  "description": "File-based routing for Marko based on Vite",
5
5
  "keywords": [],
6
6
  "author": "Ryan Turnquist <rturnq@gmail.com>",
@@ -47,19 +47,19 @@
47
47
  "typesVersions": {
48
48
  "*": {
49
49
  "*": [
50
- "./src/runtime/index.ts"
50
+ "./dist/runtime/index.d.ts"
51
51
  ],
52
52
  "router": [
53
- "./src/runtime/router.ts"
53
+ "./dist/runtime/router.d.ts"
54
54
  ],
55
55
  "vite": [
56
- "./src/vite/index.ts"
56
+ "./dist/vite/index.d.ts"
57
57
  ],
58
58
  "adapter/middleware": [
59
- "./src/adapter/middleware.ts"
59
+ "./dist/adapter/middleware.d.ts"
60
60
  ],
61
61
  "adapter": [
62
- "./src/adapter/index.ts"
62
+ "./dist/adapter/index.d.ts"
63
63
  ]
64
64
  }
65
65
  },
@@ -81,7 +81,7 @@
81
81
  "acorn": "^8.8.0",
82
82
  "cross-env": "^7.0.3",
83
83
  "esbuild": "^0.15.7",
84
- "marko": "^5.21.9",
84
+ "marko": "^5.22.4",
85
85
  "mocha": "^10.0.0",
86
86
  "mocha-snap": "^4.3.0",
87
87
  "prettier": "^2.7.1",
@@ -1,3 +0,0 @@
1
- import type { RouteHandler } from "./types";
2
- export declare function compose(...handlers: RouteHandler[]): RouteHandler;
3
- export declare function createHandler(factory: () => Promise<RouteHandler>): RouteHandler;