@wooksjs/event-http 0.7.24 → 0.7.26

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/dist/index.cjs CHANGED
@@ -198,12 +198,12 @@ async function uncompressBodyStream(encodings, src) {
198
198
 
199
199
  //#endregion
200
200
  //#region packages/event-http/src/compressor/zlib-compressors.ts
201
- const pipeline = node_stream.pipeline;
201
+ const pipeline$1 = node_stream.pipeline;
202
202
  function iterableToReadable(src) {
203
203
  return node_stream.Readable.from(src, { objectMode: false });
204
204
  }
205
205
  function pump(src, transform) {
206
- pipeline(iterableToReadable(src), transform, (err) => {
206
+ pipeline$1(iterableToReadable(src), transform, (err) => {
207
207
  if (err) transform.destroy(err);
208
208
  });
209
209
  return transform;
@@ -428,7 +428,7 @@ const isCompressedSlot = (0, _wooksjs_event_core.cached)((ctx) => {
428
428
  ].includes(p)) return true;
429
429
  return false;
430
430
  });
431
- /** @internal Exported for test pre-seeding via `ctx.set(rawBodySlot, ...)`. */
431
+ /** @internal Exported for test pre-seeding — use {@link seedRawBody} instead. */
432
432
  const rawBodySlot = (0, _wooksjs_event_core.cached)(async (ctx) => {
433
433
  const req = ctx.get(httpKind.keys.req);
434
434
  const encs = ctx.get(contentEncodingsSlot);
@@ -568,6 +568,29 @@ const useRequest = (0, _wooksjs_event_core.defineWook)((ctx) => {
568
568
  setMaxRatio
569
569
  };
570
570
  });
571
+ /**
572
+ * Seeds the raw request body of `ctx`: `useRequest(ctx).rawBody()` resolves to `raw` instead of
573
+ * reading the incoming request stream. To seed what `useBody()` (`@wooksjs/http-body`) parses,
574
+ * use its `seedBody()` — a parent's already parsed body is otherwise read through.
575
+ *
576
+ * Use it for a child event context (`new EventContext({ logger, parent })`) that runs a handler
577
+ * with its own payload: the child still reads everything else (request, headers, auth) through
578
+ * its parent, but never the parent's body. Call it before anything in the child reads the body.
579
+ *
580
+ * @param ctx - The context to seed — usually a child of the current HTTP event
581
+ * @param raw - The body bytes (a string is encoded as UTF-8)
582
+ *
583
+ * @example
584
+ * ```ts
585
+ * const child = new EventContext({ logger: parent.logger, parent })
586
+ * seedRawBody(child, JSON.stringify({ ids: [1, 2] }))
587
+ * await run(child, () => useRequest().rawBody()) // Buffer of '{"ids":[1,2]}'
588
+ * ```
589
+ */
590
+ function seedRawBody(ctx, raw) {
591
+ ctx.setOwn(rawBodySlot, Promise.resolve(buffer.Buffer.isBuffer(raw) ? raw : buffer.Buffer.from(raw)));
592
+ if (ctx.parent) ctx.getOwn(useRequest._slot);
593
+ }
571
594
 
572
595
  //#endregion
573
596
  //#region packages/event-http/src/composables/headers.ts
@@ -1057,46 +1080,41 @@ var HttpResponse = class {
1057
1080
  }
1058
1081
  this._status = defaultStatus[this._req.method] || EHttpStatusCode.OK;
1059
1082
  }
1060
- sendStream(stream$1, method) {
1083
+ sendStream(source, method) {
1061
1084
  this.autoStatus(true);
1062
1085
  this._res.writeHead(this._status, this._headers);
1063
- this._req.once("close", () => {
1064
- stream$1.destroy();
1065
- });
1066
1086
  if (method === "HEAD") {
1067
- stream$1.destroy();
1087
+ source.destroy();
1068
1088
  this._res.end();
1069
1089
  return Promise.resolve();
1070
1090
  }
1071
- return new Promise((resolve, reject) => {
1072
- stream$1.on("error", (e) => {
1073
- this._logger.error("Stream error", e);
1074
- stream$1.destroy();
1075
- this._res.end();
1076
- reject(e);
1077
- });
1078
- stream$1.on("close", () => {
1079
- stream$1.destroy();
1091
+ return this.pipeToResponse(source, "Stream error");
1092
+ }
1093
+ /**
1094
+ * Pipes `source` into the response. `pipeline` destroys the source when the RESPONSE
1095
+ * closes first (the client disconnected — the request's own 'close' fires as soon as its
1096
+ * body is read, so it can't signal that) and destroys the response when the source fails,
1097
+ * so a truncated body never looks complete. A source error is logged, never rethrown:
1098
+ * headers are already sent, and a sync handler's send() is not awaited (an unhandled
1099
+ * rejection would take the process down).
1100
+ */
1101
+ pipeToResponse(source, errorLabel) {
1102
+ return new Promise((resolve) => {
1103
+ (0, stream.pipeline)(source, this._res, (error) => {
1104
+ if (error && error.code !== "ERR_STREAM_PREMATURE_CLOSE") this._logger.error(errorLabel, error);
1080
1105
  resolve();
1081
1106
  });
1082
- stream$1.pipe(this._res);
1083
1107
  });
1084
1108
  }
1085
1109
  async sendFetchResponse(fetchResponse, method) {
1086
1110
  this._status = this._status || fetchResponse.status;
1087
1111
  this.mergeFetchResponseHeaders(fetchResponse);
1088
1112
  this._res.writeHead(this._status, this._headers);
1089
- if (method === "HEAD") {
1113
+ if (method === "HEAD" || !fetchResponse.body) {
1090
1114
  this._res.end();
1091
1115
  return;
1092
1116
  }
1093
- const fetchBody = fetchResponse.body;
1094
- if (fetchBody) try {
1095
- for await (const chunk of fetchBody) this._res.write(chunk);
1096
- } catch (error) {
1097
- this._logger.error("Error streaming fetch response body", error);
1098
- }
1099
- if (!this._res.writableEnded) this._res.end();
1117
+ return this.pipeToResponse(stream.Readable.fromWeb(fetchResponse.body), "Error streaming fetch response body");
1100
1118
  }
1101
1119
  sendRegular(method) {
1102
1120
  const renderedBody = this.renderBody();
@@ -1479,7 +1497,7 @@ function escapeHtml(value) {
1479
1497
  //#endregion
1480
1498
  //#region packages/event-http/src/response/wooks-http-response.ts
1481
1499
  let framework = {
1482
- version: "0.7.24",
1500
+ version: "0.7.26",
1483
1501
  poweredBy: "wooksjs",
1484
1502
  link: "https://wooks.moost.org/",
1485
1503
  image: "https://wooks.moost.org/wooks-full-logo.svg"
@@ -1889,7 +1907,7 @@ var WooksHttp = class extends wooks.WooksAdapterBase {
1889
1907
  requestLimits
1890
1908
  }, async () => {
1891
1909
  const ctx = (0, _wooksjs_event_core.current)();
1892
- if (bodyBuffer) ctx.set(rawBodySlot, Promise.resolve(bodyBuffer));
1910
+ if (bodyBuffer) seedRawBody(ctx, bodyBuffer);
1893
1911
  try {
1894
1912
  const handlers = this.wooks.lookupHandlers(method, pathname, ctx);
1895
1913
  if (handlers) {
@@ -2093,10 +2111,7 @@ function prepareTestHttpContext(options) {
2093
2111
  requestLimits: options.requestLimits
2094
2112
  });
2095
2113
  if (options.params) ctx.set(_wooksjs_event_core.routeParamsKey, options.params);
2096
- if (options.rawBody !== void 0) {
2097
- const buf = buffer.Buffer.isBuffer(options.rawBody) ? options.rawBody : buffer.Buffer.from(options.rawBody);
2098
- ctx.set(rawBodySlot, Promise.resolve(buf));
2099
- }
2114
+ if (options.rawBody !== void 0) seedRawBody(ctx, options.rawBody);
2100
2115
  return (cb) => (0, _wooksjs_event_core.run)(ctx, cb);
2101
2116
  }
2102
2117
 
@@ -2118,6 +2133,7 @@ exports.rawBodySlot = rawBodySlot;
2118
2133
  exports.recordToWebHeaders = recordToWebHeaders;
2119
2134
  exports.renderCacheControl = renderCacheControl;
2120
2135
  exports.securityHeaders = securityHeaders;
2136
+ exports.seedRawBody = seedRawBody;
2121
2137
  exports.useAccept = useAccept;
2122
2138
  exports.useAuthorization = useAuthorization;
2123
2139
  exports.useCookies = useCookies;
package/dist/index.d.ts CHANGED
@@ -69,7 +69,7 @@ declare const DEFAULT_LIMITS: {
69
69
  readonly maxRatio: 100;
70
70
  readonly readTimeoutMs: 10000;
71
71
  };
72
- /** @internal Exported for test pre-seeding via `ctx.set(rawBodySlot, ...)`. */
72
+ /** @internal Exported for test pre-seeding — use {@link seedRawBody} instead. */
73
73
  declare const rawBodySlot: _wooksjs_event_core.Cached<Promise<Buffer<ArrayBufferLike>>>;
74
74
  /**
75
75
  * Provides access to the incoming HTTP request (method, url, headers, body, IP).
@@ -103,6 +103,26 @@ declare const useRequest: _wooksjs_event_core.WookComposable<{
103
103
  getMaxRatio: () => number;
104
104
  setMaxRatio: (limit: number) => void;
105
105
  }>;
106
+ /**
107
+ * Seeds the raw request body of `ctx`: `useRequest(ctx).rawBody()` resolves to `raw` instead of
108
+ * reading the incoming request stream. To seed what `useBody()` (`@wooksjs/http-body`) parses,
109
+ * use its `seedBody()` — a parent's already parsed body is otherwise read through.
110
+ *
111
+ * Use it for a child event context (`new EventContext({ logger, parent })`) that runs a handler
112
+ * with its own payload: the child still reads everything else (request, headers, auth) through
113
+ * its parent, but never the parent's body. Call it before anything in the child reads the body.
114
+ *
115
+ * @param ctx - The context to seed — usually a child of the current HTTP event
116
+ * @param raw - The body bytes (a string is encoded as UTF-8)
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const child = new EventContext({ logger: parent.logger, parent })
121
+ * seedRawBody(child, JSON.stringify({ ids: [1, 2] }))
122
+ * await run(child, () => useRequest().rawBody()) // Buffer of '{"ids":[1,2]}'
123
+ * ```
124
+ */
125
+ declare function seedRawBody(ctx: EventContext, raw: Buffer | string): void;
106
126
 
107
127
  /** Maps numeric HTTP status codes to their human-readable descriptions. */
108
128
  declare const httpStatusCodes: {
@@ -450,6 +470,15 @@ declare class HttpResponse {
450
470
  private finalizeCookies;
451
471
  private autoStatus;
452
472
  private sendStream;
473
+ /**
474
+ * Pipes `source` into the response. `pipeline` destroys the source when the RESPONSE
475
+ * closes first (the client disconnected — the request's own 'close' fires as soon as its
476
+ * body is read, so it can't signal that) and destroys the response when the source fails,
477
+ * so a truncated body never looks complete. A source error is logged, never rethrown:
478
+ * headers are already sent, and a sync handler's send() is not awaited (an unhandled
479
+ * rejection would take the process down).
480
+ */
481
+ private pipeToResponse;
453
482
  private sendFetchResponse;
454
483
  private sendRegular;
455
484
  }
@@ -764,5 +793,5 @@ interface TTestHttpContext {
764
793
  */
765
794
  declare function prepareTestHttpContext(options: TTestHttpContext): <T>(cb: (...a: any[]) => T) => T;
766
795
 
767
- export { DEFAULT_FORWARD_HEADERS, DEFAULT_LIMITS, EHttpStatusCode, HttpError, HttpResponse, WooksHttp, WooksHttpResponse, WooksURLSearchParams, createHttpApp, createHttpContext, httpKind, httpStatusCodes, prepareTestHttpContext, rawBodySlot, recordToWebHeaders, renderCacheControl, securityHeaders, useAccept, useAuthorization, useCookies, useHeaders, useHttpContext, useRequest, useResponse, useUrlParams };
796
+ export { DEFAULT_FORWARD_HEADERS, DEFAULT_LIMITS, EHttpStatusCode, HttpError, HttpResponse, WooksHttp, WooksHttpResponse, WooksURLSearchParams, createHttpApp, createHttpContext, httpKind, httpStatusCodes, prepareTestHttpContext, rawBodySlot, recordToWebHeaders, renderCacheControl, securityHeaders, seedRawBody, useAccept, useAuthorization, useCookies, useHeaders, useHttpContext, useRequest, useResponse, useUrlParams };
768
797
  export type { KnownAcceptType, KnownAuthType, SecurityHeadersOptions, TCacheControl, TCookieAttributes, TCookieAttributesInput, THttpEventData, TRequestLimits, TSetCookieData, TTestHttpContext, TWooksErrorBody, TWooksErrorBodyExt, TWooksHttpOptions };
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import { promisify } from "node:util";
5
5
  import { createBrotliCompress, createBrotliDecompress, createDeflate, createGunzip, createGzip, createInflate } from "node:zlib";
6
6
  import { URLSearchParams } from "url";
7
7
  import http, { IncomingMessage, ServerResponse } from "http";
8
- import { Duplex, Readable as Readable$1 } from "stream";
8
+ import { Duplex, Readable as Readable$1, pipeline as pipeline$1 } from "stream";
9
9
  import { WooksAdapterBase } from "wooks";
10
10
  import { Socket } from "net";
11
11
 
@@ -169,12 +169,12 @@ async function uncompressBodyStream(encodings, src) {
169
169
 
170
170
  //#endregion
171
171
  //#region packages/event-http/src/compressor/zlib-compressors.ts
172
- const pipeline$1 = pipeline;
172
+ const pipeline$2 = pipeline;
173
173
  function iterableToReadable(src) {
174
174
  return Readable.from(src, { objectMode: false });
175
175
  }
176
176
  function pump(src, transform) {
177
- pipeline$1(iterableToReadable(src), transform, (err) => {
177
+ pipeline$2(iterableToReadable(src), transform, (err) => {
178
178
  if (err) transform.destroy(err);
179
179
  });
180
180
  return transform;
@@ -399,7 +399,7 @@ const isCompressedSlot = cached((ctx) => {
399
399
  ].includes(p)) return true;
400
400
  return false;
401
401
  });
402
- /** @internal Exported for test pre-seeding via `ctx.set(rawBodySlot, ...)`. */
402
+ /** @internal Exported for test pre-seeding — use {@link seedRawBody} instead. */
403
403
  const rawBodySlot = cached(async (ctx) => {
404
404
  const req = ctx.get(httpKind.keys.req);
405
405
  const encs = ctx.get(contentEncodingsSlot);
@@ -539,6 +539,29 @@ const useRequest = defineWook((ctx) => {
539
539
  setMaxRatio
540
540
  };
541
541
  });
542
+ /**
543
+ * Seeds the raw request body of `ctx`: `useRequest(ctx).rawBody()` resolves to `raw` instead of
544
+ * reading the incoming request stream. To seed what `useBody()` (`@wooksjs/http-body`) parses,
545
+ * use its `seedBody()` — a parent's already parsed body is otherwise read through.
546
+ *
547
+ * Use it for a child event context (`new EventContext({ logger, parent })`) that runs a handler
548
+ * with its own payload: the child still reads everything else (request, headers, auth) through
549
+ * its parent, but never the parent's body. Call it before anything in the child reads the body.
550
+ *
551
+ * @param ctx - The context to seed — usually a child of the current HTTP event
552
+ * @param raw - The body bytes (a string is encoded as UTF-8)
553
+ *
554
+ * @example
555
+ * ```ts
556
+ * const child = new EventContext({ logger: parent.logger, parent })
557
+ * seedRawBody(child, JSON.stringify({ ids: [1, 2] }))
558
+ * await run(child, () => useRequest().rawBody()) // Buffer of '{"ids":[1,2]}'
559
+ * ```
560
+ */
561
+ function seedRawBody(ctx, raw) {
562
+ ctx.setOwn(rawBodySlot, Promise.resolve(Buffer$1.isBuffer(raw) ? raw : Buffer$1.from(raw)));
563
+ if (ctx.parent) ctx.getOwn(useRequest._slot);
564
+ }
542
565
 
543
566
  //#endregion
544
567
  //#region packages/event-http/src/composables/headers.ts
@@ -1028,46 +1051,41 @@ var HttpResponse = class {
1028
1051
  }
1029
1052
  this._status = defaultStatus[this._req.method] || EHttpStatusCode.OK;
1030
1053
  }
1031
- sendStream(stream, method) {
1054
+ sendStream(source, method) {
1032
1055
  this.autoStatus(true);
1033
1056
  this._res.writeHead(this._status, this._headers);
1034
- this._req.once("close", () => {
1035
- stream.destroy();
1036
- });
1037
1057
  if (method === "HEAD") {
1038
- stream.destroy();
1058
+ source.destroy();
1039
1059
  this._res.end();
1040
1060
  return Promise.resolve();
1041
1061
  }
1042
- return new Promise((resolve, reject) => {
1043
- stream.on("error", (e) => {
1044
- this._logger.error("Stream error", e);
1045
- stream.destroy();
1046
- this._res.end();
1047
- reject(e);
1048
- });
1049
- stream.on("close", () => {
1050
- stream.destroy();
1062
+ return this.pipeToResponse(source, "Stream error");
1063
+ }
1064
+ /**
1065
+ * Pipes `source` into the response. `pipeline` destroys the source when the RESPONSE
1066
+ * closes first (the client disconnected — the request's own 'close' fires as soon as its
1067
+ * body is read, so it can't signal that) and destroys the response when the source fails,
1068
+ * so a truncated body never looks complete. A source error is logged, never rethrown:
1069
+ * headers are already sent, and a sync handler's send() is not awaited (an unhandled
1070
+ * rejection would take the process down).
1071
+ */
1072
+ pipeToResponse(source, errorLabel) {
1073
+ return new Promise((resolve) => {
1074
+ pipeline$1(source, this._res, (error) => {
1075
+ if (error && error.code !== "ERR_STREAM_PREMATURE_CLOSE") this._logger.error(errorLabel, error);
1051
1076
  resolve();
1052
1077
  });
1053
- stream.pipe(this._res);
1054
1078
  });
1055
1079
  }
1056
1080
  async sendFetchResponse(fetchResponse, method) {
1057
1081
  this._status = this._status || fetchResponse.status;
1058
1082
  this.mergeFetchResponseHeaders(fetchResponse);
1059
1083
  this._res.writeHead(this._status, this._headers);
1060
- if (method === "HEAD") {
1084
+ if (method === "HEAD" || !fetchResponse.body) {
1061
1085
  this._res.end();
1062
1086
  return;
1063
1087
  }
1064
- const fetchBody = fetchResponse.body;
1065
- if (fetchBody) try {
1066
- for await (const chunk of fetchBody) this._res.write(chunk);
1067
- } catch (error) {
1068
- this._logger.error("Error streaming fetch response body", error);
1069
- }
1070
- if (!this._res.writableEnded) this._res.end();
1088
+ return this.pipeToResponse(Readable$1.fromWeb(fetchResponse.body), "Error streaming fetch response body");
1071
1089
  }
1072
1090
  sendRegular(method) {
1073
1091
  const renderedBody = this.renderBody();
@@ -1450,7 +1468,7 @@ function escapeHtml(value) {
1450
1468
  //#endregion
1451
1469
  //#region packages/event-http/src/response/wooks-http-response.ts
1452
1470
  let framework = {
1453
- version: "0.7.24",
1471
+ version: "0.7.26",
1454
1472
  poweredBy: "wooksjs",
1455
1473
  link: "https://wooks.moost.org/",
1456
1474
  image: "https://wooks.moost.org/wooks-full-logo.svg"
@@ -1860,7 +1878,7 @@ var WooksHttp = class extends WooksAdapterBase {
1860
1878
  requestLimits
1861
1879
  }, async () => {
1862
1880
  const ctx = current();
1863
- if (bodyBuffer) ctx.set(rawBodySlot, Promise.resolve(bodyBuffer));
1881
+ if (bodyBuffer) seedRawBody(ctx, bodyBuffer);
1864
1882
  try {
1865
1883
  const handlers = this.wooks.lookupHandlers(method, pathname, ctx);
1866
1884
  if (handlers) {
@@ -2064,12 +2082,9 @@ function prepareTestHttpContext(options) {
2064
2082
  requestLimits: options.requestLimits
2065
2083
  });
2066
2084
  if (options.params) ctx.set(routeParamsKey, options.params);
2067
- if (options.rawBody !== void 0) {
2068
- const buf = Buffer$1.isBuffer(options.rawBody) ? options.rawBody : Buffer$1.from(options.rawBody);
2069
- ctx.set(rawBodySlot, Promise.resolve(buf));
2070
- }
2085
+ if (options.rawBody !== void 0) seedRawBody(ctx, options.rawBody);
2071
2086
  return (cb) => run(ctx, cb);
2072
2087
  }
2073
2088
 
2074
2089
  //#endregion
2075
- export { DEFAULT_FORWARD_HEADERS, DEFAULT_LIMITS, EHttpStatusCode, HttpError, HttpResponse, WooksHttp, WooksHttpResponse, WooksURLSearchParams, createHttpApp, createHttpContext, httpKind, httpStatusCodes, prepareTestHttpContext, rawBodySlot, recordToWebHeaders, renderCacheControl, securityHeaders, useAccept, useAuthorization, useCookies, useHeaders, useHttpContext, useLogger, useRequest, useResponse, useRouteParams, useUrlParams };
2090
+ export { DEFAULT_FORWARD_HEADERS, DEFAULT_LIMITS, EHttpStatusCode, HttpError, HttpResponse, WooksHttp, WooksHttpResponse, WooksURLSearchParams, createHttpApp, createHttpContext, httpKind, httpStatusCodes, prepareTestHttpContext, rawBodySlot, recordToWebHeaders, renderCacheControl, securityHeaders, seedRawBody, useAccept, useAuthorization, useCookies, useHeaders, useHttpContext, useLogger, useRequest, useResponse, useRouteParams, useUrlParams };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wooksjs/event-http",
3
- "version": "0.7.24",
3
+ "version": "0.7.26",
4
4
  "description": "@wooksjs/event-http",
5
5
  "keywords": [
6
6
  "api",
@@ -42,14 +42,14 @@
42
42
  "devDependencies": {
43
43
  "typescript": "^5.9.3",
44
44
  "vitest": "^3.2.7",
45
- "@wooksjs/event-core": "^0.7.24",
46
- "wooks": "^0.7.24"
45
+ "@wooksjs/event-core": "^0.7.26",
46
+ "wooks": "^0.7.26"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@prostojs/logger": "^0.4.3",
50
50
  "@prostojs/router": "^0.3.4",
51
- "@wooksjs/event-core": "^0.7.24",
52
- "wooks": "^0.7.24"
51
+ "@wooksjs/event-core": "^0.7.26",
52
+ "wooks": "^0.7.26"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "rolldown -c ../../rolldown.config.mjs"