@znight/http 0.1.4 → 0.2.1

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
@@ -1506,8 +1506,11 @@ A stream-lifetime hook is not provided.
1506
1506
  When CORS is off, routes (other than `@All`) without guards, interceptors, `@Sse`, `@Res()` or
1507
1507
  `@Header` take a fast lane: the plugin decides at boot what the pipeline would do for them, runs a
1508
1508
  synchronous handler without creating a promise and builds the headers of a JSON reply from a
1509
- template. The replies are the same as through the full pipeline (a thrown error, an `HttpResponse`
1510
- or anything unusual continues there, in the same request scope; a promise is awaited first). The
1509
+ template. A plain object or array result is served there, and so is an `HttpResponse` whose body is
1510
+ one, with no cookie and no header other than a lower-case `content-type` (`HttpResponse.ok(body)`,
1511
+ an explicit status, a custom JSON media type), except on `@Head` routes. The replies are the same as
1512
+ through the full pipeline (a thrown error, another `HttpResponse` or anything unusual continues
1513
+ there, in the same request scope; a promise is awaited first). The
1511
1514
  lane runs the `http:request` hooks too: listeners keep it synchronous, and wrappers (such as those of
1512
1515
  `@znight/observability`) see the same event, in the same request scope and async context, as with
1513
1516
  the full pipeline; a reply whose wrappers added response headers or cookies is completed like any
package/dist/index.mjs CHANGED
@@ -3884,7 +3884,8 @@ var Pipeline = class {
3884
3884
  },
3885
3885
  method: definition.method,
3886
3886
  jsonStatus: definition.method === "HEAD" || isBodylessStatus(json) ? void 0 : json,
3887
- emptyStatus: definition.status ?? (post ? 201 : 204)
3887
+ emptyStatus: definition.status ?? (post ? 201 : 204),
3888
+ responses: definition.method !== "HEAD"
3888
3889
  };
3889
3890
  }
3890
3891
  /**
@@ -4021,28 +4022,38 @@ var Pipeline = class {
4021
4022
  x.answer(reply);
4022
4023
  }
4023
4024
  /**
4024
- * The reply of a plain JSON value (see {@link isPlainJson}) or of `undefined`: what
4025
- * `serializeResult` and {@link #complete} produce for it, the headers in their final order.
4026
- * `undefined` when the result or the context needs the general pipeline. Throws like
4027
- * `JSON.stringify`.
4025
+ * The reply of a plain JSON value (see {@link isPlainJson}), of an `HttpResponse` carrying one
4026
+ * (see {@link fastResponseType}) or of `undefined`: what `serializeResult` and {@link #complete}
4027
+ * produce for it, the headers in their final order. `undefined` when the result or the context
4028
+ * needs the general pipeline. Throws like `JSON.stringify`.
4028
4029
  */
4029
4030
  #fastReply(route, ctx, result) {
4030
- const status = result === void 0 ? route.emptyStatus : route.jsonStatus;
4031
+ let payload = result;
4032
+ let status = result === void 0 ? route.emptyStatus : route.jsonStatus;
4033
+ let type = null;
4034
+ if (route.responses && result instanceof HttpResponse) {
4035
+ const own = fastResponseType(result);
4036
+ if (own === void 0) return void 0;
4037
+ type = own;
4038
+ payload = result.body;
4039
+ if (route.plan.definition.status === void 0 && result.status !== void 0) status = isBodylessStatus(result.status) ? void 0 : result.status;
4040
+ if (payload === void 0) return void 0;
4041
+ }
4031
4042
  if (status === void 0 || ctx.hasResponseHeaders || ctx.hasCookies || this.draining) return void 0;
4032
4043
  const requestId = ctx.requestId;
