@nifrajs/core 3.5.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 +2 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +26 -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 +24 -1
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +168 -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 +40 -8
- package/dist/idempotency.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- 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.map +1 -1
- package/dist/manifest.js +41 -37
- 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/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 +53 -3
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +191 -19
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +1206 -218
- 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 +7 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +4 -1
- 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 +31 -6
- 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 +237 -12
- package/src/idempotency.ts +72 -8
- package/src/index.ts +40 -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 +41 -36
- 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/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 +59 -2
- package/src/server/server.ts +1613 -269
- 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 +38 -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/idempotency.ts
CHANGED
|
@@ -16,6 +16,9 @@ export type IdempotencyScope = "request" | "durable"
|
|
|
16
16
|
/** Default retention for a stored idempotent response: 24 hours. */
|
|
17
17
|
export const DEFAULT_IDEMPOTENCY_TTL_MS = 86_400_000
|
|
18
18
|
|
|
19
|
+
/** Default lease on a key whose handler is still running, with a store that can renew it: 60 seconds. */
|
|
20
|
+
export const DEFAULT_IDEMPOTENCY_PENDING_TTL_MS = 60_000
|
|
21
|
+
|
|
19
22
|
/** Canonical request header carrying the client-chosen idempotency key. */
|
|
20
23
|
export const DEFAULT_IDEMPOTENCY_HEADER = "idempotency-key"
|
|
21
24
|
|
|
@@ -80,7 +83,14 @@ export interface IdempotencyEntryKey {
|
|
|
80
83
|
|
|
81
84
|
export interface IdempotencyBeginInput extends IdempotencyEntryKey {
|
|
82
85
|
readonly fingerprint: string
|
|
86
|
+
/** Retention for the completed response. */
|
|
83
87
|
readonly ttlMs: number
|
|
88
|
+
/**
|
|
89
|
+
* Lease on a `new` reservation: it lapses after this long unless {@link IdempotencyStore.renew}
|
|
90
|
+
* extends it, and `complete` then keeps the response for `ttlMs`. Passed only to a store with
|
|
91
|
+
* `renew`; when absent, the pending reservation holds the key for `ttlMs`.
|
|
92
|
+
*/
|
|
93
|
+
readonly pendingTtlMs?: number | undefined
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
export interface IdempotencyCompletionInput extends IdempotencyEntryKey {
|
|
@@ -94,6 +104,13 @@ export interface IdempotencyAbandonInput extends IdempotencyEntryKey {
|
|
|
94
104
|
readonly reservation: string
|
|
95
105
|
}
|
|
96
106
|
|
|
107
|
+
export interface IdempotencyRenewInput extends IdempotencyEntryKey {
|
|
108
|
+
/** Opaque ownership token returned by `begin(state:"new")`. */
|
|
109
|
+
readonly reservation: string
|
|
110
|
+
/** The new lease, from now. */
|
|
111
|
+
readonly ttlMs: number
|
|
112
|
+
}
|
|
113
|
+
|
|
97
114
|
/**
|
|
98
115
|
* Storage seam for idempotent responses. `begin` MUST be atomic: for one key, exactly one concurrent
|
|
99
116
|
* caller sees `new`; the rest see `in-flight` (or `replay` once completed). The in-memory store gets
|
|
@@ -115,6 +132,12 @@ export interface IdempotencyStore {
|
|
|
115
132
|
complete(input: IdempotencyCompletionInput): boolean | Promise<boolean>
|
|
116
133
|
/** Release only the pending reservation owned by `reservation`. */
|
|
117
134
|
abandon(input: IdempotencyAbandonInput): boolean | Promise<boolean>
|
|
135
|
+
/**
|
|
136
|
+
* Extend the pending reservation owned by `reservation` to `ttlMs` from now. Returns false when it
|
|
137
|
+
* is gone, lapsed, or completed. The server calls it while the handler runs, so with it a key
|
|
138
|
+
* whose process died frees after `pendingTtlMs` instead of `ttlMs`.
|
|
139
|
+
*/
|
|
140
|
+
renew?(input: IdempotencyRenewInput): boolean | Promise<boolean>
|
|
118
141
|
}
|
|
119
142
|
|
|
120
143
|
/** A key must be a non-empty, bounded, control-char-free token. Fail closed on anything else. */
|
|
@@ -312,6 +335,7 @@ export function responseFromStored(
|
|
|
312
335
|
}
|
|
313
336
|
|
|
314
337
|
interface MemoryEntry {
|
|
338
|
+
readonly namespace: string
|
|
315
339
|
readonly fingerprint: string
|
|
316
340
|
readonly reservation: string
|
|
317
341
|
readonly ttlMs: number
|
|
@@ -324,6 +348,11 @@ export interface MemoryIdempotencyStoreOptions {
|
|
|
324
348
|
readonly now?: () => number
|
|
325
349
|
/** Hard memory bound. At capacity new keys fail closed; completed/pending entries are never evicted early. */
|
|
326
350
|
readonly maxEntries?: number
|
|
351
|
+
/**
|
|
352
|
+
* Bound for one namespace, so a single tenant or principal cannot use up `maxEntries` for the
|
|
353
|
+
* rest: past it, only that namespace's new keys fail closed. Default: no separate bound.
|
|
354
|
+
*/
|
|
355
|
+
readonly maxEntriesPerNamespace?: number
|
|
327
356
|
}
|
|
328
357
|
|
|
329
358
|
/**
|
|
@@ -334,8 +363,10 @@ export interface MemoryIdempotencyStoreOptions {
|
|
|
334
363
|
export class MemoryIdempotencyStore implements IdempotencyStore {
|
|
335
364
|
readonly durability = "memory" as const
|
|
336
365
|
private readonly entries = new Map<string, MemoryEntry>()
|
|
366
|
+
private readonly perNamespace = new Map<string, number>()
|
|
337
367
|
private readonly now: () => number
|
|
338
368
|
private readonly maxEntries: number
|
|
369
|
+
private readonly maxPerNamespace: number
|
|
339
370
|
|
|
340
371
|
constructor(options: MemoryIdempotencyStoreOptions = {}) {
|
|
341
372
|
this.now = options.now ?? Date.now
|
|
@@ -343,6 +374,10 @@ export class MemoryIdempotencyStore implements IdempotencyStore {
|
|
|
343
374
|
if (!Number.isInteger(this.maxEntries) || this.maxEntries < 1) {
|
|
344
375
|
throw new RangeError("idempotency: maxEntries must be a positive integer")
|
|
345
376
|
}
|
|
377
|
+
this.maxPerNamespace = options.maxEntriesPerNamespace ?? this.maxEntries
|
|
378
|
+
if (!Number.isInteger(this.maxPerNamespace) || this.maxPerNamespace < 1) {
|
|
379
|
+
throw new RangeError("idempotency: maxEntriesPerNamespace must be a positive integer")
|
|
380
|
+
}
|
|
346
381
|
}
|
|
347
382
|
|
|
348
383
|
begin(input: IdempotencyBeginInput): IdempotencyBeginResult {
|
|
@@ -353,17 +388,19 @@ export class MemoryIdempotencyStore implements IdempotencyStore {
|
|
|
353
388
|
if (existing.response !== undefined) return { state: "replay", response: existing.response }
|
|
354
389
|
return { state: "in-flight" }
|
|
355
390
|
}
|
|
356
|
-
if (existing !== undefined) this.
|
|
357
|
-
if (this.
|
|
391
|
+
if (existing !== undefined) this.drop(storageKey, existing)
|
|
392
|
+
if (this.full(input.namespace)) {
|
|
358
393
|
this.sweep()
|
|
359
|
-
if (this.
|
|
394
|
+
if (this.full(input.namespace)) return { state: "capacity" }
|
|
360
395
|
}
|
|
361
396
|
const reservation = crypto.randomUUID()
|
|
397
|
+
this.perNamespace.set(input.namespace, (this.perNamespace.get(input.namespace) ?? 0) + 1)
|
|
362
398
|
this.entries.set(storageKey, {
|
|
399
|
+
namespace: input.namespace,
|
|
363
400
|
fingerprint: input.fingerprint,
|
|
364
401
|
reservation,
|
|
365
402
|
ttlMs: input.ttlMs,
|
|
366
|
-
expiresAt: this.now() + input.ttlMs,
|
|
403
|
+
expiresAt: this.now() + (input.pendingTtlMs ?? input.ttlMs),
|
|
367
404
|
response: undefined,
|
|
368
405
|
})
|
|
369
406
|
return { state: "new", reservation }
|
|
@@ -374,7 +411,7 @@ export class MemoryIdempotencyStore implements IdempotencyStore {
|
|
|
374
411
|
const entry = this.entries.get(key)
|
|
375
412
|
if (entry === undefined || entry.reservation !== input.reservation) return false
|
|
376
413
|
if (entry.expiresAt <= this.now()) {
|
|
377
|
-
this.
|
|
414
|
+
this.drop(key, entry)
|
|
378
415
|
return false
|
|
379
416
|
}
|
|
380
417
|
entry.response = input.response
|
|
@@ -386,7 +423,7 @@ export class MemoryIdempotencyStore implements IdempotencyStore {
|
|
|
386
423
|
const key = this.storageKey(input)
|
|
387
424
|
const entry = this.entries.get(key)
|
|
388
425
|
if (entry !== undefined && entry.expiresAt <= this.now()) {
|
|
389
|
-
this.
|
|
426
|
+
this.drop(key, entry)
|
|
390
427
|
return false
|
|
391
428
|
}
|
|
392
429
|
// Only drop a still-pending reservation; never evict a completed (replayable) response.
|
|
@@ -395,17 +432,30 @@ export class MemoryIdempotencyStore implements IdempotencyStore {
|
|
|
395
432
|
entry.response === undefined &&
|
|
396
433
|
entry.reservation === input.reservation
|
|
397
434
|
) {
|
|
398
|
-
this.
|
|
435
|
+
this.drop(key, entry)
|
|
399
436
|
return true
|
|
400
437
|
}
|
|
401
438
|
return false
|
|
402
439
|
}
|
|
403
440
|
|
|
441
|
+
renew(input: IdempotencyRenewInput): boolean {
|
|
442
|
+
const entry = this.entries.get(this.storageKey(input))
|
|
443
|
+
if (
|
|
444
|
+
entry?.reservation !== input.reservation ||
|
|
445
|
+
entry.response !== undefined ||
|
|
446
|
+
entry.expiresAt <= this.now()
|
|
447
|
+
) {
|
|
448
|
+
return false
|
|
449
|
+
}
|
|
450
|
+
entry.expiresAt = this.now() + input.ttlMs
|
|
451
|
+
return true
|
|
452
|
+
}
|
|
453
|
+
|
|
404
454
|
/** Evict expired entries. Callers may run this on an interval; access-time eviction covers the rest. */
|
|
405
455
|
sweep(): void {
|
|
406
456
|
const now = this.now()
|
|
407
457
|
for (const [key, entry] of this.entries) {
|
|
408
|
-
if (entry.expiresAt <= now) this.
|
|
458
|
+
if (entry.expiresAt <= now) this.drop(key, entry)
|
|
409
459
|
}
|
|
410
460
|
}
|
|
411
461
|
|
|
@@ -417,6 +467,20 @@ export class MemoryIdempotencyStore implements IdempotencyStore {
|
|
|
417
467
|
private storageKey(input: IdempotencyEntryKey): string {
|
|
418
468
|
return `${input.namespace.length}:${input.namespace}${input.key}`
|
|
419
469
|
}
|
|
470
|
+
|
|
471
|
+
private full(namespace: string): boolean {
|
|
472
|
+
return (
|
|
473
|
+
this.entries.size >= this.maxEntries ||
|
|
474
|
+
(this.perNamespace.get(namespace) ?? 0) >= this.maxPerNamespace
|
|
475
|
+
)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private drop(key: string, entry: MemoryEntry): void {
|
|
479
|
+
this.entries.delete(key)
|
|
480
|
+
const left = (this.perNamespace.get(entry.namespace) ?? 1) - 1
|
|
481
|
+
if (left > 0) this.perNamespace.set(entry.namespace, left)
|
|
482
|
+
else this.perNamespace.delete(entry.namespace)
|
|
483
|
+
}
|
|
420
484
|
}
|
|
421
485
|
|
|
422
486
|
/** Convenience factory mirroring the other core primitives' `create*` style. */
|
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"
|
|
@@ -83,7 +107,15 @@ export {
|
|
|
83
107
|
toVercelHandler,
|
|
84
108
|
type VercelHandler,
|
|
85
109
|
} from "./server/platform-adapters.ts"
|
|
86
|
-
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"
|
|
87
119
|
export {
|
|
88
120
|
type PlainRender,
|
|
89
121
|
type ResponseResult,
|
|
@@ -102,13 +134,18 @@ export {
|
|
|
102
134
|
defineRouterPlugin,
|
|
103
135
|
type Handler,
|
|
104
136
|
type IdentityPlugin,
|
|
137
|
+
type ListenTlsOptions,
|
|
105
138
|
type McpPromptDescriptor,
|
|
106
139
|
type McpResourceDescriptor,
|
|
107
140
|
type Middleware,
|
|
141
|
+
type MountableApp,
|
|
142
|
+
type MountFetchOptions,
|
|
143
|
+
type MountOptions,
|
|
108
144
|
type NifraFeatureVersion,
|
|
109
145
|
type NifraPlugin,
|
|
110
146
|
type NodeRequestContext,
|
|
111
147
|
type NodeRequestHook,
|
|
148
|
+
type NodeResponseBodyHook,
|
|
112
149
|
type NodeResponseContext,
|
|
113
150
|
type NodeResponseHook,
|
|
114
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
|