@nextrush/types 3.0.7 → 4.0.0-beta.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/index.d.ts CHANGED
@@ -159,6 +159,32 @@ type ContentTypeValue = (typeof ContentType)[keyof typeof ContentType];
159
159
  * This enables runtime-specific optimizations while maintaining a unified API.
160
160
  */
161
161
  type Runtime = 'node' | 'bun' | 'deno' | 'deno-deploy' | 'cloudflare-workers' | 'vercel-edge' | 'edge' | 'unknown';
162
+ /**
163
+ * Named serverless/edge deployment platform, orthogonal to {@link Runtime}.
164
+ *
165
+ * @remarks
166
+ * `Runtime` answers "which JS engine" (node/bun/deno/edge); `PlatformId`
167
+ * answers "which vendor platform" (Lambda, GCF, Azure, Cloudflare Workers,
168
+ * Vercel Edge, Netlify Edge) — two independent questions that `Runtime`
169
+ * conflating would make into one. Exposed as `ctx.platform` alongside the
170
+ * unchanged `ctx.runtime`.
171
+ *
172
+ * @see RFC-026
173
+ */
174
+ type PlatformId = 'lambda' | 'gcf' | 'azure' | 'cloudflare-workers' | 'vercel-edge' | 'netlify-edge';
175
+ /**
176
+ * Proxy-trust specification for client-IP resolution (RFC-030, SEC-01).
177
+ *
178
+ * @remarks
179
+ * `false` trusts nothing — the direct socket peer is always `ctx.ip`.
180
+ * A `number` trusts exactly that many proxy hops, selecting the
181
+ * corresponding `X-Forwarded-For` entry counting from the right rather than
182
+ * the client-authored leftmost entry. A `string[]` of CIDR ranges/literal
183
+ * IPs trusts only a direct peer (and every hop it names) falling inside the
184
+ * set. `true` and `0` are rejected at boot — see `resolveClientIp` in
185
+ * `@nextrush/runtime`.
186
+ */
187
+ type ProxyTrust = false | number | string[];
162
188
  /**
163
189
  * Runtime feature capabilities
164
190
  *
@@ -181,6 +207,10 @@ interface RuntimeCapabilities {
181
207
  cryptoSubtle: boolean;
182
208
  /** Supports Web Workers / Worker Threads */
183
209
  workers: boolean;
210
+ /** Supports TLS (HTTPS) — the runtime can terminate TLS connections */
211
+ secureServing: boolean;
212
+ /** Supports HTTP/2 negotiation via ALPN (requires secureServing) */
213
+ http2: boolean;
184
214
  }
185
215
  /**
186
216
  * Runtime information object
@@ -222,12 +252,17 @@ interface BodySource {
222
252
  */
223
253
  text(): Promise<string>;
224
254
  /**
225
- * Read the body as a Uint8Array buffer
255
+ * Read the body as a Uint8Array buffer.
226
256
  *
257
+ * @param limit - Optional per-read byte limit. When provided, it is the value
258
+ * enforced for both the Content-Length pre-check and the incremental
259
+ * streaming check (taking precedence over the source's construction-time
260
+ * limit); when omitted, the construction-time limit governs. See
261
+ * docs/RFC/request-data/017-body-source-limit-propagation.md.
227
262
  * @returns Promise resolving to the body as Uint8Array
228
263
  * @throws Error if body has already been consumed
229
264
  */
230
- buffer(): Promise<Uint8Array>;
265
+ buffer(limit?: number): Promise<Uint8Array>;
231
266
  /**
232
267
  * Read the body as JSON
233
268
  *
@@ -279,6 +314,130 @@ interface BodySourceOptions {
279
314
  encoding?: 'utf-8' | 'utf8' | 'ascii' | 'latin1' | 'iso-8859-1' | 'utf-16le' | 'utf-16be';
280
315
  }
281
316
 
317
+ /**
318
+ * @nextrush/types - Response Streaming Contracts
319
+ *
320
+ * Pure type contracts for runtime-agnostic response streaming (text/SSE/NDJSON).
321
+ * The implementation lives in `@nextrush/stream`; these interfaces live here so
322
+ * the `Context` interface can reference them without `@nextrush/types` depending
323
+ * on any higher-tier package.
324
+ *
325
+ * See `docs/RFC/request-data/003-stream.md`.
326
+ *
327
+ * @packageDocumentation
328
+ */
329
+ /**
330
+ * A single Server-Sent Event.
331
+ *
332
+ * @remarks
333
+ * `data` is the only required field. Objects are serialized with `JSON.stringify`;
334
+ * strings are sent verbatim. The framework handles all wire-format framing
335
+ * (multi-line `data:` escaping, `event:`/`id:`/`retry:` fields, terminating blank line).
336
+ */
337
+ interface SSEEvent {
338
+ /** Event payload. Objects are JSON-serialized; strings are sent as-is. */
339
+ data: unknown;
340
+ /** SSE `event:` field — lets the client dispatch to a named listener. */
341
+ event?: string;
342
+ /** SSE `id:` field — enables client auto-reconnect from the last-seen id. */
343
+ id?: string;
344
+ /** SSE `retry:` field, in milliseconds — client reconnection delay hint. */
345
+ retry?: number;
346
+ }
347
+ /**
348
+ * Capabilities shared by every response-stream writer, regardless of protocol.
349
+ *
350
+ * @remarks
351
+ * Concrete writers ({@link TextStreamWriter}, {@link SSEStreamWriter},
352
+ * {@link NDJSONStreamWriter}) add a protocol-specific `write()` on top of this.
353
+ */
354
+ interface BaseStreamWriter {
355
+ /**
356
+ * `true` once the client has disconnected.
357
+ *
358
+ * @remarks
359
+ * Use as a loop guard for long-running streams. Writing after this is `true`
360
+ * throws `StreamAbortedError`.
361
+ */
362
+ readonly aborted: boolean;
363
+ /**
364
+ * Fires when the client disconnects.
365
+ *
366
+ * @remarks
367
+ * Pass this straight into an upstream SDK's own abort option
368
+ * (e.g. `{ signal: writer.signal }`) so the upstream call is cancelled the
369
+ * instant the client goes away.
370
+ */
371
+ readonly signal: AbortSignal;
372
+ /**
373
+ * Register a cleanup callback invoked once when the client disconnects.
374
+ *
375
+ * @param fn - Cleanup callback (e.g. abort an upstream request, release a resource).
376
+ */
377
+ onAbort(fn: () => void): void;
378
+ }
379
+ /**
380
+ * Source shapes accepted by a writer's `consume()` method.
381
+ *
382
+ * @remarks
383
+ * Normalized internally to a single async-iterator path — callers never branch
384
+ * on the concrete shape.
385
+ */
386
+ type StreamSource<T> = AsyncIterable<T> | ReadableStream<T>;
387
+ /**
388
+ * Writer for a raw text/byte stream (`ctx.stream()`).
389
+ */
390
+ interface TextStreamWriter extends BaseStreamWriter {
391
+ /**
392
+ * Write text or bytes.
393
+ * @throws StreamAbortedError if the client has already disconnected.
394
+ */
395
+ write(chunk: string | Uint8Array): Promise<void>;
396
+ /**
397
+ * Consume an existing producer as this response's body.
398
+ * @param source - An `AsyncIterable` or Web `ReadableStream` of strings/bytes.
399
+ * @throws StreamAbortedError if the client disconnects mid-consume.
400
+ */
401
+ consume(source: StreamSource<string | Uint8Array>): Promise<void>;
402
+ }
403
+ /**
404
+ * Writer for a Server-Sent Events stream (`ctx.sse()`).
405
+ */
406
+ interface SSEStreamWriter extends BaseStreamWriter {
407
+ /**
408
+ * Write one Server-Sent Event. The framework handles all wire-format framing.
409
+ * @throws StreamAbortedError if the client has already disconnected.
410
+ */
411
+ write(event: SSEEvent): Promise<void>;
412
+ /**
413
+ * Consume an existing producer; each yielded chunk is sent as `{ data: chunk }`.
414
+ * @throws StreamAbortedError if the client disconnects mid-consume.
415
+ */
416
+ consume(source: StreamSource<string | Uint8Array>): Promise<void>;
417
+ }
418
+ /**
419
+ * Writer for a newline-delimited JSON stream (`ctx.ndjson()`).
420
+ */
421
+ interface NDJSONStreamWriter extends BaseStreamWriter {
422
+ /**
423
+ * Write one JSON-serializable value as a single NDJSON line.
424
+ * @throws StreamAbortedError if the client has already disconnected.
425
+ */
426
+ write(value: unknown): Promise<void>;
427
+ /**
428
+ * Consume an existing producer of JSON-serializable values, one line each.
429
+ * @throws StreamAbortedError if the client disconnects mid-consume.
430
+ */
431
+ consume(source: StreamSource<unknown>): Promise<void>;
432
+ }
433
+ /**
434
+ * A streaming callback that receives a protocol-specific writer.
435
+ *
436
+ * @remarks
437
+ * The connection auto-closes when this callback resolves.
438
+ */
439
+ type StreamRun<W extends BaseStreamWriter> = (writer: W) => Promise<void>;
440
+
282
441
  /**
283
442
  * @nextrush/types - Context Type Definitions
284
443
  *
@@ -337,11 +496,26 @@ interface Context {
337
496
  */
