@web-widget/node 2.3.1 → 3.0.0-beta.2

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.
Files changed (2) hide show
  1. package/dist/node.js +120 -56
  2. package/package.json +2 -2
package/dist/node.js CHANGED
@@ -25,10 +25,7 @@ import {
25
25
  import primitives2 from "@edge-runtime/primitives";
26
26
 
27
27
  // src/incoming-request.ts
28
- import {
29
- buildToHeaders,
30
- buildToReadableStream
31
- } from "@edge-runtime/node-utils";
28
+ import { buildToHeaders } from "@edge-runtime/node-utils";
32
29
  function isHttp2PseudoHeader(name) {
33
30
  return name.startsWith(":");
34
31
  }
@@ -76,6 +73,32 @@ function computeRequestOrigin(headers, defaultOrigin) {
76
73
  }
77
74
  return `${protocol}://${authority}`;
78
75
  }
76
+ function buildToReadableStream(dependencies2) {
77
+ const { ReadableStream: ReadableStreamCtor, Uint8Array: Uint8ArrayCtor } = dependencies2;
78
+ return function toReadableStream(stream) {
79
+ return new ReadableStreamCtor({
80
+ start(controller) {
81
+ const onData = (chunk) => {
82
+ controller.enqueue(new Uint8ArrayCtor(chunk));
83
+ if (controller.desiredSize !== null && controller.desiredSize <= 0) {
84
+ stream.pause();
85
+ }
86
+ };
87
+ const onEnd = () => controller.close();
88
+ const onError = (err) => controller.error(err);
89
+ stream.on("data", onData);
90
+ stream.on("end", onEnd);
91
+ stream.on("error", onError);
92
+ },
93
+ pull() {
94
+ stream.resume();
95
+ },
96
+ cancel() {
97
+ stream.destroy();
98
+ }
99
+ });
100
+ };
101
+ }
79
102
  function buildToRequestWithHttp2Compat(dependencies2) {
80
103
  const toHeaders = buildToHeaders(dependencies2);
81
104
  const toReadableStream = buildToReadableStream(dependencies2);
@@ -133,36 +156,40 @@ function addDuplexToInit(init) {
133
156
  }
134
157
  return init;
135
158
  }
