@nifrajs/core 3.3.0 → 4.0.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 +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 +6 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +36 -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 +36 -1
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +219 -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 +48 -9
- package/dist/idempotency.js.map +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- 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 +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +52 -46
- 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/cookies.d.ts +4 -1
- package/dist/server/cookies.d.ts.map +1 -1
- package/dist/server/cookies.js +40 -2
- package/dist/server/cookies.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 +55 -3
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +194 -19
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +1342 -274
- 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 +8 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +5 -2
- 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 +54 -7
- 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 +302 -12
- package/src/idempotency.ts +80 -9
- package/src/index.ts +41 -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 +52 -46
- 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/cookies.ts +34 -2
- 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 +61 -2
- package/src/server/server.ts +1798 -355
- 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 +39 -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/index.ts
CHANGED
|
@@ -16,14 +16,25 @@
|
|
|
16
16
|
* read its own package.json at runtime. `scripts/version.ts` rewrites it on every release bump and
|
|
17
17
|
* `check:publish` asserts it equals `@nifrajs/core`'s package version.
|
|
18
18
|
*/
|
|
19
|
-
export const VERSION = "
|
|
19
|
+
export const VERSION = "4.0.0" as const
|
|
20
20
|
|
|
21
21
|
export type Version = typeof VERSION
|
|
22
22
|
|
|
23
23
|
export { FrameworkError, RouteConfigError, type RouteConfigErrorCode } from "./errors.ts"
|
|
24
24
|
export { FRAMEWORK_NAME, type FrameworkName } from "./internal/brand.ts"
|
|
25
25
|
export { isSameOriginRequest } from "./internal/same-origin.ts"
|
|
26
|
-
export {
|
|
26
|
+
export {
|
|
27
|
+
ROUTE_PATTERN_OVERLAP_MAX_STATES,
|
|
28
|
+
RoutePatternOverlapLimitError,
|
|
29
|
+
routePatternOverlap,
|
|
30
|
+
} from "./router/overlap.ts"
|
|
31
|
+
export {
|
|
32
|
+
METHODS,
|
|
33
|
+
type Method,
|
|
34
|
+
type RouteMethod,
|
|
35
|
+
Router,
|
|
36
|
+
type RouterMatch,
|
|
37
|
+
} from "./router/router.ts"
|
|
27
38
|
export type {
|
|
28
39
|
InferInput,
|
|
29
40
|
InferOutput,
|
|
@@ -33,11 +44,24 @@ export type {
|
|
|
33
44
|
StandardTypes,
|
|
34
45
|
ValidationOutcome,
|
|
35
46
|
} from "./schema/standard.ts"
|
|
47
|
+
export {
|
|
48
|
+
type AuthenticationFailure,
|
|
49
|
+
type AuthenticationFailureReason,
|
|
50
|
+
type AuthenticationInput,
|
|
51
|
+
type AuthenticationResult,
|
|
52
|
+
type AuthenticationStage,
|
|
53
|
+
type AuthenticationSuccess,
|
|
54
|
+
type AuthHeaders,
|
|
55
|
+
type AuthMaybePromise,
|
|
56
|
+
authenticated,
|
|
57
|
+
rejected,
|
|
58
|
+
} from "./server/auth.ts"
|
|
36
59
|
export type {
|
|
37
60
|
Context,
|
|
38
61
|
Params,
|
|
39
62
|
Platform,
|
|
40
63
|
Prettify,
|
|
64
|
+
RequestPath,
|
|
41
65
|
ResponseControls,
|
|
42
66
|
RouteSchema,
|
|
43
67
|
} from "./server/context.ts"
|
|
@@ -45,6 +69,7 @@ export {
|
|
|
45
69
|
type CookieOptions,
|
|
46
70
|
type CookieSecret,
|
|
47
71
|
cookieNamePrefix,
|
|
72
|
+
hasDuplicateCookie,
|
|
48
73
|
parseCookies,
|
|
49
74
|
serializeCookie,
|
|
50
75
|
signValue,
|
|
@@ -82,7 +107,15 @@ export {
|
|
|
82
107
|
toVercelHandler,
|
|
83
108
|
type VercelHandler,
|
|
84
109
|
} from "./server/platform-adapters.ts"
|
|
85
|
-
export type {
|
|
110
|
+
export type {
|
|
111
|
+
JoinRoutePath,
|
|
112
|
+
PrefixRegistry,
|
|
113
|
+
Registry,
|
|
114
|
+
ResponseMapFor,
|
|
115
|
+
RouteInfo,
|
|
116
|
+
RouteInfoFor,
|
|
117
|
+
} from "./server/registry.ts"
|
|
118
|
+
export { replacedRequestOf } from "./server/request-lineage.ts"
|
|
86
119
|
export {
|
|
87
120
|
type PlainRender,
|
|
88
121
|
type ResponseResult,
|
|
@@ -101,13 +134,18 @@ export {
|
|
|
101
134
|
defineRouterPlugin,
|
|
102
135
|
type Handler,
|
|
103
136
|
type IdentityPlugin,
|
|
137
|
+
type ListenTlsOptions,
|
|
104
138
|
type McpPromptDescriptor,
|
|
105
139
|
type McpResourceDescriptor,
|
|
106
140
|
type Middleware,
|
|
141
|
+
type MountableApp,
|
|
142
|
+
type MountFetchOptions,
|
|
143
|
+
type MountOptions,
|
|
107
144
|
type NifraFeatureVersion,
|
|
108
145
|
type NifraPlugin,
|
|
109
146
|
type NodeRequestContext,
|
|
110
147
|
type NodeRequestHook,
|
|
148
|
+
type NodeResponseBodyHook,
|
|
111
149
|
type NodeResponseContext,
|
|
112
150
|
type NodeResponseHook,
|
|
113
151
|
type NodeServeOutcome,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code-unit order, the same on every runtime and in every locale. `localeCompare` answers by the
|
|
3
|
+
* runtime's locale and ICU data, so an artifact that is hashed, signed, or committed sorts with this
|
|
4
|
+
* instead: its bytes must not depend on the machine that built it.
|
|
5
|
+
*/
|
|
6
|
+
export function codeUnitOrder(a: string, b: string): number {
|
|
7
|
+
return a < b ? -1 : a > b ? 1 : 0
|
|
8
|
+
}
|
|
@@ -22,6 +22,8 @@ function transitionSaga(
|
|
|
22
22
|
): SagaRecord | undefined {
|
|
23
23
|
if (
|
|
24
24
|
input.record.version !== input.version + 1 ||
|
|
25
|
+
// A record filed under another saga's key could never load again, and scans would act on its id.
|
|
26
|
+
input.record.sagaId !== input.sagaId ||
|
|
25
27
|
(current !== undefined &&
|
|
26
28
|
(current.sagaId !== input.sagaId || current.version !== input.version))
|
|
27
29
|
) {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* registration.
|
|
17
17
|
*/
|
|
18
18
|
import type { CompiledRoutePattern } from "../router/pattern.ts"
|
|
19
|
-
import {
|
|
19
|
+
import { Router, type RouterMatch } from "../router/router.ts"
|
|
20
20
|
import type { RouteDescriptor } from "../server/server-types.ts"
|
|
21
21
|
import type { AssuranceDeclaration } from "./route-assurance.ts"
|
|
22
22
|
import type { RouteEntry } from "./route-execution.ts"
|
|
@@ -24,7 +24,7 @@ import type { RouteEntry } from "./route-execution.ts"
|
|
|
24
24
|
/** One canonical runtime route fact. The catalog owns matching, reflection, assurance, tool metadata,
|
|
25
25
|
* replay, and native compilation input so batch registration has one commit point. */
|
|
26
26
|
export interface CatalogRoute {
|
|
27
|
-
readonly method:
|
|
27
|
+
readonly method: string
|
|
28
28
|
readonly path: string
|
|
29
29
|
readonly pattern: CompiledRoutePattern
|
|
30
30
|
readonly entry: RouteEntry
|
|
@@ -33,10 +33,10 @@ export interface CatalogRoute {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Runtime route catalog. Single-route registration mutates directly
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
36
|
+
* Runtime route catalog. Single-route registration mutates directly. A batch adds its routes the same
|
|
37
|
+
* way and, if one is rejected, restores the catalog to what it was before the batch. A failed
|
|
38
|
+
* `implement()`/`merge()` batch therefore leaves matching and reflection unchanged, and a batch that
|
|
39
|
+
* validates costs what its routes cost one by one.
|
|
40
40
|
*
|
|
41
41
|
* Coverage note: this file reports 90% functions with every method exercised. `bun test --coverage`
|
|
42
42
|
* (1.3.14) counts one synthetic function per class that it never marks hit - a one-method class with a
|
|
@@ -58,18 +58,19 @@ export class RouteCatalog {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
addBatch(routes: readonly CatalogRoute[]): void {
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
61
|
+
const kept = this.records.length
|
|
62
|
+
const assurancePresent = this.assurancePresent
|
|
63
|
+
try {
|
|
64
|
+
for (const route of routes) this.add(route)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
// The rejected route may have left nodes in the trie, so the matcher is not patched: it is
|
|
67
|
+
// rebuilt from the routes that were there before the batch. They validated once already.
|
|
68
|
+
this.records.length = this.descriptors.length = kept
|
|
69
|
+
this.assurancePresent = assurancePresent
|
|
70
|
+
this.matcher = new Router<RouteEntry>()
|
|
71
|
+
for (const route of this.records) this.matcher.add(route.method, route.pattern, route.entry)
|
|
72
|
+
throw error
|
|
73
|
+
}
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
find(method: string, path: string): RouterMatch<RouteEntry> {
|
|
@@ -13,13 +13,14 @@
|
|
|
13
13
|
import type { EffectLifecycleObserver } from "../effect-lifecycle.ts"
|
|
14
14
|
import { RouteConfigError } from "../errors.ts"
|
|
15
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
16
|
import { assertByteLimit } from "../server/body.ts"
|
|
19
17
|
import type { RouteSchema } from "../server/context.ts"
|
|
20
18
|
import type { IdempotencyRuntime, ResolvedIdempotency } from "../server/idempotency-lane.ts"
|
|
21
19
|
import type { EffectLedgerRuntime, ResolvedEffectLedger } from "../server/ledger-lane.ts"
|
|
22
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
ResponseContractDefinition,
|
|
22
|
+
ResponseContractRuntime,
|
|
23
|
+
} from "../server/response-contract-lane.ts"
|
|
23
24
|
import {
|
|
24
25
|
CAPABILITY_GUARD,
|
|
25
26
|
type CapabilityUseEvent,
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
import type {
|
|
38
39
|
RawAfterHandle,
|
|
39
40
|
RawAround,
|
|
41
|
+
RawAuthStage,
|
|
40
42
|
RawBeforeHandle,
|
|
41
43
|
RawDerive,
|
|
42
44
|
RawErrorHandler,
|
|
@@ -55,6 +57,7 @@ export interface RouteCompilerContext {
|
|
|
55
57
|
readonly idempotencyRuntime: IdempotencyRuntime | undefined
|
|
56
58
|
readonly responseContractRuntime: ResponseContractRuntime | undefined
|
|
57
59
|
readonly derives: readonly RawDerive[]
|
|
60
|
+
readonly authStages: readonly RawAuthStage[]
|
|
58
61
|
readonly beforeHandleHooks: readonly RawBeforeHandle[]
|
|
59
62
|
readonly afterHandleHooks: readonly RawAfterHandle[]
|
|
60
63
|
readonly onErrorHooks: readonly RawErrorHandler[]
|
|
@@ -72,16 +75,18 @@ export interface CompiledRouteOptions {
|
|
|
72
75
|
readonly idempotent: ResolvedIdempotency | undefined
|
|
73
76
|
readonly ledgered: ResolvedEffectLedger | undefined
|
|
74
77
|
readonly responseContract:
|
|
75
|
-
| { readonly runtime: ResponseContractRuntime; readonly
|
|
78
|
+
| { readonly runtime: ResponseContractRuntime; readonly definition: ResponseContractDefinition }
|
|
76
79
|
| undefined
|
|
77
80
|
readonly lanes: RouteLaneSelection
|
|
78
81
|
readonly routeAssurance: readonly AssuranceDeclaration[]
|
|
82
|
+
readonly authStages: readonly RawAuthStage[]
|
|
83
|
+
readonly authenticated: boolean
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
/** Resolve all registration-time policy and lane facts for one route. */
|
|
82
87
|
export function compileRouteOptions(
|
|
83
88
|
context: RouteCompilerContext,
|
|
84
|
-
method:
|
|
89
|
+
method: string,
|
|
85
90
|
path: string,
|
|
86
91
|
schema: RouteSchema | undefined,
|
|
87
92
|
handler: (context: never) => unknown,
|
|
@@ -91,6 +96,25 @@ export function compileRouteOptions(
|
|
|
91
96
|
const invalidBodyLimit = (message: string): never => {
|
|
92
97
|
throw new RouteConfigError("INVALID_BODY_LIMIT", `route ${method} ${path}: ${message}`)
|
|
93
98
|
}
|
|
99
|
+
if (schema?.wire !== undefined && schema.wire !== "raw") {
|
|
100
|
+
throw new RouteConfigError("INVALID_WIRE", `route ${method} ${path}: wire must be "raw"`)
|
|
101
|
+
}
|
|
102
|
+
if (schema?.wire === "raw" && schema.body !== undefined) {
|
|
103
|
+
throw new RouteConfigError(
|
|
104
|
+
"INVALID_WIRE",
|
|
105
|
+
`route ${method} ${path}: wire: "raw" cannot be combined with a body schema`,
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
if (
|
|
109
|
+
schema?.validationOrder !== undefined &&
|
|
110
|
+
schema.validationOrder !== "validate-before-auth" &&
|
|
111
|
+
schema.validationOrder !== "auth-before-validation"
|
|
112
|
+
) {
|
|
113
|
+
throw new RouteConfigError(
|
|
114
|
+
"INVALID_VALIDATION_ORDER",
|
|
115
|
+
`route ${method} ${path}: validationOrder is invalid`,
|
|
116
|
+
)
|
|
117
|
+
}
|
|
94
118
|
if (schema?.bodyLimitReason !== undefined && schema.bodyLimit !== "unlimited") {
|
|
95
119
|
invalidBodyLimit('bodyLimitReason is only valid with bodyLimit: "unlimited"')
|
|
96
120
|
}
|
|
@@ -124,11 +148,13 @@ export function compileRouteOptions(
|
|
|
124
148
|
)
|
|
125
149
|
}
|
|
126
150
|
|
|
127
|
-
const authenticated =
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
151
|
+
const authenticated =
|
|
152
|
+
context.authStages.length > 0 ||
|
|
153
|
+
assuranceEvidenceFor(
|
|
154
|
+
[...context.activeAssurance, ...handlerAssurance, ...context.globalAssurance],
|
|
155
|
+
method,
|
|
156
|
+
path,
|
|
157
|
+
).some((evidence) => evidence.id === NIFRA_ASSURANCE_IDS.AUTHENTICATED)
|
|
132
158
|
const routeDecorations: Record<PropertyKey, unknown> = { ...context.decorations }
|
|
133
159
|
if (capabilities.length > 0) {
|
|
134
160
|
routeDecorations[CAPABILITY_GUARD] = createCapabilityGuard(
|
|
@@ -155,12 +181,20 @@ export function compileRouteOptions(
|
|
|
155
181
|
context.maxBodyBytes,
|
|
156
182
|
)
|
|
157
183
|
const ledgered = context.effectLedgerRuntime?.resolve(capabilities, method, path)
|
|
184
|
+
const responseDefinition =
|
|
185
|
+
schema?.response !== undefined || schema?.errors !== undefined
|
|
186
|
+
? {
|
|
187
|
+
...(schema.response === undefined ? {} : { response: schema.response }),
|
|
188
|
+
...(schema.errors === undefined ? {} : { errors: schema.errors }),
|
|
189
|
+
}
|
|
190
|
+
: undefined
|
|
158
191
|
const contracted =
|
|
159
|
-
context.responseContractRuntime !== undefined &&
|
|
160
|
-
? { runtime: context.responseContractRuntime,
|
|
192
|
+
context.responseContractRuntime !== undefined && responseDefinition !== undefined
|
|
193
|
+
? { runtime: context.responseContractRuntime, definition: responseDefinition }
|
|
161
194
|
: undefined
|
|
162
195
|
const lanes = selectRouteLanes({
|
|
163
196
|
schema,
|
|
197
|
+
hasAuth: context.authStages.length > 0,
|
|
164
198
|
hasIdempotency: idempotent !== undefined,
|
|
165
199
|
hasLedger: ledgered !== undefined,
|
|
166
200
|
hasResponseContract: contracted !== undefined,
|
|
@@ -174,7 +208,7 @@ export function compileRouteOptions(
|
|
|
174
208
|
})
|
|
175
209
|
|
|
176
210
|
const routeAssurance: AssuranceDeclaration[] = [...context.activeAssurance, ...handlerAssurance]
|
|
177
|
-
if (contracted?.runtime.mode === "enforce"
|
|
211
|
+
if (contracted?.runtime.mode === "enforce") {
|
|
178
212
|
routeAssurance.push(
|
|
179
213
|
Object.freeze({
|
|
180
214
|
id: NIFRA_ASSURANCE_IDS.RESPONSE_CONTRACT,
|
|
@@ -217,6 +251,19 @@ export function compileRouteOptions(
|
|
|
217
251
|
}),
|
|
218
252
|
)
|
|
219
253
|
}
|
|
254
|
+
if (context.authStages.length > 0) {
|
|
255
|
+
// `authenticate()` is an actual runtime enforcement stage, not a route assertion. Publish its
|
|
256
|
+
// proof directly so a core-authenticated route cannot accidentally become assurance
|
|
257
|
+
// classification-only when it is not using an integration plugin such as better-auth.
|
|
258
|
+
routeAssurance.push(
|
|
259
|
+
Object.freeze({
|
|
260
|
+
id: NIFRA_ASSURANCE_IDS.AUTHENTICATED,
|
|
261
|
+
source: "nifra.authenticate",
|
|
262
|
+
scope: "plugin",
|
|
263
|
+
provenance: "runtime" as const,
|
|
264
|
+
}),
|
|
265
|
+
)
|
|
266
|
+
}
|
|
220
267
|
|
|
221
268
|
return {
|
|
222
269
|
pattern,
|
|
@@ -230,5 +277,7 @@ export function compileRouteOptions(
|
|
|
230
277
|
responseContract: contracted,
|
|
231
278
|
lanes,
|
|
232
279
|
routeAssurance: Object.freeze(routeAssurance),
|
|
280
|
+
authStages: Object.freeze([...context.authStages]),
|
|
281
|
+
authenticated,
|
|
233
282
|
}
|
|
234
283
|
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { RequestBudget } from "../budget.ts"
|
|
20
20
|
import type { RequestLedger } from "../ledger.ts"
|
|
21
|
-
import type {
|
|
21
|
+
import type { AuthenticationStage } from "../server/auth.ts"
|
|
22
22
|
import { UNLIMITED_BODY_BYTES } from "../server/body.ts"
|
|
23
23
|
import type { Platform, RouteSchema } from "../server/context.ts"
|
|
24
24
|
import type { ResolvedIdempotency } from "../server/idempotency-lane.ts"
|
|
@@ -26,7 +26,10 @@ import type { EffectLedgerRuntime, ResolvedEffectLedger } from "../server/ledger
|
|
|
26
26
|
import type { ProtoPoisoning } from "../server/proto-guard.ts"
|
|
27
27
|
import type { Registry } from "../server/registry.ts"
|
|
28
28
|
import { RequestContext } from "../server/request-context.ts"
|
|
29
|
-
import type {
|
|
29
|
+
import type {
|
|
30
|
+
ResponseContractDefinition,
|
|
31
|
+
ResponseContractRuntime,
|
|
32
|
+
} from "../server/response-contract-lane.ts"
|
|
30
33
|
import type { HandlerResult, ResponseResult } from "../server/runtime-core.ts"
|
|
31
34
|
import type { CtxSet, MaybePromise, RawContext, RequestSource, Server } from "../server/server.ts"
|
|
32
35
|
import type { RouteProgram } from "./route-program.ts"
|
|
@@ -35,6 +38,8 @@ export type InternalHandler = (ctx: RawContext) => MaybePromise<HandlerResult>
|
|
|
35
38
|
|
|
36
39
|
/** A `derive` computes per-request context extensions; stored path-erased. */
|
|
37
40
|
export type RawDerive = (ctx: RawContext) => MaybePromise<object>
|
|
41
|
+
/** A route-scoped authentication provider, stored path-erased after registration. */
|
|
42
|
+
export type RawAuthStage = AuthenticationStage<unknown, unknown>
|
|
38
43
|
export type RawBeforeHandle = (ctx: RawContext) => MaybePromise<unknown>
|
|
39
44
|
export type RawAfterHandle = (result: unknown, ctx: RawContext) => MaybePromise<unknown>
|
|
40
45
|
export type RawErrorHandler = (error: unknown, ctx: RawContext) => MaybePromise<unknown>
|
|
@@ -311,10 +316,15 @@ export interface RouteEntry {
|
|
|
311
316
|
* at registration; `undefined` = not checked (the default, and the only state in which the route can
|
|
312
317
|
* still take the fused/native lanes). */
|
|
313
318
|
readonly responseContract:
|
|
314
|
-
| {
|
|
319
|
+
| {
|
|
320
|
+
readonly runtime: ResponseContractRuntime
|
|
321
|
+
readonly definition: ResponseContractDefinition
|
|
322
|
+
}
|
|
315
323
|
| undefined
|
|
316
324
|
/** Per-request context extensions captured at registration (order-scoped). */
|
|
317
325
|
readonly derives: ReadonlyArray<RawDerive>
|
|
326
|
+
/** Dedicated auth stages run before validation; ordinary derives keep their existing order. */
|
|
327
|
+
readonly authStages: ReadonlyArray<RawAuthStage>
|
|
318
328
|
/** Static context extensions captured at registration. */
|
|
319
329
|
readonly decorations: Record<PropertyKey, unknown>
|
|
320
330
|
/** Whether {@link decorations} has any keys - precomputed so the hot path skips a no-op
|
|
@@ -31,6 +31,7 @@ export interface RouteLaneSelection {
|
|
|
31
31
|
|
|
32
32
|
export function selectRouteLanes(options: {
|
|
33
33
|
readonly schema: RouteSchema | undefined
|
|
34
|
+
readonly hasAuth?: boolean
|
|
34
35
|
readonly hasIdempotency: boolean
|
|
35
36
|
readonly hasLedger: boolean
|
|
36
37
|
readonly hasResponseContract: boolean
|
|
@@ -56,9 +57,15 @@ export function selectRouteLanes(options: {
|
|
|
56
57
|
defaultOnValidationError,
|
|
57
58
|
} = options
|
|
58
59
|
|
|
60
|
+
const authBeforeValidation =
|
|
61
|
+
options.hasAuth === true || schema?.validationOrder === "auth-before-validation"
|
|
62
|
+
// No fused lane validates params, headers, or cookies: any of those schemas keeps the stage runner.
|
|
63
|
+
const plain =
|
|
64
|
+
schema?.params === undefined && schema?.headers === undefined && schema?.cookies === undefined
|
|
65
|
+
|
|
59
66
|
const bare =
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
!authBeforeValidation &&
|
|
68
|
+
plain &&
|
|
62
69
|
schema?.body === undefined &&
|
|
63
70
|
schema?.query === undefined &&
|
|
64
71
|
!hasIdempotency &&
|
|
@@ -76,11 +83,11 @@ export function selectRouteLanes(options: {
|
|
|
76
83
|
// exactly `validationError(issues)`, so any recovery semantics keep the generic lane.
|
|
77
84
|
const fusedQuery =
|
|
78
85
|
!bare &&
|
|
86
|
+
!authBeforeValidation &&
|
|
79
87
|
!hasResponseContract &&
|
|
80
88
|
schema?.query !== undefined &&
|
|
81
89
|
schema.body === undefined &&
|
|
82
|
-
|
|
83
|
-
schema.headers === undefined &&
|
|
90
|
+
plain &&
|
|
84
91
|
schema.onValidationError === undefined &&
|
|
85
92
|
!defaultOnValidationError &&
|
|
86
93
|
!hasIdempotency &&
|
|
@@ -92,11 +99,11 @@ export function selectRouteLanes(options: {
|
|
|
92
99
|
around === 0
|
|
93
100
|
|
|
94
101
|
const bodyOnly =
|
|
102
|
+
!authBeforeValidation &&
|
|
95
103
|
!hasResponseContract &&
|
|
96
104
|
schema?.body !== undefined &&
|
|
97
105
|
schema.query === undefined &&
|
|
98
|
-
|
|
99
|
-
schema.headers === undefined &&
|
|
106
|
+
plain &&
|
|
100
107
|
derives === 0 &&
|
|
101
108
|
beforeHandle === 0 &&
|
|
102
109
|
afterHandle === 0 &&
|
|
@@ -104,6 +111,7 @@ export function selectRouteLanes(options: {
|
|
|
104
111
|
|
|
105
112
|
const fusedBody =
|
|
106
113
|
!bare &&
|
|
114
|
+
!authBeforeValidation &&
|
|
107
115
|
bodyOnly &&
|
|
108
116
|
schema.onValidationError === undefined &&
|
|
109
117
|
!defaultOnValidationError &&
|
|
@@ -111,28 +119,29 @@ export function selectRouteLanes(options: {
|
|
|
111
119
|
!hasLedger &&
|
|
112
120
|
around === 0
|
|
113
121
|
|
|
114
|
-
const lane: RouteExecutionLane =
|
|
115
|
-
? "
|
|
116
|
-
:
|
|
117
|
-
? "
|
|
118
|
-
:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
const lane: RouteExecutionLane = authBeforeValidation
|
|
123
|
+
? "lifecycle"
|
|
124
|
+
: bare
|
|
125
|
+
? "bare"
|
|
126
|
+
: bodyOnly
|
|
127
|
+
? "body"
|
|
128
|
+
: !hasResponseContract &&
|
|
129
|
+
schema?.body === undefined &&
|
|
130
|
+
schema?.query !== undefined &&
|
|
131
|
+
plain &&
|
|
132
|
+
derives === 0 &&
|
|
133
|
+
beforeHandle === 0 &&
|
|
134
|
+
afterHandle === 0 &&
|
|
135
|
+
onError === 0
|
|
136
|
+
? "query"
|
|
137
|
+
: "lifecycle"
|
|
129
138
|
|
|
130
139
|
// Lifecycle routes with no params schema are the common middleware shape: derive/before plus an
|
|
131
140
|
// optional query or body schema. Select their complete validation stage at registration so the
|
|
132
141
|
// request path never re-checks params/body presence. Parameter-schema routes retain the generic
|
|
133
142
|
// lifecycle runner until their more involved recovery matrix is selected explicitly.
|
|
134
143
|
const lifecycleLane: LifecycleExecutionLane =
|
|
135
|
-
lane !== "lifecycle" ||
|
|
144
|
+
lane !== "lifecycle" || !plain
|
|
136
145
|
? undefined
|
|
137
146
|
: schema?.body !== undefined
|
|
138
147
|
? schema.query !== undefined
|
|
@@ -145,21 +154,21 @@ export function selectRouteLanes(options: {
|
|
|
145
154
|
// The realistic middleware shape is commonly exactly one synchronous-or-async derive followed by
|
|
146
155
|
// one before hook. Keep the generic runner for every route that can observe decorations, after hooks,
|
|
147
156
|
// error hooks, or response contracts; this lane only removes the two per-request hook-loop dispatches
|
|
148
|
-
// and preserves the same async continuations and error handling. A params or
|
|
149
|
-
// back to the generic lane because the fused builder has no
|
|
157
|
+
// and preserves the same async continuations and error handling. A params, headers, or cookies schema
|
|
158
|
+
// routes back to the generic lane because the fused builder has no validation step for them
|
|
150
159
|
// baked in - adding them would be more code than it saves. A `body` schema is the same story
|
|
151
160
|
// because the fused derive-before / derive-before-after builders don't parse the body: routes with a
|
|
152
161
|
// body schema are routed to the `body-derive-before` / `body-derive-before-after` lane below.
|
|
153
162
|
const lifecycleHookLane =
|
|
154
163
|
lane === "lifecycle" &&
|
|
164
|
+
!authBeforeValidation &&
|
|
155
165
|
!hasResponseContract &&
|
|
156
166
|
!hasDecorations &&
|
|
157
167
|
!hasIdempotency &&
|
|
158
168
|
!hasLedger &&
|
|
159
169
|
schema?.onValidationError === undefined &&
|
|
160
170
|
!defaultOnValidationError &&
|
|
161
|
-
|
|
162
|
-
schema?.headers === undefined &&
|
|
171
|
+
plain &&
|
|
163
172
|
schema?.body === undefined &&
|
|
164
173
|
derives === 1 &&
|
|
165
174
|
beforeHandle === 1 &&
|
|
@@ -177,10 +186,10 @@ export function selectRouteLanes(options: {
|
|
|
177
186
|
// win `fusedBody` already gives for the no-hooks body shape.
|
|
178
187
|
const fusedBodyLifecycle =
|
|
179
188
|
lane === "lifecycle" &&
|
|
189
|
+
!authBeforeValidation &&
|
|
180
190
|
schema?.body !== undefined &&
|
|
181
191
|
schema?.query === undefined &&
|
|
182
|
-
|
|
183
|
-
schema?.headers === undefined &&
|
|
192
|
+
plain &&
|
|
184
193
|
schema?.onValidationError === undefined &&
|
|
185
194
|
!defaultOnValidationError &&
|
|
186
195
|
!hasResponseContract &&
|