338
497
  readonly url: string;
339
498
  /**
340
- * Request path without query string
499
+ * Request path without query string, canonicalized by the router (case
500
+ * folded per `caseSensitive`, structurally normalized) — the value the
501
+ * router actually matched on, and the one path-based policy middleware
502
+ * should compare against instead of hand-rolling its own normalization
503
+ * (RFC-029). See {@link originalPath} for the untouched raw target.
341
504
  * @example '/users/123'
342
505
  * @readonly
343
506
  */
344
507
  readonly path: string;
508
+ /**
509
+ * The raw request target as received, before router canonicalization —
510
+ * still query-string-free, but not case-folded or structurally normalized.
511
+ * Optional: populated once the router's dispatch middleware runs; absent
512
+ * (or equal to {@link path}) when no router has run yet, or when a caller
513
+ * constructs a `Context` directly (e.g. a test double) without one. Prefer
514
+ * `path` for any security- or routing-relevant comparison (RFC-029).
515
+ * @example '/Users/123' when a request for '/USERS/123' matched '/users/:id'
516
+ * @readonly
517
+ */
518
+ readonly originalPath?: string;
345
519
  /**
346
520
  * Parsed query string parameters
347
521
  * @example { include: 'posts', limit: '10' }
@@ -584,6 +758,25 @@ interface Context {
584
758
  * ```
585
759
  */
586
760
  readonly runtime: Runtime;
761
+ /**
762
+ * Named deployment platform, when known — orthogonal to {@link runtime}.
763
+ *
764
+ * @remarks
765
+ * `undefined` on adapters/runtimes with no named platform to report (e.g.
766
+ * plain Node.js, Bun, Deno). On `@nextrush/adapter-edge` and
767
+ * `@nextrush/adapter-serverless`, set when the platform is detectable
768
+ * (Cloudflare Workers, Vercel Edge, Netlify Edge) or explicitly known by
769
+ * the Tier-1 handler that built this context (AWS Lambda, Google Cloud
770
+ * Functions, Azure Functions).
771
+ *
772
+ * @example
773
+ * ```typescript
774
+ * if (ctx.platform === 'lambda') {
775
+ * // AWS Lambda-specific behavior
776
+ * }
777
+ * ```
778
+ */
779
+ readonly platform: PlatformId | undefined;
587
780
  /**
588
781
  * Body source for cross-runtime body reading
589
782
  *
@@ -600,6 +793,72 @@ interface Context {
600
793
  * ```
601
794
  */
602
795
  readonly bodySource: BodySource;
796
+ /**
797
+ * Abort signal that fires when the client disconnects mid-response.
798
+ *
799
+ * @internal
800
+ * @remarks
801
+ * Adapter-level primitive consumed by `@nextrush/stream`. Handlers should use
802
+ * `writer.signal` inside `ctx.stream()`/`ctx.sse()`/`ctx.ndjson()` instead of
803
+ * accessing this directly. Synthesized on Node from response `close`/request
804
+ * `aborted`; passed through natively on Bun/Deno/Edge from `Request.signal`.
805
+ */
806
+ readonly signal: AbortSignal;
807
+ /**
808
+ * Send a raw byte stream as the response body.
809
+ *
810
+ * @internal
811
+ * @remarks
812
+ * Adapter-level transport primitive consumed by `@nextrush/stream`. Not
813
+ * intended for direct handler use — use `ctx.stream()`/`ctx.sse()`/
814
+ * `ctx.ndjson()`. Resolves when the stream is fully flushed (Node) or once the
815
+ * response body is wired (Bun/Deno/Edge).
816
+ *
817
+ * @param source - A Web `ReadableStream` of bytes to stream to the client.
818
+ */
819
+ sendStream(source: ReadableStream<Uint8Array>): Promise<void>;
820
+ /**
821
+ * Stream a raw text/byte response. The connection closes automatically when
822
+ * the callback resolves.
823
+ *
824
+ * @param run - Callback that receives a {@link TextStreamWriter}.
825
+ *
826
+ * @example
827
+ * ```typescript
828
+ * await ctx.stream(async (writer) => {
829
+ * await writer.write('Loading...\n');
830
+ * await writer.write('Done.\n');
831
+ * });
832
+ * ```
833
+ */
834
+ stream(run: StreamRun<TextStreamWriter>): Promise<void>;
835
+ /**
836
+ * Stream a Server-Sent Events (`text/event-stream`) response.
837
+ *
838
+ * @param run - Callback that receives an {@link SSEStreamWriter}.
839
+ *
840
+ * @example
841
+ * ```typescript
842
+ * await ctx.sse(async (writer) => {
843
+ * for await (const token of llm) await writer.write({ data: token });
844
+ * });
845
+ * ```
846
+ */
847
+ sse(run: StreamRun<SSEStreamWriter>): Promise<void>;
848
+ /**
849
+ * Stream a newline-delimited JSON (`application/x-ndjson`) response.
850
+ *
851
+ * @param run - Callback that receives an {@link NDJSONStreamWriter}.
852
+ *
853
+ * @example
854
+ * ```typescript
855
+ * await ctx.ndjson(async (writer) => {
856
+ * await writer.write({ step: 'search' });
857
+ * await writer.write({ step: 'done' });
858
+ * });
859
+ * ```
860
+ */
861
+ ndjson(run: StreamRun<NDJSONStreamWriter>): Promise<void>;
603
862
  }
