@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 +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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@znight/http",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Znight HTTP plugin: controllers, routing, guards, interceptors, validation with Fastify, Express or Hono",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ugho STEPHAN",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"access": "public"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@znight/core": "0.1
|
|
82
|
+
"@znight/core": "0.2.1"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
85
|
"@hono/node-server": "^2.1.5",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"fastify": "^5.12.7",
|
|
88
88
|
"hono": "^4.13.14",
|
|
89
89
|
"light-my-request": "^6.8.0",
|
|
90
|
-
"@znight/compiler": "^0.1
|
|
90
|
+
"@znight/compiler": "^0.2.1"
|
|
91
91
|
},
|
|
92
92
|
"peerDependenciesMeta": {
|
|
93
93
|
"@hono/node-server": {
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"valibot": "1.5.0",
|
|
119
119
|
"zod": "4.6.7",
|
|
120
120
|
"light-my-request": "6.8.0",
|
|
121
|
-
"@znight/compiler": "0.1
|
|
121
|
+
"@znight/compiler": "0.2.1"
|
|
122
122
|
},
|
|
123
123
|
"znight": {
|
|
124
124
|
"apiVersion": 1,
|
package/src/pipeline.ts
CHANGED
|
@@ -334,6 +334,7 @@ export class Pipeline {
|
|
|
334
334
|
// a HEAD reply has no body: JSON results of an explicit `@Head` route take the general pipeline
|
|
335
335
|
jsonStatus: definition.method === 'HEAD' || isBodylessStatus(json) ? undefined : json,
|
|
336
336
|
emptyStatus: definition.status ?? (post ? 201 : 204),
|
|
337
|
+
responses: definition.method !== 'HEAD',
|
|
337
338
|
}
|
|
338
339
|
}
|
|
339
340
|
|
|
@@ -500,36 +501,51 @@ export class Pipeline {
|
|
|
500
501
|
}
|
|
501
502
|
|
|
502
503
|
/**
|
|
503
|
-
* The reply of a plain JSON value (see {@link isPlainJson})
|
|
504
|
-
*
|
|
505
|
-
* `undefined` when the result or the context
|
|
506
|
-
* `JSON.stringify`.
|
|
504
|
+
* The reply of a plain JSON value (see {@link isPlainJson}), of an `HttpResponse` carrying one
|
|
505
|
+
* (see {@link fastResponseType}) or of `undefined`: what `serializeResult` and {@link #complete}
|
|
506
|
+
* produce for it, the headers in their final order. `undefined` when the result or the context
|
|
507
|
+
* needs the general pipeline. Throws like `JSON.stringify`.
|
|
507
508
|
*/
|
|
508
509
|
#fastReply(route: FastRoute, ctx: RequestContext, result: unknown): AdapterReply | undefined {
|
|
509
|
-
|
|
510
|
+
let payload = result
|
|
511
|
+
let status = result === undefined ? route.emptyStatus : route.jsonStatus
|
|
512
|
+
let type: string | null = null
|
|
513
|
+
if (route.responses && result instanceof HttpResponse) {
|
|
514
|
+
const own = fastResponseType(result)
|
|
515
|
+
if (own === undefined) return undefined
|
|
516
|
+
type = own
|
|
517
|
+
payload = result.body
|
|
518
|
+
// `@HttpCode` > `HttpResponse.status` > the default of the route (see serializeResult)
|
|
519
|
+
if (route.plan.definition.status === undefined && result.status !== undefined) {
|
|
520
|
+
status = isBodylessStatus(result.status) ? undefined : result.status
|
|
521
|
+
}
|
|
522
|
+
// an HttpResponse without body takes the general pipeline
|
|
523
|
+
if (payload === undefined) return undefined
|
|
524
|
+
}
|
|
510
525
|
// draining since the request started (`connection: close`, see #frameworkHeaders)
|
|
511
526
|
if (status === undefined || ctx.hasResponseHeaders || ctx.hasCookies || this.draining) return undefined
|
|
512
527
|
const requestId = ctx.requestId
|
|
513
528
|
// a custom generator's id that cannot be sent is left out (see #frameworkHeaders)
|
|
514
529
|
if (!this.#validIds && !isHeaderValue(requestId)) return undefined
|
|
515
|
-
if (
|
|
516
|
-
if (!isPlainJson(
|
|
517
|
-
const json = JSON.stringify(
|
|
518
|
-
if (json === undefined) throw new TypeError(`Cannot serialise a ${typeof
|
|
530
|
+
if (payload === undefined) return this.#templateReply(status, null, undefined, requestId, null)
|
|
531
|
+
if (!isPlainJson(payload)) return undefined
|
|
532
|
+
const json = JSON.stringify(payload)
|
|
533
|
+
if (json === undefined) throw new TypeError(`Cannot serialise a ${typeof payload} as a response body`)
|
|
519
534
|
// encoded once, its length from the bytes (see serialize.ts encodeText)
|
|
520
|
-
return this.#templateReply(status, utf8(json), json, requestId)
|
|
535
|
+
return this.#templateReply(status, utf8(json), json, requestId, type)
|
|
521
536
|
}
|
|
522
537
|
|
|
523
538
|
/**
|
|
524
539
|
* The reply of a JSON body (`text` set) or of no body, its headers copied from the template of
|
|
525
540
|
* the fast lane (see {@link #jsonHeaders}), completed with the length and the request id (one that
|
|
526
|
-
* can be sent).
|
|
541
|
+
* can be sent). `type` replaces the JSON content type in place (the `HttpResponse`'s own).
|
|
527
542
|
*/
|
|
528
543
|
#templateReply(
|
|
529
544
|
status: number,
|
|
530
545
|
body: Uint8Array | null,
|
|
531
546
|
text: string | undefined,
|
|
532
547
|
requestId: RequestId,
|
|
548
|
+
type: string | null,
|
|
533
549
|
): AdapterReply {
|
|
534
550
|
if (text === undefined) {
|
|
535
551
|
const headers: Record<string, HeaderValue> = { ...this.#emptyHeaders }
|
|
@@ -538,6 +554,8 @@ export class Pipeline {
|
|
|
538
554
|
return { status, headers, body: null }
|
|
539
555
|
}
|
|
540
556
|
const headers: Record<string, HeaderValue> = { ...this.#jsonHeaders }
|
|
557
|
+
// assignHeaders keeps the position of a key: the content type stays in place
|
|
558
|
+
if (type !== null) headers['content-type'] = type
|
|
541
559
|
headers['content-length'] = String((body as Uint8Array).byteLength)
|
|
542
560
|
const idHeader = this.#jsonIdHeader
|
|
543
561
|
if (idHeader !== undefined) headers[idHeader] = requestId
|
|
@@ -662,7 +680,13 @@ export class Pipeline {
|
|
|
662
680
|
!this.draining &&
|
|
663
681
|
(this.#validIds || isHeaderValue(x.requestId))
|
|
664
682
|
) {
|
|
665
|
-
const reply = this.#templateReply(
|
|
683
|
+
const reply = this.#templateReply(
|
|
684
|
+
outcome.status,
|
|
685
|
+
outcome.body as Uint8Array | null,
|
|
686
|
+
outcome.text,
|
|
687
|
+
x.requestId,
|
|
688
|
+
null,
|
|
689
|
+
)
|
|
666
690
|
ctx.commit()
|
|
667
691
|
ctx.releaseBody()
|
|
668
692
|
x.answer(reply)
|
|
@@ -1471,6 +1495,8 @@ interface FastRoute {
|
|
|
1471
1495
|
readonly jsonStatus: number | undefined
|
|
1472
1496
|
/** The status of a reply without value (`undefined` returned). */
|
|
1473
1497
|
readonly emptyStatus: number
|
|
1498
|
+
/** An `HttpResponse` carrying a plain JSON body may be served (not on a `@Head` route: no body). */
|
|
1499
|
+
readonly responses: boolean
|
|
1474
1500
|
}
|
|
1475
1501
|
|
|
1476
1502
|
/** `#fastAsync` reads the body and runs the handler. */
|
|
@@ -1622,6 +1648,23 @@ function isPlainJson(value: unknown): value is object {
|
|
|
1622
1648
|
return typeof (value as { pipe?: unknown }).pipe !== 'function'
|
|
1623
1649
|
}
|
|
1624
1650
|
|
|
1651
|
+
/**
|
|
1652
|
+
* The content type of an `HttpResponse` the fast lane can serve: `null` without headers, its
|
|
1653
|
+
* `content-type` when that is its only header (lower-case name, a value every adapter can send);
|
|
1654
|
+
* `undefined` when it needs the general pipeline (other headers, cookies).
|
|
1655
|
+
*/
|
|
1656
|
+
function fastResponseType(response: HttpResponse): string | null | undefined {
|
|
1657
|
+
if (response.cookies.length > 0) return undefined
|
|
1658
|
+
const headers = response.headers
|
|
1659
|
+
let type: string | null = null
|
|
1660
|
+
for (const name of Object.keys(headers)) {
|
|
1661
|
+
const value = headers[name]
|
|
1662
|
+
if (name !== 'content-type' || typeof value !== 'string' || !isHeaderValue(value)) return undefined
|
|
1663
|
+
type = value
|
|
1664
|
+
}
|
|
1665
|
+
return type
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1625
1668
|
const ENCODER = new TextEncoder()
|
|
1626
1669
|
|
|
1627
1670
|
/** UTF-8 encoding of a text body, like the serialiser (a pooled `Buffer` on Node.js, `TextEncoder` on Bun). */
|