@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,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registration-time route-lane selection.
|
|
3
|
+
*
|
|
4
|
+
* A route's lane is a property of its registered shape, not of an individual request. Keeping this
|
|
5
|
+
* decision in a small internal module gives the server kernel one vocabulary for bare/body/query/
|
|
6
|
+
* lifecycle execution while preserving the important invariant: this module is never called from the
|
|
7
|
+
* request path. The returned booleans are consumed by the existing compiled execution plan and fused
|
|
8
|
+
* builders; no adapter or extra dispatch hop is introduced at runtime.
|
|
9
|
+
*/
|
|
10
|
+
import type { RouteSchema } from "../server/context.ts"
|
|
11
|
+
import type { LifecycleExecutionLane, RouteExecutionLane } from "./route-execution.ts"
|
|
12
|
+
|
|
13
|
+
export interface RouteLaneSelection {
|
|
14
|
+
readonly bare: boolean
|
|
15
|
+
readonly fusedQuery: boolean
|
|
16
|
+
readonly bodyOnly: boolean
|
|
17
|
+
readonly fusedBody: boolean
|
|
18
|
+
readonly lane: RouteExecutionLane
|
|
19
|
+
readonly lifecycleLane: LifecycleExecutionLane
|
|
20
|
+
readonly lifecycleHookLane: "derive-before" | "derive-before-after" | undefined
|
|
21
|
+
readonly fusedLane:
|
|
22
|
+
| "bare"
|
|
23
|
+
| "body"
|
|
24
|
+
| "query"
|
|
25
|
+
| "derive-before"
|
|
26
|
+
| "derive-before-after"
|
|
27
|
+
| "body-derive-before"
|
|
28
|
+
| "body-derive-before-after"
|
|
29
|
+
| undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function selectRouteLanes(options: {
|
|
33
|
+
readonly schema: RouteSchema | undefined
|
|
34
|
+
readonly hasIdempotency: boolean
|
|
35
|
+
readonly hasLedger: boolean
|
|
36
|
+
readonly hasResponseContract: boolean
|
|
37
|
+
readonly hasDecorations: boolean
|
|
38
|
+
readonly derives: number
|
|
39
|
+
readonly beforeHandle: number
|
|
40
|
+
readonly afterHandle: number
|
|
41
|
+
readonly onError: number
|
|
42
|
+
readonly around: number
|
|
43
|
+
readonly defaultOnValidationError: boolean
|
|
44
|
+
}): RouteLaneSelection {
|
|
45
|
+
const {
|
|
46
|
+
schema,
|
|
47
|
+
hasIdempotency,
|
|
48
|
+
hasLedger,
|
|
49
|
+
hasResponseContract,
|
|
50
|
+
hasDecorations,
|
|
51
|
+
derives,
|
|
52
|
+
beforeHandle,
|
|
53
|
+
afterHandle,
|
|
54
|
+
onError,
|
|
55
|
+
around,
|
|
56
|
+
defaultOnValidationError,
|
|
57
|
+
} = options
|
|
58
|
+
|
|
59
|
+
const bare =
|
|
60
|
+
schema?.params === undefined &&
|
|
61
|
+
schema?.headers === undefined &&
|
|
62
|
+
schema?.body === undefined &&
|
|
63
|
+
schema?.query === undefined &&
|
|
64
|
+
!hasIdempotency &&
|
|
65
|
+
!hasLedger &&
|
|
66
|
+
!hasResponseContract &&
|
|
67
|
+
derives === 0 &&
|
|
68
|
+
beforeHandle === 0 &&
|
|
69
|
+
afterHandle === 0 &&
|
|
70
|
+
onError === 0
|
|
71
|
+
|
|
72
|
+
// A route whose ONLY lifecycle step is a query schema can fuse too: the parse + validate + handler +
|
|
73
|
+
// respond collapse into one closure with no lifecycle promise on the sync path. The guards mirror the
|
|
74
|
+
// query lane below PLUS everything the fused dispatch skips: around hooks, the idempotency/ledger
|
|
75
|
+
// wrappers, and validation-error recovery (schema or server default) - the fused invalid path is
|
|
76
|
+
// exactly `validationError(issues)`, so any recovery semantics keep the generic lane.
|
|
77
|
+
const fusedQuery =
|
|
78
|
+
!bare &&
|
|
79
|
+
!hasResponseContract &&
|
|
80
|
+
schema?.query !== undefined &&
|
|
81
|
+
schema.body === undefined &&
|
|
82
|
+
schema.params === undefined &&
|
|
83
|
+
schema.headers === undefined &&
|
|
84
|
+
schema.onValidationError === undefined &&
|
|
85
|
+
!defaultOnValidationError &&
|
|
86
|
+
!hasIdempotency &&
|
|
87
|
+
!hasLedger &&
|
|
88
|
+
derives === 0 &&
|
|
89
|
+
beforeHandle === 0 &&
|
|
90
|
+
afterHandle === 0 &&
|
|
91
|
+
onError === 0 &&
|
|
92
|
+
around === 0
|
|
93
|
+
|
|
94
|
+
const bodyOnly =
|
|
95
|
+
!hasResponseContract &&
|
|
96
|
+
schema?.body !== undefined &&
|
|
97
|
+
schema.query === undefined &&
|
|
98
|
+
schema.params === undefined &&
|
|
99
|
+
schema.headers === undefined &&
|
|
100
|
+
derives === 0 &&
|
|
101
|
+
beforeHandle === 0 &&
|
|
102
|
+
afterHandle === 0 &&
|
|
103
|
+
onError === 0
|
|
104
|
+
|
|
105
|
+
const fusedBody =
|
|
106
|
+
!bare &&
|
|
107
|
+
bodyOnly &&
|
|
108
|
+
schema.onValidationError === undefined &&
|
|
109
|
+
!defaultOnValidationError &&
|
|
110
|
+
!hasIdempotency &&
|
|
111
|
+
!hasLedger &&
|
|
112
|
+
around === 0
|
|
113
|
+
|
|
114
|
+
const lane: RouteExecutionLane = bare
|
|
115
|
+
? "bare"
|
|
116
|
+
: bodyOnly
|
|
117
|
+
? "body"
|
|
118
|
+
: !hasResponseContract &&
|
|
119
|
+
schema?.body === undefined &&
|
|
120
|
+
schema?.query !== undefined &&
|
|
121
|
+
schema.params === undefined &&
|
|
122
|
+
schema.headers === undefined &&
|
|
123
|
+
derives === 0 &&
|
|
124
|
+
beforeHandle === 0 &&
|
|
125
|
+
afterHandle === 0 &&
|
|
126
|
+
onError === 0
|
|
127
|
+
? "query"
|
|
128
|
+
: "lifecycle"
|
|
129
|
+
|
|
130
|
+
// Lifecycle routes with no params schema are the common middleware shape: derive/before plus an
|
|
131
|
+
// optional query or body schema. Select their complete validation stage at registration so the
|
|
132
|
+
// request path never re-checks params/body presence. Parameter-schema routes retain the generic
|
|
133
|
+
// lifecycle runner until their more involved recovery matrix is selected explicitly.
|
|
134
|
+
const lifecycleLane: LifecycleExecutionLane =
|
|
135
|
+
lane !== "lifecycle" || schema?.params !== undefined || schema?.headers !== undefined
|
|
136
|
+
? undefined
|
|
137
|
+
: schema?.body !== undefined
|
|
138
|
+
? schema.query !== undefined
|
|
139
|
+
? "body-query"
|
|
140
|
+
: "body"
|
|
141
|
+
: schema?.query !== undefined
|
|
142
|
+
? "query"
|
|
143
|
+
: "hooks"
|
|
144
|
+
|
|
145
|
+
// The realistic middleware shape is commonly exactly one synchronous-or-async derive followed by
|
|
146
|
+
// one before hook. Keep the generic runner for every route that can observe decorations, after hooks,
|
|
147
|
+
// 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 headers schema routes
|
|
149
|
+
// back to the generic lane because the fused builder has no `params` / `headers` validation step
|
|
150
|
+
// baked in - adding them would be more code than it saves. A `body` schema is the same story
|
|
151
|
+
// because the fused derive-before / derive-before-after builders don't parse the body: routes with a
|
|
152
|
+
// body schema are routed to the `body-derive-before` / `body-derive-before-after` lane below.
|
|
153
|
+
const lifecycleHookLane =
|
|
154
|
+
lane === "lifecycle" &&
|
|
155
|
+
!hasResponseContract &&
|
|
156
|
+
!hasDecorations &&
|
|
157
|
+
!hasIdempotency &&
|
|
158
|
+
!hasLedger &&
|
|
159
|
+
schema?.onValidationError === undefined &&
|
|
160
|
+
!defaultOnValidationError &&
|
|
161
|
+
schema?.params === undefined &&
|
|
162
|
+
schema?.headers === undefined &&
|
|
163
|
+
schema?.body === undefined &&
|
|
164
|
+
derives === 1 &&
|
|
165
|
+
beforeHandle === 1 &&
|
|
166
|
+
afterHandle <= 1 &&
|
|
167
|
+
onError === 0 &&
|
|
168
|
+
around === 0
|
|
169
|
+
? afterHandle === 0
|
|
170
|
+
? "derive-before"
|
|
171
|
+
: "derive-before-after"
|
|
172
|
+
: undefined
|
|
173
|
+
|
|
174
|
+
// Body + lifecycle hooks in a closed shape: a body schema plus the same 1-derive + 1-before +
|
|
175
|
+
// 0-or-1-after + 0-onError shape, with no other body/query/path conflict. The fused closure runs
|
|
176
|
+
// parse → validate → derive → before → handler → after → respond in one function frame, the same
|
|
177
|
+
// win `fusedBody` already gives for the no-hooks body shape.
|
|
178
|
+
const fusedBodyLifecycle =
|
|
179
|
+
lane === "lifecycle" &&
|
|
180
|
+
schema?.body !== undefined &&
|
|
181
|
+
schema?.query === undefined &&
|
|
182
|
+
schema?.params === undefined &&
|
|
183
|
+
schema?.headers === undefined &&
|
|
184
|
+
schema?.onValidationError === undefined &&
|
|
185
|
+
!defaultOnValidationError &&
|
|
186
|
+
!hasResponseContract &&
|
|
187
|
+
!hasDecorations &&
|
|
188
|
+
!hasIdempotency &&
|
|
189
|
+
!hasLedger &&
|
|
190
|
+
derives === 1 &&
|
|
191
|
+
beforeHandle === 1 &&
|
|
192
|
+
afterHandle <= 1 &&
|
|
193
|
+
onError === 0 &&
|
|
194
|
+
around === 0
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
bare,
|
|
198
|
+
fusedQuery,
|
|
199
|
+
bodyOnly,
|
|
200
|
+
fusedBody,
|
|
201
|
+
lane,
|
|
202
|
+
lifecycleLane,
|
|
203
|
+
lifecycleHookLane,
|
|
204
|
+
fusedLane:
|
|
205
|
+
around === 0
|
|
206
|
+
? fusedQuery
|
|
207
|
+
? "query"
|
|
208
|
+
: fusedBody
|
|
209
|
+
? "body"
|
|
210
|
+
: bare
|
|
211
|
+
? "bare"
|
|
212
|
+
: lifecycleHookLane !== undefined
|
|
213
|
+
? (lifecycleHookLane as "derive-before" | "derive-before-after")
|
|
214
|
+
: fusedBodyLifecycle
|
|
215
|
+
? afterHandle === 0
|
|
216
|
+
? "body-derive-before"
|
|
217
|
+
: "body-derive-before-after"
|
|
218
|
+
: undefined
|
|
219
|
+
: undefined,
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registration-compiled general route program.
|
|
3
|
+
*
|
|
4
|
+
* This is deliberately a data-and-control-flow compiler, not source generation: user functions and
|
|
5
|
+
* schemas remain opaque references and no request value is ever converted into executable code. The
|
|
6
|
+
* program is frozen at registration so the request path walks one immutable stage sequence instead
|
|
7
|
+
* of rediscovering which lifecycle features are present.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { StandardSchemaV1 } from "../schema/standard.ts"
|
|
11
|
+
import type { Registry } from "../server/registry.ts"
|
|
12
|
+
import {
|
|
13
|
+
CONTEXT_SET,
|
|
14
|
+
EMPTY_RESPONSE_CONTROLS,
|
|
15
|
+
isResponseResult,
|
|
16
|
+
type ResponseResult,
|
|
17
|
+
} from "../server/runtime-core.ts"
|
|
18
|
+
import type { CtxSet, MaybePromise, RawContext, RequestSource, Server } from "../server/server.ts"
|
|
19
|
+
import type {
|
|
20
|
+
InternalHandler,
|
|
21
|
+
RawAfterHandle,
|
|
22
|
+
RawBeforeHandle,
|
|
23
|
+
RawDerive,
|
|
24
|
+
RawErrorHandler,
|
|
25
|
+
RouteEntry,
|
|
26
|
+
} from "./route-execution.ts"
|
|
27
|
+
|
|
28
|
+
export type ProgramValidationKind = "headers" | "params" | "body" | "query"
|
|
29
|
+
|
|
30
|
+
export type RouteProgramStage =
|
|
31
|
+
| { readonly kind: ProgramValidationKind; readonly schema: StandardSchemaV1 }
|
|
32
|
+
| { readonly kind: "derive"; readonly run: RawDerive }
|
|
33
|
+
| { readonly kind: "before"; readonly run: RawBeforeHandle }
|
|
34
|
+
| { readonly kind: "handler"; readonly run: InternalHandler }
|
|
35
|
+
| { readonly kind: "after"; readonly run: RawAfterHandle }
|
|
36
|
+
|
|
37
|
+
/** The complete registration-time lifecycle, in semantic execution order. */
|
|
38
|
+
export interface RouteProgram {
|
|
39
|
+
readonly stages: readonly RouteProgramStage[]
|
|
40
|
+
readonly validationStages: readonly Extract<RouteProgramStage, { kind: ProgramValidationKind }>[]
|
|
41
|
+
readonly derives: readonly RawDerive[]
|
|
42
|
+
readonly beforeHandle: readonly RawBeforeHandle[]
|
|
43
|
+
readonly afterHandle: readonly RawAfterHandle[]
|
|
44
|
+
readonly onError: readonly RawErrorHandler[]
|
|
45
|
+
readonly handler: InternalHandler
|
|
46
|
+
readonly decorations: Record<PropertyKey, unknown>
|
|
47
|
+
readonly hasDecorations: boolean
|
|
48
|
+
readonly bodySchema: StandardSchemaV1 | undefined
|
|
49
|
+
readonly bodyLimit: RouteEntry["bodyLimit"]
|
|
50
|
+
readonly responseContract: RouteEntry["responseContract"]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RouteProgramInput {
|
|
54
|
+
readonly schema: RouteEntry["schema"]
|
|
55
|
+
readonly handler: InternalHandler
|
|
56
|
+
readonly derives: readonly RawDerive[]
|
|
57
|
+
readonly beforeHandle: readonly RawBeforeHandle[]
|
|
58
|
+
readonly afterHandle: readonly RawAfterHandle[]
|
|
59
|
+
readonly onError: readonly RawErrorHandler[]
|
|
60
|
+
readonly decorations: Record<PropertyKey, unknown>
|
|
61
|
+
readonly hasDecorations: boolean
|
|
62
|
+
readonly bodySchema: StandardSchemaV1 | undefined
|
|
63
|
+
readonly bodyLimit: RouteEntry["bodyLimit"]
|
|
64
|
+
readonly responseContract: RouteEntry["responseContract"]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Build the immutable stage sequence once, while the server is still configurable. */
|
|
68
|
+
export function compileRouteProgram(input: RouteProgramInput): RouteProgram {
|
|
69
|
+
const stages: RouteProgramStage[] = []
|
|
70
|
+
const validationStages: Extract<RouteProgramStage, { kind: ProgramValidationKind }>[] = []
|
|
71
|
+
const schema = input.schema
|
|
72
|
+
if (schema?.headers !== undefined) {
|
|
73
|
+
const stage = { kind: "headers", schema: schema.headers } as const
|
|
74
|
+
stages.push(stage)
|
|
75
|
+
validationStages.push(stage)
|
|
76
|
+
}
|
|
77
|
+
if (schema?.params !== undefined) {
|
|
78
|
+
const stage = { kind: "params", schema: schema.params } as const
|
|
79
|
+
stages.push(stage)
|
|
80
|
+
validationStages.push(stage)
|
|
81
|
+
}
|
|
82
|
+
if (schema?.body !== undefined) {
|
|
83
|
+
const stage = { kind: "body", schema: schema.body } as const
|
|
84
|
+
stages.push(stage)
|
|
85
|
+
validationStages.push(stage)
|
|
86
|
+
}
|
|
87
|
+
if (schema?.query !== undefined) {
|
|
88
|
+
const stage = { kind: "query", schema: schema.query } as const
|
|
89
|
+
stages.push(stage)
|
|
90
|
+
validationStages.push(stage)
|
|
91
|
+
}
|
|
92
|
+
for (const derive of input.derives) stages.push({ kind: "derive", run: derive })
|
|
93
|
+
for (const before of input.beforeHandle) stages.push({ kind: "before", run: before })
|
|
94
|
+
stages.push({ kind: "handler", run: input.handler })
|
|
95
|
+
for (const after of input.afterHandle) stages.push({ kind: "after", run: after })
|
|
96
|
+
|
|
97
|
+
return Object.freeze({
|
|
98
|
+
stages: Object.freeze(stages),
|
|
99
|
+
validationStages: Object.freeze(validationStages),
|
|
100
|
+
derives: Object.freeze([...input.derives]),
|
|
101
|
+
beforeHandle: Object.freeze([...input.beforeHandle]),
|
|
102
|
+
afterHandle: Object.freeze([...input.afterHandle]),
|
|
103
|
+
onError: Object.freeze([...input.onError]),
|
|
104
|
+
handler: input.handler,
|
|
105
|
+
decorations: Object.freeze({ ...input.decorations }),
|
|
106
|
+
hasDecorations: input.hasDecorations,
|
|
107
|
+
bodySchema: input.bodySchema,
|
|
108
|
+
bodyLimit: input.bodyLimit,
|
|
109
|
+
responseContract: input.responseContract,
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface RouteProgramRuntime {
|
|
114
|
+
validateProgramStage(
|
|
115
|
+
entry: RouteEntry,
|
|
116
|
+
stage: Extract<RouteProgramStage, { kind: ProgramValidationKind }>,
|
|
117
|
+
source: RequestSource,
|
|
118
|
+
ctx: RawContext,
|
|
119
|
+
): MaybePromise<Response | ResponseResult | undefined>
|
|
120
|
+
readProgramBody(
|
|
121
|
+
entry: RouteEntry,
|
|
122
|
+
source: RequestSource,
|
|
123
|
+
ctx: RawContext,
|
|
124
|
+
): Promise<Response | ResponseResult | undefined>
|
|
125
|
+
handleProgramError<T>(
|
|
126
|
+
entry: RouteEntry,
|
|
127
|
+
error: unknown,
|
|
128
|
+
ctx: RawContext,
|
|
129
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
130
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
131
|
+
): MaybePromise<T>
|
|
132
|
+
finishProgramResult<T>(
|
|
133
|
+
entry: RouteEntry,
|
|
134
|
+
ctx: RawContext,
|
|
135
|
+
result: unknown,
|
|
136
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
137
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
138
|
+
): MaybePromise<T>
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function responseSet(ctx: RawContext): CtxSet {
|
|
142
|
+
return ctx[CONTEXT_SET]() ?? EMPTY_RESPONSE_CONTROLS
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isEarly(value: unknown): value is Response | ResponseResult {
|
|
146
|
+
return value instanceof Response || isResponseResult(value)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
type ProgramResult<T> = MaybePromise<T>
|
|
150
|
+
|
|
151
|
+
type PendingPhase =
|
|
152
|
+
| { readonly kind: "validation"; readonly next: number }
|
|
153
|
+
| { readonly kind: "derive"; readonly next: number }
|
|
154
|
+
| { readonly kind: "before"; readonly next: number }
|
|
155
|
+
| { readonly kind: "handler" }
|
|
156
|
+
| { readonly kind: "after"; readonly next: number }
|
|
157
|
+
|
|
158
|
+
/** Execute the general program. The first synchronous stage sequence stays synchronous. */
|
|
159
|
+
export function executeRouteProgram<T, R extends Registry, Ctx>(
|
|
160
|
+
runtime: Server<R, Ctx>,
|
|
161
|
+
entry: RouteEntry,
|
|
162
|
+
program: RouteProgram,
|
|
163
|
+
source: RequestSource,
|
|
164
|
+
ctx: RawContext,
|
|
165
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
166
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
167
|
+
): ProgramResult<T> {
|
|
168
|
+
const host = runtime as unknown as RouteProgramRuntime
|
|
169
|
+
try {
|
|
170
|
+
if (program.hasDecorations) Object.assign(ctx, program.decorations)
|
|
171
|
+
return runSync(host, entry, program, source, ctx, finalize, wrapResponse, 0, undefined)
|
|
172
|
+
} catch (error) {
|
|
173
|
+
return host.handleProgramError(entry, error, ctx, finalize, wrapResponse)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function runSync<T>(
|
|
178
|
+
host: RouteProgramRuntime,
|
|
179
|
+
entry: RouteEntry,
|
|
180
|
+
program: RouteProgram,
|
|
181
|
+
source: RequestSource,
|
|
182
|
+
ctx: RawContext,
|
|
183
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
184
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
185
|
+
validationIndex: number,
|
|
186
|
+
initial: unknown,
|
|
187
|
+
): ProgramResult<T> {
|
|
188
|
+
const validations = program.validationStages
|
|
189
|
+
for (let i = validationIndex; i < validations.length; i++) {
|
|
190
|
+
const stage = validations[i]!
|
|
191
|
+
const outcome =
|
|
192
|
+
stage.kind === "body"
|
|
193
|
+
? host.readProgramBody(entry, source, ctx)
|
|
194
|
+
: host.validateProgramStage(entry, stage, source, ctx)
|
|
195
|
+
if (outcome instanceof Promise) {
|
|
196
|
+
return continueProgram(
|
|
197
|
+
host,
|
|
198
|
+
entry,
|
|
199
|
+
program,
|
|
200
|
+
source,
|
|
201
|
+
ctx,
|
|
202
|
+
finalize,
|
|
203
|
+
wrapResponse,
|
|
204
|
+
{ kind: "validation", next: i + 1 },
|
|
205
|
+
outcome,
|
|
206
|
+
initial,
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
if (outcome !== undefined) return wrapResponse(outcome)
|
|
210
|
+
}
|
|
211
|
+
return runHooksSync(host, entry, program, source, ctx, finalize, wrapResponse, 0, 0, 0, initial)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function runHooksSync<T>(
|
|
215
|
+
host: RouteProgramRuntime,
|
|
216
|
+
entry: RouteEntry,
|
|
217
|
+
program: RouteProgram,
|
|
218
|
+
source: RequestSource,
|
|
219
|
+
ctx: RawContext,
|
|
220
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
221
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
222
|
+
deriveIndex: number,
|
|
223
|
+
beforeIndex: number,
|
|
224
|
+
afterIndex: number,
|
|
225
|
+
initial: unknown,
|
|
226
|
+
): ProgramResult<T> {
|
|
227
|
+
for (let i = deriveIndex; i < program.derives.length; i++) {
|
|
228
|
+
const outcome = program.derives[i]!(ctx)
|
|
229
|
+
if (outcome instanceof Promise) {
|
|
230
|
+
return continueProgram(
|
|
231
|
+
host,
|
|
232
|
+
entry,
|
|
233
|
+
program,
|
|
234
|
+
source,
|
|
235
|
+
ctx,
|
|
236
|
+
finalize,
|
|
237
|
+
wrapResponse,
|
|
238
|
+
{ kind: "derive", next: i + 1 },
|
|
239
|
+
outcome,
|
|
240
|
+
initial,
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
if (isEarly(outcome)) return finalize(outcome, responseSet(ctx))
|
|
244
|
+
Object.assign(ctx, outcome)
|
|
245
|
+
}
|
|
246
|
+
for (let i = beforeIndex; i < program.beforeHandle.length; i++) {
|
|
247
|
+
const outcome = program.beforeHandle[i]!(ctx)
|
|
248
|
+
if (outcome instanceof Promise) {
|
|
249
|
+
return continueProgram(
|
|
250
|
+
host,
|
|
251
|
+
entry,
|
|
252
|
+
program,
|
|
253
|
+
source,
|
|
254
|
+
ctx,
|
|
255
|
+
finalize,
|
|
256
|
+
wrapResponse,
|
|
257
|
+
{ kind: "before", next: i + 1 },
|
|
258
|
+
outcome,
|
|
259
|
+
initial,
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
if (outcome !== undefined) return finalize(outcome, responseSet(ctx))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const output = program.handler(ctx)
|
|
266
|
+
if (output instanceof Promise) {
|
|
267
|
+
return continueProgram(
|
|
268
|
+
host,
|
|
269
|
+
entry,
|
|
270
|
+
program,
|
|
271
|
+
source,
|
|
272
|
+
ctx,
|
|
273
|
+
finalize,
|
|
274
|
+
wrapResponse,
|
|
275
|
+
{ kind: "handler" },
|
|
276
|
+
output,
|
|
277
|
+
initial,
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
let result = output
|
|
281
|
+
for (let i = afterIndex; i < program.afterHandle.length; i++) {
|
|
282
|
+
const outcome = program.afterHandle[i]!(result, ctx)
|
|
283
|
+
if (outcome instanceof Promise) {
|
|
284
|
+
return continueProgram(
|
|
285
|
+
host,
|
|
286
|
+
entry,
|
|
287
|
+
program,
|
|
288
|
+
source,
|
|
289
|
+
ctx,
|
|
290
|
+
finalize,
|
|
291
|
+
wrapResponse,
|
|
292
|
+
{ kind: "after", next: i + 1 },
|
|
293
|
+
outcome,
|
|
294
|
+
result,
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
result = outcome
|
|
298
|
+
}
|
|
299
|
+
return host.finishProgramResult(entry, ctx, result, finalize, wrapResponse)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function continueProgram<T>(
|
|
303
|
+
host: RouteProgramRuntime,
|
|
304
|
+
entry: RouteEntry,
|
|
305
|
+
program: RouteProgram,
|
|
306
|
+
source: RequestSource,
|
|
307
|
+
ctx: RawContext,
|
|
308
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
309
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
310
|
+
pendingPhase: PendingPhase,
|
|
311
|
+
pending: Promise<unknown>,
|
|
312
|
+
initial: unknown,
|
|
313
|
+
): Promise<T> {
|
|
314
|
+
try {
|
|
315
|
+
const settled = await pending
|
|
316
|
+
let result = initial
|
|
317
|
+
if (pendingPhase.kind === "validation") {
|
|
318
|
+
if (settled !== undefined) return wrapResponse(settled as Response | ResponseResult)
|
|
319
|
+
} else if (pendingPhase.kind === "derive") {
|
|
320
|
+
if (isEarly(settled)) return finalize(settled, responseSet(ctx))
|
|
321
|
+
Object.assign(ctx, settled)
|
|
322
|
+
} else if (pendingPhase.kind === "before") {
|
|
323
|
+
if (settled !== undefined) return finalize(settled, responseSet(ctx))
|
|
324
|
+
} else if (pendingPhase.kind === "handler") {
|
|
325
|
+
result = settled
|
|
326
|
+
} else {
|
|
327
|
+
result = settled
|
|
328
|
+
}
|
|
329
|
+
return await runAsyncStages(
|
|
330
|
+
host,
|
|
331
|
+
entry,
|
|
332
|
+
program,
|
|
333
|
+
source,
|
|
334
|
+
ctx,
|
|
335
|
+
finalize,
|
|
336
|
+
wrapResponse,
|
|
337
|
+
pendingPhase,
|
|
338
|
+
result,
|
|
339
|
+
)
|
|
340
|
+
} catch (error) {
|
|
341
|
+
return await host.handleProgramError(entry, error, ctx, finalize, wrapResponse)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function runAsyncStages<T>(
|
|
346
|
+
host: RouteProgramRuntime,
|
|
347
|
+
entry: RouteEntry,
|
|
348
|
+
program: RouteProgram,
|
|
349
|
+
source: RequestSource,
|
|
350
|
+
ctx: RawContext,
|
|
351
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
352
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
353
|
+
pendingPhase: PendingPhase,
|
|
354
|
+
result: unknown,
|
|
355
|
+
): Promise<T> {
|
|
356
|
+
try {
|
|
357
|
+
if (pendingPhase.kind === "validation") {
|
|
358
|
+
const validations = program.validationStages
|
|
359
|
+
for (let i = pendingPhase.next; i < validations.length; i++) {
|
|
360
|
+
const stage = validations[i]!
|
|
361
|
+
const outcome =
|
|
362
|
+
stage.kind === "body"
|
|
363
|
+
? await host.readProgramBody(entry, source, ctx)
|
|
364
|
+
: host.validateProgramStage(entry, stage, source, ctx)
|
|
365
|
+
const settled = outcome instanceof Promise ? await outcome : outcome
|
|
366
|
+
if (settled !== undefined) return wrapResponse(settled)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const deriveStart = pendingPhase.kind === "derive" ? pendingPhase.next : 0
|
|
371
|
+
if (pendingPhase.kind === "validation" || pendingPhase.kind === "derive") {
|
|
372
|
+
for (let i = deriveStart; i < program.derives.length; i++) {
|
|
373
|
+
const outcome = program.derives[i]!(ctx)
|
|
374
|
+
const settled = outcome instanceof Promise ? await outcome : outcome
|
|
375
|
+
if (isEarly(settled)) return finalize(settled, responseSet(ctx))
|
|
376
|
+
Object.assign(ctx, settled)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const beforeStart = pendingPhase.kind === "before" ? pendingPhase.next : 0
|
|
381
|
+
if (
|
|
382
|
+
pendingPhase.kind === "validation" ||
|
|
383
|
+
pendingPhase.kind === "derive" ||
|
|
384
|
+
pendingPhase.kind === "before"
|
|
385
|
+
) {
|
|
386
|
+
for (let i = beforeStart; i < program.beforeHandle.length; i++) {
|
|
387
|
+
const outcome = program.beforeHandle[i]!(ctx)
|
|
388
|
+
const settled = outcome instanceof Promise ? await outcome : outcome
|
|
389
|
+
if (settled !== undefined) return finalize(settled, responseSet(ctx))
|
|
390
|
+
}
|
|
391
|
+
const output = program.handler(ctx)
|
|
392
|
+
result = output instanceof Promise ? await output : output
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const afterStart = pendingPhase.kind === "after" ? pendingPhase.next : 0
|
|
396
|
+
for (let i = afterStart; i < program.afterHandle.length; i++) {
|
|
397
|
+
const outcome = program.afterHandle[i]!(result, ctx)
|
|
398
|
+
result = outcome instanceof Promise ? await outcome : outcome
|
|
399
|
+
}
|
|
400
|
+
return await host.finishProgramResult(entry, ctx, result, finalize, wrapResponse)
|
|
401
|
+
} catch (error) {
|
|
402
|
+
return await host.handleProgramError(entry, error, ctx, finalize, wrapResponse)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { INSTALL_RESPONSE_OBSERVER } from "./server/install.ts"
|
|
2
|
+
import {
|
|
3
|
+
createResponseObserverRuntime,
|
|
4
|
+
type ResponseObserverRuntime,
|
|
5
|
+
} from "./server/response-observer-runtime.ts"
|
|
6
|
+
import type { AnyServer } from "./server/server.ts"
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
ResponseBodyHook,
|
|
10
|
+
ResponseHeadersHook,
|
|
11
|
+
ResponseObserverMethods,
|
|
12
|
+
ResponseObserverRuntime,
|
|
13
|
+
} from "./server/response-observer-runtime.ts"
|
|
14
|
+
|
|
15
|
+
/** A server shape with the opt-in response observation methods installed. */
|
|
16
|
+
export type ResponseObserverServer<S extends AnyServer> = S &
|
|
17
|
+
import("./server/response-observer-runtime.ts").ResponseObserverMethods
|
|
18
|
+
|
|
19
|
+
/** A plugin that enables response header, body, and raw-response observation methods. */
|
|
20
|
+
export type ResponseObserverPlugin = (<S extends AnyServer>(
|
|
21
|
+
app: S,
|
|
22
|
+
) => ResponseObserverServer<S>) & { readonly pluginName?: string }
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Enable the response observation methods on a server.
|
|
26
|
+
*
|
|
27
|
+
* The default server does not carry the header, body, or raw-response observation adapters. Apply
|
|
28
|
+
* this plugin before using those methods:
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { responseObserver } from "@nifrajs/core/response-observer"
|
|
32
|
+
* const app = server().use(responseObserver()).onResponseHeaders((headers) => {
|
|
33
|
+
* headers.set("cache-control", "no-store")
|
|
34
|
+
* })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function responseObserver(): ResponseObserverPlugin {
|
|
38
|
+
const apply = <S extends AnyServer>(app: S): ResponseObserverServer<S> => {
|
|
39
|
+
const install = (app as unknown as Record<symbol, unknown>)[INSTALL_RESPONSE_OBSERVER]
|
|
40
|
+
if (typeof install !== "function") {
|
|
41
|
+
throw new TypeError("[nifra] responseObserver() requires a compatible Server")
|
|
42
|
+
}
|
|
43
|
+
const methods = (install as (runtime: ResponseObserverRuntime) => unknown).call(
|
|
44
|
+
app,
|
|
45
|
+
createResponseObserverRuntime(),
|
|
46
|
+
)
|
|
47
|
+
if (methods === null || typeof methods !== "object") {
|
|
48
|
+
throw new TypeError("[nifra] responseObserver() failed to install its methods")
|
|
49
|
+
}
|
|
50
|
+
Object.assign(app, methods)
|
|
51
|
+
return app as ResponseObserverServer<S>
|
|
52
|
+
}
|
|
53
|
+
return Object.assign(apply, { pluginName: "nifra:response-observer" })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Attach the opt-in observer runtime to a middleware bundle that uses response observation. */
|
|
57
|
+
export function withResponseObserver<T extends object>(middleware: T): T {
|
|
58
|
+
Object.defineProperty(middleware, INSTALL_RESPONSE_OBSERVER, {
|
|
59
|
+
value: createResponseObserverRuntime(),
|
|
60
|
+
enumerable: false,
|
|
61
|
+
configurable: false,
|
|
62
|
+
})
|
|
63
|
+
return middleware
|
|
64
|
+
}
|