@znight/http 0.1.0 → 0.1.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.
- package/LICENSE +1 -1
- package/README.md +4 -0
- package/dist/{adapter-CGSceVEN.d.mts → adapter-J0UXFnNM.d.mts} +7 -1
- package/dist/compiler.d.mts +1 -1
- package/dist/express.d.mts +1 -1
- package/dist/express.mjs +1 -1
- package/dist/fastify.d.mts +1 -1
- package/dist/fastify.mjs +57 -22
- package/dist/fastify.mjs.map +1 -1
- package/dist/hono.d.mts +1 -1
- package/dist/hono.mjs +112 -38
- package/dist/hono.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +251 -157
- package/dist/index.mjs.map +1 -1
- package/dist/{node-BUnkdSsa.mjs → node-Clm1Ia7T.mjs} +2 -2
- package/dist/{node-BUnkdSsa.mjs.map → node-Clm1Ia7T.mjs.map} +1 -1
- package/dist/{node-io-BY1ZDLRQ.mjs → node-io-CVnqpVNS.mjs} +52 -26
- package/dist/{node-io-BY1ZDLRQ.mjs.map → node-io-CVnqpVNS.mjs.map} +1 -1
- package/dist/node.d.mts +1 -1
- package/dist/node.mjs +1 -1
- package/package.json +4 -4
- package/src/adapter.ts +6 -0
- package/src/adapters/fastify.ts +66 -21
- package/src/adapters/hono.ts +135 -47
- package/src/adapters/node-io.ts +57 -24
- package/src/context.ts +7 -4
- package/src/pipeline.ts +237 -174
- package/src/plugin.ts +9 -2
- package/src/query.ts +7 -1
- package/src/serialize.ts +51 -11
package/src/query.ts
CHANGED
|
@@ -23,8 +23,14 @@ export function parseQuery(query: string): QueryRecord {
|
|
|
23
23
|
return result
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/** A request target split into its raw path and raw query string (without `?`). */
|
|
27
|
+
export interface RequestTarget {
|
|
28
|
+
readonly path: string
|
|
29
|
+
readonly query: string
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
/** Splits a request target (`/users?page=2`) into its raw path and raw query (without `?`). */
|
|
27
|
-
export function splitUrl(url: string):
|
|
33
|
+
export function splitUrl(url: string): RequestTarget {
|
|
28
34
|
const index = url.indexOf('?')
|
|
29
35
|
return index === -1 ? { path: url, query: '' } : { path: url.slice(0, index), query: url.slice(index + 1) }
|
|
30
36
|
}
|
package/src/serialize.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { ResolvedFile } from './files.ts'
|
|
|
5
5
|
import type { HttpResponse } from './response.ts'
|
|
6
6
|
import type { SseSession } from './sse.ts'
|
|
7
7
|
|
|
8
|
+
import { Buffer } from 'node:buffer'
|
|
8
9
|
import { Readable } from 'node:stream'
|
|
9
10
|
|
|
10
11
|
import { isFileBody } from './files.ts'
|
|
@@ -12,10 +13,16 @@ import { isHttpResponse } from './response.ts'
|
|
|
12
13
|
import { isSseBody } from './sse.ts'
|
|
13
14
|
import { isBodylessStatus } from './status.ts'
|
|
14
15
|
import { discardStream, isByteStream, toByteStream } from './stream.ts'
|
|
15
|
-
import { emptyRecord } from './utils.ts'
|
|
16
|
+
import { emptyRecord, isBun } from './utils.ts'
|
|
16
17
|
|
|
17
18
|
const ENCODER = new TextEncoder()
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* UTF-8 encoding of a text body: a pooled `Buffer` (a `Uint8Array`) is several times cheaper than
|
|
22
|
+
* `TextEncoder` on Node.js, `TextEncoder` is the fastest on Bun.
|
|
23
|
+
*/
|
|
24
|
+
const utf8: (text: string) => Uint8Array = isBun() ? text => ENCODER.encode(text) : text => Buffer.from(text, 'utf8')
|
|
25
|
+
|
|
19
26
|
/** Content types set by the serialiser. */
|
|
20
27
|
export const ContentType: {
|
|
21
28
|
readonly json: string
|
|
@@ -48,6 +55,10 @@ export interface SerializedReply {
|
|
|
48
55
|
readonly status: number
|
|
49
56
|
readonly headers: Record<string, HeaderValue>
|
|
50
57
|
readonly body: ReplyBody
|
|
58
|
+
/** The text `body` encodes, for a body serialised from a string (JSON, text). */
|
|
59
|
+
readonly text?: string
|
|
60
|
+
/** Only the serialiser set the headers (content type and length): they are valid by construction. */
|
|
61
|
+
readonly ownHeaders?: boolean
|
|
51
62
|
/** Cookies of the `HttpResponse` (`withCookie`), applied by the pipeline over those of the context. */
|
|
52
63
|
readonly cookies?: readonly ResponseCookie[]
|
|
53
64
|
/** The event stream of the body (`@Sse`, `HttpResponse.sse()`). */
|
|
@@ -87,10 +98,20 @@ export function serializeResult(result: unknown, options: SerializeOptions): Ser
|
|
|
87
98
|
const finalStatus = options.status ?? status ?? defaultStatus(options.routeMethod, payload)
|
|
88
99
|
const headers = emptyRecord<HeaderValue>()
|
|
89
100
|
const body = encode(payload, headers)
|
|
90
|
-
|
|
91
|
-
if (options.
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
let own = true
|
|
102
|
+
if (options.headers !== undefined) {
|
|
103
|
+
assignHeaders(headers, options.headers)
|
|
104
|
+
own = false
|
|
105
|
+
}
|
|
106
|
+
if (options.context !== undefined) {
|
|
107
|
+
assignHeaders(headers, options.context)
|
|
108
|
+
own = false
|
|
109
|
+
}
|
|
110
|
+
if (extra !== undefined) {
|
|
111
|
+
assignHeaders(headers, extra)
|
|
112
|
+
own = false
|
|
113
|
+
}
|
|
114
|
+
return withCookies(finish(finalStatus, headers, body, options.method, own), cookies)
|
|
94
115
|
}
|
|
95
116
|
|
|
96
117
|
/**
|
|
@@ -184,11 +205,16 @@ export function addMissingHeaders(
|
|
|
184
205
|
}
|
|
185
206
|
}
|
|
186
207
|
|
|
208
|
+
/**
|
|
209
|
+
* The reply of a body: a string (see {@link encode}) is sent as its UTF-8 bytes, with its text.
|
|
210
|
+
* `own`: only {@link encode} set `headers`, which are then valid for a text body.
|
|
211
|
+
*/
|
|
187
212
|
function finish(
|
|
188
213
|
status: number,
|
|
189
214
|
headers: Record<string, HeaderValue>,
|
|
190
|
-
body: ReplyBody,
|
|
215
|
+
body: ReplyBody | string,
|
|
191
216
|
method: string,
|
|
217
|
+
own = false,
|
|
192
218
|
): SerializedReply {
|
|
193
219
|
if (isBodylessStatus(status)) {
|
|
194
220
|
discard(body)
|
|
@@ -200,16 +226,23 @@ function finish(
|
|
|
200
226
|
discard(body)
|
|
201
227
|
return { status, headers, body: null }
|
|
202
228
|
}
|
|
203
|
-
return { status, headers, body }
|
|
229
|
+
if (typeof body !== 'string') return { status, headers, body }
|
|
230
|
+
return own
|
|
231
|
+
? { status, headers, body: utf8(body), text: body, ownHeaders: true }
|
|
232
|
+
: { status, headers, body: utf8(body), text: body }
|
|
204
233
|
}
|
|
205
234
|
|
|
206
|
-
function discard(body: ReplyBody): void {
|
|
235
|
+
function discard(body: ReplyBody | string): void {
|
|
207
236
|
if (body instanceof ReadableStream) discardStream(body)
|
|
208
237
|
}
|
|
209
238
|
|
|
210
|
-
|
|
239
|
+
/**
|
|
240
|
+
* The body of a payload, its `content-type` and, for bytes and text, its `content-length` set in
|
|
241
|
+
* `headers`. Text (a string, JSON) is returned as a string, encoded as UTF-8 by {@link finish}.
|
|
242
|
+
*/
|
|
243
|
+
function encode(payload: unknown, headers: Record<string, HeaderValue>): ReplyBody | string {
|
|
211
244
|
if (payload === undefined) return null
|
|
212
|
-
if (typeof payload === 'string') return
|
|
245
|
+
if (typeof payload === 'string') return text(payload, headers, ContentType.text)
|
|
213
246
|
if (payload instanceof Uint8Array) return bytes(payload, headers, ContentType.bytes)
|
|
214
247
|
if (payload instanceof ArrayBuffer) return bytes(new Uint8Array(payload), headers, ContentType.bytes)
|
|
215
248
|
if (ArrayBuffer.isView(payload)) {
|
|
@@ -233,7 +266,7 @@ function encode(payload: unknown, headers: Record<string, HeaderValue>): ReplyBo
|
|
|
233
266
|
if (isSseBody(payload)) throw new TypeError('An event stream (HttpResponse.sse()) cannot be serialised here')
|
|
234
267
|
const json = JSON.stringify(payload)
|
|
235
268
|
if (json === undefined) throw new TypeError(`Cannot serialise a ${typeof payload} as a response body`)
|
|
236
|
-
return
|
|
269
|
+
return text(json, headers, ContentType.json)
|
|
237
270
|
}
|
|
238
271
|
|
|
239
272
|
function bytes(data: Uint8Array, headers: Record<string, HeaderValue>, type: string): Uint8Array {
|
|
@@ -242,6 +275,13 @@ function bytes(data: Uint8Array, headers: Record<string, HeaderValue>, type: str
|
|
|
242
275
|
return data
|
|
243
276
|
}
|
|
244
277
|
|
|
278
|
+
function text(data: string, headers: Record<string, HeaderValue>, type: string): string {
|
|
279
|
+
headers['content-type'] = type
|
|
280
|
+
// the length of its UTF-8 encoding (a lone surrogate counts as U+FFFD, like the encoders)
|
|
281
|
+
headers['content-length'] = String(Buffer.byteLength(data, 'utf8'))
|
|
282
|
+
return data
|
|
283
|
+
}
|
|
284
|
+
|
|
245
285
|
function isNodeReadable(value: unknown): value is Readable {
|
|
246
286
|
return (
|
|
247
287
|
value instanceof Readable ||
|