159
+ function isHttp2ServerResponse(serverResponse) {
160
+ if ("stream" in serverResponse && serverResponse.stream) return true;
161
+ const httpVersionMajor = serverResponse.httpVersionMajor;
162
+ return Number(httpVersionMajor) >= 2;
163
+ }
164
+ function setHttp1StatusMessage(serverResponse, statusMessage) {
165
+ if (!isHttp2ServerResponse(serverResponse) && statusMessage) {
166
+ serverResponse.statusMessage = statusMessage;
167
+ }
168
+ }
136
169
  function toMiddleware(webHandler, options) {
137
170
  const toRequest = buildToRequestWithHttp2Compat(dependencies);
138
171
  const toFetchEvent = buildToFetchEvent(dependencies);
139
172
  return async function middleware(incomingMessage, serverResponse, next) {
173
+ const request = toRequest(incomingMessage, options);
140
174
  try {
141
- const request = toRequest(incomingMessage, options);
142
175
  const env = process.env;
143
176
  const event = toFetchEvent(request);
144
177
  const webResponse = await webHandler(request, env, event);
145
178
  await toServerResponse(webResponse, serverResponse);
146
- await next();
147
- } catch (error) {
148
- let responded = false;
149
- if (checkWritable(serverResponse) && !serverResponse.headersSent) {
150
- try {
151
- serverResponse.statusCode = 500;
152
- serverResponse.statusMessage = "Internal Server Error";
153
- serverResponse.setHeader("content-type", "text/plain; charset=utf-8");
154
- serverResponse.end(
155
- error instanceof Error ? error.message : "Internal Server Error"
156
- );
157
- responded = true;
158
- } catch {
159
- }
179
+ if (!serverResponse.headersSent && !serverResponse.writableEnded) {
180
+ await next();
160
181
  }
161
- if (responded) {
182
+ } catch (error) {
183
+ if (sendServerError(
184
+ serverResponse,
185
+ error instanceof Error ? error.message : void 0
186
+ )) {
162
187
  next();
163
188
  } else {
164
189
  next(error);
165
190
  }
191
+ } finally {
192
+ cancelRequestBody(request);
166
193
  }
167
194
  };
168
195
  }
@@ -174,14 +201,19 @@ async function toServerResponse(webResponse, serverResponse) {
174
201
  serverResponse.end();
175
202
  return;
176
203
  }
204
+ const isHttp2 = isHttp2ServerResponse(serverResponse);
205
+ const headers = toOutgoingHeaders(webResponse.headers);
206
+ const status = webResponse.status;
177
207
  if (!serverResponse.headersSent) {
178
- mergeIntoServerResponse(
179
- toOutgoingHeaders(webResponse.headers),
180
- serverResponse
181
- );
208
+ mergeIntoServerResponse(headers, serverResponse);
209
+ serverResponse.statusCode = status;
210
+ if (!isHttp2) {
211
+ setHttp1StatusMessage(serverResponse, webResponse.statusText);
212
+ }
213
+ } else if (!isHttp2) {
214
+ serverResponse.statusCode = status;
215
+ setHttp1StatusMessage(serverResponse, webResponse.statusText);
182
216
  }
183
- serverResponse.statusCode = webResponse.status;
184
- serverResponse.statusMessage = webResponse.statusText;
185
217
  if (!webResponse.body) {
186
218
  serverResponse.end();
187
219
  return;
@@ -189,31 +221,79 @@ async function toServerResponse(webResponse, serverResponse) {
189
221
  await writeReadableStreamToWritable(webResponse.body, serverResponse);
190
222
  }
191
223
  async function writeReadableStreamToWritable(stream, writable) {
192
- let reader = stream.getReader();
193
- let flushable = writable;
224
+ if (stream.locked) {
225
+ throw new TypeError("ReadableStream is locked.");
226
+ }
227
+ const reader = stream.getReader();
228
+ const flushable = writable;
229
+ const onAbort = () => {
230
+ reader.cancel().catch(() => {
231
+ });
232
+ };
233
+ writable.once("close", onAbort);
234
+ writable.once("error", onAbort);
194
235
  try {
195
- while (true) {
196
- let { done, value } = await reader.read();
236
+ while (!writable.destroyed) {
237
+ const { done, value } = await reader.read();
197
238
  if (done) {
198
- writable.end();
239
+ if (!writable.destroyed) writable.end();
199
240
  break;
200
241
  }
201
- writable.write(value);
242
+ if (writable.write(value) === false) {
243
+ await new Promise((resolve) => {
244
+ if (writable.destroyed) {
245
+ resolve();
246
+ } else {
247
+ writable.once("drain", resolve);
248
+ }
249
+ });
250
+ }
202
251
  if (typeof flushable.flush === "function") {
203
252
  flushable.flush();
204
253
  }
205
254
  }
206
255
  } catch (error) {
207
- writable.destroy(error);
208
- throw error;
256
+ if (!writable.destroyed) {
257
+ writable.destroy(error instanceof Error ? error : void 0);
258
+ throw error;
259
+ }
260
+ } finally {
261
+ writable.removeListener("close", onAbort);
262
+ writable.removeListener("error", onAbort);
263
+ try {
264
+ reader.releaseLock();
265
+ } catch {
266
+ }
209
267
  }
210
268
  }
211
269
  function checkWritable(serverResponse) {
212
270
  if (serverResponse.writableEnded || serverResponse.finished) return false;
271
+ if ("stream" in serverResponse && serverResponse.stream) return true;
213
272
  const socket = serverResponse.socket;
214
273
  if (!socket) return true;
215
274
  return socket.writable;
216
275
  }
276
+ function sendServerError(serverResponse, message) {
277
+ if (!checkWritable(serverResponse) || serverResponse.headersSent) {
278
+ return false;
279
+ }
280
+ try {
281
+ serverResponse.statusCode = 500;
282
+ setHttp1StatusMessage(serverResponse, "Internal Server Error");
283
+ serverResponse.setHeader("content-type", "text/plain; charset=utf-8");
284
+ serverResponse.end(message || "Internal Server Error");
285
+ return true;
286
+ } catch {
287
+ return false;
288
+ }
289
+ }
290
+ function cancelRequestBody(request) {
291
+ const body = request.body;
292
+ if (body && !body.locked) {
293
+ body.cancel().catch(() => {
294
+ });
295
+ }
296
+ }
217
297
  function buildToNodeHandler(dependencies2, options) {
218
298
  const toRequest = buildToRequestWithHttp2Compat(dependencies2);
219
299
  const toFetchEvent = buildToFetchEvent(dependencies2);
@@ -223,30 +303,14 @@ function buildToNodeHandler(dependencies2, options) {
223
303
  const env = process.env;
224
304
  const event = toFetchEvent(request);
225
305
  const maybePromise = webHandler(request, env, event);
306
+ const respond = (response) => toServerResponse(response, serverResponse).finally(
307
+ () => cancelRequestBody(request)
308
+ );
309
+ const fail = () => sendServerError(serverResponse);
226
310
  if (maybePromise instanceof Promise) {
227
- maybePromise.then((response) => toServerResponse(response, serverResponse)).catch(() => {
228
- if (checkWritable(serverResponse) && !serverResponse.headersSent) {
229
- serverResponse.statusCode = 500;
230
- serverResponse.statusMessage = "Internal Server Error";
231
- serverResponse.setHeader(
232
- "content-type",
233
- "text/plain; charset=utf-8"
234
- );
235
- serverResponse.end("Internal Server Error");
236
- }
237
- });
311
+ maybePromise.then(respond).catch(fail);
238
312
  } else {
239
- toServerResponse(maybePromise, serverResponse).catch(() => {
240
- if (checkWritable(serverResponse) && !serverResponse.headersSent) {
241
- serverResponse.statusCode = 500;
242
- serverResponse.statusMessage = "Internal Server Error";
243
- serverResponse.setHeader(
244
- "content-type",
245
- "text/plain; charset=utf-8"
246
- );
247
- serverResponse.end("Internal Server Error");
248
- }
249
- });
313
+ respond(maybePromise).catch(fail);
250
314
  }
251
315
  };
252
316
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@web-widget/node",
3
3
  "description": "Node.js adapter",
4
- "version": "2.3.1",
4
+ "version": "3.0.0-beta.2",
5
5
  "homepage": "https://github.com/web-widget/web-widget#readme",
6
6
  "repository": {
7
7
  "type": "git",
@@ -19,7 +19,7 @@
19
19
  "devDependencies": {
20
20
  "@types/node": "^22.2.0",
21
21
  "tsup": "^8.3.4",
22
- "typescript": "^5.5.4"
22
+ "typescript": "^5.9.3"
23
23
  },
24
24
  "engines": {
25
25
  "node": ">=18"