4033
4044
  if (!this.#validIds && !isHeaderValue(requestId)) return void 0;
4034
- if (result === void 0) return this.#templateReply(status, null, void 0, requestId);
4035
- if (!isPlainJson(result)) return void 0;
4036
- const json = JSON.stringify(result);
4037
- if (json === void 0) throw new TypeError(`Cannot serialise a ${typeof result} as a response body`);
4038
- return this.#templateReply(status, utf8(json), json, requestId);
4045
+ if (payload === void 0) return this.#templateReply(status, null, void 0, requestId, null);
4046
+ if (!isPlainJson(payload)) return void 0;
4047
+ const json = JSON.stringify(payload);
4048
+ if (json === void 0) throw new TypeError(`Cannot serialise a ${typeof payload} as a response body`);
4049
+ return this.#templateReply(status, utf8(json), json, requestId, type);
4039
4050
  }
4040
4051
  /**
4041
4052
  * The reply of a JSON body (`text` set) or of no body, its headers copied from the template of
4042
4053
  * the fast lane (see {@link #jsonHeaders}), completed with the length and the request id (one that
4043
- * can be sent).
4054
+ * can be sent). `type` replaces the JSON content type in place (the `HttpResponse`'s own).
4044
4055
  */
4045
- #templateReply(status, body, text, requestId) {
4056
+ #templateReply(status, body, text, requestId, type) {
4046
4057
  if (text === void 0) {
4047
4058
  const headers = { ...this.#emptyHeaders };
4048
4059
  const idHeader = this.#emptyIdHeader;
@@ -4054,6 +4065,7 @@ var Pipeline = class {
4054
4065
  };
4055
4066
  }
4056
4067
  const headers = { ...this.#jsonHeaders };
4068
+ if (type !== null) headers["content-type"] = type;
4057
4069
  headers["content-length"] = String(body.byteLength);
4058
4070
  const idHeader = this.#jsonIdHeader;
4059
4071
  if (idHeader !== void 0) headers[idHeader] = requestId;
@@ -4169,7 +4181,7 @@ var Pipeline = class {
4169
4181
  if (outcome === void 0) return this.#fastUnhandled(x, failed ? hookError : NOT_CALLED);
4170
4182
  if (failed) this.#wrapperFailed(x, hookError);
4171
4183
  else if (x.fast && outcome !== RAW && !hasOwnKeys(x.event.responseHeaders) && !ctx.hasCookies && !this.draining && (this.#validIds || isHeaderValue(x.requestId))) {
4172
- const reply = this.#templateReply(outcome.status, outcome.body, outcome.text, x.requestId);
4184
+ const reply = this.#templateReply(outcome.status, outcome.body, outcome.text, x.requestId, null);
4173
4185
  ctx.commit();
4174
4186
  ctx.releaseBody();
4175
4187
  x.answer(reply);
@@ -4974,6 +4986,22 @@ function isPlainJson(value) {
4974
4986
  } else if (prototype !== Array.prototype || ctor !== Array) return false;
4975
4987
  return typeof value.pipe !== "function";
4976
4988
  }
4989
+ /**
4990
+ * The content type of an `HttpResponse` the fast lane can serve: `null` without headers, its
4991
+ * `content-type` when that is its only header (lower-case name, a value every adapter can send);
4992
+ * `undefined` when it needs the general pipeline (other headers, cookies).
4993
+ */
4994
+ function fastResponseType(response) {
4995
+ if (response.cookies.length > 0) return void 0;
4996
+ const headers = response.headers;
4997
+ let type = null;
4998
+ for (const name of Object.keys(headers)) {
4999
+ const value = headers[name];
5000
+ if (name !== "content-type" || typeof value !== "string" || !isHeaderValue(value)) return void 0;
5001
+ type = value;
5002
+ }
5003
+ return type;
5004
+ }
4977
5005
  const ENCODER = new TextEncoder();
4978
5006
  /** UTF-8 encoding of a text body, like the serialiser (a pooled `Buffer` on Node.js, `TextEncoder` on Bun). */
4979
5007
  const utf8 = isBun() ? (text) => ENCODER.encode(text) : (text) => Buffer$1.from(text, "utf8");