604
863
  /**
605
864
  * Next function type (traditional Koa-style)
@@ -645,133 +904,579 @@ interface ContextOptions {
645
904
  }
646
905
 
647
906
  /**
648
- * @nextrush/types - Plugin Type Definitions
907
+ * @nextrush/types - Adapter Context Contracts (F-13)
908
+ *
909
+ * The concrete adapter context classes expose transport/lifecycle primitives
910
+ * that are not part of the runtime-neutral {@link Context} contract:
911
+ * `markResponded()` (all adapters), and `getResponse()` / `waitUntil()` / `env`
912
+ * (the Web/fetch adapters). Consumers typed as `Context` cannot see these, so
913
+ * `@nextrush/stream` and the adapters resort to structural access.
649
914
  *
650
- * Plugins are the extension mechanism for NextRush.
651
- * They allow adding functionality without bloating the core.
915
+ * These interfaces model that surface **additively** — they extend `Context`,
916
+ * they never weaken it. Adapters (and `@nextrush/stream`) can depend on these
917
+ * instead of coupling to the concrete context classes.
652
918
  *
653
919
  * @packageDocumentation
654
920
  */
655
921
 
656
922
  /**
657
- * Minimal Application interface for plugin registration
658
- * The full Application class is in @nextrush/core
923
+ * A {@link Context} plus the lifecycle primitive every adapter exposes.
924
+ *
925
+ * @remarks
926
+ * `markResponded()` lets the transport/streaming layer flag that a response has
927
+ * been committed out-of-band (e.g. after wiring a stream), so the adapter's
928
+ * "not responded" fallback path is skipped.
659
929
  */
