@nifrajs/core 2.14.1 → 3.2.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 +25 -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/durable-adapters.d.ts.map +1 -1
- package/dist/durable-adapters.js +26 -3
- package/dist/durable-adapters.js.map +1 -1
- package/dist/durable-approval.d.ts +56 -0
- package/dist/durable-approval.d.ts.map +1 -0
- package/dist/durable-approval.js +233 -0
- package/dist/durable-approval.js.map +1 -0
- package/dist/durable-effect.d.ts +42 -0
- package/dist/durable-effect.d.ts.map +1 -0
- package/dist/durable-effect.js +234 -0
- package/dist/durable-effect.js.map +1 -0
- package/dist/durable-execution.d.ts +8 -346
- package/dist/durable-execution.d.ts.map +1 -1
- package/dist/durable-execution.js +7 -1150
- package/dist/durable-execution.js.map +1 -1
- package/dist/durable-saga.d.ts +94 -0
- package/dist/durable-saga.d.ts.map +1 -0
- package/dist/durable-saga.js +550 -0
- package/dist/durable-saga.js.map +1 -0
- 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 +23 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/durable-approval-protocol.d.ts +1 -1
- package/dist/internal/durable-approval-protocol.d.ts.map +1 -1
- package/dist/internal/durable-effect-protocol.d.ts +1 -1
- package/dist/internal/durable-effect-protocol.d.ts.map +1 -1
- package/dist/internal/durable-saga-protocol.d.ts +1 -1
- package/dist/internal/durable-saga-protocol.d.ts.map +1 -1
- package/dist/internal/durable-shared.d.ts +26 -0
- package/dist/internal/durable-shared.d.ts.map +1 -0
- package/dist/internal/durable-shared.js +131 -0
- package/dist/internal/durable-shared.js.map +1 -0
- package/dist/internal/durable-types.d.ts +159 -0
- package/dist/internal/durable-types.d.ts.map +1 -0
- package/dist/internal/durable-types.js +2 -0
- package/dist/internal/durable-types.js.map +1 -0
- package/dist/internal/route-compiler.d.ts +61 -0
- package/dist/internal/route-compiler.d.ts.map +1 -0
- package/dist/internal/route-compiler.js +121 -0
- package/dist/internal/route-compiler.js.map +1 -0
- package/dist/internal/route-execution.d.ts +17 -9
- package/dist/internal/route-execution.d.ts.map +1 -1
- package/dist/internal/route-execution.js +4 -18
- package/dist/internal/route-execution.js.map +1 -1
- package/dist/internal/route-lanes.d.ts +35 -0
- package/dist/internal/route-lanes.d.ts.map +1 -0
- package/dist/internal/route-lanes.js +150 -0
- package/dist/internal/route-lanes.js.map +1 -0
- package/dist/internal/route-program.d.ts +67 -0
- package/dist/internal/route-program.d.ts.map +1 -0
- package/dist/internal/route-program.js +199 -0
- package/dist/internal/route-program.js.map +1 -0
- 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-lane.d.ts +13 -0
- package/dist/server/body-lane.d.ts.map +1 -0
- package/dist/server/body-lane.js +134 -0
- package/dist/server/body-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/contract.d.ts +40 -14
- package/dist/server/contract.d.ts.map +1 -1
- package/dist/server/contract.js +7 -0
- package/dist/server/contract.js.map +1 -1
- package/dist/server/cookies.d.ts.map +1 -1
- package/dist/server/cookies.js +16 -2
- package/dist/server/cookies.js.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 +2 -2
- package/dist/server/plugin.d.ts.map +1 -1
- package/dist/server/plugin.js +6 -1
- package/dist/server/plugin.js.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 +40 -12
- package/dist/server/registry.d.ts.map +1 -1
- package/dist/server/request-context.d.ts +2 -13
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +18 -159
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +58 -4
- 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 +76 -0
- package/dist/server/runtime-core.d.ts.map +1 -1
- package/dist/server/runtime-core.js +42 -1
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server-types.d.ts +6 -1
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +158 -116
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +707 -844
- package/dist/server/server.js.map +1 -1
- package/dist/server/validation.d.ts +5 -0
- package/dist/server/validation.d.ts.map +1 -0
- package/dist/server/validation.js +13 -0
- package/dist/server/validation.js.map +1 -0
- 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 +12 -4
- package/dist/server/ws-bun.d.ts.map +1 -1
- package/dist/server/ws-bun.js +50 -16
- package/dist/server/ws-bun.js.map +1 -1
- package/dist/server/ws-hook.d.ts +3 -2
- package/dist/server/ws-hook.d.ts.map +1 -1
- package/dist/server.d.ts +3 -2
- 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 +472 -0
- package/dist/single-copy.js.map +1 -0
- package/package.json +28 -3
- package/src/assurance.ts +58 -1
- package/src/durable-adapters.ts +20 -0
- package/src/durable-approval.ts +281 -0
- package/src/durable-effect.ts +300 -0
- package/src/durable-execution.ts +8 -1627
- package/src/durable-saga.ts +744 -0
- package/src/edge-kit.ts +27 -0
- package/src/errors.ts +1 -0
- package/src/index.ts +129 -2
- package/src/internal/durable-approval-protocol.ts +1 -1
- package/src/internal/durable-effect-protocol.ts +1 -1
- package/src/internal/durable-saga-protocol.ts +1 -1
- package/src/internal/durable-shared.ts +157 -0
- package/src/internal/durable-types.ts +194 -0
- package/src/internal/route-compiler.ts +234 -0
- package/src/internal/route-execution.ts +53 -96
- package/src/internal/route-lanes.ts +221 -0
- package/src/internal/route-program.ts +404 -0
- package/src/response-observer.ts +64 -0
- package/src/security/baseline.ts +172 -0
- package/src/server/bare-error-lane.ts +77 -0
- package/src/server/body-lane.ts +203 -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/contract.ts +70 -20
- package/src/server/cookies.ts +16 -2
- 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 +11 -5
- package/src/server/query.ts +10 -4
- package/src/server/registry.ts +71 -16
- package/src/server/request-context.ts +13 -216
- package/src/server/respond.ts +71 -3
- package/src/server/response-contract-lane.ts +6 -2
- package/src/server/response-observer-runtime.ts +135 -0
- package/src/server/runtime-core.ts +124 -1
- package/src/server/server-types.ts +6 -1
- package/src/server/server.ts +1446 -1485
- package/src/server/validation.ts +19 -0
- package/src/server/websocket.ts +127 -17
- package/src/server/ws-bun.ts +67 -15
- package/src/server/ws-hook.ts +3 -2
- package/src/server.ts +8 -2
- package/src/single-copy-register.ts +24 -0
- package/src/single-copy.ts +526 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registration-time route compilation.
|
|
3
|
+
*
|
|
4
|
+
* This module owns the policy decisions that turn a route declaration into the immutable facts the
|
|
5
|
+
* execution-plan builder and the route catalog consume. It deliberately runs only from `Server.register`
|
|
6
|
+
* and `Server.registerBatch`; no function here is reachable from the request path.
|
|
7
|
+
*
|
|
8
|
+
* The public Server remains the fluent registration surface. This module is the deep implementation seam
|
|
9
|
+
* behind that surface: body limits, capability and assurance evidence, optional runtimes, decorations, and
|
|
10
|
+
* lane selection are resolved once, together, so adding a new route policy does not require editing the
|
|
11
|
+
* kernel's dispatch code.
|
|
12
|
+
*/
|
|
13
|
+
import type { EffectLifecycleObserver } from "../effect-lifecycle.ts"
|
|
14
|
+
import { RouteConfigError } from "../errors.ts"
|
|
15
|
+
import { type CompiledRoutePattern, compileRoutePattern } from "../router/pattern.ts"
|
|
16
|
+
import type { Method } from "../router/router.ts"
|
|
17
|
+
import type { StandardSchemaV1 } from "../schema/standard.ts"
|
|
18
|
+
import { assertByteLimit } from "../server/body.ts"
|
|
19
|
+
import type { RouteSchema } from "../server/context.ts"
|
|
20
|
+
import type { IdempotencyRuntime, ResolvedIdempotency } from "../server/idempotency-lane.ts"
|
|
21
|
+
import type { EffectLedgerRuntime, ResolvedEffectLedger } from "../server/ledger-lane.ts"
|
|
22
|
+
import type { ResponseContractRuntime } from "../server/response-contract-lane.ts"
|
|
23
|
+
import {
|
|
24
|
+
CAPABILITY_GUARD,
|
|
25
|
+
type CapabilityUseEvent,
|
|
26
|
+
createCapabilityGuard,
|
|
27
|
+
normalizeRouteCapabilities,
|
|
28
|
+
type RegisteredCapabilityInterceptor,
|
|
29
|
+
} from "./capability-runtime.ts"
|
|
30
|
+
import {
|
|
31
|
+
type AssuranceDeclaration,
|
|
32
|
+
assuranceDeclarationsOf,
|
|
33
|
+
assuranceEvidenceFor,
|
|
34
|
+
NIFRA_ASSURANCE_IDS,
|
|
35
|
+
validEvidenceId,
|
|
36
|
+
} from "./route-assurance.ts"
|
|
37
|
+
import type {
|
|
38
|
+
RawAfterHandle,
|
|
39
|
+
RawAround,
|
|
40
|
+
RawBeforeHandle,
|
|
41
|
+
RawDerive,
|
|
42
|
+
RawErrorHandler,
|
|
43
|
+
} from "./route-execution.ts"
|
|
44
|
+
import { type RouteLaneSelection, selectRouteLanes } from "./route-lanes.ts"
|
|
45
|
+
|
|
46
|
+
export interface RouteCompilerContext {
|
|
47
|
+
readonly maxBodyBytes: number
|
|
48
|
+
readonly activeAssurance: readonly AssuranceDeclaration[]
|
|
49
|
+
readonly globalAssurance: readonly AssuranceDeclaration[]
|
|
50
|
+
readonly decorations: Record<string, unknown>
|
|
51
|
+
readonly onCapabilityUse: ((event: CapabilityUseEvent) => void) | undefined
|
|
52
|
+
readonly capabilityInterceptors: readonly RegisteredCapabilityInterceptor[]
|
|
53
|
+
readonly capabilityObservers: readonly EffectLifecycleObserver[]
|
|
54
|
+
readonly effectLedgerRuntime: EffectLedgerRuntime | undefined
|
|
55
|
+
readonly idempotencyRuntime: IdempotencyRuntime | undefined
|
|
56
|
+
readonly responseContractRuntime: ResponseContractRuntime | undefined
|
|
57
|
+
readonly derives: readonly RawDerive[]
|
|
58
|
+
readonly beforeHandleHooks: readonly RawBeforeHandle[]
|
|
59
|
+
readonly afterHandleHooks: readonly RawAfterHandle[]
|
|
60
|
+
readonly onErrorHooks: readonly RawErrorHandler[]
|
|
61
|
+
readonly aroundHooks: readonly RawAround[]
|
|
62
|
+
readonly defaultOnValidationError: RouteSchema["onValidationError"] | undefined
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface CompiledRouteOptions {
|
|
66
|
+
readonly pattern: CompiledRoutePattern
|
|
67
|
+
readonly bodyLimit: number | undefined
|
|
68
|
+
readonly capabilities: readonly string[]
|
|
69
|
+
readonly handlerAssurance: readonly AssuranceDeclaration[]
|
|
70
|
+
readonly routeDecorations: Record<PropertyKey, unknown>
|
|
71
|
+
readonly hasDecorations: boolean
|
|
72
|
+
readonly idempotent: ResolvedIdempotency | undefined
|
|
73
|
+
readonly ledgered: ResolvedEffectLedger | undefined
|
|
74
|
+
readonly responseContract:
|
|
75
|
+
| { readonly runtime: ResponseContractRuntime; readonly schema: StandardSchemaV1 }
|
|
76
|
+
| undefined
|
|
77
|
+
readonly lanes: RouteLaneSelection
|
|
78
|
+
readonly routeAssurance: readonly AssuranceDeclaration[]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Resolve all registration-time policy and lane facts for one route. */
|
|
82
|
+
export function compileRouteOptions(
|
|
83
|
+
context: RouteCompilerContext,
|
|
84
|
+
method: Method,
|
|
85
|
+
path: string,
|
|
86
|
+
schema: RouteSchema | undefined,
|
|
87
|
+
handler: (context: never) => unknown,
|
|
88
|
+
): CompiledRouteOptions {
|
|
89
|
+
const pattern = compileRoutePattern(path)
|
|
90
|
+
let bodyLimit: number | undefined = context.maxBodyBytes
|
|
91
|
+
const invalidBodyLimit = (message: string): never => {
|
|
92
|
+
throw new RouteConfigError("INVALID_BODY_LIMIT", `route ${method} ${path}: ${message}`)
|
|
93
|
+
}
|
|
94
|
+
if (schema?.bodyLimitReason !== undefined && schema.bodyLimit !== "unlimited") {
|
|
95
|
+
invalidBodyLimit('bodyLimitReason is only valid with bodyLimit: "unlimited"')
|
|
96
|
+
}
|
|
97
|
+
if (schema?.bodyLimit === "unlimited") {
|
|
98
|
+
if (typeof schema.bodyLimitReason !== "string" || schema.bodyLimitReason.trim().length === 0) {
|
|
99
|
+
invalidBodyLimit('bodyLimit: "unlimited" requires a non-empty bodyLimitReason')
|
|
100
|
+
}
|
|
101
|
+
if (schema.body !== undefined) {
|
|
102
|
+
invalidBodyLimit('bodyLimit: "unlimited" cannot be used with a body schema; use a finite cap')
|
|
103
|
+
}
|
|
104
|
+
if (schema.idempotency !== undefined) {
|
|
105
|
+
invalidBodyLimit('bodyLimit: "unlimited" cannot be used with idempotency')
|
|
106
|
+
}
|
|
107
|
+
bodyLimit = undefined
|
|
108
|
+
} else if (schema?.bodyLimit !== undefined) {
|
|
109
|
+
try {
|
|
110
|
+
assertByteLimit(schema.bodyLimit, "route bodyLimit")
|
|
111
|
+
} catch {
|
|
112
|
+
invalidBodyLimit('bodyLimit must be a non-negative safe integer or "unlimited"')
|
|
113
|
+
}
|
|
114
|
+
bodyLimit = schema.bodyLimit
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const capabilities = normalizeRouteCapabilities(schema?.capabilities)
|
|
118
|
+
const handlerAssurance = assuranceDeclarationsOf(handler as unknown as object)
|
|
119
|
+
const invalidHandlerScope = handlerAssurance.find((declaration) => declaration.scope !== "plugin")
|
|
120
|
+
if (invalidHandlerScope !== undefined) {
|
|
121
|
+
throw new RouteConfigError(
|
|
122
|
+
"INVALID_ASSURANCE",
|
|
123
|
+
`route handler assurance must use plugin scope (received ${invalidHandlerScope.scope})`,
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const authenticated = assuranceEvidenceFor(
|
|
128
|
+
[...context.activeAssurance, ...handlerAssurance, ...context.globalAssurance],
|
|
129
|
+
method,
|
|
130
|
+
path,
|
|
131
|
+
).some((evidence) => evidence.id === NIFRA_ASSURANCE_IDS.AUTHENTICATED)
|
|
132
|
+
const routeDecorations: Record<PropertyKey, unknown> = { ...context.decorations }
|
|
133
|
+
if (capabilities.length > 0) {
|
|
134
|
+
routeDecorations[CAPABILITY_GUARD] = createCapabilityGuard(
|
|
135
|
+
capabilities,
|
|
136
|
+
method,
|
|
137
|
+
path,
|
|
138
|
+
context.onCapabilityUse,
|
|
139
|
+
context.idempotencyRuntime?.trackEffect,
|
|
140
|
+
Object.freeze([...context.capabilityInterceptors]),
|
|
141
|
+
Object.freeze([...context.capabilityObservers]),
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
const hasDecorations = Reflect.ownKeys(routeDecorations).length > 0
|
|
145
|
+
|
|
146
|
+
if (schema?.idempotency !== undefined && context.idempotencyRuntime === undefined) {
|
|
147
|
+
throw new RouteConfigError(
|
|
148
|
+
"INVALID_IDEMPOTENCY",
|
|
149
|
+
"route declares idempotency but the idempotency plugin is not installed; add .use(idempotency())",
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
const idempotent = context.idempotencyRuntime?.resolve(
|
|
153
|
+
schema,
|
|
154
|
+
authenticated,
|
|
155
|
+
context.maxBodyBytes,
|
|
156
|
+
)
|
|
157
|
+
const ledgered = context.effectLedgerRuntime?.resolve(capabilities, method, path)
|
|
158
|
+
const contracted =
|
|
159
|
+
context.responseContractRuntime !== undefined && schema?.response !== undefined
|
|
160
|
+
? { runtime: context.responseContractRuntime, schema: schema.response }
|
|
161
|
+
: undefined
|
|
162
|
+
const lanes = selectRouteLanes({
|
|
163
|
+
schema,
|
|
164
|
+
hasIdempotency: idempotent !== undefined,
|
|
165
|
+
hasLedger: ledgered !== undefined,
|
|
166
|
+
hasResponseContract: contracted !== undefined,
|
|
167
|
+
hasDecorations,
|
|
168
|
+
derives: context.derives.length,
|
|
169
|
+
beforeHandle: context.beforeHandleHooks.length,
|
|
170
|
+
afterHandle: context.afterHandleHooks.length,
|
|
171
|
+
onError: context.onErrorHooks.length,
|
|
172
|
+
around: context.aroundHooks.length,
|
|
173
|
+
defaultOnValidationError: context.defaultOnValidationError !== undefined,
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const routeAssurance: AssuranceDeclaration[] = [...context.activeAssurance, ...handlerAssurance]
|
|
177
|
+
if (contracted?.runtime.mode === "enforce" && schema?.response !== undefined) {
|
|
178
|
+
routeAssurance.push(
|
|
179
|
+
Object.freeze({
|
|
180
|
+
id: NIFRA_ASSURANCE_IDS.RESPONSE_CONTRACT,
|
|
181
|
+
source: "response-contract",
|
|
182
|
+
scope: "plugin",
|
|
183
|
+
}),
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
for (const id of schema?.assurance ?? []) {
|
|
187
|
+
if (!validEvidenceId(id)) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`route assurance: invalid evidence id ${JSON.stringify(id)} on ${method} ${path} (use lowercase dot/dash segments)`,
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
const declared = { id, source: "declared", scope: "plugin" } as AssuranceDeclaration
|
|
193
|
+
Object.defineProperty(declared, "provenance", {
|
|
194
|
+
value: "declared",
|
|
195
|
+
enumerable: false,
|
|
196
|
+
configurable: false,
|
|
197
|
+
writable: false,
|
|
198
|
+
})
|
|
199
|
+
routeAssurance.push(Object.freeze(declared))
|
|
200
|
+
}
|
|
201
|
+
const unlimitedBody = schema?.bodyLimit === "unlimited"
|
|
202
|
+
if (!unlimitedBody && (schema?.body !== undefined || typeof schema?.bodyLimit === "number")) {
|
|
203
|
+
routeAssurance.push(
|
|
204
|
+
Object.freeze({
|
|
205
|
+
id: NIFRA_ASSURANCE_IDS.BODY_BOUNDED,
|
|
206
|
+
source: "route-schema",
|
|
207
|
+
scope: "plugin",
|
|
208
|
+
}),
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
if (schema?.idempotency !== undefined) {
|
|
212
|
+
routeAssurance.push(
|
|
213
|
+
Object.freeze({
|
|
214
|
+
id: NIFRA_ASSURANCE_IDS.IDEMPOTENCY_KEY,
|
|
215
|
+
source: "route-schema",
|
|
216
|
+
scope: "plugin",
|
|
217
|
+
}),
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
pattern,
|
|
223
|
+
bodyLimit,
|
|
224
|
+
capabilities,
|
|
225
|
+
handlerAssurance,
|
|
226
|
+
routeDecorations,
|
|
227
|
+
hasDecorations,
|
|
228
|
+
idempotent,
|
|
229
|
+
ledgered,
|
|
230
|
+
responseContract: contracted,
|
|
231
|
+
lanes,
|
|
232
|
+
routeAssurance: Object.freeze(routeAssurance),
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import type { RequestBudget } from "../budget.ts"
|
|
20
20
|
import type { RequestLedger } from "../ledger.ts"
|
|
21
21
|
import type { StandardSchemaV1 } from "../schema/standard.ts"
|
|
22
|
+
import { UNLIMITED_BODY_BYTES } from "../server/body.ts"
|
|
22
23
|
import type { Platform, RouteSchema } from "../server/context.ts"
|
|
23
24
|
import type { ResolvedIdempotency } from "../server/idempotency-lane.ts"
|
|
24
25
|
import type { EffectLedgerRuntime, ResolvedEffectLedger } from "../server/ledger-lane.ts"
|
|
@@ -26,8 +27,9 @@ import type { ProtoPoisoning } from "../server/proto-guard.ts"
|
|
|
26
27
|
import type { Registry } from "../server/registry.ts"
|
|
27
28
|
import { RequestContext } from "../server/request-context.ts"
|
|
28
29
|
import type { ResponseContractRuntime } from "../server/response-contract-lane.ts"
|
|
29
|
-
import type { HandlerResult } from "../server/runtime-core.ts"
|
|
30
|
+
import type { HandlerResult, ResponseResult } from "../server/runtime-core.ts"
|
|
30
31
|
import type { CtxSet, MaybePromise, RawContext, RequestSource, Server } from "../server/server.ts"
|
|
32
|
+
import type { RouteProgram } from "./route-program.ts"
|
|
31
33
|
|
|
32
34
|
export type InternalHandler = (ctx: RawContext) => MaybePromise<HandlerResult>
|
|
33
35
|
|
|
@@ -49,7 +51,7 @@ export type RouteExecutionRunner = <T, R extends Registry, Ctx>(
|
|
|
49
51
|
platform: Platform | undefined,
|
|
50
52
|
nativeContext: boolean,
|
|
51
53
|
finalize: (result: unknown, set: CtxSet) => T,
|
|
52
|
-
wrapResponse: (response: Response) => T,
|
|
54
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
53
55
|
) => MaybePromise<T>
|
|
54
56
|
|
|
55
57
|
export type ContextRouteRunner = <T, R extends Registry, Ctx>(
|
|
@@ -58,7 +60,7 @@ export type ContextRouteRunner = <T, R extends Registry, Ctx>(
|
|
|
58
60
|
source: RequestSource,
|
|
59
61
|
ctx: RawContext,
|
|
60
62
|
finalize: (result: unknown, set: CtxSet) => T,
|
|
61
|
-
wrapResponse: (response: Response) => T,
|
|
63
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
62
64
|
) => MaybePromise<T>
|
|
63
65
|
|
|
64
66
|
/** The kernel's private execution surface, mirrored structurally. The runner methods are private on
|
|
@@ -76,66 +78,40 @@ interface RouteExecutionRuntime {
|
|
|
76
78
|
budget: RequestBudget,
|
|
77
79
|
platform: Platform | undefined,
|
|
78
80
|
finalize: (result: unknown, set: CtxSet) => T,
|
|
79
|
-
wrapResponse: (response: Response) => T,
|
|
81
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
80
82
|
): MaybePromise<T>
|
|
81
83
|
runBare<T>(
|
|
82
84
|
entry: RouteEntry,
|
|
83
85
|
ctx: RawContext,
|
|
84
86
|
finalize: (result: unknown, set: CtxSet) => T,
|
|
85
|
-
wrapResponse: (response: Response) => T,
|
|
87
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
86
88
|
): MaybePromise<T>
|
|
87
89
|
runBodyOnly<T>(
|
|
88
90
|
entry: RouteEntry,
|
|
89
91
|
source: RequestSource,
|
|
90
92
|
ctx: RawContext,
|
|
91
93
|
finalize: (result: unknown, set: CtxSet) => T,
|
|
92
|
-
wrapResponse: (response: Response) => T,
|
|
94
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
93
95
|
): MaybePromise<T>
|
|
94
96
|
runQueryOnly<T>(
|
|
95
97
|
entry: RouteEntry,
|
|
96
98
|
ctx: RawContext,
|
|
97
99
|
finalize: (result: unknown, set: CtxSet) => T,
|
|
98
|
-
wrapResponse: (response: Response) => T,
|
|
100
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
99
101
|
): MaybePromise<T>
|
|
100
|
-
|
|
101
|
-
entry: RouteEntry,
|
|
102
|
-
ctx: RawContext,
|
|
103
|
-
finalize: (result: unknown, set: CtxSet) => T,
|
|
104
|
-
wrapResponse: (response: Response) => T,
|
|
105
|
-
): MaybePromise<T>
|
|
106
|
-
runLifecycleQuery<T>(
|
|
107
|
-
entry: RouteEntry,
|
|
108
|
-
ctx: RawContext,
|
|
109
|
-
finalize: (result: unknown, set: CtxSet) => T,
|
|
110
|
-
wrapResponse: (response: Response) => T,
|
|
111
|
-
): MaybePromise<T>
|
|
112
|
-
runLifecycleBody<T>(
|
|
102
|
+
runProgram<T>(
|
|
113
103
|
entry: RouteEntry,
|
|
114
104
|
source: RequestSource,
|
|
115
105
|
ctx: RawContext,
|
|
116
106
|
finalize: (result: unknown, set: CtxSet) => T,
|
|
117
|
-
wrapResponse: (response: Response) => T,
|
|
118
|
-
): MaybePromise<T>
|
|
119
|
-
runLifecycleBodyQuery<T>(
|
|
120
|
-
entry: RouteEntry,
|
|
121
|
-
source: RequestSource,
|
|
122
|
-
ctx: RawContext,
|
|
123
|
-
finalize: (result: unknown, set: CtxSet) => T,
|
|
124
|
-
wrapResponse: (response: Response) => T,
|
|
125
|
-
): MaybePromise<T>
|
|
126
|
-
runLifecycle<T>(
|
|
127
|
-
entry: RouteEntry,
|
|
128
|
-
source: RequestSource,
|
|
129
|
-
ctx: RawContext,
|
|
130
|
-
finalize: (result: unknown, set: CtxSet) => T,
|
|
131
|
-
wrapResponse: (response: Response) => T,
|
|
107
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
132
108
|
): MaybePromise<T>
|
|
133
109
|
runWithAround<T>(
|
|
134
110
|
entry: RouteEntry,
|
|
135
111
|
ctx: RawContext,
|
|
136
112
|
run: () => MaybePromise<T>,
|
|
137
113
|
finalize: (result: unknown, set: CtxSet) => T,
|
|
138
|
-
wrapResponse: (response: Response) => T,
|
|
114
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
139
115
|
): MaybePromise<T>
|
|
140
116
|
readonly maxBodyBytes: number
|
|
141
117
|
readonly protoPoisoning: ProtoPoisoning
|
|
@@ -157,13 +133,19 @@ export function compileRouteExecutionPlan(options: {
|
|
|
157
133
|
readonly contextless: boolean
|
|
158
134
|
readonly hasAround: boolean
|
|
159
135
|
readonly hasLedger: boolean
|
|
160
|
-
readonly lifecycleLane: LifecycleExecutionLane
|
|
161
136
|
readonly fusedWeb: FusedWebRunner | undefined
|
|
162
137
|
readonly fusedBody: FusedBodyRunner | undefined
|
|
163
|
-
readonly fusedLane:
|
|
138
|
+
readonly fusedLane:
|
|
139
|
+
| "bare"
|
|
140
|
+
| "body"
|
|
141
|
+
| "query"
|
|
142
|
+
| "derive-before"
|
|
143
|
+
| "derive-before-after"
|
|
144
|
+
| "body-derive-before"
|
|
145
|
+
| "body-derive-before-after"
|
|
146
|
+
| undefined
|
|
164
147
|
}): RouteExecutionPlan {
|
|
165
|
-
const { lane, contextless, hasAround, hasLedger,
|
|
166
|
-
options
|
|
148
|
+
const { lane, contextless, hasAround, hasLedger, fusedWeb, fusedBody, fusedLane } = options
|
|
167
149
|
|
|
168
150
|
if (contextless) {
|
|
169
151
|
const run: RouteExecutionRunner = (
|
|
@@ -190,7 +172,7 @@ export function compileRouteExecutionPlan(options: {
|
|
|
190
172
|
finalize,
|
|
191
173
|
wrapResponse,
|
|
192
174
|
)
|
|
193
|
-
return Object.freeze({ run, fusedWeb, fusedBody, fusedLane })
|
|
175
|
+
return Object.freeze({ run, fusedWeb, fusedBody, fusedLane }) as RouteExecutionPlan
|
|
194
176
|
}
|
|
195
177
|
|
|
196
178
|
let inner: ContextRouteRunner
|
|
@@ -219,55 +201,14 @@ export function compileRouteExecutionPlan(options: {
|
|
|
219
201
|
)
|
|
220
202
|
break
|
|
221
203
|
default:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
)
|
|
231
|
-
break
|
|
232
|
-
case "query":
|
|
233
|
-
inner = (runtime, entry, _source, ctx, finalize, wrapResponse) =>
|
|
234
|
-
(runtime as unknown as RouteExecutionRuntime).runLifecycleQuery(
|
|
235
|
-
entry,
|
|
236
|
-
ctx,
|
|
237
|
-
finalize,
|
|
238
|
-
wrapResponse,
|
|
239
|
-
)
|
|
240
|
-
break
|
|
241
|
-
case "body":
|
|
242
|
-
inner = (runtime, entry, source, ctx, finalize, wrapResponse) =>
|
|
243
|
-
(runtime as unknown as RouteExecutionRuntime).runLifecycleBody(
|
|
244
|
-
entry,
|
|
245
|
-
source,
|
|
246
|
-
ctx,
|
|
247
|
-
finalize,
|
|
248
|
-
wrapResponse,
|
|
249
|
-
)
|
|
250
|
-
break
|
|
251
|
-
case "body-query":
|
|
252
|
-
inner = (runtime, entry, source, ctx, finalize, wrapResponse) =>
|
|
253
|
-
(runtime as unknown as RouteExecutionRuntime).runLifecycleBodyQuery(
|
|
254
|
-
entry,
|
|
255
|
-
source,
|
|
256
|
-
ctx,
|
|
257
|
-
finalize,
|
|
258
|
-
wrapResponse,
|
|
259
|
-
)
|
|
260
|
-
break
|
|
261
|
-
default:
|
|
262
|
-
inner = (runtime, entry, source, ctx, finalize, wrapResponse) =>
|
|
263
|
-
(runtime as unknown as RouteExecutionRuntime).runLifecycle(
|
|
264
|
-
entry,
|
|
265
|
-
source,
|
|
266
|
-
ctx,
|
|
267
|
-
finalize,
|
|
268
|
-
wrapResponse,
|
|
269
|
-
)
|
|
270
|
-
}
|
|
204
|
+
inner = (runtime, entry, source, ctx, finalize, wrapResponse) =>
|
|
205
|
+
(runtime as unknown as RouteExecutionRuntime).runProgram(
|
|
206
|
+
entry,
|
|
207
|
+
source,
|
|
208
|
+
ctx,
|
|
209
|
+
finalize,
|
|
210
|
+
wrapResponse,
|
|
211
|
+
)
|
|
271
212
|
}
|
|
272
213
|
|
|
273
214
|
const execute: ContextRouteRunner = hasAround
|
|
@@ -295,7 +236,7 @@ export function compileRouteExecutionPlan(options: {
|
|
|
295
236
|
wrapResponse,
|
|
296
237
|
) => {
|
|
297
238
|
const server = runtime as unknown as RouteExecutionRuntime
|
|
298
|
-
const maxBodyBytes = entry.bodyLimit ??
|
|
239
|
+
const maxBodyBytes = entry.bodyLimit ?? UNLIMITED_BODY_BYTES
|
|
299
240
|
const ctx = nativeContext
|
|
300
241
|
? RequestContext.native(source, params, search, maxBodyBytes, platform, server.protoPoisoning)
|
|
301
242
|
: new RequestContext(
|
|
@@ -328,7 +269,7 @@ export function compileRouteExecutionPlan(options: {
|
|
|
328
269
|
}
|
|
329
270
|
return outcome
|
|
330
271
|
}
|
|
331
|
-
return Object.freeze({ run, fusedWeb, fusedBody, fusedLane })
|
|
272
|
+
return Object.freeze({ run, fusedWeb, fusedBody, fusedLane }) as RouteExecutionPlan
|
|
332
273
|
}
|
|
333
274
|
|
|
334
275
|
/** Registration-compiled route behavior. Every adapter invokes the same runner; the optional fused
|
|
@@ -340,13 +281,27 @@ export interface RouteExecutionPlan {
|
|
|
340
281
|
/** Which builder produced {@link fusedWeb} - a merge rebinds the closure to the executing server
|
|
341
282
|
* and must rebuild it with the SAME semantics (a query-fused route rebuilt as bare would skip its
|
|
342
283
|
* validation). `undefined` iff `fusedWeb` is. */
|
|
343
|
-
readonly fusedLane:
|
|
284
|
+
readonly fusedLane:
|
|
285
|
+
| "bare"
|
|
286
|
+
| "body"
|
|
287
|
+
| "query"
|
|
288
|
+
| "derive-before"
|
|
289
|
+
| "derive-before-after"
|
|
290
|
+
| "body-derive-before"
|
|
291
|
+
| "body-derive-before-after"
|
|
292
|
+
| undefined
|
|
344
293
|
}
|
|
345
294
|
|
|
346
295
|
export interface RouteEntry {
|
|
347
296
|
readonly handler: InternalHandler
|
|
348
297
|
readonly schema: RouteSchema | undefined
|
|
349
|
-
/**
|
|
298
|
+
/**
|
|
299
|
+
* Effective transport body cap, already resolved at registration: the route's own
|
|
300
|
+
* `schema.bodyLimit` when it declared one, the server's `maxBodyBytes` otherwise. `undefined` is
|
|
301
|
+
* an explicit streaming/upload exemption (`bodyLimit: "unlimited"`) and means NO cap - readers
|
|
302
|
+
* must fall back to {@link UNLIMITED_BODY_BYTES}, never to the server default, which would be a
|
|
303
|
+
* tighter bound than the route asked for.
|
|
304
|
+
*/
|
|
350
305
|
readonly bodyLimit: number | undefined
|
|
351
306
|
/** Resolved idempotency config; `undefined` = off (the dedupe lane is never entered). */
|
|
352
307
|
readonly idempotent: ResolvedIdempotency | undefined
|
|
@@ -370,11 +325,13 @@ export interface RouteEntry {
|
|
|
370
325
|
readonly afterHandle: ReadonlyArray<RawAfterHandle>
|
|
371
326
|
readonly onError: ReadonlyArray<RawErrorHandler>
|
|
372
327
|
/** Registration-specialized hook shape for the common derive + before middleware route. */
|
|
373
|
-
readonly lifecycleHookLane: "derive-before" | undefined
|
|
328
|
+
readonly lifecycleHookLane: "derive-before" | "derive-before-after" | undefined
|
|
374
329
|
/** Wraps the matched route lifecycle. Empty for the common no-around path. */
|
|
375
330
|
readonly around: ReadonlyArray<RawAround>
|
|
376
331
|
/** The single immutable execution decision consumed by portable, Node-direct, and Bun-native paths. */
|
|
377
332
|
readonly execution: RouteExecutionPlan
|
|
333
|
+
/** One immutable general lifecycle program compiled at registration. */
|
|
334
|
+
readonly program: RouteProgram
|
|
378
335
|
}
|
|
379
336
|
|
|
380
337
|
/** The fused Web lane: same inputs `routeAndRun` would hand the generic path, a `Response` out. */
|
|
@@ -399,5 +356,5 @@ export type FusedBodyRunner = <T>(
|
|
|
399
356
|
platform: Platform | undefined,
|
|
400
357
|
nativeContext: boolean,
|
|
401
358
|
finalize: (result: unknown, set: CtxSet, ctx: RawContext) => T,
|
|
402
|
-
wrapResponse: (response: Response) => T,
|
|
359
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
403
360
|
) => MaybePromise<T>
|