@marko/run 0.0.1-beta3 → 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
@@ -1,31 +1,68 @@
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/run` is an application framework for [Marko](https://markojs.com), with these features:
16
+
17
+ - Vite plugin that encapsulates [`@marko/vite`](https://github.com/marko-js/vite)
18
+ - CLI to simplify build modes
16
19
  - File-based routing with layouts and middleware
17
20
  - Efficient routing using a compiled static trie
18
- - Designed with web standards to run anywhere
21
+ - [Designed with web standards](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern/URLPattern) to run anywhere
22
+ - TypeScript support
19
23
 
20
- ## Intallation
24
+ ## Installation
21
25
 
22
- ```
26
+ ```sh
23
27
  npm install @marko/run
24
28
  ```
25
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
+
26
63
  ## Vite Plugin
27
64
 
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.
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.
29
66
 
30
67
  ```ts
31
68
  // vite.config.ts
@@ -34,49 +71,78 @@ import marko from "@marko/run/vite"; // Import the Vite plugin
34
71
 
35
72
  export default defineConfig({
36
73
  plugins: [marko()], // Register the Vite plugin
37
- build: {
38
- sourcemap: true, // Generate sourcemaps for all builds.
39
- emptyOutDir: false, // Avoid server & client deleting files from each other.
40
- }
41
74
  })
42
75
  ```
43
76
 
77
+ ## Adapters
78
+
79
+ *🎗 TODO: provide a quick overview*
80
+
44
81
  ## Runtime
45
82
 
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:
83
+ Generally, when using an adapter, this runtime will be abstracted away.
47
84
 
48
85
  ```ts
49
- import { router, getMatchedRoute } from '@marko/run`;
86
+ import { router, matchRoute, invokeRoute } from '@marko/run/router`;
50
87
  ```
51
88
 
52
89
  ### `router`
90
+
53
91
  ```ts
54
- (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>;
55
100
  ```
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.
57
101
 
58
- ### `getMatchedRoute`
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.
103
+
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.
109
+
110
+ ### `matchRoute`
111
+
59
112
  ```ts
60
- (method: string, url: URL) => {
113
+ interface interface Route {
61
114
  params: Record<string, string>;
62
115
  meta: unknown;
63
- invoke(request: Request): Promise<Response>;
64
- } | null;
116
+ }
117
+
118
+ function matchRoute(method: string, pathname: string) => Route | null;
65
119
  ```
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.
120
+
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.
122
+
123
+ - `params` - a `{ key: value }` collection of any path parameters for the route
124
+ - `meta` - metadata for the route
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
+
70
134
 
71
135
  ## File-based Routing
72
136
 
73
- ## Nested Routing
137
+ ### Nested Routing
74
138
 
75
- *TODO: provide a quick overview*
139
+ *🎗 TODO: provide a quick overview*
76
140
 
77
141
  ### Routes Directory
78
142
 
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:
143
+ The plugin looks for route files in the configured **routes directory**. By default, that’s `./src/routes`, relative to the Vite config file.
144
+
145
+ To change what directory routes are found in:
80
146
 
81
147
  ```ts
82
148
  // vite.config.ts
@@ -86,55 +152,56 @@ import marko from "@marko/run/vite";
86
152
  export default defineConfig({
87
153
  plugins: [marko({
88
154
  routesDir: 'src/pages' // Use `./src/pages` (relative to this file) as the routes directory
89
- })],
90
- //...
155
+ })]
91
156
  })
92
157
  ```
93
158
 
94
159
  ### Routeable Files
95
160
 
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_.
161
+ To allow for colocation of files that shouldn’t be served (like tests, assets, etc.), the router only recognizes certain filenames.
162
+
163
+ The following filenames will be discovered in any directory inside your application’s [routes directory](#routes-directory).
97
164
 
98
165
  #### `+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.
166
+
167
+ 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
168
 
101
169
  #### `+layout.marko`
102
- These files provide a layout component which will wrap all nested layouts and pages.
103
170
 
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>
171
+ These files provide a **layout component**, which will wrap all nested layouts and pages.
172
+
173
+ 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.
174
+
175
+ ```marko
176
+ <main>
177
+ <h1>My Products</h1>
178
+
179
+ ${input.renderBody} // render the page or layout here
180
+ </main>
181
+ ```
118
182
 
119
183
  #### `+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
184
+
185
+ 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? -->
186
+
187
+ 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
188
 
122
189
  <details>
123
190
  <summary>More Info</summary>
124
191
 
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.
192
+ - Valid exports are functions named `get`, `post`, `put`, or `del`.
193
+ - Each export receives a `context` and `next` argument, and should return a WHATWG `Response` either synchronously or asynchronously.
194
+ - The `context` argument contains the WHATWG request object, path parameters, URL, and route metadata.
195
+ - The `next` argument will call the page for get requests where applicable or return a `204` response.
129
196
 
