@nextrush/types 3.0.6 → 4.0.0-beta.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/dist/index.d.ts CHANGED
@@ -222,12 +222,17 @@ interface BodySource {
222
222
  */
223
223
  text(): Promise<string>;
224
224
  /**
225
- * Read the body as a Uint8Array buffer
225
+ * Read the body as a Uint8Array buffer.
226
226
  *
227
+ * @param limit - Optional per-read byte limit. When provided, it is the value
228
+ * enforced for both the Content-Length pre-check and the incremental
229
+ * streaming check (taking precedence over the source's construction-time
230
+ * limit); when omitted, the construction-time limit governs. See
231
+ * docs/RFC/request-data/017-body-source-limit-propagation.md.
227
232
  * @returns Promise resolving to the body as Uint8Array
228
233
  * @throws Error if body has already been consumed
229
234
  */
230
- buffer(): Promise<Uint8Array>;
235
+ buffer(limit?: number): Promise<Uint8Array>;
231
236
  /**
232
237
  * Read the body as JSON
233
238
  *
@@ -279,6 +284,130 @@ interface BodySourceOptions {
279
284
  encoding?: 'utf-8' | 'utf8' | 'ascii' | 'latin1' | 'iso-8859-1' | 'utf-16le' | 'utf-16be';
280
285
  }
281
286
 
287
+ /**
288
+ * @nextrush/types - Response Streaming Contracts
289
+ *
290
+ * Pure type contracts for runtime-agnostic response streaming (text/SSE/NDJSON).
291
+ * The implementation lives in `@nextrush/stream`; these interfaces live here so
292
+ * the `Context` interface can reference them without `@nextrush/types` depending
293
+ * on any higher-tier package.
294
+ *
295
+ * See `docs/RFC/request-data/003-stream.md`.
296
+ *
297
+ * @packageDocumentation
298
+ */
299
+ /**
300
+ * A single Server-Sent Event.
301
+ *
302
+ * @remarks
303
+ * `data` is the only required field. Objects are serialized with `JSON.stringify`;
304
+ * strings are sent verbatim. The framework handles all wire-format framing
305
+ * (multi-line `data:` escaping, `event:`/`id:`/`retry:` fields, terminating blank line).
306
+ */
307
+ interface SSEEvent {
308
+ /** Event payload. Objects are JSON-serialized; strings are sent as-is. */
309
+ data: unknown;
310
+ /** SSE `event:` field — lets the client dispatch to a named listener. */
311
+ event?: string;
312
+ /** SSE `id:` field — enables client auto-reconnect from the last-seen id. */
313
+ id?: string;
314
+ /** SSE `retry:` field, in milliseconds — client reconnection delay hint. */
315
+ retry?: number;
316
+ }
317
+ /**
318
+ * Capabilities shared by every response-stream writer, regardless of protocol.
319
+ *
320
+ * @remarks
321
+ * Concrete writers ({@link TextStreamWriter}, {@link SSEStreamWriter},
322
+ * {@link NDJSONStreamWriter}) add a protocol-specific `write()` on top of this.
323
+ */
324
+ interface BaseStreamWriter {
325
+ /**
326
+ * `true` once the client has disconnected.
327
+ *
328
+ * @remarks
329
+ * Use as a loop guard for long-running streams. Writing after this is `true`
330
+ * throws `StreamAbortedError`.
331
+ */
332
+ readonly aborted: boolean;
333
+ /**
334
+ * Fires when the client disconnects.
335
+ *
336
+ * @remarks
337
+ * Pass this straight into an upstream SDK's own abort option
338
+ * (e.g. `{ signal: writer.signal }`) so the upstream call is cancelled the
339
+ * instant the client goes away.
340
+ */
341
+ readonly signal: AbortSignal;
342
+ /**
343
+ * Register a cleanup callback invoked once when the client disconnects.
344
+ *
345
+ * @param fn - Cleanup callback (e.g. abort an upstream request, release a resource).
346
+ */
347
+ onAbort(fn: () => void): void;
348
+ }
349
+ /**
350
+ * Source shapes accepted by a writer's `consume()` method.
351
+ *
352
+ * @remarks
353
+ * Normalized internally to a single async-iterator path — callers never branch
354
+ * on the concrete shape.
355
+ */
356
+ type StreamSource<T> = AsyncIterable<T> | ReadableStream<T>;
357
+ /**
358
+ * Writer for a raw text/byte stream (`ctx.stream()`).
359
+ */
360
+ interface TextStreamWriter extends BaseStreamWriter {
361
+ /**
362
+ * Write text or bytes.
363
+ * @throws StreamAbortedError if the client has already disconnected.
364
+ */
365
+ write(chunk: string | Uint8Array): Promise<void>;
366
+ /**
367
+ * Consume an existing producer as this response's body.
368
+ * @param source - An `AsyncIterable` or Web `ReadableStream` of strings/bytes.
369
+ * @throws StreamAbortedError if the client disconnects mid-consume.
370
+ */
371
+ consume(source: StreamSource<string | Uint8Array>): Promise<void>;
372
+ }
373
+ /**
374
+ * Writer for a Server-Sent Events stream (`ctx.sse()`).
375
+ */
376
+ interface SSEStreamWriter extends BaseStreamWriter {
377
+ /**
378
+ * Write one Server-Sent Event. The framework handles all wire-format framing.
379
+ * @throws StreamAbortedError if the client has already disconnected.
380
+ */
381
+ write(event: SSEEvent): Promise<void>;
382
+ /**
383
+ * Consume an existing producer; each yielded chunk is sent as `{ data: chunk }`.
384
+ * @throws StreamAbortedError if the client disconnects mid-consume.
385
+ */
386
+ consume(source: StreamSource<string | Uint8Array>): Promise<void>;
387
+ }
388
+ /**
389
+ * Writer for a newline-delimited JSON stream (`ctx.ndjson()`).
390
+ */
391
+ interface NDJSONStreamWriter extends BaseStreamWriter {
392
+ /**
393
+ * Write one JSON-serializable value as a single NDJSON line.
394
+ * @throws StreamAbortedError if the client has already disconnected.
395
+ */
396
+ write(value: unknown): Promise<void>;
397
+ /**
398
+ * Consume an existing producer of JSON-serializable values, one line each.
399
+ * @throws StreamAbortedError if the client disconnects mid-consume.
400
+ */
401
+ consume(source: StreamSource<unknown>): Promise<void>;
402
+ }
403
+ /**
404
+ * A streaming callback that receives a protocol-specific writer.
405
+ *
406
+ * @remarks
407
+ * The connection auto-closes when this callback resolves.
408
+ */
409
+ type StreamRun<W extends BaseStreamWriter> = (writer: W) => Promise<void>;
410
+
282
411
  /**
283
412
  * @nextrush/types - Context Type Definitions
284
413
  *
@@ -600,6 +729,72 @@ interface Context {
600
729
  * ```
601
730
  */
602
731
  readonly bodySource: BodySource;
732
+ /**
733
+ * Abort signal that fires when the client disconnects mid-response.
734
+ *
735
+ * @internal
736
+ * @remarks
737
+ * Adapter-level primitive consumed by `@nextrush/stream`. Handlers should use
738
+ * `writer.signal` inside `ctx.stream()`/`ctx.sse()`/`ctx.ndjson()` instead of
739
+ * accessing this directly. Synthesized on Node from response `close`/request
740
+ * `aborted`; passed through natively on Bun/Deno/Edge from `Request.signal`.
741
+ */
742
+ readonly signal: AbortSignal;
743
+ /**
744
+ * Send a raw byte stream as the response body.
745
+ *
746
+ * @internal
747
+ * @remarks
748
+ * Adapter-level transport primitive consumed by `@nextrush/stream`. Not
749
+ * intended for direct handler use — use `ctx.stream()`/`ctx.sse()`/
750
+ * `ctx.ndjson()`. Resolves when the stream is fully flushed (Node) or once the
751
+ * response body is wired (Bun/Deno/Edge).
752
+ *
753
+ * @param source - A Web `ReadableStream` of bytes to stream to the client.
754
+ */
755
+ sendStream(source: ReadableStream<Uint8Array>): Promise<void>;
756
+ /**
757
+ * Stream a raw text/byte response. The connection closes automatically when
758
+ * the callback resolves.
759
+ *
760
+ * @param run - Callback that receives a {@link TextStreamWriter}.
761
+ *
762
+ * @example
763
+ * ```typescript
764
+ * await ctx.stream(async (writer) => {
765
+ * await writer.write('Loading...\n');
766
+ * await writer.write('Done.\n');
767
+ * });
768
+ * ```
769
+ */
770
+ stream(run: StreamRun<TextStreamWriter>): Promise<void>;
771
+ /**
772
+ * Stream a Server-Sent Events (`text/event-stream`) response.
773
+ *
774
+ * @param run - Callback that receives an {@link SSEStreamWriter}.
775
+ *
776
+ * @example
777
+ * ```typescript
778
+ * await ctx.sse(async (writer) => {
779
+ * for await (const token of llm) await writer.write({ data: token });
780
+ * });
781
+ * ```
782
+ */
783
+ sse(run: StreamRun<SSEStreamWriter>): Promise<void>;
784
+ /**
785
+ * Stream a newline-delimited JSON (`application/x-ndjson`) response.
786
+ *
787
+ * @param run - Callback that receives an {@link NDJSONStreamWriter}.
788
+ *
789
+ * @example
790
+ * ```typescript
791
+ * await ctx.ndjson(async (writer) => {
792
+ * await writer.write({ step: 'search' });
793
+ * await writer.write({ step: 'done' });
794
+ * });
795
+ * ```
796
+ */
797
+ ndjson(run: StreamRun<NDJSONStreamWriter>): Promise<void>;
603
798
  }
