@nifrajs/core 5.4.0 → 5.5.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/CHANGELOG.md +35 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/route-lanes.d.ts.map +1 -1
- package/dist/internal/route-lanes.js +2 -1
- package/dist/internal/route-lanes.js.map +1 -1
- package/dist/internal/route-program.d.ts +5 -0
- package/dist/internal/route-program.d.ts.map +1 -1
- package/dist/internal/route-program.js +36 -15
- package/dist/internal/route-program.js.map +1 -1
- package/dist/router/router.d.ts +3 -2
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +22 -6
- package/dist/router/router.js.map +1 -1
- package/dist/security/baseline.d.ts.map +1 -1
- package/dist/security/baseline.js +4 -3
- package/dist/security/baseline.js.map +1 -1
- package/dist/server/body-lane.d.ts.map +1 -1
- package/dist/server/body-lane.js +4 -3
- package/dist/server/body-lane.js.map +1 -1
- package/dist/server/body.d.ts +2 -0
- package/dist/server/body.d.ts.map +1 -1
- package/dist/server/body.js +5 -0
- package/dist/server/body.js.map +1 -1
- package/dist/server/cookies.d.ts.map +1 -1
- package/dist/server/cookies.js +9 -2
- package/dist/server/cookies.js.map +1 -1
- package/dist/server/core-copies.js +1 -1
- package/dist/server/idempotency-lane.d.ts +11 -12
- package/dist/server/idempotency-lane.d.ts.map +1 -1
- package/dist/server/idempotency-lane.js +301 -151
- package/dist/server/idempotency-lane.js.map +1 -1
- package/dist/server/methods.d.ts +3 -2
- package/dist/server/methods.d.ts.map +1 -1
- package/dist/server/methods.js.map +1 -1
- package/dist/server/multipart.d.ts.map +1 -1
- package/dist/server/multipart.js +42 -13
- package/dist/server/multipart.js.map +1 -1
- package/dist/server/node-outcome.d.ts.map +1 -1
- package/dist/server/node-outcome.js +2 -2
- package/dist/server/node-outcome.js.map +1 -1
- package/dist/server/query.d.ts +1 -1
- package/dist/server/query.d.ts.map +1 -1
- package/dist/server/query.js +13 -5
- package/dist/server/query.js.map +1 -1
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +3 -3
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/respond.d.ts +17 -0
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +81 -3
- package/dist/server/respond.js.map +1 -1
- package/dist/server/runtime-core.d.ts +15 -0
- package/dist/server/runtime-core.d.ts.map +1 -1
- package/dist/server/runtime-core.js +41 -1
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server-types.d.ts +5 -3
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +14 -5
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +179 -59
- package/dist/server/server.js.map +1 -1
- package/dist/server/transport-lane.d.ts +2 -2
- package/dist/server/transport-lane.d.ts.map +1 -1
- package/dist/server/transport-lane.js +20 -20
- package/dist/server/transport-lane.js.map +1 -1
- package/dist/server/ws-bun.d.ts.map +1 -1
- package/dist/server/ws-bun.js +7 -1
- package/dist/server/ws-bun.js.map +1 -1
- package/dist/static-file.d.ts +29 -6
- package/dist/static-file.d.ts.map +1 -1
- package/dist/static-file.js +43 -12
- package/dist/static-file.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/internal/route-lanes.ts +18 -16
- package/src/internal/route-program.ts +76 -12
- package/src/router/router.ts +26 -5
- package/src/security/baseline.ts +4 -3
- package/src/server/body-lane.ts +4 -4
- package/src/server/body.ts +6 -0
- package/src/server/cookies.ts +7 -2
- package/src/server/core-copies.ts +1 -1
- package/src/server/idempotency-lane.ts +343 -209
- package/src/server/methods.ts +3 -2
- package/src/server/multipart.ts +62 -14
- package/src/server/node-outcome.ts +2 -1
- package/src/server/query.ts +12 -5
- package/src/server/request-context.ts +6 -3
- package/src/server/respond.ts +108 -6
- package/src/server/runtime-core.ts +54 -1
- package/src/server/server-types.ts +5 -3
- package/src/server/server.ts +249 -99
- package/src/server/transport-lane.ts +28 -22
- package/src/server/ws-bun.ts +12 -1
- package/src/static-file.ts +72 -16
package/src/router/router.ts
CHANGED
|
@@ -10,8 +10,18 @@ import {
|
|
|
10
10
|
mixedSegmentShape,
|
|
11
11
|
} from "./pattern.ts"
|
|
12
12
|
|
|
13
|
-
/** The standard HTTP methods: the ones
|
|
14
|
-
|
|
13
|
+
/** The standard HTTP methods: the ones the typed registry and the typed client know. Each has a server
|
|
14
|
+
* builder except `QUERY` (RFC 9110's safe, idempotent read with a body), which `method()` registers. */
|
|
15
|
+
export const METHODS = [
|
|
16
|
+
"GET",
|
|
17
|
+
"POST",
|
|
18
|
+
"PUT",
|
|
19
|
+
"PATCH",
|
|
20
|
+
"DELETE",
|
|
21
|
+
"HEAD",
|
|
22
|
+
"OPTIONS",
|
|
23
|
+
"QUERY",
|
|
24
|
+
] as const
|
|
15
25
|
|
|
16
26
|
export type Method = (typeof METHODS)[number]
|
|
17
27
|
|
|
@@ -37,6 +47,8 @@ export function isRegistrableMethod(method: string): boolean {
|
|
|
37
47
|
const SLASH = 47
|
|
38
48
|
export const EMPTY_PARAMS: Record<string, string> = Object.freeze({})
|
|
39
49
|
const DYNAMIC_MATCH_CACHE_MAX = 2048
|
|
50
|
+
// The client picks every key, so their length is bounded too: 2048 paths of 8 KiB held 16 MiB.
|
|
51
|
+
const DYNAMIC_MATCH_CACHE_CHARS = 256 * 1024
|
|
40
52
|
|
|
41
53
|
/**
|
|
42
54
|
* Result of {@link Router.find}. The `found: false` cases deliberately separate
|
|
@@ -67,6 +79,8 @@ interface DynamicMatchCacheEntry<T> {
|
|
|
67
79
|
|
|
68
80
|
interface DynamicMatchCacheState<T> {
|
|
69
81
|
cache: Map<string, DynamicMatchCacheEntry<T>> | undefined
|
|
82
|
+
/** Total length of the cached paths. */
|
|
83
|
+
chars: number
|
|
70
84
|
pendingPath: string | undefined
|
|
71
85
|
}
|
|
72
86
|
|
|
@@ -79,6 +93,7 @@ function dynamicMatchCacheStateFor<T>(owner: object): DynamicMatchCacheState<T>
|
|
|
79
93
|
}
|
|
80
94
|
const state: DynamicMatchCacheState<T> = {
|
|
81
95
|
cache: undefined,
|
|
96
|
+
chars: 0,
|
|
82
97
|
pendingPath: undefined,
|
|
83
98
|
}
|
|
84
99
|
DYNAMIC_MATCH_CACHES.set(owner, state as DynamicMatchCacheState<unknown>)
|
|
@@ -476,13 +491,19 @@ export class Router<T> {
|
|
|
476
491
|
cache = new Map<string, DynamicMatchCacheEntry<T>>()
|
|
477
492
|
state.cache = cache
|
|
478
493
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
494
|
+
for (const oldest of cache.keys()) {
|
|
495
|
+
if (
|
|
496
|
+
cache.size < DYNAMIC_MATCH_CACHE_MAX &&
|
|
497
|
+
state.chars + path.length <= DYNAMIC_MATCH_CACHE_CHARS
|
|
498
|
+
)
|
|
499
|
+
break
|
|
500
|
+
cache.delete(oldest)
|
|
501
|
+
state.chars -= oldest.length
|
|
482
502
|
}
|
|
483
503
|
cache.set(path, {
|
|
484
504
|
terminal,
|
|
485
505
|
values: values.slice(0, terminal.paramNames.length),
|
|
486
506
|
})
|
|
507
|
+
state.chars += path.length
|
|
487
508
|
}
|
|
488
509
|
}
|
package/src/security/baseline.ts
CHANGED
|
@@ -41,9 +41,10 @@ export interface SecurityBaselineOptions {
|
|
|
41
41
|
readonly requireRuntimeProvenance?: boolean
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/** A mutation is every method these leave out: POST, PUT, PATCH, DELETE and any custom method.
|
|
45
|
-
|
|
46
|
-
const
|
|
44
|
+
/** A mutation is every method these leave out: POST, PUT, PATCH, DELETE and any custom method. QUERY
|
|
45
|
+
* reads, with a body that the generic body rule still bounds. */
|
|
46
|
+
const NOT_MUTATING: readonly (Method | "WS")[] = ["GET", "HEAD", "OPTIONS", "QUERY", "WS"]
|
|
47
|
+
const READING = ["GET", "HEAD", "QUERY"] as const
|
|
47
48
|
/** The classification threshold above which the baseline demands authentication. */
|
|
48
49
|
const SENSITIVE = "pii" as const
|
|
49
50
|
|
package/src/server/body-lane.ts
CHANGED
|
@@ -11,8 +11,7 @@ import { isJsonMediaType, isUrlEncodedForm, readBoundedForm } from "./query.ts"
|
|
|
11
11
|
import {
|
|
12
12
|
headerOf,
|
|
13
13
|
isResponseResult,
|
|
14
|
-
|
|
15
|
-
type PreDecodedBody,
|
|
14
|
+
preDecodedBody,
|
|
16
15
|
type ResponseResult,
|
|
17
16
|
TEXT_DECODER,
|
|
18
17
|
} from "./runtime-core.ts"
|
|
@@ -52,7 +51,7 @@ export function readBoundedJsonSource<T>(
|
|
|
52
51
|
onResult?: JsonResultContinuation<T>,
|
|
53
52
|
onError?: JsonErrorContinuation<T>,
|
|
54
53
|
): Promise<unknown | ResponseResult | T> {
|
|
55
|
-
const preDecoded = (req
|
|
54
|
+
const preDecoded = preDecodedBody(req)
|
|
56
55
|
if (preDecoded !== undefined) {
|
|
57
56
|
const value =
|
|
58
57
|
preDecoded.byteLength > maxBytes ? plainError(413, "payload_too_large") : preDecoded.value
|
|
@@ -237,7 +236,8 @@ export function readBodyFramed<T>(
|
|
|
237
236
|
formOrigins?: FormOrigins,
|
|
238
237
|
): Promise<T> {
|
|
239
238
|
const contentType = headerOf(source, "content-type") ?? ""
|
|
240
|
-
|
|
239
|
+
// A body a transport codec decoded keeps its media type; the JSON lane below takes its value.
|
|
240
|
+
if (!isJsonMediaType(contentType) && preDecodedBody(source) === undefined) {
|
|
241
241
|
// JSON needs a preflight, so CORS judges it; a form body reaches here from any site with cookies.
|
|
242
242
|
if (isFormMediaType(contentType) && !formOriginAllowed(source, formOrigins)) {
|
|
243
243
|
return Promise.resolve(wrapResponse(plainError(403, "forbidden_origin")))
|
package/src/server/body.ts
CHANGED
|
@@ -462,6 +462,12 @@ export function markTransportCap(source: object, maxBytes: number): void {
|
|
|
462
462
|
;(source as { [TRANSPORT_CAP]?: number })[TRANSPORT_CAP] = maxBytes
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
+
/** The cap {@link markTransportCap} set on `source`, for a copy that must keep it. */
|
|
466
|
+
export function transportCapOf(source: object): number | undefined {
|
|
467
|
+
const cap: unknown = Reflect.get(source, TRANSPORT_CAP)
|
|
468
|
+
return typeof cap === "number" ? cap : undefined
|
|
469
|
+
}
|
|
470
|
+
|
|
465
471
|
/**
|
|
466
472
|
* Enforce a marked source's transport byte cap on **direct user reads** of its request
|
|
467
473
|
* (`c.req.json()`, `c.req.arrayBuffer()`, the raw `body` stream, `clone()`, ...), without swapping
|
package/src/server/cookies.ts
CHANGED
|
@@ -78,6 +78,9 @@ export function parseCookies(header: string | null | undefined): Record<string,
|
|
|
78
78
|
if (!header) return out
|
|
79
79
|
const len = header.length
|
|
80
80
|
let pos = 0
|
|
81
|
+
// The first "=" at or after the segment, kept while it lies in a later one, so a run of bare
|
|
82
|
+
// segments does not rescan the rest of the header each time. -1: none left.
|
|
83
|
+
let eq = header.indexOf("=")
|
|
81
84
|
while (pos < len) {
|
|
82
85
|
let end = header.indexOf(";", pos)
|
|
83
86
|
if (end === -1) end = len
|
|
@@ -86,7 +89,7 @@ export function parseCookies(header: string | null | undefined): Record<string,
|
|
|
86
89
|
while (start < end && header.charCodeAt(start) === 32) start++
|
|
87
90
|
let stop = end
|
|
88
91
|
while (stop > start && header.charCodeAt(stop - 1) === 32) stop--
|
|
89
|
-
|
|
92
|
+
if (eq !== -1 && eq < start) eq = header.indexOf("=", start)
|
|
90
93
|
if (eq > start && eq < stop) {
|
|
91
94
|
// name: trim trailing spaces before '='; value: trim leading spaces after it
|
|
92
95
|
let nameEnd = eq
|
|
@@ -125,6 +128,8 @@ export function hasDuplicateCookie(header: string | null | undefined, targetName
|
|
|
125
128
|
let found = false
|
|
126
129
|
const len = header.length
|
|
127
130
|
let pos = 0
|
|
131
|
+
// Kept across segments as in parseCookies.
|
|
132
|
+
let eq = header.indexOf("=")
|
|
128
133
|
while (pos < len) {
|
|
129
134
|
let end = header.indexOf(";", pos)
|
|
130
135
|
if (end === -1) end = len
|
|
@@ -132,7 +137,7 @@ export function hasDuplicateCookie(header: string | null | undefined, targetName
|
|
|
132
137
|
while (start < end && header.charCodeAt(start) === 32) start++
|
|
133
138
|
let stop = end
|
|
134
139
|
while (stop > start && header.charCodeAt(stop - 1) === 32) stop--
|
|
135
|
-
|
|
140
|
+
if (eq !== -1 && eq < start) eq = header.indexOf("=", start)
|
|
136
141
|
if (eq > start && eq < stop) {
|
|
137
142
|
let nameEnd = eq
|
|
138
143
|
while (nameEnd > start && header.charCodeAt(nameEnd - 1) === 32) nameEnd--
|
|
@@ -23,7 +23,7 @@ if (!copies.includes(here) && copies.push(here) > 1) {
|
|
|
23
23
|
|
|
24
24
|
/** This copy's version: a literal like the package root's `VERSION` (no fs on the edge), which
|
|
25
25
|
* `scripts/version.ts` rewrites on every release and `check:publish` asserts. */
|
|
26
|
-
const COPY_VERSION = "5.
|
|
26
|
+
const COPY_VERSION = "5.5.1"
|
|
27
27
|
|
|
28
28
|
/** Record this copy's version beside its URL. Called by each `server()` behind the development guard,
|
|
29
29
|
* so a production bundle drops it with the version; a copy that built a server has recorded one. */
|