@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/README.md +309 -321
- package/dist/index.d.ts +878 -93
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
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 -
|
|
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
|
-
*
|
|
651
|
-
*
|
|
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
|
-
*
|
|
658
|
-
*
|
|
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
|
|
930
|
+
interface AdapterContext extends Context {
|
|
661
931
|
/**
|
|
662
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
972
|
+
* The shape of an adapter's context factory.
|
|
672
973
|
*
|
|
673
|
-
*
|
|
674
|
-
* -
|
|
675
|
-
* -
|
|
676
|
-
*
|
|
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
|
-
*
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
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
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
-
*
|
|
707
|
-
* Called when app.plugin() is invoked
|
|
1048
|
+
* Host alias.
|
|
708
1049
|
*
|
|
709
|
-
* @
|
|
1050
|
+
* @deprecated Use {@link ServerAddress.host}. Retained so Bun/Deno, which
|
|
1051
|
+
* historically returned `{ port, hostname }`, keep working during migration.
|
|
710
1052
|
*/
|
|
711
|
-
|
|
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
|
-
*
|
|
720
|
-
*
|
|
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
|
|
1071
|
+
interface FetchHandlerOptions {
|
|
723
1072
|
/**
|
|
724
|
-
*
|
|
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
|
-
|
|
1078
|
+
timeout?: number;
|
|
727
1079
|
/**
|
|
728
|
-
*
|
|
1080
|
+
* Custom error handler. Receives the error and the fetch context and returns
|
|
1081
|
+
* the `Response` to send.
|
|
729
1082
|
*/
|
|
730
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
737
|
-
*
|
|
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
|
-
|
|
1256
|
+
decorate(name: string, value: unknown): void;
|
|
740
1257
|
}
|
|
741
1258
|
/**
|
|
742
|
-
*
|
|
743
|
-
*
|
|
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
|
-
*
|
|
748
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
|
|
766
|
-
/**
|
|
767
|
-
|
|
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
|
|
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, ...
|
|
1528
|
+
get(path: string, ...entries: RouteEntry[]): this;
|
|
824
1529
|
/**
|
|
825
1530
|
* Register a POST route
|
|
826
1531
|
*/
|
|
827
|
-
post(path: string, ...
|
|
1532
|
+
post(path: string, ...entries: RouteEntry[]): this;
|
|
828
1533
|
/**
|
|
829
1534
|
* Register a PUT route
|
|
830
1535
|
*/
|
|
831
|
-
put(path: string, ...
|
|
1536
|
+
put(path: string, ...entries: RouteEntry[]): this;
|
|
832
1537
|
/**
|
|
833
1538
|
* Register a DELETE route
|
|
834
1539
|
*/
|
|
835
|
-
delete(path: string, ...
|
|
1540
|
+
delete(path: string, ...entries: RouteEntry[]): this;
|
|
836
1541
|
/**
|
|
837
1542
|
* Register a PATCH route
|
|
838
1543
|
*/
|
|
839
|
-
patch(path: string, ...
|
|
1544
|
+
patch(path: string, ...entries: RouteEntry[]): this;
|
|
840
1545
|
/**
|
|
841
1546
|
* Register a HEAD route
|
|
842
1547
|
*/
|
|
843
|
-
head(path: string, ...
|
|
1548
|
+
head(path: string, ...entries: RouteEntry[]): this;
|
|
844
1549
|
/**
|
|
845
1550
|
* Register an OPTIONS route
|
|
846
1551
|
*/
|
|
847
|
-
options(path: string, ...
|
|
1552
|
+
options(path: string, ...entries: RouteEntry[]): this;
|
|
848
1553
|
/**
|
|
849
1554
|
* Register a route for any HTTP method
|
|
850
1555
|
*/
|
|
851
|
-
all(path: string, ...
|
|
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, ...
|
|
1560
|
+
route(method: HttpMethod, path: string, ...entries: RouteEntry[]): this;
|
|
856
1561
|
/**
|
|
857
1562
|
* Mount router middleware
|
|
858
1563
|
*/
|
|
859
|
-
|
|
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
|
-
|
|
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 };
|