@lovrozagar/flare 0.9.4 → 0.9.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovrozagar/flare",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "Solid meta-framework. Server-driven, NDJSON streaming, renderToStream.",
5
5
  "keywords": [
6
6
  "flare",
@@ -12,8 +12,11 @@ interface NodeReq {
12
12
  }
13
13
 
14
14
  interface NodeRes {
15
+ destroy: () => void;
15
16
  end: (data?: unknown) => void;
17
+ headersSent?: boolean;
16
18
  on: (event: string, fn: () => void) => void;
19
+ socket?: { off: (event: string, fn: () => void) => void; on: (event: string, fn: () => void) => void } | null;
17
20
  writableEnded?: boolean;
18
21
  write: (chunk: unknown) => void;
19
22
  writeHead: (status: number, headers: Record<string, string | string[]>) => void;
@@ -71,7 +74,7 @@ function nodeToWebRequest(req: NodeReq, url: URL): Request {
71
74
  } as RequestInit);
72
75
  }
73
76
 
74
- async function streamResponse(response: Response, res: NodeRes): Promise<void> {
77
+ export async function streamResponse(response: Response, res: NodeRes): Promise<void> {
75
78
  const responseHeaders: Record<string, string | string[]> = {};
76
79
  /* Set-Cookie must be sent as separate headers — Node.js writeHead
77
80
  * accepts string[] for multi-value headers like Set-Cookie */
@@ -88,28 +91,42 @@ async function streamResponse(response: Response, res: NodeRes): Promise<void> {
88
91
  }
89
92
  res.writeHead(response.status, responseHeaders);
90
93
 
91
- if (response.body) {
92
- const reader = response.body.getReader();
93
- const pump = async (): Promise<void> => {
94
+ if (!response.body) {
95
+ res.end();
96
+ return;
97
+ }
98
+ const reader = response.body.getReader();
99
+ /* A client that leaves mid-stream (closed tab, aborted navigation): cancel the body so
100
+ rendering stops, and never write to the closed response. Node and Bun 1.4 close `res`;
101
+ Bun 1.3 only closes the socket, so listen to both. */
102
+ let left = false;
103
+ const leave = () => {
104
+ if (left || res.writableEnded) return;
105
+ left = true;
106
+ reader.cancel().catch(() => {});
107
+ };
108
+ const socket = res.socket;
109
+ res.on("close", leave);
110
+ socket?.on("close", leave);
111
+ try {
112
+ while (true) {
94
113
  const { done, value } = await reader.read();
95
- if (done) {
96
- res.end();
97
- return;
98
- }
114
+ if (done || left) break;
99
115
  res.write(value);
100
- return pump();
101
- };
102
- try {
103
- await pump();
104
- } finally {
105
- reader.cancel().catch(() => {});
106
- if (!res.writableEnded) res.end();
107
116
  }
108
- } else {
109
- res.end();
117
+ } finally {
118
+ /* Keep-alive sockets outlive this response. */
119
+ socket?.off("close", leave);
120
+ if (!left && !res.writableEnded) res.end();
110
121
  }
111
122
  }
112
123
 
124
+ /* Once the status line is out, an error can't become a 500: drop the connection instead. */
125
+ function failResponse(res: NodeRes, next: (err?: unknown) => void, error: unknown): void {
126
+ if (res.headersSent) res.destroy();
127
+ else next(error);
128
+ }
129
+
113
130
  const INLINE_PLACEHOLDER = (i: number) => `<!--flare-inline-${i}-->`;
114
131
  const INLINE_PLACEHOLDER_RE = /<!--flare-inline-(\d+)-->/g;
115
132
 
@@ -238,7 +255,7 @@ export function createDevServerPlugin(entries: ResolvedEntries, _assetsBase: str
238
255
  if (e instanceof Error) {
239
256
  vite.ssrFixStacktrace(e);
240
257
  }
241
- next(e);
258
+ failResponse(res, next, e);
242
259
  }
243
260
  });
244
261
  };
@@ -325,7 +342,7 @@ export function createPreviewServerPlugin(assetsBase: string = "/assets"): ViteP
325
342
  const response = await handler.fetch(webReq);
326
343
  await streamResponse(response, res);
327
344
  } catch (e: unknown) {
328
- next(e);
345
+ failResponse(res, next, e);
329
346
  }
330
347
  });
331
348
  };