660
- interface ApplicationLike {
930
+ interface AdapterContext extends Context {
661
931
  /**
662
- * Register middleware
932
+ * Mark the response as already sent.
933
+ *
934
+ * @remarks
935
+ * Used by streaming and adapter transport code when a response is committed
936
+ * without going through `json()`/`send()`/`html()`/`redirect()`.
663
937
  */
664
- use(middleware: Middleware): this;
938
+ markResponded(): void;
939
+ }
940
+ /**
941
+ * An {@link AdapterContext} for Web/fetch runtimes (Bun, Deno, Edge).
942
+ *
943
+ * @remarks
944
+ * Adds the fetch-specific surface: `getResponse()` (materialize the built
945
+ * `Response`), and the optional edge primitives `waitUntil()` and `env`
946
+ * (platform bindings — reachable only on runtimes that provide them, e.g.
947
+ * Cloudflare Workers).
948
+ */
949
+ interface FetchContext extends AdapterContext {
665
950
  /**
666
- * Get installed plugin by name
951
+ * Build and return the Web `Response` accumulated by the context.
952
+ *
953
+ * @returns The `Response` to hand back to the runtime.
667
954
  */
668
- getPlugin<T extends Plugin>(name: string): T | undefined;
955
+ getResponse(): Response;
956
+ /**
957
+ * Extend the request's lifetime for fire-and-forget work (logging,
958
+ * analytics, cache writes). Only present on runtimes that expose an
959
+ * execution context (e.g. Cloudflare Workers, Vercel Edge).
960
+ *
961
+ * @param promise - Work that should outlive the response.
962
+ */
963
+ waitUntil?(promise: Promise<unknown>): void;
964
+ /**
965
+ * Platform bindings passed by the runtime (e.g. Cloudflare `env`: KV, D1, R2,
966
+ * Durable Objects, secrets). Typed as `unknown` at the contract level; a
967
+ * concrete adapter may narrow it via a generic.
968
+ */
969
+ env?: unknown;
669
970
  }
670
971
  /**
671
- * Plugin interface for extending NextRush
972
+ * The shape of an adapter's context factory.
672
973
  *
673
- * Plugins are installed via app.plugin() and can:
674
- * - Add middleware
675
- * - Extend context
676
- * - Add lifecycle hooks
974
+ * @remarks
975
+ * Every adapter builds the per-request context through a factory that takes
976
+ * platform-specific inputs (e.g. Node's `(req, res, options)`, a fetch runtime's
977
+ * `(request, options)`) and returns an {@link AdapterContext} over the shared
978
+ * {@link Context} contract. This type formalizes that invariant — "adapters build
979
+ * `Context` via a factory and run `app.callback()`" — at the type level, generic
980
+ * over the platform input tuple so server- and fetch-style factories both conform.
981
+ *
982
+ * @typeParam Args - The platform-specific factory argument tuple.
983
+ * @typeParam Ctx - The concrete {@link AdapterContext} the factory returns.
677
984
  *
678
985
  * @example
679
986
  * ```typescript
680
- * class LoggerPlugin implements Plugin {
681
- * readonly name = 'logger';
682
- * readonly version = '1.0.0';
683
- *
684
- * install(app: ApplicationLike) {
685
- * app.use(async (ctx, next) => {
686
- * const start = Date.now();
687
- * await next();
688
- * console.log(`${ctx.method} ${ctx.path} - ${Date.now() - start}ms`);
689
- * });
690
- * }
691
- * }
987
+ * // Node adapter
988
+ * const _factory = createNodeContext satisfies AdapterContextFactory<
989
+ * [IncomingMessage, ServerResponse, NodeContextOptions?],
990
+ * NodeContext
991
+ * >;
692
992
  * ```
693
993
  */
694
- interface Plugin {
695
- /**
696
- * Unique plugin name
697
- * Used for identification and getPlugin() lookup
698
- */
699
- readonly name: string;
700
- /**
701
- * Plugin version (optional)
702
- * Follows semver format
703
- */
704
- readonly version?: string;
994
+ type AdapterContextFactory<Args extends readonly unknown[], Ctx extends AdapterContext = AdapterContext> = (...args: Args) => Ctx;
995
+
996
+ /**
997
+ * @nextrush/types - Logger contract
998
+ *
999
+ * The structured logging interface shared by the application, adapters, and
1000
+ * extensions. Lives here (the lowest package) so any layer can depend on the
1001
+ * contract without depending on `@nextrush/core`.
1002
+ *
1003
+ * @packageDocumentation
1004
+ */
1005
+ /**
1006
+ * Pluggable logger interface.
1007
+ *
1008
+ * Pass `console` for quick development logging, or a structured logger
1009
+ * (pino, winston, `@nextrush/logger`) in production.
1010
+ */
1011
+ interface Logger {
1012
+ error(...args: unknown[]): void;
1013
+ warn(...args: unknown[]): void;
1014
+ info(...args: unknown[]): void;
1015
+ debug(...args: unknown[]): void;
1016
+ }
1017
+
1018
+ /**
1019
+ * @nextrush/types - Adapter Conformance Contract (F-01)
1020
+ *
1021
+ * A light, additive compile-time contract that server- and fetch-style adapters
1022
+ * `satisfies` at export time, so the *shape* of `serve`/`createHandler`/
1023
+ * `createFetchHandler` and the `ServerHandle` cannot drift silently across
1024
+ * adapters. This is the cheap secondary guard; a shared behavioral conformance
1025
+ * suite is the primary one (see the audit's Adapter Conformance Specification).
1026
+ *
1027
+ * The contract is generic over the concrete application type because
1028
+ * `@nextrush/types` sits below `@nextrush/core` in the package hierarchy and
1029
+ * must not import `Application`. Adapters bind `App = Application` when they
1030
+ * `satisfies` these shapes.
1031
+ *
1032
+ * @packageDocumentation
1033
+ */
1034
+
1035
+ /**
1036
+ * The single canonical network address a server adapter binds to.
1037
+ *
1038
+ * @remarks
1039
+ * Replaces the per-adapter `{ port, host }` vs `{ port, hostname }` divergence
1040
+ * (audit F-05). `host` is the canonical key; adapters normalize internally.
1041
+ */
1042
+ interface ServerAddress {
1043
+ /** Port the server is listening on. */
1044
+ readonly port: number;
1045
+ /** Host/address the server is bound to (canonical key). */
1046
+ readonly host: string;
705
1047
  /**
706
- * Install plugin into application
707
- * Called when app.plugin() is invoked
1048
+ * Host alias.
708
1049
  *
709
- * @param app - Application instance
1050
+ * @deprecated Use {@link ServerAddress.host}. Retained so Bun/Deno, which
1051
+ * historically returned `{ port, hostname }`, keep working during migration.
710
1052
  */
711
- install(app: ApplicationLike): void | Promise<void>;
712
- /**
713
- * Cleanup when application shuts down (optional)
714
- * Called during graceful shutdown
715
- */
716
- destroy?(): void | Promise<void>;
1053
+ readonly hostname?: string;
717
1054
  }
718
1055
  /**
719
- * Extended plugin interface with lifecycle hooks
720
- * For advanced plugins that need deeper integration
1056
+ * Options common to server-style handler factories (node/bun/deno).
1057
+ *
1058
+ * @remarks
1059
+ * Intentionally light. Individual adapters may accept a superset; this pins the
1060
+ * shared, portable subset.
1061
+ */
1062
+ interface HandlerOptions {
1063
+ /** Logger for adapter diagnostics. Defaults to the application logger. */
1064
+ logger?: Logger;
1065
+ /** Per-request timeout in milliseconds. */
1066
+ timeout?: number;
1067
+ }
1068
+ /**
1069
+ * Options for fetch-style handler factories (edge).
721
1070
  */
722
- interface PluginWithHooks extends Plugin {
1071
+ interface FetchHandlerOptions {
723
1072
  /**
724
- * Called before each request
1073
+ * Per-request timeout in milliseconds. Individual adapters may apply their
1074
+ * own default when omitted (e.g. the edge adapter's `DEFAULT_EDGE_TIMEOUT_MS`,
1075
+ * F-07/ADR-0010) rather than leaving the timeout unenforced — this shared
1076
+ * type only pins the option's shape, not a specific default.
725
1077
  */
726
- onRequest?(ctx: Context): void | Promise<void>;
1078
+ timeout?: number;
727
1079
  /**
728
- * Called after each response
1080
+ * Custom error handler. Receives the error and the fetch context and returns
1081
+ * the `Response` to send.
729
1082
  */
730
- onResponse?(ctx: Context): void | Promise<void>;
1083
+ onError?: (error: Error, ctx: FetchContext) => Response | Promise<Response>;
1084
+ }
1085
+ /**
1086
+ * A running server instance with one canonical address shape and async close.
1087
+ *
1088
+ * @remarks
1089
+ * The canonical handle every server adapter's `serve()` resolves to. Concrete
1090
+ * adapters may expose additional fields (e.g. the raw `server`) on a subtype.
1091
+ */
1092
+ interface ServerHandle {
1093
+ /** The address the server is bound to. */
1094
+ address(): ServerAddress;
1095
+ /** Stop the server, draining in-flight requests. */
1096
+ close(): Promise<void>;
1097
+ }
1098
+ /**
1099
+ * A fetch handler: maps a Web `Request` (plus an optional runtime execution
1100
+ * context) to a `Response`.
1101
+ *
1102
+ * @typeParam Exec - The runtime execution-context type (e.g. an edge
1103
+ * `waitUntil` context). Defaults to `unknown`.
1104
+ */
1105
+ type FetchHandler<Exec = unknown> = (request: Request, executionContext?: Exec) => Response | Promise<Response>;
1106
+ /**
1107
+ * Contract for server-style adapters (node/bun/deno).
1108
+ *
1109
+ * @typeParam App - The application type (adapters bind `Application`).
1110
+ * @typeParam Opts - The adapter's `serve` options type.
1111
+ * @typeParam Instance - The `serve` return type (a {@link ServerHandle} subtype).
1112
+ */
1113
+ interface ServerAdapter<App = unknown, Opts = unknown, Instance extends ServerHandle = ServerHandle> {
1114
+ /** Start a server for the application. */
1115
+ serve(app: App, options?: Opts): Promise<Instance>;
1116
+ /** Build a request handler for the application without owning the server. */
1117
+ createHandler(app: App, options?: HandlerOptions): unknown;
1118
+ }
1119
+ /**
1120
+ * Contract for fetch-style adapters (edge).
1121
+ *
1122
+ * @typeParam App - The application type (adapters bind `Application`).
1123
+ */
1124
+ interface FetchAdapter<App = unknown, Exec = unknown> {
1125
+ /** Build a fetch handler for the application. */
1126
+ createFetchHandler(app: App, options?: FetchHandlerOptions): FetchHandler<Exec>;
1127
+ }
1128
+
1129
+ /**
1130
+ * @nextrush/types - Dependency Injection contract
1131
+ *
1132
+ * The container contract shared by `@nextrush/di` (implementation),
1133
+ * `@nextrush/core` (each app owns one), and consumers. Lives here (the lowest
1134
+ * package) so any layer can reference the contract without depending on `di`.
1135
+ *
1136
+ * @packageDocumentation
1137
+ */
1138
+ /** Constructor type for class-based tokens. */
1139
+ type Constructor<T = unknown> = new (...args: unknown[]) => T;
1140
+ /** Token used to identify a dependency — a class, string, or symbol. */
1141
+ type Token<T = unknown> = Constructor<T> | string | symbol;
1142
+ /** Provider that uses a class constructor. */
1143
+ interface ClassProvider<T> {
1144
+ useClass: Constructor<T>;
1145
+ }
1146
+ /** Provider that uses a factory function (optionally with injected deps). */
1147
+ interface FactoryProvider<T> {
1148
+ useFactory: (...args: unknown[]) => T | Promise<T>;
1149
+ inject?: Token[];
1150
+ }
1151
+ /** Provider that uses a constant value. */
1152
+ interface ValueProvider<T> {
1153
+ useValue: T;
1154
+ }
1155
+ /** Union of all provider kinds. */
1156
+ type Provider<T> = ClassProvider<T> | FactoryProvider<T> | ValueProvider<T>;
1157
+ /**
1158
+ * Lifecycle scope for registered services.
1159
+ *
1160
+ * - `'singleton'` — one shared instance for the process lifetime.
1161
+ * - `'transient'` — a fresh instance on every resolve.
1162
+ * - `'request'` — one instance per request, shared within that request. Backed
1163
+ * by a per-request child container; see RFC-NEXTRUSH-REQUEST-SCOPE.
1164
+ */
1165
+ type Scope = 'singleton' | 'transient' | 'request';
1166
+ /** Options for service registration. */
1167
+ interface ServiceOptions {
1168
+ scope?: Scope;
1169
+ }
1170
+ /** Registration options for `Container.register()`. */
1171
+ interface RegisterOptions {
1172
+ /** Lifecycle scope — defaults to 'transient' if not specified. */
1173
+ scope?: Scope;
1174
+ }
1175
+ /**
1176
+ * Dependency injection container contract.
1177
+ *
1178
+ * Each NextRush {@link Application} may own one (per-app, not a global
1179
+ * singleton). `@nextrush/di` provides the implementation.
1180
+ */
1181
+ interface Container {
1182
+ /** Register a dependency. */
1183
+ register<T>(token: Token<T>, provider: Provider<T>, options?: RegisterOptions): void;
1184
+ /** Resolve a dependency synchronously. */
1185
+ resolve<T>(token: Token<T>): T;
1186
+ /** Resolve a dependency that may have been registered with an async factory. */
1187
+ resolveAsync<T>(token: Token<T>): Promise<T>;
1188
+ /** Bootstrap all factory providers (awaits async factories, caches results). */
1189
+ bootstrap(): Promise<void>;
1190
+ /** Resolve all dependencies registered under a token. */
1191
+ resolveAll<T>(token: Token<T>): T[];
1192
+ /** Check if a token is registered. */
1193
+ isRegistered<T>(token: Token<T>): boolean;
1194
+ /** Clear all registered instances (testing). */
1195
+ clearInstances(): void;
1196
+ /** Reset the container completely (testing). */
1197
+ reset(): void;
1198
+ /** Create a child container with isolated scope. */
1199
+ createChild(): Container;
1200
+ }
1201
+
1202
+ /**
1203
+ * @nextrush/types - Extension contract
1204
+ *
1205
+ * Extensions are the rare (~0.1%) long-lived runtime services that must attach
1206
+ * state to the app, run async boot, and/or tear down on shutdown — an event bus,
1207
+ * a database pool, a websocket attach. Most framework features are **middleware**
1208
+ * (`app.use`) or plain **registrar** functions, not Extensions.
1209
+ *
1210
+ * See docs/RFC/class-runtime/005-plugin-system.md.
1211
+ *
1212
+ * @packageDocumentation
1213
+ */
1214
+
1215
+ /**
1216
+ * The subset of `Application` an {@link Extension} may use during `setup()`.
1217
+ *
1218
+ * Kept structural (not the concrete `Application`) so {@link ExtensionContext}
1219
+ * can live in `@nextrush/types` without importing `@nextrush/core`. The
1220
+ * `Application` class implements this interface. Routing methods are added here
1221
+ * when the app-owned router lands (RFC §11.1).
1222
+ */
1223
+ interface ExtensionHost {
1224
+ /** Register middleware on the application. */
1225
+ use(middleware: Middleware): this;
1226
+ /** Whether a decoration already occupies `name`. */
1227
+ hasDecorator(name: string): boolean;
1228
+ }
1229
+ /**
1230
+ * The argument passed to {@link Extension.setup}.
1231
+ *
1232
+ * A context object rather than the bare app, so future fields (`runtime`,
1233
+ * `container`, `config`, …) are **additive** and never break existing
1234
+ * extensions — the same reason VS Code uses `activate(context)`.
1235
+ */
1236
+ interface ExtensionContext {
1237
+ /** The application instance — add middleware, read decorations. */
1238
+ readonly app: ExtensionHost;
1239
+ /** The app logger (structured, pluggable). */
1240
+ readonly logger: Logger;
731
1241
  /**
732
- * Called when an error occurs
1242
+ * The app's DI container, if one was configured (per-app, not a global
1243
+ * singleton). Present when the app was created via `nextrush/class` or with
1244
+ * `createApp({ container })`; `undefined` for functional, DI-free apps.
733
1245
  */
734
- onError?(error: Error, ctx: Context): void | Promise<void>;
1246
+ readonly container?: Container;
1247
+ /** Environment mode. */
1248
+ readonly env: 'development' | 'production' | 'test';
1249
+ /** This extension's own name (for scoped diagnostics). */
1250
+ readonly name: string;
735
1251
  /**
736
- * Extend the context object
737
- * Add custom properties or methods
1252
+ * Attach a value to the app under `name`. The extension-author primitive for
1253
+ * exposing app-level surface (e.g. `app.events`). Throws if `name` is already
1254
+ * decorated or collides with a core `Application` member.
738
1255
  */
739
- extendContext?(ctx: Context): void;
1256
+ decorate(name: string, value: unknown): void;
740
1257
  }
741
1258
  /**
742
- * Plugin factory function type
743
- * Allows creating plugins with options
1259
+ * A NextRush Extension.
1260
+ *
1261
+ * Registered via `app.extend()` (queues) and booted once at `app.ready()`
1262
+ * (runs `setup` in registration order). Torn down at `app.close()`.
1263
+ *
1264
+ * @example
1265
+ * ```typescript
1266
+ * export function events(): Extension {
1267
+ * const emitter = new EventEmitter();
1268
+ * return {
1269
+ * name: 'events',
1270
+ * setup(ctx) { ctx.decorate('events', emitter); },
1271
+ * destroy() { emitter.clear(); },
1272
+ * };
1273
+ * }
1274
+ * ```
1275
+ */
1276
+ /**
1277
+ * @template TDecorated - Shape decorated onto the app by this extension's
1278
+ * `setup()`. Phantom — never read at runtime, only carried through the type
1279
+ * system so `Application.extend()` can return `this & TDecorated`, giving
1280
+ * `app.<decoratedProperty>` static inference with zero `declare module`
1281
+ * augmentation. Defaults to `{}` so plain, untyped `Extension` usages
1282
+ * (the common case for internal/test extensions) are unaffected.
1283
+ *
1284
+ * @remarks
1285
+ * Two things to know before relying on this:
1286
+ *
1287
+ * - **TypeScript trusts `TDecorated`, it never verifies it.** Nothing checks
1288
+ * that the declared shape actually matches what `setup()` calls
1289
+ * `ctx.decorate()` with — `Extension<{ foo: Foo }>` whose `setup()` never
1290
+ * decorates `foo` still typechecks, and `app.foo` will be `undefined` at
1291
+ * runtime with no warning. Keep the generic and the `decorate()` call in
1292
+ * sync by hand.
1293
+ * - **The inferred type is lost on `let` reassignment.** Chain in one
1294
+ * expression (`const app = createApp().extend(x)`), not
1295
+ * `let app = createApp(); app = app.extend(x);` — TypeScript widens the
1296
+ * reassignment to the `let` binding's originally-inferred type, silently
1297
+ * dropping `TDecorated`. Every example below chains for this reason.
744
1298
  *
745
1299
  * @example
746
1300
  * ```typescript
747
- * const logger = createLoggerPlugin({ level: 'info' });
748
- * app.plugin(logger);
1301
+ * function events<T extends EventMap>(): Extension<{ events: EventEmitter<T> }> {
1302
+ * return {
1303
+ * name: 'events',
1304
+ * setup(ctx) { ctx.decorate('events', new EventEmitter<T>()); },
1305
+ * };
1306
+ * }
1307
+ *
1308
+ * const app = createApp().extend(events<MyEvents>());
1309
+ * app.events.emit('user:created', { id: '1' }); // inferred, no cast
749
1310
  * ```
750
1311
  */
751
- type PluginFactory<TOptions = unknown> = (options?: TOptions) => Plugin;
1312
+ interface Extension<TDecorated = Record<string, never>> {
1313
+ /** Unique name — used for collision detection, dependency assertion, diagnostics. */
1314
+ readonly name: string;
1315
+ /**
1316
+ * Names of other extensions that MUST already be registered before this one.
1317
+ * Asserted at `app.ready()` in registration order — not auto-sorted.
1318
+ */
1319
+ readonly needs?: readonly string[];
1320
+ /** Set up the extension. Runs once, at `app.ready()`, in registration order. */
1321
+ setup(ctx: ExtensionContext): void | Promise<void>;
1322
+ /** Tear down on `app.close()`. Runs in reverse registration order. */
1323
+ destroy?(): void | Promise<void>;
1324
+ /**
1325
+ * Phantom marker carrying `TDecorated` through the type system. Never set
1326
+ * at runtime — `TDecorated` has no inhabitant, so no implementation ever
1327
+ * assigns this. Purely what lets `extend()` infer its return type.
1328
+ */
1329
+ readonly __decorated?: TDecorated;
1330
+ }
1331
+
752
1332
  /**
753
- * Plugin metadata for registration and discovery
1333
+ * @nextrush/types - Standard Schema Contract
1334
+ *
1335
+ * Structural copy of the `@standard-schema/spec` v1 interface
1336
+ * (https://github.com/standard-schema/standard-schema, MIT licensed), vendored
1337
+ * as a type so NextRush stays dependency-free. It lives in `@nextrush/types`
1338
+ * (not in any single consumer) because it is now a shared contract: request
1339
+ * validation, route metadata, and OpenAPI generation all reference it.
1340
+ *
1341
+ * Because TypeScript is structural, any schema implementing Standard Schema —
1342
+ * Zod 3.24+, Valibot 1.0+, ArkType 2.0+, and others — satisfies this interface
1343
+ * without an adapter.
1344
+ *
1345
+ * @packageDocumentation
754
1346
  */
755
- interface PluginMeta {
756
- /** Plugin name */
757
- name: string;
758
- /** Plugin version */
759
- version: string;
760
- /** Plugin description */
761
- description?: string;
762
- /** Plugin author */
763
- author?: string;
764
- /** Plugin repository URL */
765
- repository?: string;
766
- /** Plugin dependencies (other plugin names) */
767
- dependencies?: string[];
1347
+ /**
1348
+ * A schema that exposes the Standard Schema v1 contract on its `~standard`
1349
+ * property. This is the only surface NextRush depends on.
1350
+ */
1351
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
1352
+ readonly '~standard': StandardSchemaProps<Input, Output>;
1353
+ }
1354
+ /** The `~standard` property contract. */
1355
+ interface StandardSchemaProps<Input = unknown, Output = Input> {
1356
+ /** Spec version always `1`. */
1357
+ readonly version: 1;
1358
+ /** Identifier of the schema library (e.g. `'zod'`, `'valibot'`). */
1359
+ readonly vendor: string;
1360
+ /** Validates and (optionally) coerces `value`; may be sync or async. */
1361
+ readonly validate: (value: unknown) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;
1362
+ /** Phantom types for input/output inference; never present at runtime. */
1363
+ readonly types?: {
1364
+ readonly input: Input;
1365
+ readonly output: Output;
1366
+ } | undefined;
1367
+ }
1368
+ /** Result of a validation: either a success carrying `value`, or a failure carrying `issues`. */
1369
+ type StandardSchemaResult<Output> = {
1370
+ readonly value: Output;
1371
+ readonly issues?: undefined;
1372
+ } | {
1373
+ readonly issues: readonly StandardSchemaIssue[];
1374
+ };
1375
+ /** A single validation issue reported by a schema. */
1376
+ interface StandardSchemaIssue {
1377
+ /** Human-readable message. */
1378
+ readonly message: string;
1379
+ /** Path to the offending value; segments are keys or `{ key }` objects. */
1380
+ readonly path?: readonly (PropertyKey | StandardSchemaPathSegment)[] | undefined;
1381
+ }
1382
+ /** A structured path segment. */
1383
+ interface StandardSchemaPathSegment {
1384
+ readonly key: PropertyKey;
1385
+ }
1386
+ /** Infer the validated output type of a Standard Schema. */
1387
+ type InferOutput<S extends StandardSchemaV1> = NonNullable<S['~standard']['types']>['output'];
1388
+
1389
+ /**
1390
+ * @nextrush/types - Route Metadata Contracts
1391
+ *
1392
+ * `RouteDefinition` is the single source of truth for what a route *is and
1393
+ * means*. The router collects it once at registration; renderers
1394
+ * (`@nextrush/openapi`, and later SDK/Postman/RPC generators) read it. The
1395
+ * router itself is renderer-agnostic — it stores raw schemas and generic
1396
+ * metadata, never OpenAPI shapes.
1397
+ *
1398
+ * See docs/RFC/request-data/002-route-metadata.md.
1399
+ *
1400
+ * @packageDocumentation
1401
+ */
1402
+
1403
+ /**
1404
+ * Well-known symbol by which anything — a middleware function (e.g. `validate()`)
1405
+ * or a pure marker (e.g. `endpoint()`) — contributes metadata to the route it is
1406
+ * registered on. The router reads this symbol off every route entry at
1407
+ * registration and merges the contributions.
1408
+ *
1409
+ * `Symbol.for` (global registry) is used so the identity holds even across
1410
+ * duplicate package instances.
1411
+ */
1412
+ declare const ROUTE_METADATA: unique symbol;
1413
+ /** A partial metadata contribution; contributions merge in registration order. */
1414
+ type MetadataContribution = Partial<RouteMetadata>;
1415
+ /**
1416
+ * A pure-metadata marker carried in a route's argument list. Not a middleware —
1417
+ * it has no runtime behavior and never enters the executed chain.
1418
+ */
1419
+ interface RouteMetaMarker {
1420
+ readonly [ROUTE_METADATA]: MetadataContribution;
1421
+ }
1422
+ /** An entry in a route's argument list: behavior (`Middleware`) or pure metadata (a marker). */
1423
+ type RouteEntry = Middleware | RouteMetaMarker;
1424
+ /**
1425
+ * Generic, renderer-agnostic description of a route's request/response shapes
1426
+ * and documentation facts. Every field is a fact any renderer needs; no
1427
+ * renderer-specific artifacts (e.g. OpenAPI `operationId`) live here.
1428
+ */
1429
+ interface RouteMetadata {
1430
+ /** Request shapes — contributed by `validate()`; never hand-written on the golden path. */
1431
+ readonly request?: {
1432
+ readonly body?: StandardSchemaV1;
1433
+ readonly query?: StandardSchemaV1;
1434
+ readonly params?: StandardSchemaV1;
1435
+ };
1436
+ /** Response shapes by numeric status — contributed by `endpoint()`. */
1437
+ readonly responses?: Readonly<Record<number, StandardSchemaV1>>;
1438
+ readonly summary?: string;
1439
+ readonly description?: string;
1440
+ readonly tags?: readonly string[];
1441
+ readonly deprecated?: boolean;
1442
+ /** Cross-renderer intent — an `'internal'` route is excluded from public specs/SDKs. */
1443
+ readonly visibility?: 'public' | 'internal';
1444
+ }
1445
+ /**
1446
+ * The canonical description of a registered endpoint, produced by the router
1447
+ * and consumed by renderers.
1448
+ */
1449
+ interface RouteDefinition {
1450
+ /**
1451
+ * Canonical route key — `${METHOD} ${pathPattern}` (e.g. `"GET /users/:id"`),
1452
+ * the key the router uses internally. Deterministic and stable across
1453
+ * restarts, but it encodes the path, so it changes if the path or a param
1454
+ * name changes — a key, not a rename-stable opaque id.
1455
+ */
1456
+ readonly key: string;
1457
+ readonly method: HttpMethod;
1458
+ /** Full, mount/prefix-resolved path pattern. */
1459
+ readonly path: string;
1460
+ readonly metadata?: RouteMetadata;
1461
+ /**
1462
+ * `true` when this entry represents an any-method route (registered via
1463
+ * `router.all()` / `@All()`), matching every standard HTTP method under a
1464
+ * single introspection row rather than one row per method (T016). `method`
1465
+ * is still a real `HttpMethod` value for structural compatibility with
1466
+ * existing consumers that read `.method` unconditionally — check this flag
1467
+ * first when the distinction matters (e.g. an OpenAPI renderer expanding
1468
+ * one row into per-verb operations). Absent (`undefined`) for every
1469
+ * ordinary single-method route.
1470
+ */
1471
+ readonly isAnyMethod?: boolean;
768
1472
  }
769
1473
 
770
1474
  /**
771
1475
  * @nextrush/types - Router Type Definitions
772
1476
  *
773
1477
  * Types for the NextRush router system.
774
- * The router uses a radix tree for efficient route matching.
1478
+ * The router matches routes with a segment trie keyed by whole path segments,
1479
+ * giving O(k) lookups where k is the number of path segments.
775
1480
  *
776
1481
  * @packageDocumentation
777
1482
  */
@@ -820,43 +1525,53 @@ interface Router {
820
1525
  /**
821
1526
  * Register a GET route
822
1527
  */
823
- get(path: string, ...handlers: RouteHandler[]): this;
1528
+ get(path: string, ...entries: RouteEntry[]): this;
824
1529
  /**
825
1530
  * Register a POST route
826
1531
  */
827
- post(path: string, ...handlers: RouteHandler[]): this;
1532
+ post(path: string, ...entries: RouteEntry[]): this;
828
1533
  /**
829
1534
  * Register a PUT route
830
1535
  */
831
- put(path: string, ...handlers: RouteHandler[]): this;
1536
+ put(path: string, ...entries: RouteEntry[]): this;
832
1537
  /**
833
1538
  * Register a DELETE route
834
1539
  */
835
- delete(path: string, ...handlers: RouteHandler[]): this;
1540
+ delete(path: string, ...entries: RouteEntry[]): this;
836
1541
  /**
837
1542
  * Register a PATCH route
838
1543
  */
839
- patch(path: string, ...handlers: RouteHandler[]): this;
1544
+ patch(path: string, ...entries: RouteEntry[]): this;
840
1545
  /**
841
1546
  * Register a HEAD route
842
1547
  */
843
- head(path: string, ...handlers: RouteHandler[]): this;
1548
+ head(path: string, ...entries: RouteEntry[]): this;
844
1549
  /**
845
1550
  * Register an OPTIONS route
846
1551
  */
847
- options(path: string, ...handlers: RouteHandler[]): this;
1552
+ options(path: string, ...entries: RouteEntry[]): this;
848
1553
  /**
849
1554
  * Register a route for any HTTP method
850
1555
  */
851
- all(path: string, ...handlers: RouteHandler[]): this;
1556
+ all(path: string, ...entries: RouteEntry[]): this;
852
1557
  /**
853
1558
  * Register a route for specific method
854
1559
  */
855
- route(method: HttpMethod, path: string, ...handlers: RouteHandler[]): this;
1560
+ route(method: HttpMethod, path: string, ...entries: RouteEntry[]): this;
856
1561
  /**
857
1562
  * Mount router middleware
858
1563
  */
859
- use(path: string, router: Router): this;
1564
+ /**
1565
+ * Mount middleware.
1566
+ *
1567
+ * @remarks
1568
+ * Sub-router mounting (`router.use(path, subRouter)`) is a concrete
1569
+ * `@nextrush/router` `Router` capability, not part of this structural
1570
+ * interface — mounting needs internal tree access
1571
+ * (`Router.mount()`/the concrete class's own `use()` overload provide it).
1572
+ * Cross-package router composition goes through `Application.route()`
1573
+ * (`Routable`, `routes()`-only) instead.
1574
+ */
860
1575
  use(middleware: Middleware): this;
861
1576
  /**
862
1577
  * Register a redirect from one path to another
@@ -871,6 +1586,12 @@ interface Router {
871
1586
  * Mount this on the application
872
1587
  */
873
1588
  routes(): Middleware;
1589
+ /**
1590
+ * Return every registered route as a read-only list of RouteDefinitions.
1591
+ * Consumed by renderers (`@nextrush/openapi`, SDK/Postman generators).
1592
+ * Doc-generation-time projection — never called on the request hot path.
1593
+ */
1594
+ getRoutes(): readonly RouteDefinition[];
874
1595
  /**
875
1596
  * Match a route
876
1597
  */
@@ -895,6 +1616,13 @@ interface RouterOptions {
895
1616
  * @default false
896
1617
  */
897
1618
  strict?: boolean;
1619
+ /**
1620
+ * Whether to percent-decode extracted param and wildcard values
1621
+ * (via `decodeURIComponent`). Malformed encoding falls back to the raw value
1622
+ * and never throws. Set to `false` to receive raw, undecoded values.
1623
+ * @default true
1624
+ */
1625
+ decode?: boolean;
898
1626
  }
899
1627
  /**
900
1628
  * Supported route pattern types
@@ -912,4 +1640,61 @@ interface RouteParam {
912
1640
  pattern?: RegExp;
913
1641
  }
914
1642
 
915
- export { type ApplicationLike, type BodySource, type BodySourceOptions, type CommonHttpMethod, ContentType, type ContentTypeValue, type Context, type ContextOptions, type ContextState, HTTP_METHODS, type HttpMethod, HttpStatus, type HttpStatusCode, type IncomingHeaders, type Middleware, type Next, type NodeStreamLike, type OutgoingHeaders, type ParsedBody, type Plugin, type PluginFactory, type PluginMeta, type PluginWithHooks, type QueryParams, type RawHttp, type ResponseBody, type Route, type RouteHandler, type RouteMatch, type RouteParam, type RouteParams, type RoutePattern, type Router, type RouterOptions, type Runtime, type RuntimeCapabilities, type RuntimeInfo, type WebStreamLike };
1643
+ /**
1644
+ * @nextrush/types - Security Audit Contribution Contract
1645
+ *
1646
+ * `core` cannot import `@nextrush/{cors,static,cookies,errors}` (lower
1647
+ * packages never import from higher ones — `architecture.instructions.md`),
1648
+ * so `Application` cannot inspect a middleware instance's configuration
1649
+ * directly. This contract lets a middleware factory (`cors()`, `serveStatic()`,
1650
+ * `cookies()`, `errorHandler()`, …) optionally attach a boot-time check to the
1651
+ * `Middleware` function it already returns from `app.use()` — the same
1652
+ * "contribute via a well-known symbol, the owner collects it later" shape as
1653
+ * {@link ROUTE_METADATA}, applied to production-safety auditing instead of
1654
+ * route documentation (RFC gates this in `docs/RFC/` under the
1655
+ * `security-boundaries` capability's boot-audit requirement).
1656
+ *
1657
+ * @packageDocumentation
1658
+ */
1659
+
1660
+ /**
1661
+ * Well-known symbol by which a middleware factory contributes a security
1662
+ * audit check to the `Middleware` function it returns. `Application.use()`
1663
+ * reads this symbol off every registered middleware and collects any checks
1664
+ * present; `app.ready()` runs the collected set once, in production only.
1665
+ *
1666
+ * `Symbol.for` (global registry) matches {@link ROUTE_METADATA}'s choice, so
1667
+ * identity holds even across duplicate package instances.
1668
+ */
1669
+ declare const SECURITY_AUDIT: unique symbol;
1670
+ /**
1671
+ * The outcome of one security audit check.
1672
+ *
1673
+ * - `ok` — configuration is safe; nothing is reported.
1674
+ * - `warn` — configuration is unsafe but has a legitimate use; logged once,
1675
+ * never blocks boot.
1676
+ * - `throw` — configuration has no legitimate production use; boot fails.
1677
+ */
1678
+ type SecurityAuditVerdict = {
1679
+ readonly level: 'ok';
1680
+ } | {
1681
+ readonly level: 'warn';
1682
+ readonly message: string;
1683
+ } | {
1684
+ readonly level: 'throw';
1685
+ readonly message: string;
1686
+ };
1687
+ /**
1688
+ * A middleware's self-reported boot-time safety check, invoked only when the
1689
+ * application is booting in production. Pure and synchronous — a check
1690
+ * inspects configuration already closed over at construction time, never I/O.
1691
+ */
1692
+ type SecurityAuditCheck = () => SecurityAuditVerdict;
1693
+ /** A middleware function carrying a {@link SecurityAuditCheck} contribution. */
1694
+ interface SecurityAudited {
1695
+ readonly [SECURITY_AUDIT]: SecurityAuditCheck;
1696
+ }
1697
+ /** A registered middleware, optionally carrying a security audit contribution. */
1698
+ type AuditableMiddleware = Middleware & Partial<SecurityAudited>;
1699
+
1700
+ export { type AdapterContext, type AdapterContextFactory, type AuditableMiddleware, type BaseStreamWriter, type BodySource, type BodySourceOptions, type ClassProvider, type CommonHttpMethod, type Constructor, type Container, ContentType, type ContentTypeValue, type Context, type ContextOptions, type ContextState, type Extension, type ExtensionContext, type ExtensionHost, type FactoryProvider, type FetchAdapter, type FetchContext, type FetchHandler, type FetchHandlerOptions, HTTP_METHODS, type HandlerOptions, type HttpMethod, HttpStatus, type HttpStatusCode, type IncomingHeaders, type InferOutput, type Logger, type MetadataContribution, type Middleware, type NDJSONStreamWriter, type Next, type NodeStreamLike, type OutgoingHeaders, type ParsedBody, type PlatformId, type Provider, type ProxyTrust, type QueryParams, ROUTE_METADATA, type RawHttp, type RegisterOptions, type ResponseBody, type Route, type RouteDefinition, type RouteEntry, type RouteHandler, type RouteMatch, type RouteMetaMarker, type RouteMetadata, type RouteParam, type RouteParams, type RoutePattern, type Router, type RouterOptions, type Runtime, type RuntimeCapabilities, type RuntimeInfo, SECURITY_AUDIT, type SSEEvent, type SSEStreamWriter, type Scope, type SecurityAuditCheck, type SecurityAuditVerdict, type SecurityAudited, type ServerAdapter, type ServerAddress, type ServerHandle, type ServiceOptions, type StandardSchemaIssue, type StandardSchemaPathSegment, type StandardSchemaProps, type StandardSchemaResult, type StandardSchemaV1, type StreamRun, type StreamSource, type TextStreamWriter, type Token, type ValueProvider, type WebStreamLike };