@octanejs/app-core 0.0.53 → 0.0.55
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 +3 -3
- package/src/server/node-http.js +55 -21
- package/src/server/server-entry.js +1 -1
- package/types/node.d.ts +10 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octanejs/app-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.55",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -79,10 +79,10 @@
|
|
|
79
79
|
"esbuild": "^0.28.1"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
|
-
"octane": "^0.3
|
|
82
|
+
"octane": "^0.4.3"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@types/node": "^24.13.3",
|
|
86
|
-
"octane": "0.3
|
|
86
|
+
"octane": "0.4.3"
|
|
87
87
|
}
|
|
88
88
|
}
|
package/src/server/node-http.js
CHANGED
|
@@ -129,11 +129,14 @@ function shouldGzip(request, status, headers, hasBody) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
|
-
* Convert a Node.js IncomingMessage to a Web Request.
|
|
132
|
+
* Convert a Node.js IncomingMessage to a Web Request. Passing its response
|
|
133
|
+
* keeps request.signal active through streaming and cancels it on disconnect.
|
|
134
|
+
* Without a response, cancellation only covers interrupted request uploads.
|
|
133
135
|
* @param {import('node:http').IncomingMessage} nodeRequest
|
|
136
|
+
* @param {import('node:http').ServerResponse} [nodeResponse]
|
|
134
137
|
* @returns {Request}
|
|
135
138
|
*/
|
|
136
|
-
export function nodeRequestToWebRequest(nodeRequest) {
|
|
139
|
+
export function nodeRequestToWebRequest(nodeRequest, nodeResponse) {
|
|
137
140
|
const host = nodeRequest.headers.host || 'localhost';
|
|
138
141
|
const url = new URL(nodeRequest.url || '/', `http://${host}`);
|
|
139
142
|
|
|
@@ -151,29 +154,60 @@ export function nodeRequestToWebRequest(nodeRequest) {
|
|
|
151
154
|
/** @type {RequestInit & { duplex?: 'half' }} */
|
|
152
155
|
const init = { method, headers };
|
|
153
156
|
const abortController = new AbortController();
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
abortRequest();
|
|
161
|
-
} else {
|
|
162
|
-
nodeRequest.once('aborted', abortRequest);
|
|
163
|
-
nodeRequest.once('close', () => {
|
|
164
|
-
if (!nodeRequest.complete) abortRequest();
|
|
165
|
-
});
|
|
157
|
+
const disconnected =
|
|
158
|
+
nodeRequest.aborted ||
|
|
159
|
+
(nodeRequest.destroyed && !nodeRequest.complete) ||
|
|
160
|
+
(nodeResponse?.destroyed && !nodeResponse.writableFinished);
|
|
161
|
+
if (disconnected) {
|
|
162
|
+
abortController.abort(new Error('The client disconnected before the request completed.'));
|
|
166
163
|
}
|
|
167
164
|
init.signal = abortController.signal;
|
|
168
165
|
if (method !== 'GET' && method !== 'HEAD') {
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
166
|
+
// A destroyed upload is already disturbed and cannot become a Request body.
|
|
167
|
+
// Keep its read failure observable when dev middleware converts it late.
|
|
168
|
+
if (disconnected) {
|
|
169
|
+
init.body = new ReadableStream({
|
|
170
|
+
start(controller) {
|
|
171
|
+
controller.error(abortController.signal.reason);
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
} else {
|
|
175
|
+
// node:stream/web's ReadableStream and the DOM lib's are structurally the
|
|
176
|
+
// same at runtime; the lib types disagree on BYOB details.
|
|
177
|
+
init.body = /** @type {ReadableStream} */ (
|
|
178
|
+
/** @type {unknown} */ (Readable.toWeb(nodeRequest))
|
|
179
|
+
);
|
|
180
|
+
}
|
|
174
181
|
init.duplex = 'half';
|
|
175
182
|
}
|
|
176
|
-
|
|
183
|
+
const request = new Request(url, init);
|
|
184
|
+
const cleanup = () => {
|
|
185
|
+
nodeRequest.off('aborted', abortRequest);
|
|
186
|
+
nodeRequest.off('close', onRequestClose);
|
|
187
|
+
nodeResponse?.off('close', onResponseClose);
|
|
188
|
+
nodeResponse?.off('finish', cleanup);
|
|
189
|
+
};
|
|
190
|
+
const abortRequest = () => {
|
|
191
|
+
cleanup();
|
|
192
|
+
abortController.abort(new Error('The client disconnected before the request completed.'));
|
|
193
|
+
};
|
|
194
|
+
const onRequestClose = () => {
|
|
195
|
+
if (!nodeRequest.complete) abortRequest();
|
|
196
|
+
else if (nodeResponse === undefined) cleanup();
|
|
197
|
+
};
|
|
198
|
+
const onResponseClose = () => {
|
|
199
|
+
if (nodeResponse?.writableFinished) cleanup();
|
|
200
|
+
else abortRequest();
|
|
201
|
+
};
|
|
202
|
+
// IncomingMessage closes when its body is consumed. The response owns the
|
|
203
|
+
// remaining lifetime, including async handler work and streaming output.
|
|
204
|
+
if (!disconnected && !nodeResponse?.writableFinished) {
|
|
205
|
+
nodeRequest.once('aborted', abortRequest);
|
|
206
|
+
nodeRequest.once('close', onRequestClose);
|
|
207
|
+
nodeResponse?.once('close', onResponseClose);
|
|
208
|
+
nodeResponse?.once('finish', cleanup);
|
|
209
|
+
}
|
|
210
|
+
return request;
|
|
177
211
|
}
|
|
178
212
|
|
|
179
213
|
/**
|
|
@@ -452,7 +486,7 @@ export function createNodeServer(handler, options = {}) {
|
|
|
452
486
|
) {
|
|
453
487
|
return;
|
|
454
488
|
}
|
|
455
|
-
const response = await handler(nodeRequestToWebRequest(req));
|
|
489
|
+
const response = await handler(nodeRequestToWebRequest(req, res));
|
|
456
490
|
await sendWebResponseForRequest(res, response, req);
|
|
457
491
|
})().catch((error) => {
|
|
458
492
|
console.error('[octane] Request error:', error);
|
|
@@ -471,7 +471,7 @@ export const handler = createHandler(
|
|
|
471
471
|
*/
|
|
472
472
|
export async function nodeHandler(req, res) {
|
|
473
473
|
try {
|
|
474
|
-
const response = await handler(nodeRequestToWebRequest(req));
|
|
474
|
+
const response = await handler(nodeRequestToWebRequest(req, res));
|
|
475
475
|
await sendWebResponse(res, response);
|
|
476
476
|
} catch (error) {
|
|
477
477
|
console.error('[octane] Request error:', error);
|
package/types/node.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse, Server } from 'node:http';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Convert a Node.js IncomingMessage to a Web Request. Pass its response to
|
|
5
|
+
* cancel request.signal on a disconnect until the response finishes, including
|
|
6
|
+
* after the request body has been consumed. Without a response, cancellation
|
|
7
|
+
* only covers interrupted request uploads.
|
|
8
|
+
*/
|
|
9
|
+
export function nodeRequestToWebRequest(
|
|
10
|
+
nodeRequest: IncomingMessage,
|
|
11
|
+
nodeResponse?: ServerResponse,
|
|
12
|
+
): Request;
|
|
5
13
|
|
|
6
14
|
/**
|
|
7
15
|
* Pipe a Web Response to a Node.js ServerResponse, streaming chunk-by-chunk
|