@nifrajs/core 2.13.0 → 3.1.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/README.md +4 -0
- package/dist/assurance.d.ts +16 -8
- package/dist/assurance.d.ts.map +1 -1
- package/dist/assurance.js +33 -0
- package/dist/assurance.js.map +1 -1
- package/dist/binary.d.ts +32 -0
- package/dist/binary.d.ts.map +1 -1
- package/dist/binary.js +17 -0
- package/dist/binary.js.map +1 -1
- package/dist/durable-adapters.d.ts.map +1 -1
- package/dist/durable-adapters.js +21 -50
- package/dist/durable-adapters.js.map +1 -1
- package/dist/durable-execution.d.ts.map +1 -1
- package/dist/durable-execution.js +49 -69
- package/dist/durable-execution.js.map +1 -1
- package/dist/edge-kit.d.ts +27 -0
- package/dist/edge-kit.d.ts.map +1 -0
- package/dist/edge-kit.js +24 -0
- package/dist/edge-kit.js.map +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/durable-approval-protocol.d.ts +13 -0
- package/dist/internal/durable-approval-protocol.d.ts.map +1 -0
- package/dist/internal/durable-approval-protocol.js +66 -0
- package/dist/internal/durable-approval-protocol.js.map +1 -0
- package/dist/internal/durable-effect-protocol.d.ts +14 -0
- package/dist/internal/durable-effect-protocol.d.ts.map +1 -0
- package/dist/internal/durable-effect-protocol.js +16 -0
- package/dist/internal/durable-effect-protocol.js.map +1 -0
- package/dist/internal/durable-saga-protocol.d.ts +8 -0
- package/dist/internal/durable-saga-protocol.d.ts.map +1 -0
- package/dist/internal/durable-saga-protocol.js +21 -0
- package/dist/internal/durable-saga-protocol.js.map +1 -0
- package/dist/internal/route-catalog.d.ts +3 -0
- package/dist/internal/route-catalog.d.ts.map +1 -1
- package/dist/internal/route-catalog.js +5 -0
- package/dist/internal/route-catalog.js.map +1 -1
- package/dist/internal/route-execution.d.ts +11 -5
- package/dist/internal/route-execution.d.ts.map +1 -1
- package/dist/internal/route-execution.js +2 -1
- package/dist/internal/route-execution.js.map +1 -1
- package/dist/response-observer.d.ts +25 -0
- package/dist/response-observer.d.ts.map +1 -0
- package/dist/response-observer.js +40 -0
- package/dist/response-observer.js.map +1 -0
- package/dist/security/baseline.d.ts +68 -0
- package/dist/security/baseline.d.ts.map +1 -0
- package/dist/security/baseline.js +118 -0
- package/dist/security/baseline.js.map +1 -0
- package/dist/server/bare-error-lane.d.ts +26 -0
- package/dist/server/bare-error-lane.d.ts.map +1 -0
- package/dist/server/bare-error-lane.js +60 -0
- package/dist/server/bare-error-lane.js.map +1 -0
- package/dist/server/body.d.ts +7 -0
- package/dist/server/body.d.ts.map +1 -1
- package/dist/server/body.js +7 -0
- package/dist/server/body.js.map +1 -1
- package/dist/server/bridge-markers.d.ts +25 -0
- package/dist/server/bridge-markers.d.ts.map +1 -0
- package/dist/server/bridge-markers.js +25 -0
- package/dist/server/bridge-markers.js.map +1 -0
- package/dist/server/context.d.ts +6 -1
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/header-case.d.ts.map +1 -1
- package/dist/server/header-case.js +2 -1
- package/dist/server/header-case.js.map +1 -1
- package/dist/server/http.d.ts +36 -3
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +49 -3
- package/dist/server/http.js.map +1 -1
- package/dist/server/install.d.ts +6 -8
- package/dist/server/install.d.ts.map +1 -1
- package/dist/server/install.js +7 -0
- package/dist/server/install.js.map +1 -1
- package/dist/server/node-outcome-hook.d.ts +2 -1
- package/dist/server/node-outcome-hook.d.ts.map +1 -1
- package/dist/server/node-outcome-hook.js.map +1 -1
- package/dist/server/node-outcome.d.ts +17 -2
- package/dist/server/node-outcome.d.ts.map +1 -1
- package/dist/server/node-outcome.js +37 -3
- package/dist/server/node-outcome.js.map +1 -1
- package/dist/server/plugin.d.ts +1 -1
- package/dist/server/plugin.d.ts.map +1 -1
- package/dist/server/query.d.ts +7 -1
- package/dist/server/query.d.ts.map +1 -1
- package/dist/server/query.js +8 -3
- package/dist/server/query.js.map +1 -1
- package/dist/server/registry.d.ts +11 -2
- package/dist/server/registry.d.ts.map +1 -1
- package/dist/server/request-context.d.ts +17 -4
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +82 -45
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +19 -2
- package/dist/server/respond.js.map +1 -1
- package/dist/server/response-contract-lane.d.ts.map +1 -1
- package/dist/server/response-contract-lane.js +5 -2
- package/dist/server/response-contract-lane.js.map +1 -1
- package/dist/server/response-observer-runtime.d.ts +20 -0
- package/dist/server/response-observer-runtime.d.ts.map +1 -0
- package/dist/server/response-observer-runtime.js +95 -0
- package/dist/server/response-observer-runtime.js.map +1 -0
- package/dist/server/runtime-core.d.ts +60 -0
- package/dist/server/runtime-core.d.ts.map +1 -1
- package/dist/server/runtime-core.js +65 -1
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server-types.d.ts +18 -1
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +89 -48
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +397 -276
- package/dist/server/server.js.map +1 -1
- package/dist/server/websocket.d.ts +20 -4
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +81 -11
- package/dist/server/websocket.js.map +1 -1
- package/dist/server/ws-bun.d.ts +1 -1
- package/dist/server/ws-bun.d.ts.map +1 -1
- package/dist/server/ws-bun.js +12 -8
- package/dist/server/ws-bun.js.map +1 -1
- package/dist/server.d.ts +2 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/dist/single-copy-register.d.ts +2 -0
- package/dist/single-copy-register.d.ts.map +1 -0
- package/dist/single-copy-register.js +24 -0
- package/dist/single-copy-register.js.map +1 -0
- package/dist/single-copy.d.ts +156 -0
- package/dist/single-copy.d.ts.map +1 -0
- package/dist/single-copy.js +470 -0
- package/dist/single-copy.js.map +1 -0
- package/package.json +24 -2
- package/src/assurance.ts +58 -1
- package/src/binary.ts +35 -0
- package/src/durable-adapters.ts +26 -51
- package/src/durable-execution.ts +46 -88
- package/src/edge-kit.ts +27 -0
- package/src/errors.ts +1 -0
- package/src/index.ts +1 -1
- package/src/internal/durable-approval-protocol.ts +92 -0
- package/src/internal/durable-effect-protocol.ts +37 -0
- package/src/internal/durable-saga-protocol.ts +55 -0
- package/src/internal/route-catalog.ts +6 -0
- package/src/internal/route-execution.ts +23 -16
- package/src/response-observer.ts +64 -0
- package/src/security/baseline.ts +172 -0
- package/src/server/bare-error-lane.ts +76 -0
- package/src/server/body.ts +8 -0
- package/src/server/bridge-markers.ts +24 -0
- package/src/server/context.ts +6 -1
- package/src/server/header-case.ts +3 -1
- package/src/server/http.ts +53 -3
- package/src/server/install.ts +14 -0
- package/src/server/node-outcome-hook.ts +2 -1
- package/src/server/node-outcome.ts +44 -3
- package/src/server/plugin.ts +1 -1
- package/src/server/query.ts +10 -4
- package/src/server/registry.ts +19 -4
- package/src/server/request-context.ts +106 -51
- package/src/server/respond.ts +26 -1
- package/src/server/response-contract-lane.ts +6 -2
- package/src/server/response-observer-runtime.ts +135 -0
- package/src/server/runtime-core.ts +98 -1
- package/src/server/server-types.ts +18 -1
- package/src/server/server.ts +607 -376
- package/src/server/websocket.ts +127 -17
- package/src/server/ws-bun.ts +26 -7
- package/src/server.ts +2 -1
- package/src/single-copy-register.ts +24 -0
- package/src/single-copy.ts +524 -0
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
* its values) so `request-context`, `respond`, `node-outcome`, and the kernel form a cycle-free graph.
|
|
6
6
|
*/
|
|
7
7
|
import { createUnboundedRequestBudget, type RequestBudget } from "../budget.ts"
|
|
8
|
+
import { NODE_BRIDGE_MARKER_KEYS } from "./bridge-markers.ts"
|
|
8
9
|
import type { CtxSet, RequestSource } from "./server.ts"
|
|
9
10
|
|
|
10
11
|
/** A handler returns a `Response` (used as-is) or any value (serialized to JSON). */
|
|
11
12
|
export type HandlerResult = Response | unknown
|
|
12
13
|
|
|
13
|
-
export const RESPONSE_RESULT = Symbol.for(
|
|
14
|
+
export const RESPONSE_RESULT = Symbol.for(NODE_BRIDGE_MARKER_KEYS.responseResult)
|
|
14
15
|
export const CONTEXT_SET = Symbol("nifra.context.set")
|
|
15
16
|
export const CONTEXT_SEARCH = Symbol("nifra.context.search")
|
|
16
17
|
|
|
@@ -28,6 +29,28 @@ export interface PreDecodedBody {
|
|
|
28
29
|
readonly value: unknown
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* A response described as plain data - the status, any headers of its own, and a body still in value
|
|
34
|
+
* form. A `ResponseResult` carrying one is rendered on the SAME lane a handler's plain return takes:
|
|
35
|
+
* `JSON.stringify` straight into the node writer's `kind: "json"` outcome, or the web lane's prebuilt
|
|
36
|
+
* JSON init. No `Response` is constructed anywhere on the node path.
|
|
37
|
+
*
|
|
38
|
+
* That matters because building one is the dominant cost of answering early. Measured on the rig, one
|
|
39
|
+
* server per shape, all five answering the same 401: `c.set.status` + a plain object 82802 req/s,
|
|
40
|
+
* `beforeHandle` returning that same object 81819, `return new Response(...)` 49668, `throw new
|
|
41
|
+
* Response(...)` 47505, the same throw from a `derive` 48746. The `Response` costs 40%; the throw
|
|
42
|
+
* around it costs another 4%; unwinding a lifecycle stage instead of a handler costs nothing
|
|
43
|
+
* measurable. So the fix for a slow rejection is not a faster throw - it is not allocating the
|
|
44
|
+
* `Response`.
|
|
45
|
+
*/
|
|
46
|
+
export interface PlainRender {
|
|
47
|
+
readonly status: number
|
|
48
|
+
/** Headers belonging to this render. They win over anything the request left in `c.set.headers`. */
|
|
49
|
+
readonly headers?: Readonly<Record<string, string>>
|
|
50
|
+
/** The body as a value, serialized by the lane that renders it. `undefined` means no body. */
|
|
51
|
+
readonly body: unknown
|
|
52
|
+
}
|
|
53
|
+
|
|
31
54
|
export interface ResponseResult {
|
|
32
55
|
readonly [RESPONSE_RESULT]: true
|
|
33
56
|
toResponse(): Response
|
|
@@ -36,6 +59,9 @@ export interface ResponseResult {
|
|
|
36
59
|
readonly headers: Readonly<Record<string, string | readonly string[]>> | undefined
|
|
37
60
|
readonly body: string | Uint8Array
|
|
38
61
|
}
|
|
62
|
+
/** Present when this result can be rendered as plain data - see {@link PlainRender}. Every lane
|
|
63
|
+
* checks it before `toNodeBody`/`toResponse`, so a carrier that has one never builds a `Response`. */
|
|
64
|
+
readonly plain?: PlainRender
|
|
39
65
|
}
|
|
40
66
|
|
|
41
67
|
export function isResponseResult(value: unknown): value is ResponseResult {
|
|
@@ -47,6 +73,77 @@ export function isResponseResult(value: unknown): value is ResponseResult {
|
|
|
47
73
|
)
|
|
48
74
|
}
|
|
49
75
|
|
|
76
|
+
/**
|
|
77
|
+
* The headers a plain render ships: its own on top of whatever the request left in `c.set.headers`,
|
|
78
|
+
* so an ambient header (a request id, say) survives an early exit the way it survives an ordinary
|
|
79
|
+
* return, and a header named at the exit site still wins.
|
|
80
|
+
*
|
|
81
|
+
* Always a fresh object when the render has headers of its own: the node writers mutate the record
|
|
82
|
+
* they are handed (content-type, content-length, cookies), and a `status(...)` value is commonly
|
|
83
|
+
* hoisted to module scope and answered from on every request.
|
|
84
|
+
*/
|
|
85
|
+
export function plainRenderHeaders(
|
|
86
|
+
plain: PlainRender,
|
|
87
|
+
set: CtxSet,
|
|
88
|
+
): Record<string, string> | undefined {
|
|
89
|
+
const own = plain.headers
|
|
90
|
+
if (own === undefined) return set._headers
|
|
91
|
+
return set._headers === undefined ? { ...own } : { ...set._headers, ...own }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Finish the request here, with this status and body, without building a `Response`.
|
|
96
|
+
*
|
|
97
|
+
* Returned or thrown, from a handler or from any lifecycle stage:
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* app.derive((c) => {
|
|
101
|
+
* const user = sessionOf(c)
|
|
102
|
+
* if (user === undefined) return status(401, { error: "unauthorized" })
|
|
103
|
+
* return { user }
|
|
104
|
+
* })
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* A `derive` is the reason this exists as a value rather than as a rule about `beforeHandle`: a
|
|
108
|
+
* `beforeHandle` already short-circuits by returning a value, but a `derive`'s return IS the context
|
|
109
|
+
* extension, so before this its only exit was `throw new Response(...)` - the most expensive way to
|
|
110
|
+
* say 401 (see {@link PlainRender} for the measurements). Returning it is preferred; throwing it
|
|
111
|
+
* carries the same cost as any other throw and stays supported so a guard helper called for effect
|
|
112
|
+
* (`requireSession(c)`) can still end the request from inside a call it makes.
|
|
113
|
+
*
|
|
114
|
+
* The body is serialized by the lane that renders it, exactly like a handler's plain return, so the
|
|
115
|
+
* response carries a `content-length` rather than falling to chunked, and queued cookies still apply.
|
|
116
|
+
*/
|
|
117
|
+
export function status(
|
|
118
|
+
code: number,
|
|
119
|
+
body?: unknown,
|
|
120
|
+
init?: { readonly headers?: Readonly<Record<string, string>> },
|
|
121
|
+
): ResponseResult {
|
|
122
|
+
// The range `Response` accepts, enforced HERE so the plain lane cannot outrun it. A plain render
|
|
123
|
+
// is written straight to the socket by the Node adapter, where `writeHead` takes 100-999 - so a
|
|
124
|
+
// status this rejects would otherwise ship on Node and throw on every Web runtime, from the same
|
|
125
|
+
// code. One integer comparison against the value already in a register; the divergence it closes
|
|
126
|
+
// is the kind that only shows up in the runtime an app did not test on.
|
|
127
|
+
if (!Number.isInteger(code) || code < 200 || code > 599) {
|
|
128
|
+
throw new RangeError(`[nifra] status(${String(code)}): HTTP status must be an integer 200-599`)
|
|
129
|
+
}
|
|
130
|
+
const headers = init?.headers
|
|
131
|
+
const plain: PlainRender =
|
|
132
|
+
headers === undefined ? { status: code, body } : { status: code, headers, body }
|
|
133
|
+
return {
|
|
134
|
+
[RESPONSE_RESULT]: true,
|
|
135
|
+
plain,
|
|
136
|
+
// Only the lanes that cannot take plain data reach this - an edge runtime handed the value by a
|
|
137
|
+
// caller outside the server, say. Built on demand, never on the lanes that matter.
|
|
138
|
+
toResponse(): Response {
|
|
139
|
+
const headers: Record<string, string> = { ...init?.headers }
|
|
140
|
+
if (body === undefined) return new Response(null, { status: code, headers })
|
|
141
|
+
headers["content-type"] ??= "application/json;charset=utf-8"
|
|
142
|
+
return new Response(JSON.stringify(body), { status: code, headers })
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
50
147
|
/** The concrete `Request` for a source - itself when a real `Request` was passed (the Web path), or the
|
|
51
148
|
* lazily-built one (the Node adapter). A real `Request` IS a `RequestSource`, so no wrapper is allocated
|
|
52
149
|
* on the Web hot path. */
|
|
@@ -144,6 +144,21 @@ export interface ServerOptions {
|
|
|
144
144
|
* Fastify's `setErrorHandler`) instead of repeating a formatter per route.
|
|
145
145
|
*/
|
|
146
146
|
readonly onValidationError?: RouteSchema["onValidationError"]
|
|
147
|
+
/**
|
|
148
|
+
* What to do when an order-scoped hook (`derive`/`beforeHandle`/`around`/`aroundCapability`/
|
|
149
|
+
* `afterHandle`/`onError`/`decorate`) covers no route because it was added after the last route was
|
|
150
|
+
* registered. `register()` snapshots the current chain into each route as it is declared, so a hook
|
|
151
|
+
* appended after every route reaches nothing and is silently dead. `"warn"` (default) logs once at
|
|
152
|
+
* seal (first `listen()`/`fetch`/`resolveNode`); `"error"` throws a `FrameworkError`
|
|
153
|
+
* (`UNUSED_SCOPED_HOOKS`); `"off"` skips the bookkeeping entirely, including the one-frame stack
|
|
154
|
+
* capture. App-global hooks (`onRequest`/`onResponse`/...) are unaffected in every mode.
|
|
155
|
+
*
|
|
156
|
+
* This is a development-time diagnostic: the whole audit is guarded by `process.env.NODE_ENV !==
|
|
157
|
+
* "production"`, so any bundler that defines `NODE_ENV` (vite/esbuild/webpack/next, `bun build
|
|
158
|
+
* --production`) dead-code-eliminates it, leaving zero bytes and zero per-request cost in the shipped
|
|
159
|
+
* bundle. It fires in development, test, and CI - where you catch the mistake - not in production.
|
|
160
|
+
*/
|
|
161
|
+
readonly unusedScopedHooks?: "warn" | "error" | "off"
|
|
147
162
|
}
|
|
148
163
|
|
|
149
164
|
/** A fetch-compatible handler used by {@link Server.mountFetch}. */
|
|
@@ -246,7 +261,9 @@ export interface RunningServer {
|
|
|
246
261
|
/**
|
|
247
262
|
* A bundle of lifecycle hooks applied together via {@link Server.use} - the unit
|
|
248
263
|
* `@nifrajs/middleware` ships (cors, security headers, rate-limit). Every hook is
|
|
249
|
-
* optional and wired to its lifecycle point. Middleware
|
|
264
|
+
* optional and wired to its lifecycle point. Middleware carrying a portable response tier must be
|
|
265
|
+
* marked with `withResponseObserver()` from `@nifrajs/core/response-observer`, or the server must
|
|
266
|
+
* install `responseObserver()` before the bundle is applied. Middleware is context-agnostic (sees
|
|
250
267
|
* the base `Context`); `use` does no context-type merging - the full type-merging
|
|
251
268
|
* plugin system is deferred, and `.use` is reserved as its future entry point.
|
|
252
269
|
*/
|