@nifrajs/core 5.5.1 → 5.7.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/CHANGELOG.md +246 -0
- package/README.md +5 -0
- package/dist/assurance.d.ts.map +1 -1
- package/dist/assurance.js.map +1 -1
- package/dist/binary.d.ts +15 -1
- package/dist/binary.d.ts.map +1 -1
- package/dist/binary.js +1 -0
- package/dist/binary.js.map +1 -1
- package/dist/edge-kit.d.ts +1 -0
- package/dist/edge-kit.d.ts.map +1 -1
- package/dist/edge-kit.js +1 -0
- package/dist/edge-kit.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/evidence.d.ts +4 -0
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +4 -0
- package/dist/evidence.js.map +1 -1
- package/dist/file-type.d.ts +30 -0
- package/dist/file-type.d.ts.map +1 -0
- package/dist/file-type.js +118 -0
- package/dist/file-type.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/hook-origins.d.ts +2 -0
- package/dist/internal/hook-origins.d.ts.map +1 -1
- package/dist/internal/hook-origins.js +12 -0
- package/dist/internal/hook-origins.js.map +1 -1
- package/dist/internal/route-program.d.ts +11 -0
- package/dist/internal/route-program.d.ts.map +1 -1
- package/dist/internal/route-program.js +133 -85
- package/dist/internal/route-program.js.map +1 -1
- package/dist/reflection.d.ts +19 -1
- package/dist/reflection.d.ts.map +1 -1
- package/dist/reflection.js +46 -7
- package/dist/reflection.js.map +1 -1
- package/dist/response-observer.d.ts +1 -0
- package/dist/response-observer.d.ts.map +1 -1
- package/dist/response-observer.js +1 -0
- package/dist/response-observer.js.map +1 -1
- package/dist/server/answer-step.d.ts +27 -0
- package/dist/server/answer-step.d.ts.map +1 -0
- package/dist/server/answer-step.js +33 -0
- package/dist/server/answer-step.js.map +1 -0
- package/dist/server/bare-error-lane.d.ts +4 -1
- package/dist/server/bare-error-lane.d.ts.map +1 -1
- package/dist/server/bare-error-lane.js +9 -3
- package/dist/server/bare-error-lane.js.map +1 -1
- package/dist/server/base64-answers.d.ts +5 -0
- package/dist/server/base64-answers.d.ts.map +1 -0
- package/dist/server/base64-answers.js +132 -0
- package/dist/server/base64-answers.js.map +1 -0
- package/dist/server/before-handler.d.ts +10 -0
- package/dist/server/before-handler.d.ts.map +1 -0
- package/dist/server/before-handler.js +12 -0
- package/dist/server/before-handler.js.map +1 -0
- package/dist/server/body-lane.d.ts.map +1 -1
- package/dist/server/body-lane.js +8 -2
- package/dist/server/body-lane.js.map +1 -1
- package/dist/server/body-parser.d.ts.map +1 -1
- package/dist/server/body-parser.js +2 -5
- package/dist/server/body-parser.js.map +1 -1
- package/dist/server/body.d.ts +24 -8
- package/dist/server/body.d.ts.map +1 -1
- package/dist/server/body.js +86 -33
- package/dist/server/body.js.map +1 -1
- package/dist/server/core-copies.d.ts.map +1 -1
- package/dist/server/core-copies.js +5 -2
- package/dist/server/core-copies.js.map +1 -1
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +6 -17
- package/dist/server/http.js.map +1 -1
- package/dist/server/install.d.ts +2 -0
- package/dist/server/install.d.ts.map +1 -1
- package/dist/server/install.js +2 -0
- package/dist/server/install.js.map +1 -1
- package/dist/server/json-response.d.ts +3 -0
- package/dist/server/json-response.d.ts.map +1 -0
- package/dist/server/json-response.js +14 -0
- package/dist/server/json-response.js.map +1 -0
- package/dist/server/json-text.d.ts +16 -0
- package/dist/server/json-text.d.ts.map +1 -0
- package/dist/server/json-text.js +34 -0
- package/dist/server/json-text.js.map +1 -0
- package/dist/server/multipart.d.ts +3 -0
- package/dist/server/multipart.d.ts.map +1 -1
- package/dist/server/multipart.js +226 -97
- package/dist/server/multipart.js.map +1 -1
- package/dist/server/node-outcome.d.ts +5 -0
- package/dist/server/node-outcome.d.ts.map +1 -1
- package/dist/server/node-outcome.js +3 -4
- package/dist/server/node-outcome.js.map +1 -1
- package/dist/server/proto-guard.d.ts.map +1 -1
- package/dist/server/proto-guard.js +39 -4
- package/dist/server/proto-guard.js.map +1 -1
- package/dist/server/query.d.ts.map +1 -1
- package/dist/server/query.js +8 -14
- package/dist/server/query.js.map +1 -1
- package/dist/server/repeated-prefix.d.ts +17 -0
- package/dist/server/repeated-prefix.d.ts.map +1 -0
- package/dist/server/repeated-prefix.js +17 -0
- package/dist/server/repeated-prefix.js.map +1 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +5 -5
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/respond.d.ts +3 -4
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +45 -37
- package/dist/server/respond.js.map +1 -1
- package/dist/server/response-contract-lane.d.ts +12 -2
- package/dist/server/response-contract-lane.d.ts.map +1 -1
- package/dist/server/response-contract-lane.js +137 -9
- package/dist/server/response-contract-lane.js.map +1 -1
- package/dist/server/route-template.d.ts +10 -0
- package/dist/server/route-template.d.ts.map +1 -0
- package/dist/server/route-template.js +21 -0
- package/dist/server/route-template.js.map +1 -0
- package/dist/server/runtime-core.d.ts +7 -0
- package/dist/server/runtime-core.d.ts.map +1 -1
- package/dist/server/runtime-core.js +10 -2
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server-types.d.ts +17 -8
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +27 -12
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +326 -108
- package/dist/server/server.js.map +1 -1
- package/dist/server/sse.d.ts +1 -1
- package/dist/server/sse.d.ts.map +1 -1
- package/dist/server/sse.js +7 -5
- package/dist/server/sse.js.map +1 -1
- package/dist/server/text-slots.d.ts +8 -0
- package/dist/server/text-slots.d.ts.map +1 -0
- package/dist/server/text-slots.js +19 -0
- package/dist/server/text-slots.js.map +1 -0
- package/dist/server/webhook.d.ts +1 -1
- package/dist/server/webhook.d.ts.map +1 -1
- package/dist/server/webhook.js +24 -19
- package/dist/server/webhook.js.map +1 -1
- package/dist/server/websocket.d.ts +18 -3
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +45 -5
- package/dist/server/websocket.js.map +1 -1
- package/dist/server/ws-bun.d.ts.map +1 -1
- package/dist/server/ws-bun.js +7 -4
- package/dist/server/ws-bun.js.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/dist/single-copy.d.ts +5 -0
- package/dist/single-copy.d.ts.map +1 -1
- package/dist/single-copy.js +26 -17
- package/dist/single-copy.js.map +1 -1
- package/dist/tool-cli.d.ts +64 -0
- package/dist/tool-cli.d.ts.map +1 -0
- package/dist/tool-cli.js +495 -0
- package/dist/tool-cli.js.map +1 -0
- package/dist/transport-codec.d.ts.map +1 -1
- package/dist/transport-codec.js +2 -1
- package/dist/transport-codec.js.map +1 -1
- package/package.json +11 -1
- package/src/assurance.ts +1 -0
- package/src/binary.ts +17 -0
- package/src/edge-kit.ts +1 -0
- package/src/errors.ts +1 -0
- package/src/evidence.ts +8 -0
- package/src/file-type.ts +130 -0
- package/src/index.ts +3 -1
- package/src/internal/hook-origins.ts +10 -0
- package/src/internal/route-program.ts +261 -93
- package/src/reflection.ts +71 -7
- package/src/response-observer.ts +6 -0
- package/src/server/answer-step.ts +68 -0
- package/src/server/bare-error-lane.ts +9 -4
- package/src/server/base64-answers.ts +138 -0
- package/src/server/before-handler.ts +14 -0
- package/src/server/body-lane.ts +19 -9
- package/src/server/body-parser.ts +1 -5
- package/src/server/body.ts +96 -36
- package/src/server/core-copies.ts +5 -2
- package/src/server/http.ts +6 -15
- package/src/server/install.ts +3 -0
- package/src/server/json-response.ts +13 -0
- package/src/server/json-text.ts +34 -0
- package/src/server/multipart.ts +232 -100
- package/src/server/node-outcome.ts +4 -5
- package/src/server/proto-guard.ts +39 -4
- package/src/server/query.ts +7 -13
- package/src/server/repeated-prefix.ts +25 -0
- package/src/server/request-context.ts +5 -5
- package/src/server/respond.ts +47 -30
- package/src/server/response-contract-lane.ts +132 -9
- package/src/server/route-template.ts +24 -0
- package/src/server/runtime-core.ts +12 -2
- package/src/server/server-types.ts +18 -6
- package/src/server/server.ts +361 -112
- package/src/server/sse.ts +8 -6
- package/src/server/text-slots.ts +21 -0
- package/src/server/webhook.ts +29 -24
- package/src/server/websocket.ts +57 -8
- package/src/server/ws-bun.ts +7 -3
- package/src/server.ts +2 -0
- package/src/single-copy.ts +31 -15
- package/src/tool-cli.ts +584 -0
- package/src/transport-codec.ts +3 -1
package/src/binary.ts
CHANGED
|
@@ -51,6 +51,23 @@ declare const NIFRA_RAW: unique symbol
|
|
|
51
51
|
*/
|
|
52
52
|
export type RawResponse<T> = Response & { readonly [NIFRA_RAW]: T }
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Marker on a `t.bytes()` field's TYPE. Declared OPTIONAL, unlike the two above: the handler returns a
|
|
56
|
+
* plain `Uint8Array` (or a `Buffer`), so the field's type has to accept one. Every `Uint8Array` meets an
|
|
57
|
+
* optional brand, so `extends` cannot test it; `Jsonify` asks whether the key is present instead, which
|
|
58
|
+
* only the declared field's type answers yes to.
|
|
59
|
+
*/
|
|
60
|
+
declare const NIFRA_BASE64: unique symbol
|
|
61
|
+
|
|
62
|
+
/** The key `Jsonify` looks for to tell a {@link Base64Bytes} field from any other `Uint8Array`. */
|
|
63
|
+
export type Base64BytesBrand = typeof NIFRA_BASE64
|
|
64
|
+
|
|
65
|
+
/** A `t.bytes()` field: a `Uint8Array` in the handler, a base64 string on the wire and in the client. */
|
|
66
|
+
export type Base64Bytes = Uint8Array & { readonly [NIFRA_BASE64]?: true }
|
|
67
|
+
|
|
68
|
+
export type { AnswerStages, AnswerStep, JsonReplyContext } from "./server/answer-step.ts"
|
|
69
|
+
export { withBase64Bytes } from "./server/base64-answers.ts"
|
|
70
|
+
|
|
54
71
|
/** What can be sent as bytes without being re-encoded on the way out. */
|
|
55
72
|
export type BinaryBody = ArrayBuffer | ArrayBufferView | Blob | ReadableStream<Uint8Array>
|
|
56
73
|
|
package/src/edge-kit.ts
CHANGED
|
@@ -24,6 +24,7 @@ export { type ErrorLogDetail, emitRequestErrorLog } from "./server/bare-error-la
|
|
|
24
24
|
export { assertByteLimit, capBodyBytes } from "./server/body.ts"
|
|
25
25
|
export { type FormOrigins, readBodyFramed } from "./server/body-lane.ts"
|
|
26
26
|
export { plainError } from "./server/http.ts"
|
|
27
|
+
export { jsonResponse } from "./server/json-response.ts"
|
|
27
28
|
export {
|
|
28
29
|
answerNotFound,
|
|
29
30
|
type NotFoundHandler,
|
package/src/errors.ts
CHANGED
package/src/evidence.ts
CHANGED
|
@@ -31,6 +31,8 @@ export interface ProjectEvidenceSchemaPart {
|
|
|
31
31
|
readonly fields?: readonly ReflectedSchemaField[]
|
|
32
32
|
/** The media types a body schema reads through a parser of its own. */
|
|
33
33
|
readonly mediaTypes?: readonly string[]
|
|
34
|
+
/** Set when the schema reads text values with `t.query`'s string coercion. */
|
|
35
|
+
readonly coerces?: true
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export interface ProjectEvidenceSchema {
|
|
@@ -46,6 +48,8 @@ export interface ProjectEvidenceSchema {
|
|
|
46
48
|
readonly response?: ProjectEvidenceSchemaPart
|
|
47
49
|
readonly errors?: Readonly<Record<string, ProjectEvidenceSchemaPart>>
|
|
48
50
|
readonly sse?: ProjectEvidenceSchemaPart
|
|
51
|
+
/** Set when an `onValidationError` hook answers the route's validation failures. */
|
|
52
|
+
readonly validationErrorHook?: true
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
export interface ProjectEvidenceSourceLocation {
|
|
@@ -143,6 +147,7 @@ function schemaPart(value: SchemaReflection | undefined): ProjectEvidenceSchemaP
|
|
|
143
147
|
...(value.mediaTypes !== undefined
|
|
144
148
|
? { mediaTypes: Object.freeze([...value.mediaTypes].sort()) }
|
|
145
149
|
: {}),
|
|
150
|
+
...(value.coerces === true ? { coerces: true as const } : {}),
|
|
146
151
|
})
|
|
147
152
|
}
|
|
148
153
|
|
|
@@ -173,6 +178,7 @@ function schemaOf(route: ReflectedRoute): ProjectEvidenceSchema | undefined {
|
|
|
173
178
|
...(response !== undefined ? { response } : {}),
|
|
174
179
|
...(Object.keys(errors).length > 0 ? { errors: Object.freeze(errors) } : {}),
|
|
175
180
|
...(sse !== undefined ? { sse } : {}),
|
|
181
|
+
...(source.validationErrorHook === true ? { validationErrorHook: true as const } : {}),
|
|
176
182
|
}
|
|
177
183
|
return Object.freeze(schema)
|
|
178
184
|
}
|
|
@@ -488,6 +494,7 @@ function schemaPartToReflection(
|
|
|
488
494
|
jsonSchema: value.jsonSchema,
|
|
489
495
|
fields: value.fields,
|
|
490
496
|
...(value.mediaTypes !== undefined ? { mediaTypes: value.mediaTypes } : {}),
|
|
497
|
+
...(value.coerces === true ? { coerces: true as const } : {}),
|
|
491
498
|
}
|
|
492
499
|
}
|
|
493
500
|
|
|
@@ -518,6 +525,7 @@ function schemaFromEvidence(schema: ProjectEvidenceSchema | undefined): Reflecte
|
|
|
518
525
|
...(response !== undefined ? { response } : {}),
|
|
519
526
|
...(Object.keys(errors).length > 0 ? { errors } : {}),
|
|
520
527
|
...(sse !== undefined ? { sse } : {}),
|
|
528
|
+
...(schema.validationErrorHook === true ? { validationErrorHook: true as const } : {}),
|
|
521
529
|
}
|
|
522
530
|
}
|
|
523
531
|
|
package/src/file-type.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Magic-byte file-type detection - trust the bytes, not the `Content-Type` header (which a client
|
|
3
|
+
* sets freely). Reads only the leading bytes; imports nothing, so it is edge-safe. Covers the common
|
|
4
|
+
* upload types; returns `null` for anything unrecognized (incl. text formats like SVG/CSV that have no
|
|
5
|
+
* magic number - handle those explicitly if you accept them).
|
|
6
|
+
*
|
|
7
|
+
* The shared owner for every type-by-bytes check: `@nifrajs/schema/form`'s `t.file({ accept })` and
|
|
8
|
+
* `@nifrajs/uploads`'s `validateUpload` must give one file one verdict, so both read this module.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface FileType {
|
|
12
|
+
/** Detected MIME type, e.g. `"image/png"`. */
|
|
13
|
+
readonly mime: string
|
|
14
|
+
/** Canonical extension (no dot), e.g. `"png"`. */
|
|
15
|
+
readonly ext: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How many leading bytes {@link detectFileType} looks at. Reading this many from the start of a file
|
|
20
|
+
* (`file.slice(0, FILE_TYPE_PREFIX_BYTES)`) is enough for every type it knows: it reaches the first four
|
|
21
|
+
* compatible brands of an ISO-BMFF `ftyp` box, where an AVIF with the generic `mif1` brand says `avif`.
|
|
22
|
+
*/
|
|
23
|
+
export const FILE_TYPE_PREFIX_BYTES = 32
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Every MIME type {@link detectFileType} can return. An allow-list entry outside this set (and
|
|
27
|
+
* outside a `type/*` wildcard that covers one of them) can never match, so check a configured
|
|
28
|
+
* allow-list against it up front instead of rejecting every upload at request time.
|
|
29
|
+
*/
|
|
30
|
+
export const DETECTABLE_MIME_TYPES: readonly string[] = [
|
|
31
|
+
"image/jpeg",
|
|
32
|
+
"image/png",
|
|
33
|
+
"image/gif",
|
|
34
|
+
"image/webp",
|
|
35
|
+
"audio/wav",
|
|
36
|
+
"video/x-msvideo",
|
|
37
|
+
"image/avif",
|
|
38
|
+
"image/heic",
|
|
39
|
+
"audio/mp4",
|
|
40
|
+
"video/mp4",
|
|
41
|
+
"video/webm",
|
|
42
|
+
"audio/ogg",
|
|
43
|
+
"audio/mpeg",
|
|
44
|
+
"application/pdf",
|
|
45
|
+
"application/zip",
|
|
46
|
+
"application/gzip",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* An MPEG audio frame header: an 11-bit frame sync, then a version, layer, bitrate and sample rate that
|
|
51
|
+
* are not reserved values. An MP3 needs no ID3 tag, so this is how an untagged one starts. AAC's ADTS
|
|
52
|
+
* sync shares the first 12 bits, but its layer is always the reserved `00`.
|
|
53
|
+
*/
|
|
54
|
+
function mpegAudioFrame(bytes: Uint8Array): boolean {
|
|
55
|
+
const [b0 = 0, b1 = 0, b2 = 0] = bytes
|
|
56
|
+
if (bytes.length < 4 || b0 !== 0xff || (b1 & 0xe0) !== 0xe0) return false
|
|
57
|
+
const version = (b1 >> 3) & 0b11
|
|
58
|
+
const layer = (b1 >> 1) & 0b11
|
|
59
|
+
const bitrate = b2 >> 4
|
|
60
|
+
const sampleRate = (b2 >> 2) & 0b11
|
|
61
|
+
return version !== 0b01 && layer !== 0b00 && bitrate !== 0b1111 && sampleRate !== 0b11
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Detect a file's type from its magic bytes, or `null` if unrecognized. */
|
|
65
|
+
export function detectFileType(bytes: Uint8Array): FileType | null {
|
|
66
|
+
const at = (offset: number, ...sig: number[]): boolean =>
|
|
67
|
+
sig.every((byte, i) => bytes[offset + i] === byte)
|
|
68
|
+
|
|
69
|
+
// Images
|
|
70
|
+
if (at(0, 0xff, 0xd8, 0xff)) return { mime: "image/jpeg", ext: "jpg" }
|
|
71
|
+
if (at(0, 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a))
|
|
72
|
+
return { mime: "image/png", ext: "png" }
|
|
73
|
+
if (at(0, 0x47, 0x49, 0x46, 0x38)) return { mime: "image/gif", ext: "gif" }
|
|
74
|
+
|
|
75
|
+
// RIFF container (bytes 0-3 "RIFF", 8-11 the form type)
|
|
76
|
+
if (at(0, 0x52, 0x49, 0x46, 0x46)) {
|
|
77
|
+
if (at(8, 0x57, 0x45, 0x42, 0x50)) return { mime: "image/webp", ext: "webp" }
|
|
78
|
+
if (at(8, 0x57, 0x41, 0x56, 0x45)) return { mime: "audio/wav", ext: "wav" }
|
|
79
|
+
if (at(8, 0x41, 0x56, 0x49, 0x20)) return { mime: "video/x-msvideo", ext: "avi" }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ISO-BMFF (`ftyp` box at offset 4) - mp4 / avif / heic / m4a, disambiguated by the major brand.
|
|
83
|
+
if (at(4, 0x66, 0x74, 0x79, 0x70)) {
|
|
84
|
+
const brandAt = (offset: number): string =>
|
|
85
|
+
String.fromCharCode(
|
|
86
|
+
bytes[offset] ?? 0,
|
|
87
|
+
bytes[offset + 1] ?? 0,
|
|
88
|
+
bytes[offset + 2] ?? 0,
|
|
89
|
+
bytes[offset + 3] ?? 0,
|
|
90
|
+
)
|
|
91
|
+
const brand = brandAt(8)
|
|
92
|
+
// The compatible brands follow the minor version, to the end of the ftyp box. Only those inside the
|
|
93
|
+
// prefix count: the box size is the sender's claim, and a verdict must not depend on bytes a caller
|
|
94
|
+
// that sniffs `FILE_TYPE_PREFIX_BYTES` never reads.
|
|
95
|
+
const boxEnd = Math.min(
|
|
96
|
+
bytes.length,
|
|
97
|
+
FILE_TYPE_PREFIX_BYTES,
|
|
98
|
+
((bytes[0] ?? 0) << 24) | ((bytes[1] ?? 0) << 16) | ((bytes[2] ?? 0) << 8) | (bytes[3] ?? 0),
|
|
99
|
+
)
|
|
100
|
+
const compatible: string[] = []
|
|
101
|
+
for (let offset = 16; offset + 4 <= boxEnd; offset += 4) compatible.push(brandAt(offset))
|
|
102
|
+
// A HEIF-family file whose major brand is the generic `mif1`/`msf1`/`miaf` names its codec among
|
|
103
|
+
// the compatible brands: AVIF says `avif`/`avis`.
|
|
104
|
+
const avif = (name: string): boolean => name === "avif" || name === "avis"
|
|
105
|
+
const generic = brand === "mif1" || brand === "msf1" || brand === "miaf"
|
|
106
|
+
if (avif(brand) || (generic && compatible.some(avif)))
|
|
107
|
+
return { mime: "image/avif", ext: "avif" }
|
|
108
|
+
if (brand.startsWith("hei") || brand.startsWith("hev") || generic) {
|
|
109
|
+
return { mime: "image/heic", ext: "heic" }
|
|
110
|
+
}
|
|
111
|
+
// Audio brands share the ISO-BMFF container but are NOT video - labeling them `video/mp4` would
|
|
112
|
+
// both reject real audio under an `audio/*` allow-list and admit it under `video/*`. `M4A `/`M4B `
|
|
113
|
+
// are AAC audio / audiobooks.
|
|
114
|
+
if (brand === "M4A " || brand === "M4B ") return { mime: "audio/mp4", ext: "m4a" }
|
|
115
|
+
return { mime: "video/mp4", ext: "mp4" }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// A/V + archives
|
|
119
|
+
if (at(0, 0x1a, 0x45, 0xdf, 0xa3)) return { mime: "video/webm", ext: "webm" } // Matroska/WebM
|
|
120
|
+
if (at(0, 0x4f, 0x67, 0x67, 0x53)) return { mime: "audio/ogg", ext: "ogg" }
|
|
121
|
+
if (at(0, 0x49, 0x44, 0x33)) return { mime: "audio/mpeg", ext: "mp3" } // ID3 tag
|
|
122
|
+
if (mpegAudioFrame(bytes)) return { mime: "audio/mpeg", ext: "mp3" } // an untagged MP3
|
|
123
|
+
if (at(0, 0x25, 0x50, 0x44, 0x46)) return { mime: "application/pdf", ext: "pdf" } // "%PDF"
|
|
124
|
+
if (at(0, 0x50, 0x4b, 0x03, 0x04) || at(0, 0x50, 0x4b, 0x05, 0x06)) {
|
|
125
|
+
return { mime: "application/zip", ext: "zip" } // also docx/xlsx/odt containers
|
|
126
|
+
}
|
|
127
|
+
if (at(0, 0x1f, 0x8b)) return { mime: "application/gzip", ext: "gz" }
|
|
128
|
+
|
|
129
|
+
return null
|
|
130
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* read its own package.json at runtime. `scripts/version.ts` rewrites it on every release bump and
|
|
17
17
|
* `check:publish` asserts it equals `@nifrajs/core`'s package version.
|
|
18
18
|
*/
|
|
19
|
-
export const VERSION = "5.
|
|
19
|
+
export const VERSION = "5.7.0" as const
|
|
20
20
|
|
|
21
21
|
export type Version = typeof VERSION
|
|
22
22
|
|
|
@@ -58,6 +58,7 @@ export {
|
|
|
58
58
|
markAuthenticatedRequest,
|
|
59
59
|
rejected,
|
|
60
60
|
} from "./server/auth.ts"
|
|
61
|
+
export { beforeHandler } from "./server/before-handler.ts"
|
|
61
62
|
export type {
|
|
62
63
|
Context,
|
|
63
64
|
Params,
|
|
@@ -119,6 +120,7 @@ export type {
|
|
|
119
120
|
} from "./server/registry.ts"
|
|
120
121
|
export { replacedRequestOf } from "./server/request-lineage.ts"
|
|
121
122
|
export { writableResponse } from "./server/respond.ts"
|
|
123
|
+
export { routeTemplateOf } from "./server/route-template.ts"
|
|
122
124
|
export {
|
|
123
125
|
type PlainRender,
|
|
124
126
|
type ResponseResult,
|
|
@@ -112,6 +112,16 @@ export function tagSource(wrapper: unknown, source: unknown): void {
|
|
|
112
112
|
if (isRef(wrapper)) sources.set(wrapper, source)
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/** A wrapper that runs in `hook`'s place on one route, reported as `hook` is. */
|
|
116
|
+
export function tagStandIn(wrapper: unknown, hook: unknown): void {
|
|
117
|
+
if (!isRef(wrapper) || !isRef(hook)) return
|
|
118
|
+
const origin = origins.get(hook)
|
|
119
|
+
if (origin !== undefined) origins.set(wrapper, origin)
|
|
120
|
+
const scope = scopes.get(hook)
|
|
121
|
+
if (scope !== undefined) scopes.set(wrapper, scope)
|
|
122
|
+
sources.set(wrapper, hook)
|
|
123
|
+
}
|
|
124
|
+
|
|
115
125
|
/** Pair each scoped wrapper with the hook it gates and the scope it gates it by. */
|
|
116
126
|
export function tagScoped(
|
|
117
127
|
originals: readonly unknown[],
|