@nifrajs/core 3.5.0 → 4.0.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/README.md +26 -0
- package/dist/assurance.d.ts +7 -0
- package/dist/assurance.d.ts.map +1 -1
- package/dist/assurance.js +39 -3
- package/dist/assurance.js.map +1 -1
- package/dist/capabilities.d.ts +20 -2
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +47 -15
- package/dist/capabilities.js.map +1 -1
- package/dist/diff.d.ts +2 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +26 -2
- package/dist/diff.js.map +1 -1
- package/dist/durable-adapters.d.ts.map +1 -1
- package/dist/durable-adapters.js +16 -3
- package/dist/durable-adapters.js.map +1 -1
- package/dist/durable-effect.d.ts.map +1 -1
- package/dist/durable-effect.js +6 -3
- package/dist/durable-effect.js.map +1 -1
- package/dist/edge-kit.d.ts +2 -0
- package/dist/edge-kit.d.ts.map +1 -1
- package/dist/edge-kit.js +2 -0
- package/dist/edge-kit.js.map +1 -1
- package/dist/effect-lifecycle.d.ts.map +1 -1
- package/dist/effect-lifecycle.js +2 -1
- package/dist/effect-lifecycle.js.map +1 -1
- package/dist/effect-scope.d.ts.map +1 -1
- package/dist/effect-scope.js +1 -1
- package/dist/effect-scope.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 +24 -1
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +168 -13
- package/dist/evidence.js.map +1 -1
- package/dist/idempotency.d.ts +31 -0
- package/dist/idempotency.d.ts.map +1 -1
- package/dist/idempotency.js +40 -8
- package/dist/idempotency.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/code-unit-order.d.ts +7 -0
- package/dist/internal/code-unit-order.d.ts.map +1 -0
- package/dist/internal/code-unit-order.js +9 -0
- package/dist/internal/code-unit-order.js.map +1 -0
- package/dist/internal/durable-saga-protocol.d.ts.map +1 -1
- package/dist/internal/durable-saga-protocol.js +2 -0
- package/dist/internal/durable-saga-protocol.js.map +1 -1
- package/dist/internal/route-catalog.d.ts +6 -6
- package/dist/internal/route-catalog.d.ts.map +1 -1
- package/dist/internal/route-catalog.js +20 -18
- package/dist/internal/route-catalog.js.map +1 -1
- package/dist/internal/route-compiler.d.ts +7 -6
- package/dist/internal/route-compiler.d.ts.map +1 -1
- package/dist/internal/route-compiler.js +36 -4
- package/dist/internal/route-compiler.js.map +1 -1
- package/dist/internal/route-execution.d.ts +7 -3
- package/dist/internal/route-execution.d.ts.map +1 -1
- package/dist/internal/route-execution.js.map +1 -1
- package/dist/internal/route-lanes.d.ts +1 -0
- package/dist/internal/route-lanes.d.ts.map +1 -1
- package/dist/internal/route-lanes.js +34 -29
- package/dist/internal/route-lanes.js.map +1 -1
- package/dist/internal/route-program.d.ts +8 -2
- package/dist/internal/route-program.d.ts.map +1 -1
- package/dist/internal/route-program.js +222 -20
- package/dist/internal/route-program.js.map +1 -1
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +8 -5
- package/dist/ledger.js.map +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +41 -37
- package/dist/manifest.js.map +1 -1
- package/dist/mount.d.ts +36 -0
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +32 -0
- package/dist/mount.js.map +1 -1
- package/dist/reflection.d.ts +26 -0
- package/dist/reflection.d.ts.map +1 -1
- package/dist/reflection.js +65 -1
- package/dist/reflection.js.map +1 -1
- package/dist/response-observer.d.ts +1 -1
- package/dist/response-observer.d.ts.map +1 -1
- package/dist/response-observer.js.map +1 -1
- package/dist/router/overlap.d.ts +23 -0
- package/dist/router/overlap.d.ts.map +1 -0
- package/dist/router/overlap.js +224 -0
- package/dist/router/overlap.js.map +1 -0
- package/dist/router/pattern.d.ts +117 -2
- package/dist/router/pattern.d.ts.map +1 -1
- package/dist/router/pattern.js +283 -23
- package/dist/router/pattern.js.map +1 -1
- package/dist/router/router.d.ts +18 -4
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +51 -44
- package/dist/router/router.js.map +1 -1
- package/dist/server/auth.d.ts +53 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +9 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/bare-error-lane.d.ts +12 -7
- package/dist/server/bare-error-lane.d.ts.map +1 -1
- package/dist/server/bare-error-lane.js +13 -11
- package/dist/server/bare-error-lane.js.map +1 -1
- package/dist/server/body-lane.d.ts +20 -2
- package/dist/server/body-lane.d.ts.map +1 -1
- package/dist/server/body-lane.js +26 -7
- package/dist/server/body-lane.js.map +1 -1
- package/dist/server/body-parser.d.ts +68 -0
- package/dist/server/body-parser.d.ts.map +1 -0
- package/dist/server/body-parser.js +172 -0
- package/dist/server/body-parser.js.map +1 -0
- package/dist/server/body.d.ts.map +1 -1
- package/dist/server/body.js +62 -7
- package/dist/server/body.js.map +1 -1
- package/dist/server/client-ip.d.ts +8 -0
- package/dist/server/client-ip.d.ts.map +1 -1
- package/dist/server/client-ip.js +8 -0
- package/dist/server/client-ip.js.map +1 -1
- package/dist/server/context.d.ts +71 -6
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/contract.d.ts +15 -5
- package/dist/server/contract.d.ts.map +1 -1
- package/dist/server/contract.js +25 -4
- package/dist/server/contract.js.map +1 -1
- package/dist/server/core-copies.d.ts +2 -0
- package/dist/server/core-copies.d.ts.map +1 -0
- package/dist/server/core-copies.js +20 -0
- package/dist/server/core-copies.js.map +1 -0
- package/dist/server/edge.d.ts.map +1 -1
- package/dist/server/edge.js +2 -0
- package/dist/server/edge.js.map +1 -1
- package/dist/server/http.d.ts +11 -0
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +36 -0
- package/dist/server/http.js.map +1 -1
- package/dist/server/idempotency-lane.d.ts +6 -0
- package/dist/server/idempotency-lane.d.ts.map +1 -1
- package/dist/server/idempotency-lane.js +130 -73
- package/dist/server/idempotency-lane.js.map +1 -1
- package/dist/server/install.d.ts +4 -0
- package/dist/server/install.d.ts.map +1 -1
- package/dist/server/install.js +4 -0
- package/dist/server/install.js.map +1 -1
- package/dist/server/methods.d.ts +45 -0
- package/dist/server/methods.d.ts.map +1 -0
- package/dist/server/methods.js +37 -0
- package/dist/server/methods.js.map +1 -0
- package/dist/server/multipart.d.ts +63 -0
- package/dist/server/multipart.d.ts.map +1 -0
- package/dist/server/multipart.js +374 -0
- package/dist/server/multipart.js.map +1 -0
- package/dist/server/node-outcome-hook.d.ts +20 -0
- package/dist/server/node-outcome-hook.d.ts.map +1 -1
- package/dist/server/node-outcome-hook.js +15 -6
- package/dist/server/node-outcome-hook.js.map +1 -1
- package/dist/server/not-found-answer.d.ts +40 -0
- package/dist/server/not-found-answer.d.ts.map +1 -0
- package/dist/server/not-found-answer.js +56 -0
- package/dist/server/not-found-answer.js.map +1 -0
- package/dist/server/not-found.d.ts +23 -0
- package/dist/server/not-found.d.ts.map +1 -0
- package/dist/server/not-found.js +81 -0
- package/dist/server/not-found.js.map +1 -0
- package/dist/server/plugin.d.ts +26 -1
- package/dist/server/plugin.d.ts.map +1 -1
- package/dist/server/plugin.js.map +1 -1
- package/dist/server/proto-guard.d.ts +18 -5
- package/dist/server/proto-guard.d.ts.map +1 -1
- package/dist/server/proto-guard.js +114 -9
- package/dist/server/proto-guard.js.map +1 -1
- package/dist/server/query.d.ts +7 -0
- package/dist/server/query.d.ts.map +1 -1
- package/dist/server/query.js +47 -0
- package/dist/server/query.js.map +1 -1
- package/dist/server/registry.d.ts +13 -2
- package/dist/server/registry.d.ts.map +1 -1
- package/dist/server/request-context.d.ts +11 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +33 -19
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/request-lineage.d.ts +8 -0
- package/dist/server/request-lineage.d.ts.map +1 -0
- package/dist/server/request-lineage.js +14 -0
- package/dist/server/request-lineage.js.map +1 -0
- package/dist/server/request-target.d.ts +31 -0
- package/dist/server/request-target.d.ts.map +1 -0
- package/dist/server/request-target.js +44 -0
- package/dist/server/request-target.js.map +1 -0
- package/dist/server/respond.d.ts +1 -1
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +16 -4
- package/dist/server/respond.js.map +1 -1
- package/dist/server/response-contract-lane.d.ts +19 -1
- package/dist/server/response-contract-lane.d.ts.map +1 -1
- package/dist/server/response-contract-lane.js +59 -14
- package/dist/server/response-contract-lane.js.map +1 -1
- package/dist/server/response-observer-runtime.d.ts +9 -5
- package/dist/server/response-observer-runtime.d.ts.map +1 -1
- package/dist/server/response-observer-runtime.js +10 -6
- package/dist/server/response-observer-runtime.js.map +1 -1
- package/dist/server/runtime-core.d.ts +2 -0
- package/dist/server/runtime-core.d.ts.map +1 -1
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server-types.d.ts +53 -3
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +191 -19
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +1206 -218
- package/dist/server/server.js.map +1 -1
- package/dist/server/sse.d.ts.map +1 -1
- package/dist/server/sse.js +10 -7
- package/dist/server/sse.js.map +1 -1
- package/dist/server/static-headers.d.ts +3 -0
- package/dist/server/static-headers.d.ts.map +1 -1
- package/dist/server/static-headers.js +19 -1
- package/dist/server/static-headers.js.map +1 -1
- package/dist/server/transport-lane.d.ts +8 -0
- package/dist/server/transport-lane.d.ts.map +1 -1
- package/dist/server/transport-lane.js +9 -8
- package/dist/server/transport-lane.js.map +1 -1
- package/dist/server/validation.d.ts.map +1 -1
- package/dist/server/validation.js +5 -1
- package/dist/server/validation.js.map +1 -1
- package/dist/server/websocket.d.ts +10 -3
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +7 -4
- package/dist/server/websocket.js.map +1 -1
- package/dist/server/ws-bun.d.ts +2 -0
- package/dist/server/ws-bun.d.ts.map +1 -1
- package/dist/server/ws-bun.js +6 -3
- package/dist/server/ws-bun.js.map +1 -1
- package/dist/server/ws-hook.d.ts +2 -1
- package/dist/server/ws-hook.d.ts.map +1 -1
- package/dist/server.d.ts +7 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +4 -1
- package/dist/server.js.map +1 -1
- package/dist/single-copy-register.js +5 -0
- package/dist/single-copy-register.js.map +1 -1
- package/dist/single-copy.d.ts +46 -3
- package/dist/single-copy.d.ts.map +1 -1
- package/dist/single-copy.js +125 -12
- package/dist/single-copy.js.map +1 -1
- package/dist/tool-contract.d.ts +23 -0
- package/dist/tool-contract.d.ts.map +1 -1
- package/dist/tool-contract.js +37 -3
- package/dist/tool-contract.js.map +1 -1
- 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 +27 -2
- package/src/assurance.ts +52 -2
- package/src/capabilities.ts +81 -22
- package/src/diff.ts +31 -6
- package/src/durable-adapters.ts +20 -3
- package/src/durable-effect.ts +10 -3
- package/src/edge-kit.ts +7 -0
- package/src/effect-lifecycle.ts +5 -1
- package/src/effect-scope.ts +2 -1
- package/src/errors.ts +3 -0
- package/src/evidence.ts +237 -12
- package/src/idempotency.ts +72 -8
- package/src/index.ts +40 -3
- package/src/internal/code-unit-order.ts +8 -0
- package/src/internal/durable-saga-protocol.ts +2 -0
- package/src/internal/route-catalog.ts +19 -18
- package/src/internal/route-compiler.ts +62 -13
- package/src/internal/route-execution.ts +13 -3
- package/src/internal/route-lanes.ts +37 -28
- package/src/internal/route-program.ts +434 -20
- package/src/ledger.ts +11 -5
- package/src/manifest.ts +41 -36
- package/src/mount.ts +62 -0
- package/src/reflection.ts +84 -1
- package/src/response-observer.ts +1 -0
- package/src/router/overlap.ts +258 -0
- package/src/router/pattern.ts +349 -25
- package/src/router/router.ts +76 -51
- package/src/server/auth.ts +75 -0
- package/src/server/bare-error-lane.ts +12 -11
- package/src/server/body-lane.ts +46 -6
- package/src/server/body-parser.ts +241 -0
- package/src/server/body.ts +82 -9
- package/src/server/client-ip.ts +9 -0
- package/src/server/context.ts +219 -12
- package/src/server/contract.ts +54 -9
- package/src/server/core-copies.ts +21 -0
- package/src/server/edge.ts +2 -0
- package/src/server/http.ts +34 -0
- package/src/server/idempotency-lane.ts +147 -72
- package/src/server/install.ts +6 -0
- package/src/server/methods.ts +143 -0
- package/src/server/multipart.ts +424 -0
- package/src/server/node-outcome-hook.ts +40 -7
- package/src/server/not-found-answer.ts +109 -0
- package/src/server/not-found.ts +121 -0
- package/src/server/plugin.ts +45 -1
- package/src/server/proto-guard.ts +128 -9
- package/src/server/query.ts +49 -0
- package/src/server/registry.ts +17 -2
- package/src/server/request-context.ts +44 -16
- package/src/server/request-lineage.ts +15 -0
- package/src/server/request-target.ts +46 -0
- package/src/server/respond.ts +14 -3
- package/src/server/response-contract-lane.ts +97 -16
- package/src/server/response-observer-runtime.ts +37 -8
- package/src/server/runtime-core.ts +2 -0
- package/src/server/server-types.ts +59 -2
- package/src/server/server.ts +1613 -269
- package/src/server/sse.ts +10 -8
- package/src/server/static-headers.ts +22 -1
- package/src/server/transport-lane.ts +17 -9
- package/src/server/validation.ts +7 -1
- package/src/server/websocket.ts +23 -9
- package/src/server/ws-bun.ts +8 -3
- package/src/server/ws-hook.ts +2 -1
- package/src/server.ts +38 -2
- package/src/single-copy-register.ts +5 -0
- package/src/single-copy.ts +160 -12
- package/src/tool-contract.ts +66 -3
- package/src/transport-codec.ts +2 -1
package/src/server/sse.ts
CHANGED
|
@@ -106,12 +106,6 @@ function formatMessage(m: SSEMessage): string {
|
|
|
106
106
|
return `${frame}\n` // the blank line terminates the event
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
let neverAbort: AbortSignal | undefined
|
|
110
|
-
function getNeverAbort(): AbortSignal {
|
|
111
|
-
neverAbort ??= new AbortController().signal
|
|
112
|
-
return neverAbort
|
|
113
|
-
}
|
|
114
|
-
|
|
115
109
|
export function sse(
|
|
116
110
|
c: SSEContext,
|
|
117
111
|
run: (stream: SSEStream) => void | Promise<void>,
|
|
@@ -119,6 +113,9 @@ export function sse(
|
|
|
119
113
|
): Response {
|
|
120
114
|
const encoder = new TextEncoder()
|
|
121
115
|
const requestSignal: AbortSignal | undefined = c.req?.signal
|
|
116
|
+
// The producer's signal also aborts when the runtime cancels the body: a writer that finds the
|
|
117
|
+
// client gone cancels the stream, and a request signal alone may never fire.
|
|
118
|
+
const stopped = new AbortController()
|
|
122
119
|
let closed = false
|
|
123
120
|
let heartbeat: ReturnType<typeof setInterval> | undefined
|
|
124
121
|
let onAbort: (() => void) | undefined
|
|
@@ -147,16 +144,20 @@ export function sse(
|
|
|
147
144
|
if (!closed) controller.enqueue(encoder.encode(formatMessage(message)))
|
|
148
145
|
},
|
|
149
146
|
close,
|
|
150
|
-
signal:
|
|
147
|
+
signal: stopped.signal,
|
|
151
148
|
}
|
|
152
149
|
|
|
153
150
|
// Stop the moment the client goes away.
|
|
154
151
|
if (requestSignal !== undefined) {
|
|
155
152
|
if (requestSignal.aborted) {
|
|
153
|
+
stopped.abort()
|
|
156
154
|
close()
|
|
157
155
|
return
|
|
158
156
|
}
|
|
159
|
-
onAbort =
|
|
157
|
+
onAbort = () => {
|
|
158
|
+
stopped.abort()
|
|
159
|
+
close()
|
|
160
|
+
}
|
|
160
161
|
requestSignal.addEventListener("abort", onAbort, { once: true })
|
|
161
162
|
}
|
|
162
163
|
|
|
@@ -183,6 +184,7 @@ export function sse(
|
|
|
183
184
|
// Consumer/runtime canceled (client gone) - pending sends become no-ops.
|
|
184
185
|
closed = true
|
|
185
186
|
teardown()
|
|
187
|
+
stopped.abort()
|
|
186
188
|
},
|
|
187
189
|
})
|
|
188
190
|
|
|
@@ -39,6 +39,9 @@ export interface StaticResponseHeaders {
|
|
|
39
39
|
readonly jsonHeaders: Headers
|
|
40
40
|
/** `{ status: 200, headers: jsonHeaders }` - a static app's whole per-response header cost. */
|
|
41
41
|
readonly jsonInit200: ResponseInit
|
|
42
|
+
/** For a group: the values its enclosing scopes declared. On the group's routes its own value of
|
|
43
|
+
* the same name replaces one of these, so the more specific scope wins. */
|
|
44
|
+
readonly inherited?: Readonly<Record<string, string>> | undefined
|
|
42
45
|
/**
|
|
43
46
|
* The same shape, but carrying the content-type THIS runtime's `Response.json` emits - which is not
|
|
44
47
|
* always the framework init's (Deno omits the charset). Used by the lane that would otherwise call
|
|
@@ -134,8 +137,26 @@ export function mergeStaticHeaderRecord<V>(
|
|
|
134
137
|
markLowercase = false,
|
|
135
138
|
): Record<string, V | string> {
|
|
136
139
|
const merged: Record<string, V | string> = { ...statics }
|
|
140
|
+
const ownNames = Object.keys(own)
|
|
141
|
+
|
|
142
|
+
// The common Node-native route has one request-owned lower-case header (usually the request id)
|
|
143
|
+
// on top of a few static defaults. Keep the static-first property order, but avoid the generic
|
|
144
|
+
// case-folding loop and its per-name helper calls for that shape. A mixed-case name, an undefined
|
|
145
|
+
// value, or multiple names falls through to the full collision-aware path below.
|
|
146
|
+
if (ownNames.length === 1) {
|
|
147
|
+
const name = ownNames[0]!
|
|
148
|
+
const value = own[name] as V
|
|
149
|
+
if (value !== undefined && name.toLowerCase() === name) {
|
|
150
|
+
if (name === "__proto__") store(merged, name, value)
|
|
151
|
+
else merged[name] = value
|
|
152
|
+
if (markLowercase)
|
|
153
|
+
markLowercaseHeaderKeys(merged as Record<string, string | readonly string[]>)
|
|
154
|
+
return merged
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
137
158
|
let allLowercase = true
|
|
138
|
-
for (const name of
|
|
159
|
+
for (const name of ownNames) {
|
|
139
160
|
const value = own[name] as V
|
|
140
161
|
if (value === undefined) continue
|
|
141
162
|
const lower = name.toLowerCase()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { TransportCodec, TransportCodecRegistry } from "../transport-codec.ts"
|
|
3
3
|
import { readBoundedBytes } from "./body.ts"
|
|
4
4
|
import { jsonError } from "./http.ts"
|
|
5
|
-
import {
|
|
5
|
+
import { guardDecodedValue, type ProtoPoisoning } from "./proto-guard.ts"
|
|
6
6
|
import { isResponseResult, PRE_DECODED_BODY, type PreDecodedBody } from "./runtime-core.ts"
|
|
7
7
|
import type { AnyServer, IdentityPlugin } from "./server.ts"
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ interface TransportBodySource {
|
|
|
14
14
|
|
|
15
15
|
export type TransportDecodeResult =
|
|
16
16
|
| { readonly matched: false }
|
|
17
|
-
| { readonly matched: true; readonly value: unknown }
|
|
17
|
+
| { readonly matched: true; readonly value: unknown; readonly byteLength: number }
|
|
18
18
|
| { readonly matched: true; readonly response: Response }
|
|
19
19
|
|
|
20
20
|
export interface TransportRuntime {
|
|
@@ -36,6 +36,13 @@ export interface TransportCodecsOptions {
|
|
|
36
36
|
* a poisoned payload answers the same flat 400 as an undecodable one.
|
|
37
37
|
*/
|
|
38
38
|
readonly protoPoisoning?: ProtoPoisoning
|
|
39
|
+
/**
|
|
40
|
+
* Whether a decoded request body may hold a `RegExp` (the rich wire codec carries them). Default
|
|
41
|
+
* `false`: a client-supplied pattern is code, and one with catastrophic backtracking stalls the
|
|
42
|
+
* event loop the moment anything runs it. A refused body answers the same flat 400 as an
|
|
43
|
+
* undecodable one.
|
|
44
|
+
*/
|
|
45
|
+
readonly acceptRegExp?: boolean
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
export function transportCodecs(
|
|
@@ -47,6 +54,7 @@ export function transportCodecs(
|
|
|
47
54
|
throw new RangeError("transport maxBytes must be a non-negative safe integer")
|
|
48
55
|
}
|
|
49
56
|
const protoPoisoning = options.protoPoisoning ?? "reject"
|
|
57
|
+
const acceptRegExp = options.acceptRegExp === true
|
|
50
58
|
const runtime: TransportRuntime = Object.freeze({
|
|
51
59
|
responseCodec(accept: string | null): TransportCodec {
|
|
52
60
|
try {
|
|
@@ -84,7 +92,8 @@ export function transportCodecs(
|
|
|
84
92
|
// payload answers exactly like an undecodable one.
|
|
85
93
|
return {
|
|
86
94
|
matched: true,
|
|
87
|
-
value:
|
|
95
|
+
value: guardDecodedValue(codec.decode(text), protoPoisoning, acceptRegExp),
|
|
96
|
+
byteLength: read.bytes.byteLength,
|
|
88
97
|
}
|
|
89
98
|
} catch {
|
|
90
99
|
return {
|
|
@@ -110,7 +119,6 @@ export function transportCodecs(
|
|
|
110
119
|
) {
|
|
111
120
|
return undefined
|
|
112
121
|
}
|
|
113
|
-
const replacement = request.clone()
|
|
114
122
|
const decoded = await runtime.decodeRequest(request, contentType, maxBytes)
|
|
115
123
|
if (!decoded.matched) return undefined
|
|
116
124
|
if ("response" in decoded) return decoded.response
|
|
@@ -119,19 +127,19 @@ export function transportCodecs(
|
|
|
119
127
|
// the body lane takes the stash verbatim (this lane owns the cap and poisoning policy), so
|
|
120
128
|
// the placeholder body is never parsed and codec machinery stays out of the kernel.
|
|
121
129
|
const headers: Record<string, string> = {}
|
|
122
|
-
|
|
130
|
+
request.headers.forEach((value, name) => {
|
|
123
131
|
headers[name] = value
|
|
124
132
|
})
|
|
125
133
|
headers["content-type"] = "application/json"
|
|
126
134
|
headers["content-length"] = "2"
|
|
127
135
|
delete headers["transfer-encoding"]
|
|
128
|
-
const normalized = new Request(
|
|
129
|
-
method:
|
|
136
|
+
const normalized = new Request(request.url, {
|
|
137
|
+
method: request.method,
|
|
130
138
|
headers,
|
|
131
139
|
body: "{}",
|
|
132
|
-
signal:
|
|
140
|
+
signal: request.signal as never,
|
|
133
141
|
})
|
|
134
|
-
const stash: PreDecodedBody = { value: decoded.value }
|
|
142
|
+
const stash: PreDecodedBody = { value: decoded.value, byteLength: decoded.byteLength }
|
|
135
143
|
Object.defineProperty(normalized, PRE_DECODED_BODY, { value: stash })
|
|
136
144
|
return normalized
|
|
137
145
|
})
|
package/src/server/validation.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import type { StandardIssue } from "../schema/standard.ts"
|
|
2
2
|
import { type ResponseResult, status } from "./runtime-core.ts"
|
|
3
3
|
|
|
4
|
+
/** The most issues a 422 body lists: a body of a million bad array items must not answer with a
|
|
5
|
+
* response many times its own size. */
|
|
6
|
+
const MAX_VALIDATION_ISSUES = 100
|
|
7
|
+
|
|
4
8
|
function validationIssues(issues: ReadonlyArray<StandardIssue>): {
|
|
5
9
|
ok: false
|
|
6
10
|
error: string
|
|
7
11
|
issues: unknown[]
|
|
8
12
|
} {
|
|
9
|
-
const
|
|
13
|
+
const listed =
|
|
14
|
+
issues.length > MAX_VALIDATION_ISSUES ? issues.slice(0, MAX_VALIDATION_ISSUES) : issues
|
|
15
|
+
const serialized = listed.map((issue) => {
|
|
10
16
|
const path = issue.path?.map((seg) => String(typeof seg === "object" ? seg.key : seg))
|
|
11
17
|
return path !== undefined ? { message: issue.message, path } : { message: issue.message }
|
|
12
18
|
})
|
package/src/server/websocket.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
} from "../schema/standard.ts"
|
|
18
18
|
import { validateStandard } from "../schema/standard.ts"
|
|
19
19
|
import { decodeTransportFrame, type TransportCodecRegistry } from "../transport-codec.ts"
|
|
20
|
+
import { guardDecodedValue, type ProtoPoisoning, parseJsonGuarded } from "./proto-guard.ts"
|
|
20
21
|
|
|
21
22
|
type MaybePromise<T> = T | Promise<T>
|
|
22
23
|
|
|
@@ -180,6 +181,10 @@ export interface WebSocketHandler<
|
|
|
180
181
|
transport?: {
|
|
181
182
|
readonly registry: TransportCodecRegistry
|
|
182
183
|
readonly maxBytes?: number
|
|
184
|
+
/** Whether an inbound frame may decode to a value holding a `RegExp`. Default `false`: a
|
|
185
|
+
* client-supplied pattern is code (catastrophic backtracking stalls the event loop), so such a
|
|
186
|
+
* frame is treated as invalid. */
|
|
187
|
+
readonly acceptRegExp?: boolean
|
|
183
188
|
}
|
|
184
189
|
open?(ws: NifraWebSocket<Data>): MaybePromise<void>
|
|
185
190
|
message?(ws: NifraWebSocket<Data>, data: WsMessageInput<Schema>): MaybePromise<void>
|
|
@@ -223,7 +228,7 @@ export type WsAttach = (
|
|
|
223
228
|
socket: StandardWebSocket,
|
|
224
229
|
handler: WebSocketHandler,
|
|
225
230
|
data: unknown,
|
|
226
|
-
options: { openNow: boolean; pubsub: TopicRegistry; maxPayloadBytes?: number },
|
|
231
|
+
options: { openNow: boolean; pubsub: TopicRegistry; maxPayloadBytes?: number | undefined },
|
|
227
232
|
) => NifraWebSocket
|
|
228
233
|
|
|
229
234
|
/**
|
|
@@ -263,7 +268,7 @@ export function attachWebSocket<
|
|
|
263
268
|
socket: StandardWebSocket,
|
|
264
269
|
handler: WebSocketHandler<Data, Env, Schema, Send>,
|
|
265
270
|
data: unknown,
|
|
266
|
-
options: { openNow: boolean; pubsub: TopicRegistry; maxPayloadBytes?: number },
|
|
271
|
+
options: { openNow: boolean; pubsub: TopicRegistry; maxPayloadBytes?: number | undefined },
|
|
267
272
|
): NifraWebSocket<Data> {
|
|
268
273
|
const { pubsub } = options
|
|
269
274
|
let ws!: NifraWebSocket<Data>
|
|
@@ -436,8 +441,13 @@ export function createWebSocketSender(
|
|
|
436
441
|
* parse as JSON, run the Standard Schema, then call the user's `message` with the typed value, or
|
|
437
442
|
* `onInvalidMessage` on failure. Returns the handler unchanged when no schema is set. Called once at
|
|
438
443
|
* `app.ws()` registration, so every adapter dispatches validated messages with no per-adapter code.
|
|
444
|
+
* `protoPoisoning` is the app's body policy: a frame with a `__proto__` key gets what a JSON body
|
|
445
|
+
* with one gets, and under `"reject"` reads as invalid JSON.
|
|
439
446
|
*/
|
|
440
|
-
export function wrapWebSocketMessageValidation(
|
|
447
|
+
export function wrapWebSocketMessageValidation(
|
|
448
|
+
handler: WebSocketHandler,
|
|
449
|
+
protoPoisoning: ProtoPoisoning = "reject",
|
|
450
|
+
): WebSocketHandler {
|
|
441
451
|
const schema = handler.messageSchema
|
|
442
452
|
if (schema === undefined) return handler
|
|
443
453
|
// The handler is type-erased here; the user's `message` really accepts the schema's validated output
|
|
@@ -452,12 +462,16 @@ export function wrapWebSocketMessageValidation(handler: WebSocketHandler): WebSo
|
|
|
452
462
|
const text = typeof raw === "string" ? raw : WS_MESSAGE_DECODER.decode(raw)
|
|
453
463
|
parsed =
|
|
454
464
|
handler.transport === undefined
|
|
455
|
-
?
|
|
456
|
-
:
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
465
|
+
? parseJsonGuarded(text, protoPoisoning)
|
|
466
|
+
: guardDecodedValue(
|
|
467
|
+
decodeTransportFrame(text, handler.transport.registry, {
|
|
468
|
+
...(handler.transport.maxBytes === undefined
|
|
469
|
+
? {}
|
|
470
|
+
: { maxBytes: handler.transport.maxBytes }),
|
|
471
|
+
}),
|
|
472
|
+
protoPoisoning,
|
|
473
|
+
handler.transport.acceptRegExp === true,
|
|
474
|
+
)
|
|
461
475
|
} catch {
|
|
462
476
|
return onInvalid?.(ws, [{ message: "invalid JSON" }], raw)
|
|
463
477
|
}
|
package/src/server/ws-bun.ts
CHANGED
|
@@ -18,6 +18,8 @@ import type { BunWsHandlers } from "./ws-hook.ts"
|
|
|
18
18
|
* `upgrade()`-seeded user data, and the memoized portable {@link NifraWebSocket} wrapper. */
|
|
19
19
|
export interface BunWsData {
|
|
20
20
|
readonly handler: WebSocketHandler
|
|
21
|
+
/** Mounted/modern upgrades carry this registry; old/manual fixtures may omit it. */
|
|
22
|
+
readonly pubsub?: TopicRegistry
|
|
21
23
|
data: unknown
|
|
22
24
|
nifra?: NifraWebSocket
|
|
23
25
|
}
|
|
@@ -57,6 +59,9 @@ function wrapBunSocket(
|
|
|
57
59
|
nativePubsub: boolean,
|
|
58
60
|
): NifraWebSocket {
|
|
59
61
|
let ws!: NifraWebSocket
|
|
62
|
+
// Mounted child apps carry their own TopicRegistry in the upgrade outcome. Keep the argument as a
|
|
63
|
+
// fallback for old/manual Bun upgrades, but use the per-connection registry for composed mounts.
|
|
64
|
+
const pubsub = raw.data.pubsub ?? topics
|
|
60
65
|
const reportError = (error: unknown): void => reportWsError(error, ws, handler)
|
|
61
66
|
const send = createWebSocketSender(
|
|
62
67
|
(data) => {
|
|
@@ -75,10 +80,10 @@ function wrapBunSocket(
|
|
|
75
80
|
},
|
|
76
81
|
subscribe: nativePubsub
|
|
77
82
|
? (topic) => raw.subscribe(topic)
|
|
78
|
-
: (topic) =>
|
|
83
|
+
: (topic) => pubsub.subscribe(topic, ws),
|
|
79
84
|
unsubscribe: nativePubsub
|
|
80
85
|
? (topic) => raw.unsubscribe(topic)
|
|
81
|
-
: (topic) =>
|
|
86
|
+
: (topic) => pubsub.unsubscribe(topic, ws),
|
|
82
87
|
get data() {
|
|
83
88
|
return raw.data.data
|
|
84
89
|
},
|
|
@@ -151,7 +156,7 @@ export function createBunWsHandlers(topics: TopicRegistry, nativePubsub = false)
|
|
|
151
156
|
const nifra = ws.data.nifra ?? wrapBunSocket(ws, topics, ws.data.handler, nativePubsub)
|
|
152
157
|
// Registry mode: drop topic subscriptions so it never holds a dead socket. Native mode: Bun
|
|
153
158
|
// unsubscribes the socket from every topic on close, so there is nothing to sweep here.
|
|
154
|
-
if (!nativePubsub) topics.unsubscribeAll(nifra)
|
|
159
|
+
if (!nativePubsub) (ws.data.pubsub ?? topics).unsubscribeAll(nifra)
|
|
155
160
|
dispatchWsCallback(() => ws.data.handler.close?.(nifra, code, reason), nifra, ws.data.handler)
|
|
156
161
|
},
|
|
157
162
|
}
|
package/src/server/ws-hook.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Only type declarations below - this module must stay weightless, it is always in the base bundle.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import type { ProtoPoisoning } from "./proto-guard.ts"
|
|
11
12
|
import type {
|
|
12
13
|
NifraWebSocket,
|
|
13
14
|
StandardWebSocket,
|
|
@@ -27,7 +28,7 @@ export interface BunWsHandlers {
|
|
|
27
28
|
/** What `@nifrajs/core/ws` registers: everything `server.ts` and `toFetchHandler` need at runtime. */
|
|
28
29
|
export interface WsRuntime {
|
|
29
30
|
/** `wrapWebSocketMessageValidation` - applied once at `app.ws()` registration. */
|
|
30
|
-
wrapHandler(handler: WebSocketHandler): WebSocketHandler
|
|
31
|
+
wrapHandler(handler: WebSocketHandler, protoPoisoning: ProtoPoisoning): WebSocketHandler
|
|
31
32
|
/** One in-process pub/sub registry per app (backs `ws.subscribe` + `app.publish`). */
|
|
32
33
|
createTopics(): TopicRegistry
|
|
33
34
|
/** The Bun `websocket` config for `listen()` when the app has WS routes. `nativePubsub` routes
|
package/src/server.ts
CHANGED
|
@@ -11,7 +11,18 @@
|
|
|
11
11
|
export { FrameworkError, RouteConfigError, type RouteConfigErrorCode } from "./errors.ts"
|
|
12
12
|
export { FRAMEWORK_NAME, type FrameworkName } from "./internal/brand.ts"
|
|
13
13
|
export { isSameOriginRequest } from "./internal/same-origin.ts"
|
|
14
|
-
export {
|
|
14
|
+
export {
|
|
15
|
+
ROUTE_PATTERN_OVERLAP_MAX_STATES,
|
|
16
|
+
RoutePatternOverlapLimitError,
|
|
17
|
+
routePatternOverlap,
|
|
18
|
+
} from "./router/overlap.ts"
|
|
19
|
+
export {
|
|
20
|
+
METHODS,
|
|
21
|
+
type Method,
|
|
22
|
+
type RouteMethod,
|
|
23
|
+
Router,
|
|
24
|
+
type RouterMatch,
|
|
25
|
+
} from "./router/router.ts"
|
|
15
26
|
export type {
|
|
16
27
|
InferInput,
|
|
17
28
|
InferOutput,
|
|
@@ -21,11 +32,24 @@ export type {
|
|
|
21
32
|
StandardTypes,
|
|
22
33
|
ValidationOutcome,
|
|
23
34
|
} from "./schema/standard.ts"
|
|
35
|
+
export {
|
|
36
|
+
type AuthenticationFailure,
|
|
37
|
+
type AuthenticationFailureReason,
|
|
38
|
+
type AuthenticationInput,
|
|
39
|
+
type AuthenticationResult,
|
|
40
|
+
type AuthenticationStage,
|
|
41
|
+
type AuthenticationSuccess,
|
|
42
|
+
type AuthHeaders,
|
|
43
|
+
type AuthMaybePromise,
|
|
44
|
+
authenticated,
|
|
45
|
+
rejected,
|
|
46
|
+
} from "./server/auth.ts"
|
|
24
47
|
export type {
|
|
25
48
|
Context,
|
|
26
49
|
Params,
|
|
27
50
|
Platform,
|
|
28
51
|
Prettify,
|
|
52
|
+
RequestPath,
|
|
29
53
|
ResponseControls,
|
|
30
54
|
RouteSchema,
|
|
31
55
|
} from "./server/context.ts"
|
|
@@ -71,7 +95,15 @@ export {
|
|
|
71
95
|
toVercelHandler,
|
|
72
96
|
type VercelHandler,
|
|
73
97
|
} from "./server/platform-adapters.ts"
|
|
74
|
-
export type {
|
|
98
|
+
export type {
|
|
99
|
+
JoinRoutePath,
|
|
100
|
+
PrefixRegistry,
|
|
101
|
+
Registry,
|
|
102
|
+
ResponseMapFor,
|
|
103
|
+
RouteInfo,
|
|
104
|
+
RouteInfoFor,
|
|
105
|
+
} from "./server/registry.ts"
|
|
106
|
+
export { replacedRequestOf } from "./server/request-lineage.ts"
|
|
75
107
|
export {
|
|
76
108
|
type PlainRender,
|
|
77
109
|
type ResponseResult,
|
|
@@ -90,9 +122,13 @@ export {
|
|
|
90
122
|
defineRouterPlugin,
|
|
91
123
|
type Handler,
|
|
92
124
|
type IdentityPlugin,
|
|
125
|
+
type ListenTlsOptions,
|
|
93
126
|
type McpPromptDescriptor,
|
|
94
127
|
type McpResourceDescriptor,
|
|
95
128
|
type Middleware,
|
|
129
|
+
type MountableApp,
|
|
130
|
+
type MountFetchOptions,
|
|
131
|
+
type MountOptions,
|
|
96
132
|
type NifraFeatureVersion,
|
|
97
133
|
type NifraPlugin,
|
|
98
134
|
type NodeRequestContext,
|
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
*
|
|
19
19
|
* `nifra check` reads this specifier out of `bunfig.toml` as the proof that the runtime arm is armed,
|
|
20
20
|
* so the string above is a contract rather than a suggestion.
|
|
21
|
+
*
|
|
22
|
+
* A declared package that cannot be collapsed (two versions, or a file with no counterpart in the
|
|
23
|
+
* app's copy) prints one warning per package naming both copies. Set
|
|
24
|
+
* `"singleCopy": { "packages": [...], "strict": true }` in package.json, or `NIFRA_SINGLE_COPY_STRICT=1`,
|
|
25
|
+
* to make the preload throw instead.
|
|
21
26
|
*/
|
|
22
27
|
import { registerSingleCopy } from "./single-copy"
|
|
23
28
|
|