@znight/http 0.1.4 → 0.2.0
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 +5 -2
- package/dist/index.mjs +42 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/pipeline.ts +55 -12
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.
|
|
1510
|
-
|
|
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})
|
|
4025
|
-
*
|
|
4026
|
-
* `undefined` when the result or the context
|
|
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
|
-
|
|
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 (
|
|
4035
|
-
if (!isPlainJson(
|
|
4036
|
-
const json = JSON.stringify(
|
|
4037
|
-
if (json === void 0) throw new TypeError(`Cannot serialise a ${typeof
|
|
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");
|