604
799
  /**
605
800
  * Next function type (traditional Koa-style)
@@ -645,133 +840,579 @@ interface ContextOptions {
645
840
  }
646
841
 
647
842
  /**
648
- * @nextrush/types - Plugin Type Definitions
843
+ * @nextrush/types - Adapter Context Contracts (F-13)
844
+ *
845
+ * The concrete adapter context classes expose transport/lifecycle primitives
846
+ * that are not part of the runtime-neutral {@link Context} contract:
847
+ * `markResponded()` (all adapters), and `getResponse()` / `waitUntil()` / `env`
848
+ * (the Web/fetch adapters). Consumers typed as `Context` cannot see these, so
849
+ * `@nextrush/stream` and the adapters resort to structural access.
649
850
  *
650
- * Plugins are the extension mechanism for NextRush.
651
- * They allow adding functionality without bloating the core.
851
+ * These interfaces model that surface **additively** — they extend `Context`,
852
+ * they never weaken it. Adapters (and `@nextrush/stream`) can depend on these
853
+ * instead of coupling to the concrete context classes.
652
854
  *
653
855
  * @packageDocumentation
654
856
  */
655
857
 
656
858
  /**
657
- * Minimal Application interface for plugin registration
658
- * The full Application class is in @nextrush/core
859
+ * A {@link Context} plus the lifecycle primitive every adapter exposes.
860
+ *
861
+ * @remarks
862
+ * `markResponded()` lets the transport/streaming layer flag that a response has
863
+ * been committed out-of-band (e.g. after wiring a stream), so the adapter's
864
+ * "not responded" fallback path is skipped.
659
865
  */
