@nifrajs/core 2.13.0 → 2.14.1
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/dist/binary.d.ts +32 -0
- package/dist/binary.d.ts.map +1 -1
- package/dist/binary.js +17 -0
- package/dist/binary.js.map +1 -1
- package/dist/durable-adapters.d.ts.map +1 -1
- package/dist/durable-adapters.js +21 -50
- package/dist/durable-adapters.js.map +1 -1
- package/dist/durable-execution.d.ts.map +1 -1
- package/dist/durable-execution.js +49 -69
- package/dist/durable-execution.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/durable-approval-protocol.d.ts +13 -0
- package/dist/internal/durable-approval-protocol.d.ts.map +1 -0
- package/dist/internal/durable-approval-protocol.js +66 -0
- package/dist/internal/durable-approval-protocol.js.map +1 -0
- package/dist/internal/durable-effect-protocol.d.ts +14 -0
- package/dist/internal/durable-effect-protocol.d.ts.map +1 -0
- package/dist/internal/durable-effect-protocol.js +16 -0
- package/dist/internal/durable-effect-protocol.js.map +1 -0
- package/dist/internal/durable-saga-protocol.d.ts +8 -0
- package/dist/internal/durable-saga-protocol.d.ts.map +1 -0
- package/dist/internal/durable-saga-protocol.js +21 -0
- package/dist/internal/durable-saga-protocol.js.map +1 -0
- package/dist/internal/route-catalog.d.ts +3 -0
- package/dist/internal/route-catalog.d.ts.map +1 -1
- package/dist/internal/route-catalog.js +5 -0
- package/dist/internal/route-catalog.js.map +1 -1
- package/dist/server/plugin.d.ts +1 -1
- package/dist/server/plugin.d.ts.map +1 -1
- package/dist/server/registry.d.ts +11 -2
- package/dist/server/registry.d.ts.map +1 -1
- package/dist/server/request-context.js +21 -18
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/server-types.d.ts +15 -0
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +37 -9
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +130 -5
- package/dist/server/server.js.map +1 -1
- package/package.json +1 -1
- package/src/binary.ts +35 -0
- package/src/durable-adapters.ts +26 -51
- package/src/durable-execution.ts +46 -88
- package/src/index.ts +1 -1
- package/src/internal/durable-approval-protocol.ts +92 -0
- package/src/internal/durable-effect-protocol.ts +37 -0
- package/src/internal/durable-saga-protocol.ts +55 -0
- package/src/internal/route-catalog.ts +6 -0
- package/src/server/plugin.ts +1 -1
- package/src/server/registry.ts +19 -4
- package/src/server/request-context.ts +23 -20
- package/src/server/server-types.ts +15 -0
- package/src/server/server.ts +171 -9
|
@@ -144,6 +144,21 @@ export interface ServerOptions {
|
|
|
144
144
|
* Fastify's `setErrorHandler`) instead of repeating a formatter per route.
|
|
145
145
|
*/
|
|
146
146
|
readonly onValidationError?: RouteSchema["onValidationError"]
|
|
147
|
+
/**
|
|
148
|
+
* What to do when an order-scoped hook (`derive`/`beforeHandle`/`around`/`aroundCapability`/
|
|
149
|
+
* `afterHandle`/`onError`/`decorate`) covers no route because it was added after the last route was
|
|
150
|
+
* registered. `register()` snapshots the current chain into each route as it is declared, so a hook
|
|
151
|
+
* appended after every route reaches nothing and is silently dead. `"warn"` (default) logs once at
|
|
152
|
+
* seal (first `listen()`/`fetch`/`resolveNode`); `"error"` throws a `FrameworkError`
|
|
153
|
+
* (`UNUSED_SCOPED_HOOKS`); `"off"` skips the bookkeeping entirely, including the one-frame stack
|
|
154
|
+
* capture. App-global hooks (`onRequest`/`onResponse`/...) are unaffected in every mode.
|
|
155
|
+
*
|
|
156
|
+
* This is a development-time diagnostic: the whole audit is guarded by `process.env.NODE_ENV !==
|
|
157
|
+
* "production"`, so any bundler that defines `NODE_ENV` (vite/esbuild/webpack/next, `bun build
|
|
158
|
+
* --production`) dead-code-eliminates it, leaving zero bytes and zero per-request cost in the shipped
|
|
159
|
+
* bundle. It fires in development, test, and CI - where you catch the mistake - not in production.
|
|
160
|
+
*/
|
|
161
|
+
readonly unusedScopedHooks?: "warn" | "error" | "off"
|
|
147
162
|
}
|
|
148
163
|
|
|
149
164
|
/** A fetch-compatible handler used by {@link Server.mountFetch}. */
|
package/src/server/server.ts
CHANGED
|
@@ -228,7 +228,12 @@ import {
|
|
|
228
228
|
import type { EffectLedgerRuntime, ResolvedEffectLedger } from "./ledger-lane.ts"
|
|
229
229
|
import { jsonLogger, type Logger } from "./logger.ts"
|
|
230
230
|
import type { McpRuntime } from "./mcp-hook.ts"
|
|
231
|
-
import type {
|
|
231
|
+
import type {
|
|
232
|
+
ContextPlugin,
|
|
233
|
+
IdentityPlugin,
|
|
234
|
+
PluginTypeCollapsed,
|
|
235
|
+
ServerTypeUnpinned,
|
|
236
|
+
} from "./plugin.ts"
|
|
232
237
|
import type {
|
|
233
238
|
AddRoute,
|
|
234
239
|
EmptyRegistry,
|
|
@@ -576,6 +581,104 @@ export type NifraFeatureVersion = FeatureVersionOf<Version>
|
|
|
576
581
|
const IDENTITY_RESPONSE = (response: Response): Response => response
|
|
577
582
|
const RESPONSE_TIMEOUT = (): Response => jsonError(503, "request_timeout")
|
|
578
583
|
|
|
584
|
+
// The unused-order-scoped-hook audit (recorder, seal check, stack capture, message) is a dev-time
|
|
585
|
+
// diagnostic. Every guard below keeps the INLINE literal `process.env.NODE_ENV !== "production"` at the
|
|
586
|
+
// branch - never a shared const holding the folded boolean - because a bundler's `process.env.NODE_ENV`
|
|
587
|
+
// define only dead-code-eliminates the audit when the literal appears at the `if`; Bun does not
|
|
588
|
+
// propagate a module const's value into the branch. Every real production bundler (vite/esbuild/webpack/
|
|
589
|
+
// next, `bun build --production`) injects that define, turning each guard into `hookAuditRuntime &&
|
|
590
|
+
// false`, which folds away - dropping the whole audit and restoring the bare kernel size.
|
|
591
|
+
//
|
|
592
|
+
// `hookAuditRuntime` is the one shared flag: `typeof globalThis.Deno === "undefined"`. Deno ships a
|
|
593
|
+
// node-compat `process`, so `typeof process` does NOT gate it out there, yet reading `process.env`
|
|
594
|
+
// throws `NotCapable` without `--allow-env`. The flag short-circuits before the env read on Deno (audit
|
|
595
|
+
// off) while a production define still folds `<flag> && false` to `false` on every other runtime.
|
|
596
|
+
const hookAuditRuntime = typeof (globalThis as { Deno?: unknown }).Deno === "undefined"
|
|
597
|
+
|
|
598
|
+
// This module's own path, captured once from a sentinel stack. Every internal frame of an
|
|
599
|
+
// order-scoped hook push (the public method, its `use()`-bundle dispatcher, the recorder) lives here,
|
|
600
|
+
// so `captureCallerSite` skips anything starting with it and lands on the user's calling frame.
|
|
601
|
+
const SERVER_MODULE_PATH = /* @__PURE__ */ ((): string | undefined => {
|
|
602
|
+
const frame = new Error().stack?.split("\n")[1]
|
|
603
|
+
return frame?.match(/\(?([^()\s]+):\d+:\d+\)?\s*$/)?.[1]
|
|
604
|
+
})()
|
|
605
|
+
|
|
606
|
+
/** One `file:line:col` frame for the caller of an order-scoped hook method, for the unused-hook
|
|
607
|
+
* report. Only ever called behind the `unusedScopedHooks !== "off"` guard, so the `Error` (the sole
|
|
608
|
+
* non-trivial cost) is never constructed when the check is disabled. */
|
|
609
|
+
const captureCallerSite = (): string | undefined => {
|
|
610
|
+
const stack = new Error().stack
|
|
611
|
+
if (stack === undefined) return undefined
|
|
612
|
+
for (const line of stack.split("\n").slice(1)) {
|
|
613
|
+
const frame = line.match(/\(?([^()\s]+:\d+:\d+)\)?\s*$/)?.[1]
|
|
614
|
+
if (frame === undefined) continue
|
|
615
|
+
if (SERVER_MODULE_PATH !== undefined && frame.startsWith(`${SERVER_MODULE_PATH}:`)) continue
|
|
616
|
+
return frame
|
|
617
|
+
}
|
|
618
|
+
return undefined
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/** Per-server bookkeeping for the unused-order-scoped-hook audit. Deliberately NOT `Server` fields or
|
|
622
|
+
* methods: those emit class members (a field declaration, a method shell) that a bundler cannot remove
|
|
623
|
+
* even after their bodies fold away. Held here in a module WeakMap and reached only through the
|
|
624
|
+
* functions below, every call guarded by `process.env.NODE_ENV !== "production"`, so a production define
|
|
625
|
+
* folds the guards away, leaves this map and these functions unreferenced, and the bundler eliminates
|
|
626
|
+
* the whole audit - restoring the bare kernel size. */
|
|
627
|
+
interface HookAuditState {
|
|
628
|
+
readonly policy: "warn" | "error" | "off"
|
|
629
|
+
readonly hasCustomLogger: boolean
|
|
630
|
+
readonly sites: {
|
|
631
|
+
readonly kind: string
|
|
632
|
+
readonly routesAtPush: number
|
|
633
|
+
readonly site: string | undefined
|
|
634
|
+
}[]
|
|
635
|
+
checked: boolean
|
|
636
|
+
}
|
|
637
|
+
const hookAudit = /* @__PURE__ */ new WeakMap<object, HookAuditState>()
|
|
638
|
+
|
|
639
|
+
/** Start tracking one server's order-scoped hook pushes (once, from the constructor). */
|
|
640
|
+
const beginHookAudit = (
|
|
641
|
+
key: object,
|
|
642
|
+
policy: "warn" | "error" | "off",
|
|
643
|
+
hasCustomLogger: boolean,
|
|
644
|
+
): void => {
|
|
645
|
+
hookAudit.set(key, { policy, hasCustomLogger, sites: [], checked: false })
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** Record one order-scoped hook push: the route count at that moment (so `routesAtPush === total` at
|
|
649
|
+
* seal means no route was declared after it) and the caller's frame. */
|
|
650
|
+
const recordScopedHook = (key: object, routesAtPush: number, kind: string): void => {
|
|
651
|
+
const state = hookAudit.get(key)
|
|
652
|
+
if (state === undefined || state.policy === "off") return
|
|
653
|
+
state.sites.push({ kind, routesAtPush, site: captureCallerSite() })
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** Run the deadness check exactly once. A hook whose recorded route count equals the final route count
|
|
657
|
+
* had no route declared after it - unambiguously dead, since the scoping feature (a hook that covers
|
|
658
|
+
* only later routes) always leaves at least one such route. Zero-route apps are exempt (different
|
|
659
|
+
* mistake, and the warning would be noise). */
|
|
660
|
+
const sealHookAudit = (
|
|
661
|
+
key: object,
|
|
662
|
+
total: number,
|
|
663
|
+
logger: { warn(message: string): void },
|
|
664
|
+
): void => {
|
|
665
|
+
const state = hookAudit.get(key)
|
|
666
|
+
if (state === undefined || state.checked) return
|
|
667
|
+
state.checked = true
|
|
668
|
+
if (state.policy === "off" || total === 0) return
|
|
669
|
+
const dead = state.sites.filter((hook) => hook.routesAtPush === total)
|
|
670
|
+
if (dead.length === 0) return
|
|
671
|
+
const detail = dead.map((hook) => ` ${hook.kind}() at ${hook.site ?? "<unknown>"}`).join("\n")
|
|
672
|
+
const message =
|
|
673
|
+
`[nifra] ${dead.length} order-scoped hook(s) apply to no route - they were added after the ` +
|
|
674
|
+
`last route was registered, and register() snapshots the chain into each route as it is ` +
|
|
675
|
+
`declared:\n${detail}\nMove them before the routes they should cover. App-global hooks ` +
|
|
676
|
+
`(onRequest/onResponse) are not order-scoped and can be added at any time.`
|
|
677
|
+
if (state.policy === "error") throw new FrameworkError("UNUSED_SCOPED_HOOKS", message)
|
|
678
|
+
if (state.hasCustomLogger) logger.warn(message)
|
|
679
|
+
else console.warn(message)
|
|
680
|
+
}
|
|
681
|
+
|
|
579
682
|
/**
|
|
580
683
|
* The inline server. Routes are chainable and fully type-inferred. `derive`/
|
|
581
684
|
* `decorate` extend the handler context (`Ctx`) for routes defined *after* them,
|
|
@@ -744,6 +847,11 @@ export class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {
|
|
|
744
847
|
this.defaultOnValidationError = options.onValidationError
|
|
745
848
|
this.bunServer = undefined
|
|
746
849
|
this.sealed = false
|
|
850
|
+
// Guarded so a production define strips the call, leaving `beginHookAudit`/`hookAudit` unreferenced
|
|
851
|
+
// for the bundler to eliminate; a non-bundled production run skips it the same way.
|
|
852
|
+
if (hookAuditRuntime && process.env.NODE_ENV !== "production") {
|
|
853
|
+
beginHookAudit(this, options.unusedScopedHooks ?? "warn", options.logger !== undefined)
|
|
854
|
+
}
|
|
747
855
|
this.derives = []
|
|
748
856
|
this.decorations = {}
|
|
749
857
|
this.beforeHandleHooks = []
|
|
@@ -812,17 +920,30 @@ export class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {
|
|
|
812
920
|
return this
|
|
813
921
|
}
|
|
814
922
|
|
|
815
|
-
/**
|
|
923
|
+
/**
|
|
924
|
+
* Add a per-request, computed context extension. Order-scoped: it is snapshotted into every route
|
|
925
|
+
* declared *after* this call, and reaches none declared before. One added after the last route
|
|
926
|
+
* (e.g. on the app returned by a route-registering factory) covers nothing - see
|
|
927
|
+
* {@link ServerOptions.unusedScopedHooks}.
|
|
928
|
+
*/
|
|
816
929
|
derive<D extends object>(fn: (context: Context & Ctx) => MaybePromise<D>): Server<R, Ctx & D> {
|
|
817
930
|
this.assertConfigurable("derive()")
|
|
818
931
|
this.derives.push(fn as unknown as RawDerive)
|
|
932
|
+
if (hookAuditRuntime && process.env.NODE_ENV !== "production")
|
|
933
|
+
recordScopedHook(this, this.catalog.size, "derive")
|
|
819
934
|
return this as unknown as Server<R, Ctx & D>
|
|
820
935
|
}
|
|
821
936
|
|
|
822
|
-
/**
|
|
937
|
+
/**
|
|
938
|
+
* Add a static context value. Order-scoped: captured by every route declared *after* this call, and
|
|
939
|
+
* by none before; one added after the last route reaches nothing
|
|
940
|
+
* (see {@link ServerOptions.unusedScopedHooks}).
|
|
941
|
+
*/
|
|
823
942
|
decorate<const K extends string, V>(key: K, value: V): Server<R, Ctx & Record<K, V>> {
|
|
824
943
|
this.assertConfigurable("decorate()")
|
|
825
944
|
this.decorations[key] = value
|
|
945
|
+
if (hookAuditRuntime && process.env.NODE_ENV !== "production")
|
|
946
|
+
recordScopedHook(this, this.catalog.size, "decorate")
|
|
826
947
|
return this as unknown as Server<R, Ctx & Record<K, V>>
|
|
827
948
|
}
|
|
828
949
|
|
|
@@ -840,10 +961,16 @@ export class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {
|
|
|
840
961
|
return this
|
|
841
962
|
}
|
|
842
963
|
|
|
843
|
-
/**
|
|
964
|
+
/**
|
|
965
|
+
* Run after validation, before the handler; a non-`undefined` return short-circuits. Order-scoped:
|
|
966
|
+
* it is snapshotted into every route declared *after* this call and covers none declared before, so
|
|
967
|
+
* one added after the last route reaches nothing (see {@link ServerOptions.unusedScopedHooks}).
|
|
968
|
+
*/
|
|
844
969
|
beforeHandle(fn: (context: Context & Ctx) => MaybePromise<unknown>): this {
|
|
845
970
|
this.assertConfigurable("beforeHandle()")
|
|
846
971
|
this.beforeHandleHooks.push(fn as unknown as RawBeforeHandle)
|
|
972
|
+
if (hookAuditRuntime && process.env.NODE_ENV !== "production")
|
|
973
|
+
recordScopedHook(this, this.catalog.size, "beforeHandle")
|
|
847
974
|
return this
|
|
848
975
|
}
|
|
849
976
|
|
|
@@ -851,10 +978,15 @@ export class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {
|
|
|
851
978
|
* Wrap the matched route lifecycle for subsequent routes. This is intentionally generic over the
|
|
852
979
|
* route output, so wrappers like async context storage do not force Node's direct JSON path through
|
|
853
980
|
* a Web `Response`. The first registered wrapper is outermost.
|
|
981
|
+
*
|
|
982
|
+
* Order-scoped: captured by every route declared *after* this call, none before; one added after the
|
|
983
|
+
* last route reaches nothing (see {@link ServerOptions.unusedScopedHooks}).
|
|
854
984
|
*/
|
|
855
985
|
around(fn: <T>(context: Context & Ctx, next: () => MaybePromise<T>) => MaybePromise<T>): this {
|
|
856
986
|
this.assertConfigurable("around()")
|
|
857
987
|
this.aroundHooks.push(fn as unknown as RawAround)
|
|
988
|
+
if (hookAuditRuntime && process.env.NODE_ENV !== "production")
|
|
989
|
+
recordScopedHook(this, this.catalog.size, "around")
|
|
858
990
|
return this
|
|
859
991
|
}
|
|
860
992
|
|
|
@@ -879,6 +1011,8 @@ export class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {
|
|
|
879
1011
|
this.capabilityInterceptors.push(
|
|
880
1012
|
Object.freeze({ interceptor, timeoutMs: Math.min(timeoutMs, 2_147_483_647) }),
|
|
881
1013
|
)
|
|
1014
|
+
if (hookAuditRuntime && process.env.NODE_ENV !== "production")
|
|
1015
|
+
recordScopedHook(this, this.catalog.size, "aroundCapability")
|
|
882
1016
|
return this
|
|
883
1017
|
}
|
|
884
1018
|
|
|
@@ -892,17 +1026,25 @@ export class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {
|
|
|
892
1026
|
return this
|
|
893
1027
|
}
|
|
894
1028
|
|
|
895
|
-
/** Transform the handler's result before it is serialized. Order-scoped
|
|
1029
|
+
/** Transform the handler's result before it is serialized. Order-scoped: captured by routes declared
|
|
1030
|
+
* *after* this call, none before; one added after the last route reaches nothing (see
|
|
1031
|
+
* {@link ServerOptions.unusedScopedHooks}). */
|
|
896
1032
|
afterHandle(fn: (result: unknown, context: Context & Ctx) => MaybePromise<unknown>): this {
|
|
897
1033
|
this.assertConfigurable("afterHandle()")
|
|
898
1034
|
this.afterHandleHooks.push(fn as unknown as RawAfterHandle)
|
|
1035
|
+
if (hookAuditRuntime && process.env.NODE_ENV !== "production")
|
|
1036
|
+
recordScopedHook(this, this.catalog.size, "afterHandle")
|
|
899
1037
|
return this
|
|
900
1038
|
}
|
|
901
1039
|
|
|
902
|
-
/** Handle a thrown error; a non-`undefined` return becomes the response (else the default 500).
|
|
1040
|
+
/** Handle a thrown error; a non-`undefined` return becomes the response (else the default 500).
|
|
1041
|
+
* Order-scoped: captured by routes declared *after* this call, none before; one added after the last
|
|
1042
|
+
* route reaches nothing (see {@link ServerOptions.unusedScopedHooks}). */
|
|
903
1043
|
onError(fn: (error: unknown, context: Context & Ctx) => MaybePromise<unknown>): this {
|
|
904
1044
|
this.assertConfigurable("onError()")
|
|
905
1045
|
this.onErrorHooks.push(fn as unknown as RawErrorHandler)
|
|
1046
|
+
if (hookAuditRuntime && process.env.NODE_ENV !== "production")
|
|
1047
|
+
recordScopedHook(this, this.catalog.size, "onError")
|
|
906
1048
|
return this
|
|
907
1049
|
}
|
|
908
1050
|
|
|
@@ -1059,13 +1201,23 @@ export class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {
|
|
|
1059
1201
|
* called with `this` and its result is returned, so an inline plugin's `derive`/`decorate` thread
|
|
1060
1202
|
* the added context to handlers defined after `use` (the overload is generic over the concrete
|
|
1061
1203
|
* `this`). A named plugin already applied is skipped (idempotent dedupe).
|
|
1204
|
+
*
|
|
1205
|
+
* If the plugin's return type is unpinned - `Server<any, any>`, as a hand-rolled `(app) => app` or a
|
|
1206
|
+
* `NifraPlugin<AnyServer, AnyServer>` infers (e.g. an auth plugin whose own types collapsed) - this
|
|
1207
|
+
* returns the non-callable {@link PluginTypeCollapsed} instead of silently widening the whole typed
|
|
1208
|
+
* client to `any`. `.get()`/`.post()` then fail right here rather than surfacing as `any` hundreds of
|
|
1209
|
+
* lines away. Fix at the plugin: build it with {@link defineIdentityPlugin}/{@link defineContextPlugin},
|
|
1210
|
+
* or pin its input server type. This mirrors the guard {@link definePlugin} already applies.
|
|
1062
1211
|
*/
|
|
1063
|
-
use<Out extends AnyServer>(
|
|
1212
|
+
use<Out extends AnyServer>(
|
|
1213
|
+
plugin: (app: this) => Out,
|
|
1214
|
+
): [ServerTypeUnpinned<Out>] extends [true] ? PluginTypeCollapsed : Out
|
|
1064
1215
|
/**
|
|
1065
1216
|
* Apply a {@link Middleware} bundle - wire each hook it provides to its lifecycle point. Returns
|
|
1066
1217
|
* `this` (no context-type merging); call it before the routes its `beforeHandle`/`afterHandle`
|
|
1067
|
-
* should cover (those are order-scoped; `onRequest`/`onResponse` are global). A
|
|
1068
|
-
*
|
|
1218
|
+
* should cover (those are order-scoped; `onRequest`/`onResponse` are global). A bundle applied after
|
|
1219
|
+
* the last route reaches nothing - {@link ServerOptions.unusedScopedHooks} logs that at seal. A named
|
|
1220
|
+
* bundle already applied is skipped (idempotent).
|
|
1069
1221
|
*/
|
|
1070
1222
|
use(mw: Middleware): this
|
|
1071
1223
|
use(arg: Middleware | ((app: this) => AnyServer)): AnyServer {
|
|
@@ -2027,6 +2179,10 @@ export class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {
|
|
|
2027
2179
|
source: RequestSource,
|
|
2028
2180
|
platform?: Platform<EnvOf<Ctx>>,
|
|
2029
2181
|
): MaybePromise<Response> {
|
|
2182
|
+
// Seal once on the first request so test servers (no `listen()`) still get the dead-hook check.
|
|
2183
|
+
// After that it is a single boolean read - the per-request cost the option promises to keep at zero.
|
|
2184
|
+
if (hookAuditRuntime && process.env.NODE_ENV !== "production")
|
|
2185
|
+
sealHookAudit(this, this.catalog.size, this.logger)
|
|
2030
2186
|
// Off path (default): straight through - one property check, no closure, no promise.
|
|
2031
2187
|
if (this.capacityGate === undefined) return this.fetchSourceInner(source, platform)
|
|
2032
2188
|
return this.admitGated(requestOf(source), () => this.fetchSourceInner(source, platform))
|
|
@@ -2151,6 +2307,9 @@ export class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {
|
|
|
2151
2307
|
platform?: Platform<EnvOf<Ctx>>,
|
|
2152
2308
|
suppliedRuntime?: NodeOutcomeRuntime,
|
|
2153
2309
|
): MaybePromise<NodeServeOutcome> {
|
|
2310
|
+
// Seal once on the Node-direct lane too - it can bypass `fetchSource` entirely (hookless outcome).
|
|
2311
|
+
if (hookAuditRuntime && process.env.NODE_ENV !== "production")
|
|
2312
|
+
sealHookAudit(this, this.catalog.size, this.logger)
|
|
2154
2313
|
// A paired header-only native hook can preserve the Node-direct JSON/body outcome for successful
|
|
2155
2314
|
// responses. Arbitrary onResponse transforms need a real Web Response, but a buffered outcome can
|
|
2156
2315
|
// be materialized with a direct-write marker and return to the socket path when the hook mutates
|
|
@@ -4873,6 +5032,9 @@ export class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {
|
|
|
4873
5032
|
"listen() uses Bun.serve and runs only on Bun. Serve on Node with @nifrajs/node or on Deno with @nifrajs/deno, or hand app.fetch to any fetch-compatible runtime (Workers, etc.).",
|
|
4874
5033
|
)
|
|
4875
5034
|
}
|
|
5035
|
+
// Seal before Bun.serve: in `"error"` mode this throws instead of leaving a bound port behind.
|
|
5036
|
+
if (hookAuditRuntime && process.env.NODE_ENV !== "production")
|
|
5037
|
+
sealHookAudit(this, this.catalog.size, this.logger)
|
|
4876
5038
|
// Bun's `Server` is the concrete handle; we expose the stable `RunningServer`
|
|
4877
5039
|
// subset so the public types don't depend on the ambient `Bun` global. The cast
|
|
4878
5040
|
// bridges them - Bun's `.port` is `number | undefined` (undefined only for unix
|