130
- ```ts
197
+ ```js
131
198
  export function post(context, next) {
132
199
  const { request, params, url, meta } = context;
133
- return new Response('updated', { status: 200 });
200
+ return new Response('Successfully updated', { status: 200 });
134
201
  }
135
202
 
136
203
  export function put(context, next) {
137
- return new Response('created', { status: 201 }); // handle the request
204
+ return new Response('Successfully created', { status: 201 }); // handle the request
138
205
  }
139
206
 
140
207
  export function get(context, next) {
@@ -142,29 +209,33 @@ These files establish a route at the current directory path which can handle req
142
209
  }
143
210
 
144
211
  export function del(context, next) {
145
- return new Response('removed', { status: 204 });
212
+ return new Response('Successfully removed', { status: 204 });
146
213
  }
147
214
  ```
148
215
  </details>
149
216
 
150
217
 
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.
218
+ #### `+middleware.*`
219
+
220
+ These files are like layouts, but for handlers. Middleware get called before handlers and let you perform arbitrary work before and after.
221
+
222
+ > **Note**: Unlike handlers, middleware run for all HTTP methods.
153
223
 
154
224
  <details>
155
225
  <summary>More Info</summary>
156
226
 
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.
227
+ Expects a `default` export that receives a `context` and `next` argument, and should return a WHATWG response either synchronously or asynchronously.
228
+
229
+ - The `context` argument contains the WHATWG `Request` object, path parameters, URL, and route metadata.
230
+ - The `next` argument will call the next middleware, handler, or page for the route.
160
231
 
161
232
  ```ts
162
233
  export default async function(context, next) {
163
- const requestName = `${ctx.request.method} ${ctx.url.href}`;
234
+ const requestName = `${ctx.request.method} ${ctx.url.href}`; // TODO: could this be just `ctx.url` with a `toString` that then grabs `.href`?
164
235
  let success = true;
165
236
  console.log(`${requestName} request started`)
166
237
  try {
167
- return await next(); // wait for subsequent middleware/handler/page
238
+ return await next(); // Wait for subsequent middleware/handler/page
168
239
  } catch (err) {
169
240
  success = false;
170
241
  throw err;
@@ -176,30 +247,42 @@ These files are analagous to layouts for handlers. Middlware get called before h
176
247
  </details>
177
248
 
178
249
  #### `+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.
250
+
251
+ 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
252
 
181
253
  ### Special Files
182
254
 
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_:
255
+ 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” -->
256
+
257
+ These special pages are subject to a root layout file (`pages/+layout.marko` in the default configuration).
184
258
 
185
259
  #### `+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.
260
+
261
+ This special page responds to any request where:
262
+
263
+ - The `Accept` request header includes `text/html`
264
+ - *And* no other handler or page rendered the request
265
+
266
+ Responses with this page will have a `404` status code.
187
267
 
188
268
  #### `+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
269
 
270
+ This special page responds to any request where:
271
+
272
+ - The `Accept` request header includes `text/html`
273
+ - *And* an uncaught error occurs while serving the request
274
+
275
+ Responses with this page will have a `500` status code.
191
276
 
192
277
  ### Execution Order
193
278
 
194
- For a matched route the routable files execute in the following order
279
+ For a matched route, the routable files execute in the following order:
280
+
195
281
  1. Middlewares from root-most to leaf-most
196
282
  2. Handler
197
283
  3. Layouts from root-most to leaf-most
198
284
  4. Page
199
285
 
200
- <details>
201
- <summary>Diagram</summary>
202
-
203
286
  ```mermaid
204
287
  sequenceDiagram
205
288
  participant Middleware1
@@ -218,13 +301,12 @@ sequenceDiagram
218
301
  Handler-->>Middleware2: Response
219
302
  Middleware2-->>Middleware1: Response
220
303
  ```
221
- </details>
222
304
 
223
305
  ### Path Structure
224
306
 
225
- Within the _routes directory_ the directory structure will determine the path the route will be served. There are four types of directory names:
307
+ 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
308
 
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.
309
+ 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
310
 
229
311
  Examples:
230
312
  ```
@@ -233,7 +315,7 @@ Within the _routes directory_ the directory structure will determine the path th
233
315
  /projects
234
316
  ```
235
317
 
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.
318
+ 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
319
 
238
320
  Examples:
239
321
  ```
@@ -242,7 +324,7 @@ Within the _routes directory_ the directory structure will determine the path th
242
324
  /index
243
325
  ```
244
326
 
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.
327
+ 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
328
 
247
329
  Examples:
248
330
  ```
@@ -251,9 +333,9 @@ Within the _routes directory_ the directory structure will determine the path th
251
333
  /$
252
334
  ```
253
335
 
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.
336
+ 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
337
 
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.
338
+ 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
339
 
258
340
  Examples:
259
341
  ```
@@ -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;