660
- interface ApplicationLike {
866
+ interface AdapterContext extends Context {
661
867
  /**
662
- * Register middleware
868
+ * Mark the response as already sent.
869
+ *
870
+ * @remarks
871
+ * Used by streaming and adapter transport code when a response is committed
872
+ * without going through `json()`/`send()`/`html()`/`redirect()`.
663
873
  */
664
- use(middleware: Middleware): this;
874
+ markResponded(): void;
875
+ }
876
+ /**
877
+ * An {@link AdapterContext} for Web/fetch runtimes (Bun, Deno, Edge).
878
+ *
879
+ * @remarks
880
+ * Adds the fetch-specific surface: `getResponse()` (materialize the built
881
+ * `Response`), and the optional edge primitives `waitUntil()` and `env`
882
+ * (platform bindings — reachable only on runtimes that provide them, e.g.
883
+ * Cloudflare Workers).
884
+ */
885
+ interface FetchContext extends AdapterContext {
665
886
  /**
666
- * Get installed plugin by name
887
+ * Build and return the Web `Response` accumulated by the context.
888
+ *
889
+ * @returns The `Response` to hand back to the runtime.
667
890
  */
668
- getPlugin<T extends Plugin>(name: string): T | undefined;
891
+ getResponse(): Response;
892
+ /**
893
+ * Extend the request's lifetime for fire-and-forget work (logging,
894
+ * analytics, cache writes). Only present on runtimes that expose an
895
+ * execution context (e.g. Cloudflare Workers, Vercel Edge).
896
+ *
897
+ * @param promise - Work that should outlive the response.
898
+ */
899
+ waitUntil?(promise: Promise<unknown>): void;
900
+ /**
901
+ * Platform bindings passed by the runtime (e.g. Cloudflare `env`: KV, D1, R2,
902
+ * Durable Objects, secrets). Typed as `unknown` at the contract level; a
903
+ * concrete adapter may narrow it via a generic.
904
+ */
905
+ env?: unknown;
669
906
  }
670
907
  /**
671
- * Plugin interface for extending NextRush
908
+ * The shape of an adapter's context factory.
672
909
  *
673
- * Plugins are installed via app.plugin() and can:
674
- * - Add middleware
675
- * - Extend context
676
- * - Add lifecycle hooks
910
+ * @remarks
911
+ * Every adapter builds the per-request context through a factory that takes
912
+ * platform-specific inputs (e.g. Node's `(req, res, options)`, a fetch runtime's
913
+ * `(request, options)`) and returns an {@link AdapterContext} over the shared
914
+ * {@link Context} contract. This type formalizes that invariant — "adapters build
915
+ * `Context` via a factory and run `app.callback()`" — at the type level, generic
916
+ * over the platform input tuple so server- and fetch-style factories both conform.
917
+ *
918
+ * @typeParam Args - The platform-specific factory argument tuple.
919
+ * @typeParam Ctx - The concrete {@link AdapterContext} the factory returns.
677
920
  *
678
921
  * @example
679
922
  * ```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
- * }
923
+ * // Node adapter
924
+ * const _factory = createNodeContext satisfies AdapterContextFactory<
925
+ * [IncomingMessage, ServerResponse, NodeContextOptions?],
926
+ * NodeContext
927
+ * >;
692
928
  * ```
693
929
  */
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;
930
+ type AdapterContextFactory<Args extends readonly unknown[], Ctx extends AdapterContext = AdapterContext> = (...args: Args) => Ctx;
931
+
932
+ /**
933
+ * @nextrush/types - Logger contract
934
+ *
935
+ * The structured logging interface shared by the application, adapters, and
936
+ * extensions. Lives here (the lowest package) so any layer can depend on the
937
+ * contract without depending on `@nextrush/core`.
938
+ *
939
+ * @packageDocumentation
940
+ */
941
+ /**
942
+ * Pluggable logger interface.
943
+ *
944
+ * Pass `console` for quick development logging, or a structured logger
945
+ * (pino, winston, `@nextrush/logger`) in production.
946
+ */
947
+ interface Logger {
948
+ error(...args: unknown[]): void;
949
+ warn(...args: unknown[]): void;
950
+ info(...args: unknown[]): void;
951
+ debug(...args: unknown[]): void;
952
+ }
953
+
954
+ /**
955
+ * @nextrush/types - Adapter Conformance Contract (F-01)
956
+ *
957
+ * A light, additive compile-time contract that server- and fetch-style adapters
958
+ * `satisfies` at export time, so the *shape* of `serve`/`createHandler`/
959
+ * `createFetchHandler` and the `ServerHandle` cannot drift silently across
960
+ * adapters. This is the cheap secondary guard; a shared behavioral conformance
961
+ * suite is the primary one (see the audit's Adapter Conformance Specification).
962
+ *
963
+ * The contract is generic over the concrete application type because
964
+ * `@nextrush/types` sits below `@nextrush/core` in the package hierarchy and
965
+ * must not import `Application`. Adapters bind `App = Application` when they
966
+ * `satisfies` these shapes.
967
+ *
968
+ * @packageDocumentation
969
+ */
970
+
971
+ /**
972
+ * The single canonical network address a server adapter binds to.
973
+ *
974
+ * @remarks
975
+ * Replaces the per-adapter `{ port, host }` vs `{ port, hostname }` divergence
976
+ * (audit F-05). `host` is the canonical key; adapters normalize internally.
977
+ */
978
+ interface ServerAddress {
979
+ /** Port the server is listening on. */
980
+ readonly port: number;
981
+ /** Host/address the server is bound to (canonical key). */
982
+ readonly host: string;
705
983
  /**
706
- * Install plugin into application
707
- * Called when app.plugin() is invoked
984
+ * Host alias.
708
985
  *
709
- * @param app - Application instance
986
+ * @deprecated Use {@link ServerAddress.host}. Retained so Bun/Deno, which
987
+ * historically returned `{ port, hostname }`, keep working during migration.
710
988
  */
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>;
989
+ readonly hostname?: string;
717
990
  }
718
991
  /**
719
- * Extended plugin interface with lifecycle hooks
720
- * For advanced plugins that need deeper integration
992
+ * Options common to server-style handler factories (node/bun/deno).
993
+ *
994
+ * @remarks
995
+ * Intentionally light. Individual adapters may accept a superset; this pins the
996
+ * shared, portable subset.
997
+ */
998
+ interface HandlerOptions {
999
+ /** Logger for adapter diagnostics. Defaults to the application logger. */
1000
+ logger?: Logger;
1001
+ /** Per-request timeout in milliseconds. */
1002
+ timeout?: number;
1003
+ }
1004
+ /**
1005
+ * Options for fetch-style handler factories (edge).
721
1006
  */
722
- interface PluginWithHooks extends Plugin {
1007
+ interface FetchHandlerOptions {
723
1008
  /**
724
- * Called before each request
1009
+ * Per-request timeout in milliseconds. Individual adapters may apply their
1010
+ * own default when omitted (e.g. the edge adapter's `DEFAULT_EDGE_TIMEOUT_MS`,
1011
+ * F-07/ADR-0010) rather than leaving the timeout unenforced — this shared
1012
+ * type only pins the option's shape, not a specific default.
725
1013
  */
726
- onRequest?(ctx: Context): void | Promise<void>;
1014
+ timeout?: number;
727
1015
  /**
728
- * Called after each response
1016
+ * Custom error handler. Receives the error and the fetch context and returns
1017
+ * the `Response` to send.
729
1018
  */
730
- onResponse?(ctx: Context): void | Promise<void>;
1019
+ onError?: (error: Error, ctx: FetchContext) => Response | Promise<Response>;
1020
+ }
1021
+ /**
1022
+ * A running server instance with one canonical address shape and async close.
1023
+ *
1024
+ * @remarks
1025
+ * The canonical handle every server adapter's `serve()` resolves to. Concrete
1026
+ * adapters may expose additional fields (e.g. the raw `server`) on a subtype.
1027
+ */
1028
+ interface ServerHandle {
1029
+ /** The address the server is bound to. */
1030
+ address(): ServerAddress;
1031
+ /** Stop the server, draining in-flight requests. */
1032
+ close(): Promise<void>;
1033
+ }
1034
+ /**
1035
+ * A fetch handler: maps a Web `Request` (plus an optional runtime execution
1036
+ * context) to a `Response`.
1037
+ *
1038
+ * @typeParam Exec - The runtime execution-context type (e.g. an edge
1039
+ * `waitUntil` context). Defaults to `unknown`.
1040
+ */
1041
+ type FetchHandler<Exec = unknown> = (request: Request, executionContext?: Exec) => Response | Promise<Response>;
1042
+ /**
1043
+ * Contract for server-style adapters (node/bun/deno).
1044
+ *
1045
+ * @typeParam App - The application type (adapters bind `Application`).
1046
+ * @typeParam Opts - The adapter's `serve` options type.
1047
+ * @typeParam Instance - The `serve` return type (a {@link ServerHandle} subtype).
1048
+ */
1049
+ interface ServerAdapter<App = unknown, Opts = unknown, Instance extends ServerHandle = ServerHandle> {
1050
+ /** Start a server for the application. */
1051
+ serve(app: App, options?: Opts): Promise<Instance>;
1052
+ /** Build a request handler for the application without owning the server. */
1053
+ createHandler(app: App, options?: HandlerOptions): unknown;
1054
+ }
1055
+ /**
1056
+ * Contract for fetch-style adapters (edge).
1057
+ *
1058
+ * @typeParam App - The application type (adapters bind `Application`).
1059
+ */
1060
+ interface FetchAdapter<App = unknown, Exec = unknown> {
1061
+ /** Build a fetch handler for the application. */
1062
+ createFetchHandler(app: App, options?: FetchHandlerOptions): FetchHandler<Exec>;
1063
+ }
1064
+
1065
+ /**
1066
+ * @nextrush/types - Dependency Injection contract
1067
+ *
1068
+ * The container contract shared by `@nextrush/di` (implementation),
1069
+ * `@nextrush/core` (each app owns one), and consumers. Lives here (the lowest
1070
+ * package) so any layer can reference the contract without depending on `di`.
1071
+ *
1072
+ * @packageDocumentation
1073
+ */
1074
+ /** Constructor type for class-based tokens. */
1075
+ type Constructor<T = unknown> = new (...args: unknown[]) => T;
1076
+ /** Token used to identify a dependency — a class, string, or symbol. */
1077
+ type Token<T = unknown> = Constructor<T> | string | symbol;
1078
+ /** Provider that uses a class constructor. */
1079
+ interface ClassProvider<T> {
1080
+ useClass: Constructor<T>;
1081
+ }
1082
+ /** Provider that uses a factory function (optionally with injected deps). */
1083
+ interface FactoryProvider<T> {
1084
+ useFactory: (...args: unknown[]) => T | Promise<T>;
1085
+ inject?: Token[];
1086
+ }
1087
+ /** Provider that uses a constant value. */
1088
+ interface ValueProvider<T> {
1089
+ useValue: T;
1090
+ }
1091
+ /** Union of all provider kinds. */
1092
+ type Provider<T> = ClassProvider<T> | FactoryProvider<T> | ValueProvider<T>;
1093
+ /**
1094
+ * Lifecycle scope for registered services.
1095
+ *
1096
+ * - `'singleton'` — one shared instance for the process lifetime.
1097
+ * - `'transient'` — a fresh instance on every resolve.
1098
+ * - `'request'` — one instance per request, shared within that request. Backed
1099
+ * by a per-request child container; see RFC-NEXTRUSH-REQUEST-SCOPE.
1100
+ */
1101
+ type Scope = 'singleton' | 'transient' | 'request';
1102
+ /** Options for service registration. */
1103
+ interface ServiceOptions {
1104
+ scope?: Scope;
1105
+ }
1106
+ /** Registration options for `Container.register()`. */
1107
+ interface RegisterOptions {
1108
+ /** Lifecycle scope — defaults to 'transient' if not specified. */
1109
+ scope?: Scope;
1110
+ }
1111
+ /**
1112
+ * Dependency injection container contract.
1113
+ *
1114
+ * Each NextRush {@link Application} may own one (per-app, not a global
1115
+ * singleton). `@nextrush/di` provides the implementation.
1116
+ */
1117
+ interface Container {
1118
+ /** Register a dependency. */
1119
+ register<T>(token: Token<T>, provider: Provider<T>, options?: RegisterOptions): void;
1120
+ /** Resolve a dependency synchronously. */
1121
+ resolve<T>(token: Token<T>): T;
1122
+ /** Resolve a dependency that may have been registered with an async factory. */
1123
+ resolveAsync<T>(token: Token<T>): Promise<T>;
1124
+ /** Bootstrap all factory providers (awaits async factories, caches results). */
1125
+ bootstrap(): Promise<void>;
1126
+ /** Resolve all dependencies registered under a token. */
1127
+ resolveAll<T>(token: Token<T>): T[];
1128
+ /** Check if a token is registered. */
1129
+ isRegistered<T>(token: Token<T>): boolean;
1130
+ /** Clear all registered instances (testing). */
1131
+ clearInstances(): void;
1132
+ /** Reset the container completely (testing). */
1133
+ reset(): void;
1134
+ /** Create a child container with isolated scope. */
1135
+ createChild(): Container;
1136
+ }
1137
+
1138
+ /**
1139
+ * @nextrush/types - Extension contract
1140
+ *
1141
+ * Extensions are the rare (~0.1%) long-lived runtime services that must attach
1142
+ * state to the app, run async boot, and/or tear down on shutdown — an event bus,
1143
+ * a database pool, a websocket attach. Most framework features are **middleware**
1144
+ * (`app.use`) or plain **registrar** functions, not Extensions.
1145
+ *
1146
+ * See docs/RFC/class-runtime/005-plugin-system.md.
1147
+ *
1148
+ * @packageDocumentation
1149
+ */
1150
+
1151
+ /**
1152
+ * The subset of `Application` an {@link Extension} may use during `setup()`.
1153
+ *
1154
+ * Kept structural (not the concrete `Application`) so {@link ExtensionContext}
1155
+ * can live in `@nextrush/types` without importing `@nextrush/core`. The
1156
+ * `Application` class implements this interface. Routing methods are added here
1157
+ * when the app-owned router lands (RFC §11.1).
1158
+ */
1159
+ interface ExtensionHost {
1160
+ /** Register middleware on the application. */
1161
+ use(middleware: Middleware): this;
1162
+ /** Whether a decoration already occupies `name`. */
1163
+ hasDecorator(name: string): boolean;
1164
+ }
1165
+ /**
1166
+ * The argument passed to {@link Extension.setup}.
1167
+ *
1168
+ * A context object rather than the bare app, so future fields (`runtime`,
1169
+ * `container`, `config`, …) are **additive** and never break existing
1170
+ * extensions — the same reason VS Code uses `activate(context)`.
1171
+ */
1172
+ interface ExtensionContext {
1173
+ /** The application instance — add middleware, read decorations. */
1174
+ readonly app: ExtensionHost;
1175
+ /** The app logger (structured, pluggable). */
1176
+ readonly logger: Logger;
731
1177
  /**
732
- * Called when an error occurs
1178
+ * The app's DI container, if one was configured (per-app, not a global
1179
+ * singleton). Present when the app was created via `nextrush/class` or with
1180
+ * `createApp({ container })`; `undefined` for functional, DI-free apps.
733
1181
  */
734
- onError?(error: Error, ctx: Context): void | Promise<void>;
1182
+ readonly container?: Container;
1183
+ /** Environment mode. */
1184
+ readonly env: 'development' | 'production' | 'test';
1185
+ /** This extension's own name (for scoped diagnostics). */
1186
+ readonly name: string;
735
1187
  /**
736
- * Extend the context object
737
- * Add custom properties or methods
1188
+ * Attach a value to the app under `name`. The extension-author primitive for
1189
+ * exposing app-level surface (e.g. `app.events`). Throws if `name` is already
1190
+ * decorated or collides with a core `Application` member.
738
1191
  */
739
- extendContext?(ctx: Context): void;
1192
+ decorate(name: string, value: unknown): void;
740
1193
  }
741
1194
  /**
742
- * Plugin factory function type
743
- * Allows creating plugins with options
1195
+ * A NextRush Extension.
1196
+ *
1197
+ * Registered via `app.extend()` (queues) and booted once at `app.ready()`
1198
+ * (runs `setup` in registration order). Torn down at `app.close()`.
744
1199
  *
745
1200
  * @example
746
1201
  * ```typescript
747
- * const logger = createLoggerPlugin({ level: 'info' });
748
- * app.plugin(logger);
1202
+ * export function events(): Extension {
1203
+ * const emitter = new EventEmitter();
1204
+ * return {
1205
+ * name: 'events',
1206
+ * setup(ctx) { ctx.decorate('events', emitter); },
1207
+ * destroy() { emitter.clear(); },
1208
+ * };
1209
+ * }
749
1210
  * ```
750
1211
  */
751
- type PluginFactory<TOptions = unknown> = (options?: TOptions) => Plugin;
752
1212
  /**
753
- * Plugin metadata for registration and discovery
1213
+ * @template TDecorated - Shape decorated onto the app by this extension's
1214
+ * `setup()`. Phantom — never read at runtime, only carried through the type
1215
+ * system so `Application.extend()` can return `this & TDecorated`, giving
1216
+ * `app.<decoratedProperty>` static inference with zero `declare module`
1217
+ * augmentation. Defaults to `{}` so plain, untyped `Extension` usages
1218
+ * (the common case for internal/test extensions) are unaffected.
1219
+ *
1220
+ * @remarks
1221
+ * Two things to know before relying on this:
1222
+ *
1223
+ * - **TypeScript trusts `TDecorated`, it never verifies it.** Nothing checks
1224
+ * that the declared shape actually matches what `setup()` calls
1225
+ * `ctx.decorate()` with — `Extension<{ foo: Foo }>` whose `setup()` never
1226
+ * decorates `foo` still typechecks, and `app.foo` will be `undefined` at
1227
+ * runtime with no warning. Keep the generic and the `decorate()` call in
1228
+ * sync by hand.
1229
+ * - **The inferred type is lost on `let` reassignment.** Chain in one
1230
+ * expression (`const app = createApp().extend(x)`), not
1231
+ * `let app = createApp(); app = app.extend(x);` — TypeScript widens the
1232
+ * reassignment to the `let` binding's originally-inferred type, silently
1233
+ * dropping `TDecorated`. Every example below chains for this reason.
1234
+ *
1235
+ * @example
1236
+ * ```typescript
1237
+ * function events<T extends EventMap>(): Extension<{ events: EventEmitter<T> }> {
1238
+ * return {
1239
+ * name: 'events',
1240
+ * setup(ctx) { ctx.decorate('events', new EventEmitter<T>()); },
1241
+ * };
1242
+ * }
1243
+ *
1244
+ * const app = createApp().extend(events<MyEvents>());
1245
+ * app.events.emit('user:created', { id: '1' }); // inferred, no cast
1246
+ * ```
754
1247
  */
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[];
1248
+ interface Extension<TDecorated = Record<string, never>> {
1249
+ /** Unique name — used for collision detection, dependency assertion, diagnostics. */
1250
+ readonly name: string;
1251
+ /**
1252
+ * Names of other extensions that MUST already be registered before this one.
1253
+ * Asserted at `app.ready()` in registration order — not auto-sorted.
1254
+ */
1255
+ readonly needs?: readonly string[];
1256
+ /** Set up the extension. Runs once, at `app.ready()`, in registration order. */
1257
+ setup(ctx: ExtensionContext): void | Promise<void>;
1258
+ /** Tear down on `app.close()`. Runs in reverse registration order. */
1259
+ destroy?(): void | Promise<void>;
1260
+ /**
1261
+ * Phantom marker carrying `TDecorated` through the type system. Never set
1262
+ * at runtime — `TDecorated` has no inhabitant, so no implementation ever
1263
+ * assigns this. Purely what lets `extend()` infer its return type.
1264
+ */
1265
+ readonly __decorated?: TDecorated;
1266
+ }
1267
+
1268
+ /**
1269
+ * @nextrush/types - Standard Schema Contract
1270
+ *
1271
+ * Structural copy of the `@standard-schema/spec` v1 interface
1272
+ * (https://github.com/standard-schema/standard-schema, MIT licensed), vendored
1273
+ * as a type so NextRush stays dependency-free. It lives in `@nextrush/types`
1274
+ * (not in any single consumer) because it is now a shared contract: request
1275
+ * validation, route metadata, and OpenAPI generation all reference it.
1276
+ *
1277
+ * Because TypeScript is structural, any schema implementing Standard Schema —
1278
+ * Zod 3.24+, Valibot 1.0+, ArkType 2.0+, and others — satisfies this interface
1279
+ * without an adapter.
1280
+ *
1281
+ * @packageDocumentation
1282
+ */
1283
+ /**
1284
+ * A schema that exposes the Standard Schema v1 contract on its `~standard`
1285
+ * property. This is the only surface NextRush depends on.
1286
+ */
1287
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
1288
+ readonly '~standard': StandardSchemaProps<Input, Output>;
1289
+ }
1290
+ /** The `~standard` property contract. */
1291
+ interface StandardSchemaProps<Input = unknown, Output = Input> {
1292
+ /** Spec version — always `1`. */
1293
+ readonly version: 1;
1294
+ /** Identifier of the schema library (e.g. `'zod'`, `'valibot'`). */
1295
+ readonly vendor: string;
1296
+ /** Validates and (optionally) coerces `value`; may be sync or async. */
1297
+ readonly validate: (value: unknown) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;
1298
+ /** Phantom types for input/output inference; never present at runtime. */
1299
+ readonly types?: {
1300
+ readonly input: Input;
1301
+ readonly output: Output;
1302
+ } | undefined;
1303
+ }
1304
+ /** Result of a validation: either a success carrying `value`, or a failure carrying `issues`. */
1305
+ type StandardSchemaResult<Output> = {
1306
+ readonly value: Output;
1307
+ readonly issues?: undefined;
1308
+ } | {
1309
+ readonly issues: readonly StandardSchemaIssue[];
1310
+ };
1311
+ /** A single validation issue reported by a schema. */
1312
+ interface StandardSchemaIssue {
1313
+ /** Human-readable message. */
1314
+ readonly message: string;
1315
+ /** Path to the offending value; segments are keys or `{ key }` objects. */
1316
+ readonly path?: readonly (PropertyKey | StandardSchemaPathSegment)[] | undefined;
1317
+ }
1318
+ /** A structured path segment. */
1319
+ interface StandardSchemaPathSegment {
1320
+ readonly key: PropertyKey;
1321
+ }
1322
+ /** Infer the validated output type of a Standard Schema. */
1323
+ type InferOutput<S extends StandardSchemaV1> = NonNullable<S['~standard']['types']>['output'];
1324
+
1325
+ /**
1326
+ * @nextrush/types - Route Metadata Contracts
1327
+ *
1328
+ * `RouteDefinition` is the single source of truth for what a route *is and
1329
+ * means*. The router collects it once at registration; renderers
1330
+ * (`@nextrush/openapi`, and later SDK/Postman/RPC generators) read it. The
1331
+ * router itself is renderer-agnostic — it stores raw schemas and generic
1332
+ * metadata, never OpenAPI shapes.
1333
+ *
1334
+ * See docs/RFC/request-data/002-route-metadata.md.
1335
+ *
1336
+ * @packageDocumentation
1337
+ */
1338
+
1339
+ /**
1340
+ * Well-known symbol by which anything — a middleware function (e.g. `validate()`)
1341
+ * or a pure marker (e.g. `endpoint()`) — contributes metadata to the route it is
1342
+ * registered on. The router reads this symbol off every route entry at
1343
+ * registration and merges the contributions.
1344
+ *
1345
+ * `Symbol.for` (global registry) is used so the identity holds even across
1346
+ * duplicate package instances.
1347
+ */
1348
+ declare const ROUTE_METADATA: unique symbol;
1349
+ /** A partial metadata contribution; contributions merge in registration order. */
1350
+ type MetadataContribution = Partial<RouteMetadata>;
1351
+ /**
1352
+ * A pure-metadata marker carried in a route's argument list. Not a middleware —
1353
+ * it has no runtime behavior and never enters the executed chain.
1354
+ */
1355
+ interface RouteMetaMarker {
1356
+ readonly [ROUTE_METADATA]: MetadataContribution;
1357
+ }
1358
+ /** An entry in a route's argument list: behavior (`Middleware`) or pure metadata (a marker). */
1359
+ type RouteEntry = Middleware | RouteMetaMarker;
1360
+ /**
1361
+ * Generic, renderer-agnostic description of a route's request/response shapes
1362
+ * and documentation facts. Every field is a fact any renderer needs; no
1363
+ * renderer-specific artifacts (e.g. OpenAPI `operationId`) live here.
1364
+ */
1365
+ interface RouteMetadata {
1366
+ /** Request shapes — contributed by `validate()`; never hand-written on the golden path. */
1367
+ readonly request?: {
1368
+ readonly body?: StandardSchemaV1;
1369
+ readonly query?: StandardSchemaV1;
1370
+ readonly params?: StandardSchemaV1;
1371
+ };
1372
+ /** Response shapes by numeric status — contributed by `endpoint()`. */
1373
+ readonly responses?: Readonly<Record<number, StandardSchemaV1>>;
1374
+ readonly summary?: string;
1375
+ readonly description?: string;
1376
+ readonly tags?: readonly string[];
1377
+ readonly deprecated?: boolean;
1378
+ /** Cross-renderer intent — an `'internal'` route is excluded from public specs/SDKs. */
1379
+ readonly visibility?: 'public' | 'internal';
1380
+ }
1381
+ /**
1382
+ * The canonical description of a registered endpoint, produced by the router
1383
+ * and consumed by renderers.
1384
+ */
1385
+ interface RouteDefinition {
1386
+ /**
1387
+ * Canonical route key — `${METHOD} ${pathPattern}` (e.g. `"GET /users/:id"`),
1388
+ * the key the router uses internally. Deterministic and stable across
1389
+ * restarts, but it encodes the path, so it changes if the path or a param
1390
+ * name changes — a key, not a rename-stable opaque id.
1391
+ */
1392
+ readonly key: string;
1393
+ readonly method: HttpMethod;
1394
+ /** Full, mount/prefix-resolved path pattern. */
1395
+ readonly path: string;
1396
+ readonly metadata?: RouteMetadata;
1397
+ /**
1398
+ * `true` when this entry represents an any-method route (registered via
1399
+ * `router.all()` / `@All()`), matching every standard HTTP method under a
1400
+ * single introspection row rather than one row per method (T016). `method`
1401
+ * is still a real `HttpMethod` value for structural compatibility with
1402
+ * existing consumers that read `.method` unconditionally — check this flag
1403
+ * first when the distinction matters (e.g. an OpenAPI renderer expanding
1404
+ * one row into per-verb operations). Absent (`undefined`) for every
1405
+ * ordinary single-method route.
1406
+ */
1407
+ readonly isAnyMethod?: boolean;
768
1408
  }
769
1409
 
770
1410
  /**
771
1411
  * @nextrush/types - Router Type Definitions
772
1412
  *
773
1413
  * Types for the NextRush router system.
774
- * The router uses a radix tree for efficient route matching.
1414
+ * The router matches routes with a segment trie keyed by whole path segments,
1415
+ * giving O(k) lookups where k is the number of path segments.
775
1416
  *
776
1417
  * @packageDocumentation
777
1418
  */
@@ -820,43 +1461,53 @@ interface Router {
820
1461
  /**
821
1462
  * Register a GET route
822
1463
  */
823
- get(path: string, ...handlers: RouteHandler[]): this;
1464
+ get(path: string, ...entries: RouteEntry[]): this;
824
1465
  /**
825
1466
  * Register a POST route
826
1467
  */
827
- post(path: string, ...handlers: RouteHandler[]): this;
1468
+ post(path: string, ...entries: RouteEntry[]): this;
828
1469
  /**
829
1470
  * Register a PUT route
830
1471
  */
831
- put(path: string, ...handlers: RouteHandler[]): this;
1472
+ put(path: string, ...entries: RouteEntry[]): this;
832
1473
  /**
833
1474
  * Register a DELETE route
834
1475
  */
835
- delete(path: string, ...handlers: RouteHandler[]): this;
1476
+ delete(path: string, ...entries: RouteEntry[]): this;
836
1477
  /**
837
1478
  * Register a PATCH route
838
1479
  */
839
- patch(path: string, ...handlers: RouteHandler[]): this;
1480
+ patch(path: string, ...entries: RouteEntry[]): this;
840
1481
  /**
841
1482
  * Register a HEAD route
842
1483
  */
843
- head(path: string, ...handlers: RouteHandler[]): this;
1484
+ head(path: string, ...entries: RouteEntry[]): this;
844
1485
  /**
845
1486
  * Register an OPTIONS route
846
1487
  */
847
- options(path: string, ...handlers: RouteHandler[]): this;
1488
+ options(path: string, ...entries: RouteEntry[]): this;
848
1489
  /**
849
1490
  * Register a route for any HTTP method
850
1491
  */
851
- all(path: string, ...handlers: RouteHandler[]): this;
1492
+ all(path: string, ...entries: RouteEntry[]): this;
852
1493
  /**
853
1494
  * Register a route for specific method
854
1495
  */
855
- route(method: HttpMethod, path: string, ...handlers: RouteHandler[]): this;
1496
+ route(method: HttpMethod, path: string, ...entries: RouteEntry[]): this;
856
1497
  /**
857
1498
  * Mount router middleware
858
1499
  */
859
- use(path: string, router: Router): this;
1500
+ /**
1501
+ * Mount middleware.
1502
+ *
1503
+ * @remarks
1504
+ * Sub-router mounting (`router.use(path, subRouter)`) is a concrete
1505
+ * `@nextrush/router` `Router` capability, not part of this structural
1506
+ * interface — mounting needs internal tree access
1507
+ * (`Router.mount()`/the concrete class's own `use()` overload provide it).
1508
+ * Cross-package router composition goes through `Application.route()`
1509
+ * (`Routable`, `routes()`-only) instead.
1510
+ */
860
1511
  use(middleware: Middleware): this;
861
1512
  /**
862
1513
  * Register a redirect from one path to another
@@ -871,6 +1522,12 @@ interface Router {
871
1522
  * Mount this on the application
872
1523
  */
873
1524
  routes(): Middleware;
1525
+ /**
1526
+ * Return every registered route as a read-only list of RouteDefinitions.
1527
+ * Consumed by renderers (`@nextrush/openapi`, SDK/Postman generators).
1528
+ * Doc-generation-time projection — never called on the request hot path.
1529
+ */
1530
+ getRoutes(): readonly RouteDefinition[];
874
1531
  /**
875
1532
  * Match a route
876
1533
  */
@@ -895,6 +1552,13 @@ interface RouterOptions {
895
1552
  * @default false
896
1553
  */
897
1554
  strict?: boolean;
1555
+ /**
1556
+ * Whether to percent-decode extracted param and wildcard values
1557
+ * (via `decodeURIComponent`). Malformed encoding falls back to the raw value
1558
+ * and never throws. Set to `false` to receive raw, undecoded values.
1559
+ * @default true
1560
+ */
1561
+ decode?: boolean;
898
1562
  }
899
1563
  /**
900
1564
  * Supported route pattern types
@@ -912,4 +1576,4 @@ interface RouteParam {
912
1576
  pattern?: RegExp;
913
1577
  }
914
1578
 
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 };
1579
+ export { type AdapterContext, type AdapterContextFactory, 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 Provider, 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, type SSEEvent, type SSEStreamWriter, type Scope, 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 };