@nifrajs/core 4.0.2 → 5.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 +6 -2
- package/dist/assurance.d.ts +86 -6
- package/dist/assurance.d.ts.map +1 -1
- package/dist/assurance.js +186 -95
- package/dist/assurance.js.map +1 -1
- package/dist/capabilities.d.ts +8 -0
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +53 -1
- package/dist/capabilities.js.map +1 -1
- package/dist/durable-effect.js +1 -1
- package/dist/durable-effect.js.map +1 -1
- package/dist/durable-saga.d.ts +6 -0
- package/dist/durable-saga.d.ts.map +1 -1
- package/dist/durable-saga.js +83 -10
- package/dist/durable-saga.js.map +1 -1
- package/dist/edge-kit.d.ts +8 -3
- package/dist/edge-kit.d.ts.map +1 -1
- package/dist/edge-kit.js +7 -2
- package/dist/edge-kit.js.map +1 -1
- package/dist/effect-scope.d.ts.map +1 -1
- package/dist/effect-scope.js +16 -5
- 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 +2 -0
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +9 -1
- package/dist/evidence.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/durable-shared.d.ts +1 -2
- package/dist/internal/durable-shared.d.ts.map +1 -1
- package/dist/internal/durable-shared.js +25 -44
- package/dist/internal/durable-shared.js.map +1 -1
- package/dist/internal/durable-types.d.ts +3 -0
- package/dist/internal/durable-types.d.ts.map +1 -1
- package/dist/internal/hook-origins.d.ts +67 -0
- package/dist/internal/hook-origins.d.ts.map +1 -0
- package/dist/internal/hook-origins.js +86 -0
- package/dist/internal/hook-origins.js.map +1 -0
- package/dist/internal/route-assurance.d.ts +6 -3
- package/dist/internal/route-assurance.d.ts.map +1 -1
- package/dist/internal/route-assurance.js +29 -14
- package/dist/internal/route-assurance.js.map +1 -1
- package/dist/internal/route-compiler.d.ts +9 -0
- package/dist/internal/route-compiler.d.ts.map +1 -1
- package/dist/internal/route-compiler.js +95 -13
- package/dist/internal/route-compiler.js.map +1 -1
- package/dist/internal/route-program.d.ts +1 -1
- package/dist/internal/route-program.d.ts.map +1 -1
- package/dist/internal/route-program.js +59 -16
- package/dist/internal/route-program.js.map +1 -1
- package/dist/internal/same-origin.d.ts +11 -1
- package/dist/internal/same-origin.d.ts.map +1 -1
- package/dist/internal/same-origin.js +8 -0
- package/dist/internal/same-origin.js.map +1 -1
- package/dist/reconciliation-worker.d.ts.map +1 -1
- package/dist/reconciliation-worker.js +12 -2
- package/dist/reconciliation-worker.js.map +1 -1
- package/dist/reflection.d.ts +53 -2
- package/dist/reflection.d.ts.map +1 -1
- package/dist/reflection.js +352 -5
- package/dist/reflection.js.map +1 -1
- package/dist/router/pattern.d.ts +1 -1
- package/dist/router/pattern.d.ts.map +1 -1
- package/dist/router/pattern.js +6 -3
- package/dist/router/pattern.js.map +1 -1
- package/dist/security/baseline.d.ts +3 -0
- package/dist/security/baseline.d.ts.map +1 -1
- package/dist/security/baseline.js +9 -5
- package/dist/security/baseline.js.map +1 -1
- package/dist/server/auth.d.ts +32 -3
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +23 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/body-lane.d.ts +8 -2
- package/dist/server/body-lane.d.ts.map +1 -1
- package/dist/server/body-lane.js +27 -2
- package/dist/server/body-lane.js.map +1 -1
- package/dist/server/body.d.ts +7 -0
- package/dist/server/body.d.ts.map +1 -1
- package/dist/server/body.js +34 -1
- package/dist/server/body.js.map +1 -1
- package/dist/server/client-ip.d.ts +3 -0
- package/dist/server/client-ip.d.ts.map +1 -1
- package/dist/server/client-ip.js +6 -5
- package/dist/server/client-ip.js.map +1 -1
- package/dist/server/context.d.ts +2 -1
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/cookies.d.ts.map +1 -1
- package/dist/server/cookies.js +13 -1
- package/dist/server/cookies.js.map +1 -1
- package/dist/server/edge.d.ts.map +1 -1
- package/dist/server/edge.js +2 -1
- package/dist/server/edge.js.map +1 -1
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +5 -1
- package/dist/server/http.js.map +1 -1
- package/dist/server/idempotency-lane.d.ts +6 -3
- package/dist/server/idempotency-lane.d.ts.map +1 -1
- package/dist/server/idempotency-lane.js +38 -17
- package/dist/server/idempotency-lane.js.map +1 -1
- package/dist/server/install.d.ts +2 -0
- package/dist/server/install.d.ts.map +1 -1
- package/dist/server/install.js +2 -0
- package/dist/server/install.js.map +1 -1
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/logger.js +3 -1
- package/dist/server/logger.js.map +1 -1
- package/dist/server/methods.d.ts +1 -0
- package/dist/server/methods.d.ts.map +1 -1
- package/dist/server/methods.js +1 -0
- package/dist/server/methods.js.map +1 -1
- package/dist/server/node-outcome.d.ts.map +1 -1
- package/dist/server/node-outcome.js +29 -11
- package/dist/server/node-outcome.js.map +1 -1
- package/dist/server/platform-adapters.d.ts.map +1 -1
- package/dist/server/platform-adapters.js +3 -2
- package/dist/server/platform-adapters.js.map +1 -1
- package/dist/server/request-context.d.ts +2 -1
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +4 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/respond.d.ts +44 -11
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +192 -79
- package/dist/server/respond.js.map +1 -1
- package/dist/server/response-contract-lane.d.ts +21 -6
- package/dist/server/response-contract-lane.d.ts.map +1 -1
- package/dist/server/response-contract-lane.js +139 -14
- package/dist/server/response-contract-lane.js.map +1 -1
- package/dist/server/response-observer-runtime.d.ts.map +1 -1
- package/dist/server/response-observer-runtime.js +10 -41
- 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 +2 -0
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server-types.d.ts +45 -10
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +67 -32
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +481 -334
- package/dist/server/server.js.map +1 -1
- package/dist/server/sse.d.ts +11 -1
- package/dist/server/sse.d.ts.map +1 -1
- package/dist/server/sse.js +18 -3
- package/dist/server/sse.js.map +1 -1
- package/dist/server/transport-lane.d.ts +7 -1
- package/dist/server/transport-lane.d.ts.map +1 -1
- package/dist/server/transport-lane.js.map +1 -1
- package/dist/server/webhook.d.ts +5 -2
- package/dist/server/webhook.d.ts.map +1 -1
- package/dist/server/webhook.js +17 -1
- package/dist/server/webhook.js.map +1 -1
- package/dist/server/websocket.d.ts +33 -14
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +47 -12
- package/dist/server/websocket.js.map +1 -1
- package/dist/server/ws-bun.d.ts +7 -1
- package/dist/server/ws-bun.d.ts.map +1 -1
- package/dist/server/ws-bun.js +42 -2
- package/dist/server/ws-bun.js.map +1 -1
- package/dist/server/ws-hook.d.ts +15 -3
- package/dist/server/ws-hook.d.ts.map +1 -1
- package/dist/server.d.ts +4 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -2
- package/dist/server.js.map +1 -1
- package/dist/single-copy.d.ts +6 -0
- package/dist/single-copy.d.ts.map +1 -1
- package/dist/single-copy.js +55 -6
- package/dist/single-copy.js.map +1 -1
- package/dist/tool-contract.d.ts.map +1 -1
- package/dist/tool-contract.js +71 -40
- package/dist/tool-contract.js.map +1 -1
- package/dist/wire.d.ts +5 -0
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +5 -0
- package/dist/wire.js.map +1 -1
- package/dist/ws.d.ts.map +1 -1
- package/dist/ws.js +4 -1
- package/dist/ws.js.map +1 -1
- package/package.json +1 -1
- package/src/assurance.ts +299 -111
- package/src/capabilities.ts +62 -1
- package/src/durable-effect.ts +1 -1
- package/src/durable-saga.ts +94 -10
- package/src/edge-kit.ts +8 -3
- package/src/effect-scope.ts +15 -5
- package/src/errors.ts +1 -0
- package/src/evidence.ts +12 -3
- package/src/index.ts +7 -2
- package/src/internal/durable-shared.ts +25 -37
- package/src/internal/durable-types.ts +3 -0
- package/src/internal/hook-origins.ts +153 -0
- package/src/internal/route-assurance.ts +32 -18
- package/src/internal/route-compiler.ts +107 -16
- package/src/internal/route-program.ts +98 -44
- package/src/internal/same-origin.ts +14 -1
- package/src/reconciliation-worker.ts +10 -2
- package/src/reflection.ts +455 -4
- package/src/router/pattern.ts +6 -3
- package/src/security/baseline.ts +10 -5
- package/src/server/auth.ts +45 -3
- package/src/server/body-lane.ts +36 -1
- package/src/server/body.ts +34 -1
- package/src/server/client-ip.ts +8 -5
- package/src/server/context.ts +2 -1
- package/src/server/cookies.ts +12 -1
- package/src/server/edge.ts +2 -1
- package/src/server/http.ts +7 -1
- package/src/server/idempotency-lane.ts +64 -22
- package/src/server/install.ts +3 -0
- package/src/server/logger.ts +3 -1
- package/src/server/methods.ts +1 -0
- package/src/server/node-outcome.ts +34 -12
- package/src/server/platform-adapters.ts +3 -2
- package/src/server/request-context.ts +5 -0
- package/src/server/respond.ts +222 -69
- package/src/server/response-contract-lane.ts +162 -10
- package/src/server/response-observer-runtime.ts +16 -38
- package/src/server/runtime-core.ts +2 -0
- package/src/server/server-types.ts +45 -10
- package/src/server/server.ts +707 -539
- package/src/server/sse.ts +84 -55
- package/src/server/transport-lane.ts +7 -1
- package/src/server/webhook.ts +22 -3
- package/src/server/websocket.ts +83 -18
- package/src/server/ws-bun.ts +48 -1
- package/src/server/ws-hook.ts +14 -2
- package/src/server.ts +6 -1
- package/src/single-copy.ts +55 -5
- package/src/tool-contract.ts +67 -39
- package/src/wire.ts +14 -0
- package/src/ws.ts +6 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where each registered hook came from, for `reflectHooks` (`nifra routes --hooks`). Development-only:
|
|
3
|
+
* every write sits behind the inline `process.env.NODE_ENV !== "production"` guard at its call site in
|
|
4
|
+
* `server.ts`, so a production define folds the writes away and this module drops out of the bundle.
|
|
5
|
+
* Reads happen only from reflection, never on the request path.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type HookKind =
|
|
9
|
+
| "onRequest"
|
|
10
|
+
| "around"
|
|
11
|
+
| "authenticate"
|
|
12
|
+
| "derive"
|
|
13
|
+
| "beforeHandle"
|
|
14
|
+
| "afterHandle"
|
|
15
|
+
| "onError"
|
|
16
|
+
| "onResponse"
|
|
17
|
+
| "responseHeaders"
|
|
18
|
+
| "onResponseFinalized"
|
|
19
|
+
|
|
20
|
+
export interface HookOrigin {
|
|
21
|
+
readonly kind: HookKind
|
|
22
|
+
/** The server the hook was registered on. */
|
|
23
|
+
readonly owner: object
|
|
24
|
+
/** The plugin being applied when it was registered, if any. */
|
|
25
|
+
readonly plugin: string | undefined
|
|
26
|
+
/** A group's static headers turned into this hook at adoption: report the owner's declarations. */
|
|
27
|
+
readonly statics?: true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Which requests an adopted hook still runs for: under a group prefix, or a merged server's routes. */
|
|
31
|
+
export type HookScopeTag =
|
|
32
|
+
| { readonly inner: unknown; readonly prefix: string }
|
|
33
|
+
| {
|
|
34
|
+
readonly inner: unknown
|
|
35
|
+
readonly routes: readonly { readonly method: string; readonly path: string }[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** One `responseHeaders()` declaration that reached a server's static tier. */
|
|
39
|
+
export interface StaticHeadersTag {
|
|
40
|
+
readonly owner: object
|
|
41
|
+
readonly names: readonly string[]
|
|
42
|
+
readonly source: unknown
|
|
43
|
+
readonly plugin: string | undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AdoptionTag {
|
|
47
|
+
readonly source: object
|
|
48
|
+
readonly prefix: string | undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const origins = new WeakMap<object, HookOrigin>()
|
|
52
|
+
const scopes = new WeakMap<object, HookScopeTag>()
|
|
53
|
+
const sources = new WeakMap<object, unknown>()
|
|
54
|
+
const statics = new WeakMap<object, StaticHeadersTag[]>()
|
|
55
|
+
const adoptions = new WeakMap<object, AdoptionTag[]>()
|
|
56
|
+
let applying: string | undefined
|
|
57
|
+
// A merged server whose static headers are about to become one of the target's response hooks.
|
|
58
|
+
let staticsOwner: object | undefined
|
|
59
|
+
|
|
60
|
+
const isRef = (value: unknown): value is object =>
|
|
61
|
+
(typeof value === "function" || typeof value === "object") && value !== null
|
|
62
|
+
|
|
63
|
+
/** Record a hook's origin. The first registration wins, so a hook re-adopted elsewhere keeps its site. */
|
|
64
|
+
export function tagHook(hook: unknown, kind: HookKind, owner: object, groupStatics?: true): void {
|
|
65
|
+
if (!isRef(hook) || origins.has(hook)) return
|
|
66
|
+
if (kind === "responseHeaders" && staticsOwner !== undefined) {
|
|
67
|
+
origins.set(hook, { kind, owner: staticsOwner, plugin: applying, statics: true })
|
|
68
|
+
staticsOwner = undefined
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
origins.set(
|
|
72
|
+
hook,
|
|
73
|
+
groupStatics === true
|
|
74
|
+
? { kind, owner, plugin: applying, statics: true }
|
|
75
|
+
: { kind, owner, plugin: applying },
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A merge folds the source's static headers into the target: into its static tier, or - once the
|
|
81
|
+
* target has a response hook - into a new hook, which the next `responseHeaders` tag then is.
|
|
82
|
+
*/
|
|
83
|
+
export function adoptStaticTags(target: object, source: object, becomesHook: boolean): void {
|
|
84
|
+
if (becomesHook) {
|
|
85
|
+
staticsOwner = source
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
for (const tag of statics.get(source) ?? []) {
|
|
89
|
+
tagStatic(target, tag.names, tag.source, tag.owner, tag.plugin)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The author's function (or header record) an internal wrapper adapts, for naming it. */
|
|
94
|
+
export function tagSource(wrapper: unknown, source: unknown): void {
|
|
95
|
+
if (isRef(wrapper)) sources.set(wrapper, source)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Pair each scoped wrapper with the hook it gates and the scope it gates it by. */
|
|
99
|
+
export function tagScoped(
|
|
100
|
+
originals: readonly unknown[],
|
|
101
|
+
wrapped: readonly unknown[],
|
|
102
|
+
scope:
|
|
103
|
+
| { readonly prefix: string }
|
|
104
|
+
| { readonly routes: readonly { readonly method: string; readonly path: string }[] },
|
|
105
|
+
): void {
|
|
106
|
+
for (let i = 0; i < wrapped.length; i++) {
|
|
107
|
+
const wrapper = wrapped[i]
|
|
108
|
+
if (isRef(wrapper) && wrapper !== originals[i])
|
|
109
|
+
scopes.set(wrapper, { inner: originals[i], ...scope })
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function tagStatic(
|
|
114
|
+
server: object,
|
|
115
|
+
names: readonly string[],
|
|
116
|
+
source: unknown,
|
|
117
|
+
owner: object = server,
|
|
118
|
+
plugin: string | undefined = applying,
|
|
119
|
+
): void {
|
|
120
|
+
const list = statics.get(server)
|
|
121
|
+
const tag = { owner, names: names.map((name) => name.toLowerCase()), source, plugin }
|
|
122
|
+
if (list === undefined) statics.set(server, [tag])
|
|
123
|
+
else list.push(tag)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function tagAdoption(target: object, source: object, prefix: string | undefined): void {
|
|
127
|
+
const list = adoptions.get(target)
|
|
128
|
+
const tag = { source, prefix }
|
|
129
|
+
if (list === undefined) adoptions.set(target, [tag])
|
|
130
|
+
else list.push(tag)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const outer: (string | undefined)[] = []
|
|
134
|
+
|
|
135
|
+
/** Mark the plugin a `use()` is applying, until the matching {@link leavePlugin}. */
|
|
136
|
+
export function enterPlugin(name: string | undefined): void {
|
|
137
|
+
outer.push(applying)
|
|
138
|
+
if (name !== undefined) applying = name
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function leavePlugin(): void {
|
|
142
|
+
applying = outer.pop()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const hookOriginOf = (hook: unknown): HookOrigin | undefined =>
|
|
146
|
+
isRef(hook) ? origins.get(hook) : undefined
|
|
147
|
+
export const hookScopeOf = (hook: unknown): HookScopeTag | undefined =>
|
|
148
|
+
isRef(hook) ? scopes.get(hook) : undefined
|
|
149
|
+
export const hookSourceOf = (hook: unknown): unknown =>
|
|
150
|
+
isRef(hook) ? sources.get(hook) : undefined
|
|
151
|
+
export const staticHeadersOf = (server: object): readonly StaticHeadersTag[] =>
|
|
152
|
+
statics.get(server) ?? []
|
|
153
|
+
export const adoptionsOf = (server: object): readonly AdoptionTag[] => adoptions.get(server) ?? []
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { METHODS, type Method } from "../router/router.ts"
|
|
1
|
+
import { isRegistrableMethod, METHODS, type Method, type RouteMethod } from "../router/router.ts"
|
|
2
2
|
|
|
3
3
|
/** Where enforcement evidence follows Nifra's route-registration semantics. */
|
|
4
4
|
export type AssuranceScope = "global" | "subsequent" | "plugin"
|
|
@@ -15,8 +15,11 @@ export interface AssuranceEvidence {
|
|
|
15
15
|
/** Metadata installed on a middleware/plugin by {@link withRouteAssurance}. */
|
|
16
16
|
export interface AssuranceDeclaration extends AssuranceEvidence {
|
|
17
17
|
readonly scope: AssuranceScope
|
|
18
|
-
/** Restrict evidence to these HTTP methods. Omit for every
|
|
19
|
-
|
|
18
|
+
/** Restrict evidence to these HTTP methods, a custom one such as `PURGE` included. Omit for every
|
|
19
|
+
* method. */
|
|
20
|
+
readonly methods?: readonly RouteMethod[]
|
|
21
|
+
/** Withhold evidence from these HTTP methods. Every other method keeps it, custom ones included. */
|
|
22
|
+
readonly exceptMethods?: readonly RouteMethod[]
|
|
20
23
|
/** Restrict evidence to these absolute route globs. Omit for every path. */
|
|
21
24
|
readonly paths?: readonly string[]
|
|
22
25
|
}
|
|
@@ -117,12 +120,8 @@ function normalizeDeclaration(value: AssuranceDeclaration): AssuranceDeclaration
|
|
|
117
120
|
if (value.scope !== "global" && value.scope !== "subsequent" && value.scope !== "plugin") {
|
|
118
121
|
throw new Error(`route assurance: invalid scope ${JSON.stringify(value.scope)}`)
|
|
119
122
|
}
|
|
120
|
-
const methods = value.methods
|
|
121
|
-
|
|
122
|
-
if (!METHOD_SET.has(method)) {
|
|
123
|
-
throw new Error(`route assurance: unsupported HTTP method ${JSON.stringify(method)}`)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
123
|
+
const methods = registrableMethods(value.methods)
|
|
124
|
+
const exceptMethods = registrableMethods(value.exceptMethods)
|
|
126
125
|
const paths = value.paths?.map((path) => {
|
|
127
126
|
routeGlob(path)
|
|
128
127
|
return path
|
|
@@ -134,16 +133,31 @@ function normalizeDeclaration(value: AssuranceDeclaration): AssuranceDeclaration
|
|
|
134
133
|
) {
|
|
135
134
|
throw new Error(`route assurance: invalid provenance ${JSON.stringify(value.provenance)}`)
|
|
136
135
|
}
|
|
137
|
-
|
|
136
|
+
const declaration: { -readonly [K in keyof AssuranceDeclaration]: AssuranceDeclaration[K] } = {
|
|
138
137
|
id: value.id,
|
|
139
138
|
source: value.source.trim(),
|
|
140
139
|
scope: value.scope,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
140
|
+
}
|
|
141
|
+
// Carried, not defaulted: evidence attached by an author (rather than installed by a running
|
|
142
|
+
// hook) must stay distinguishable so `requireProvenance: "runtime"` rules can still reject it.
|
|
143
|
+
if (value.provenance !== undefined) declaration.provenance = value.provenance
|
|
144
|
+
if (methods !== undefined) declaration.methods = methods
|
|
145
|
+
if (exceptMethods !== undefined) declaration.exceptMethods = exceptMethods
|
|
146
|
+
if (paths !== undefined) declaration.paths = Object.freeze(paths)
|
|
147
|
+
return Object.freeze(declaration)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function registrableMethods(
|
|
151
|
+
values: readonly string[] | undefined,
|
|
152
|
+
): readonly RouteMethod[] | undefined {
|
|
153
|
+
if (values === undefined) return undefined
|
|
154
|
+
const methods = values.map((method) => method.toUpperCase())
|
|
155
|
+
for (const method of methods) {
|
|
156
|
+
if (!isRegistrableMethod(method)) {
|
|
157
|
+
throw new Error(`route assurance: unsupported HTTP method ${JSON.stringify(method)}`)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return Object.freeze(methods)
|
|
147
161
|
}
|
|
148
162
|
|
|
149
163
|
/** Validate and freeze declarations, one or many. */
|
|
@@ -179,8 +193,8 @@ export function declarationApplies(
|
|
|
179
193
|
method: string,
|
|
180
194
|
path: string,
|
|
181
195
|
): boolean {
|
|
182
|
-
if (declaration.methods !== undefined && !declaration.methods.includes(method
|
|
183
|
-
|
|
196
|
+
if (declaration.methods !== undefined && !declaration.methods.includes(method)) return false
|
|
197
|
+
if (declaration.exceptMethods?.includes(method) === true) return false
|
|
184
198
|
return (
|
|
185
199
|
declaration.paths === undefined ||
|
|
186
200
|
declaration.paths.some((pattern) => routeGlob(pattern).test(path))
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
NIFRA_ASSURANCE_IDS,
|
|
36
36
|
validEvidenceId,
|
|
37
37
|
} from "./route-assurance.ts"
|
|
38
|
+
import type { CatalogRoute } from "./route-catalog.ts"
|
|
38
39
|
import type {
|
|
39
40
|
RawAfterHandle,
|
|
40
41
|
RawAround,
|
|
@@ -43,7 +44,9 @@ import type {
|
|
|
43
44
|
RawDerive,
|
|
44
45
|
RawErrorHandler,
|
|
45
46
|
} from "./route-execution.ts"
|
|
47
|
+
import { compileRouteExecutionPlan } from "./route-execution.ts"
|
|
46
48
|
import { type RouteLaneSelection, selectRouteLanes } from "./route-lanes.ts"
|
|
49
|
+
import { compileRouteProgram } from "./route-program.ts"
|
|
47
50
|
|
|
48
51
|
export interface RouteCompilerContext {
|
|
49
52
|
readonly maxBodyBytes: number
|
|
@@ -83,6 +86,108 @@ export interface CompiledRouteOptions {
|
|
|
83
86
|
readonly authenticated: boolean
|
|
84
87
|
}
|
|
85
88
|
|
|
89
|
+
const RESPONSE_CONTRACT_EVIDENCE: AssuranceDeclaration = Object.freeze({
|
|
90
|
+
id: NIFRA_ASSURANCE_IDS.RESPONSE_CONTRACT,
|
|
91
|
+
source: "response-contract",
|
|
92
|
+
scope: "plugin",
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
function responseDefinitionOf(
|
|
96
|
+
schema: RouteSchema | undefined,
|
|
97
|
+
): ResponseContractDefinition | undefined {
|
|
98
|
+
return schema?.response !== undefined || schema?.errors !== undefined
|
|
99
|
+
? {
|
|
100
|
+
...(schema.response === undefined ? {} : { response: schema.response }),
|
|
101
|
+
...(schema.errors === undefined ? {} : { errors: schema.errors }),
|
|
102
|
+
}
|
|
103
|
+
: undefined
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Recompile a route another server compiled, under the response contract and effect ledger the
|
|
108
|
+
* adopting server installed and the route's own server did not. Both only add a check or a record,
|
|
109
|
+
* so a merged route can never serve more than it did standalone - and without this, the same
|
|
110
|
+
* `.use()` covered a `group()` route but not a `merge()`d one. A route that already resolved its
|
|
111
|
+
* own contract or ledger keeps it.
|
|
112
|
+
*/
|
|
113
|
+
export function adoptRouteRuntimes(
|
|
114
|
+
route: CatalogRoute,
|
|
115
|
+
responseContractRuntime: ResponseContractRuntime | undefined,
|
|
116
|
+
effectLedgerRuntime: EffectLedgerRuntime | undefined,
|
|
117
|
+
): CatalogRoute {
|
|
118
|
+
const { entry, descriptor } = route
|
|
119
|
+
const definition =
|
|
120
|
+
entry.responseContract === undefined && responseContractRuntime !== undefined
|
|
121
|
+
? responseDefinitionOf(entry.schema)
|
|
122
|
+
: undefined
|
|
123
|
+
const ledgered =
|
|
124
|
+
entry.ledgered ??
|
|
125
|
+
effectLedgerRuntime?.resolve(descriptor.capabilities ?? [], route.method, route.path)
|
|
126
|
+
if (definition === undefined && ledgered === entry.ledgered) return route
|
|
127
|
+
const responseContract =
|
|
128
|
+
definition === undefined || responseContractRuntime === undefined
|
|
129
|
+
? entry.responseContract
|
|
130
|
+
: { runtime: responseContractRuntime, definition }
|
|
131
|
+
// Either runtime rules out every fused lane, so the route needs only the generic plan here - and
|
|
132
|
+
// nothing the source server's validation-error default decided about fusion can matter any more.
|
|
133
|
+
const lanes = selectRouteLanes({
|
|
134
|
+
schema: entry.schema,
|
|
135
|
+
hasAuth: entry.authStages.length > 0,
|
|
136
|
+
hasIdempotency: entry.idempotent !== undefined,
|
|
137
|
+
hasLedger: ledgered !== undefined,
|
|
138
|
+
hasResponseContract: responseContract !== undefined,
|
|
139
|
+
hasDecorations: entry.hasDecorations,
|
|
140
|
+
derives: entry.derives.length,
|
|
141
|
+
beforeHandle: entry.beforeHandle.length,
|
|
142
|
+
afterHandle: entry.afterHandle.length,
|
|
143
|
+
onError: entry.onError.length,
|
|
144
|
+
around: entry.around.length,
|
|
145
|
+
defaultOnValidationError: false,
|
|
146
|
+
})
|
|
147
|
+
const program = compileRouteProgram({
|
|
148
|
+
schema: entry.schema,
|
|
149
|
+
handler: entry.handler,
|
|
150
|
+
derives: entry.derives,
|
|
151
|
+
authStages: entry.authStages,
|
|
152
|
+
beforeHandle: entry.beforeHandle,
|
|
153
|
+
afterHandle: entry.afterHandle,
|
|
154
|
+
onError: entry.onError,
|
|
155
|
+
decorations: entry.decorations,
|
|
156
|
+
hasDecorations: entry.hasDecorations,
|
|
157
|
+
bodySchema: entry.schema?.body,
|
|
158
|
+
bodyLimit: entry.bodyLimit,
|
|
159
|
+
responseContract,
|
|
160
|
+
})
|
|
161
|
+
const execution = compileRouteExecutionPlan({
|
|
162
|
+
lane: lanes.lane,
|
|
163
|
+
contextless: false,
|
|
164
|
+
hasAround: entry.around.length > 0,
|
|
165
|
+
hasLedger: ledgered !== undefined,
|
|
166
|
+
fusedWeb: undefined,
|
|
167
|
+
fusedBody: undefined,
|
|
168
|
+
fusedLane: undefined,
|
|
169
|
+
})
|
|
170
|
+
const enforced = definition !== undefined && responseContractRuntime?.mode === "enforce"
|
|
171
|
+
return {
|
|
172
|
+
...route,
|
|
173
|
+
entry: {
|
|
174
|
+
...entry,
|
|
175
|
+
ledgered,
|
|
176
|
+
responseContract,
|
|
177
|
+
lifecycleHookLane: undefined,
|
|
178
|
+
execution,
|
|
179
|
+
program,
|
|
180
|
+
},
|
|
181
|
+
descriptor:
|
|
182
|
+
definition === undefined || responseContractRuntime === undefined
|
|
183
|
+
? descriptor
|
|
184
|
+
: { ...descriptor, responseContract: responseContractRuntime.mode },
|
|
185
|
+
assurance: enforced
|
|
186
|
+
? Object.freeze([...route.assurance, RESPONSE_CONTRACT_EVIDENCE])
|
|
187
|
+
: route.assurance,
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
86
191
|
/** Resolve all registration-time policy and lane facts for one route. */
|
|
87
192
|
export function compileRouteOptions(
|
|
88
193
|
context: RouteCompilerContext,
|
|
@@ -181,13 +286,7 @@ export function compileRouteOptions(
|
|
|
181
286
|
context.maxBodyBytes,
|
|
182
287
|
)
|
|
183
288
|
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
|
|
289
|
+
const responseDefinition = responseDefinitionOf(schema)
|
|
191
290
|
const contracted =
|
|
192
291
|
context.responseContractRuntime !== undefined && responseDefinition !== undefined
|
|
193
292
|
? { runtime: context.responseContractRuntime, definition: responseDefinition }
|
|
@@ -208,15 +307,7 @@ export function compileRouteOptions(
|
|
|
208
307
|
})
|
|
209
308
|
|
|
210
309
|
const routeAssurance: AssuranceDeclaration[] = [...context.activeAssurance, ...handlerAssurance]
|
|
211
|
-
if (contracted?.runtime.mode === "enforce")
|
|
212
|
-
routeAssurance.push(
|
|
213
|
-
Object.freeze({
|
|
214
|
-
id: NIFRA_ASSURANCE_IDS.RESPONSE_CONTRACT,
|
|
215
|
-
source: "response-contract",
|
|
216
|
-
scope: "plugin",
|
|
217
|
-
}),
|
|
218
|
-
)
|
|
219
|
-
}
|
|
310
|
+
if (contracted?.runtime.mode === "enforce") routeAssurance.push(RESPONSE_CONTRACT_EVIDENCE)
|
|
220
311
|
for (const id of schema?.assurance ?? []) {
|
|
221
312
|
if (!validEvidenceId(id)) {
|
|
222
313
|
throw new Error(
|