@fishenv/http 0.0.0-stage → 0.0.3

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.
Files changed (106) hide show
  1. package/dist/contract/builder.d.ts +87 -0
  2. package/dist/contract/builder.js +89 -0
  3. package/dist/contract/index.d.ts +4 -0
  4. package/dist/contract/index.js +2 -0
  5. package/dist/contract/types.d.ts +47 -0
  6. package/dist/contract/types.js +0 -0
  7. package/dist/contract/ws.d.ts +178 -0
  8. package/dist/contract/ws.js +164 -0
  9. package/dist/core/compose.d.ts +22 -0
  10. package/dist/core/compose.js +87 -0
  11. package/dist/core/cors.d.ts +38 -0
  12. package/dist/core/cors.js +96 -0
  13. package/dist/core/error.d.ts +73 -0
  14. package/dist/core/error.js +142 -0
  15. package/dist/core/index.d.ts +20 -0
  16. package/dist/core/index.js +23 -0
  17. package/dist/core/matcher.d.ts +17 -0
  18. package/dist/core/matcher.js +110 -0
  19. package/dist/core/request.d.ts +28 -0
  20. package/dist/core/request.js +106 -0
  21. package/dist/core/route-builder.d.ts +58 -0
  22. package/dist/core/route-builder.js +122 -0
  23. package/dist/core/router.d.ts +162 -0
  24. package/dist/core/router.js +327 -0
  25. package/dist/core/transport.d.ts +39 -0
  26. package/dist/core/transport.js +68 -0
  27. package/dist/core/types.d.ts +86 -0
  28. package/dist/core/types.js +0 -0
  29. package/dist/core/upgrade.d.ts +85 -0
  30. package/dist/core/upgrade.js +110 -0
  31. package/dist/core/utils/headers.d.ts +1 -0
  32. package/dist/core/utils/headers.js +2 -0
  33. package/dist/core/utils/url.d.ts +27 -0
  34. package/dist/core/utils/url.js +130 -0
  35. package/dist/core/validation.d.ts +13 -0
  36. package/dist/core/validation.js +24 -0
  37. package/dist/sse/connection.d.ts +30 -0
  38. package/dist/sse/connection.js +104 -0
  39. package/dist/sse/encode.d.ts +10 -0
  40. package/dist/sse/encode.js +27 -0
  41. package/dist/sse/hub.d.ts +33 -0
  42. package/dist/sse/hub.js +115 -0
  43. package/dist/sse/index.d.ts +38 -0
  44. package/dist/sse/index.js +40 -0
  45. package/dist/sse/response.d.ts +10 -0
  46. package/dist/sse/response.js +50 -0
  47. package/dist/sse/route-builder.d.ts +44 -0
  48. package/dist/sse/route-builder.js +65 -0
  49. package/dist/sse/router.d.ts +11 -0
  50. package/dist/sse/router.js +16 -0
  51. package/dist/sse/types.d.ts +43 -0
  52. package/dist/sse/types.js +4 -0
  53. package/dist/static/index.d.ts +27 -0
  54. package/dist/static/index.js +25 -0
  55. package/dist/static/negotiate.d.ts +23 -0
  56. package/dist/static/negotiate.js +85 -0
  57. package/dist/static/options.d.ts +44 -0
  58. package/dist/static/options.js +190 -0
  59. package/dist/static/resolve.d.ts +56 -0
  60. package/dist/static/resolve.js +113 -0
  61. package/dist/static/response.d.ts +53 -0
  62. package/dist/static/response.js +146 -0
  63. package/dist/static/router.d.ts +59 -0
  64. package/dist/static/router.js +217 -0
  65. package/dist/static/rules.d.ts +21 -0
  66. package/dist/static/rules.js +49 -0
  67. package/dist/static/types.d.ts +76 -0
  68. package/dist/static/types.js +0 -0
  69. package/dist/ws/channels/channel.d.ts +175 -0
  70. package/dist/ws/channels/channel.js +125 -0
  71. package/dist/ws/channels/handler.d.ts +33 -0
  72. package/dist/ws/channels/handler.js +486 -0
  73. package/dist/ws/channels/protocol.d.ts +96 -0
  74. package/dist/ws/channels/protocol.js +94 -0
  75. package/dist/ws/channels/target.d.ts +36 -0
  76. package/dist/ws/channels/target.js +56 -0
  77. package/dist/ws/connection.d.ts +56 -0
  78. package/dist/ws/connection.js +172 -0
  79. package/dist/ws/helpers.d.ts +17 -0
  80. package/dist/ws/helpers.js +48 -0
  81. package/dist/ws/hub.d.ts +172 -0
  82. package/dist/ws/hub.js +489 -0
  83. package/dist/ws/index.d.ts +65 -0
  84. package/dist/ws/index.js +66 -0
  85. package/dist/ws/lifecycle.d.ts +21 -0
  86. package/dist/ws/lifecycle.js +97 -0
  87. package/dist/ws/limits.d.ts +32 -0
  88. package/dist/ws/limits.js +67 -0
  89. package/dist/ws/message.d.ts +19 -0
  90. package/dist/ws/message.js +58 -0
  91. package/dist/ws/origin.d.ts +40 -0
  92. package/dist/ws/origin.js +152 -0
  93. package/dist/ws/presence.d.ts +96 -0
  94. package/dist/ws/presence.js +420 -0
  95. package/dist/ws/route-builder.d.ts +136 -0
  96. package/dist/ws/route-builder.js +265 -0
  97. package/dist/ws/router.d.ts +21 -0
  98. package/dist/ws/router.js +36 -0
  99. package/dist/ws/tickets.d.ts +82 -0
  100. package/dist/ws/tickets.js +161 -0
  101. package/dist/ws/typed.d.ts +54 -0
  102. package/dist/ws/typed.js +104 -0
  103. package/dist/ws/types.d.ts +69 -0
  104. package/dist/ws/types.js +70 -0
  105. package/package.json +54 -4
  106. package/README.md +0 -3
@@ -0,0 +1,106 @@
1
+ import { BadRequestError, HttpError } from "./error.js";
2
+ import { validateField, validatePathParams } from "./validation.js";
3
+ /**
4
+ * Parse and validate all input from a request according to the route definition.
5
+ */
6
+ export async function parseInput(req, def, rawParams) {
7
+ const cookies = parseCookies(req);
8
+ const headers = extractHeaders(req);
9
+ const query = extractQuery(req);
10
+ const body = await parseBody(req, def.inputKind, def.inputOptions);
11
+ const path = validatePathParams(rawParams, def.paramSchemas);
12
+ // Validate each field if a schema is provided
13
+ const opts = def.inputOptions;
14
+ return {
15
+ body: opts.body ? validateField(opts.body, body, "body") : body,
16
+ headers: (opts.headers
17
+ ? validateField(opts.headers, headers, "headers")
18
+ : headers),
19
+ query: (opts.query
20
+ ? validateField(opts.query, query, "query")
21
+ : query),
22
+ cookies: (opts.cookies
23
+ ? validateField(opts.cookies, cookies, "cookies")
24
+ : cookies),
25
+ path,
26
+ };
27
+ }
28
+ /**
29
+ * Parse the request body based on InputKind.
30
+ */
31
+ export async function parseBody(req, kind, opts) {
32
+ switch (kind) {
33
+ case "json":
34
+ try {
35
+ return await req.json();
36
+ }
37
+ catch (e) {
38
+ throw new BadRequestError("Invalid JSON", e);
39
+ }
40
+ case "multipart":
41
+ case "urlencoded":
42
+ enforceMaxSize(req, opts.maxSize);
43
+ return req.formData();
44
+ case "blob":
45
+ enforceMaxSize(req, opts.maxSize);
46
+ return req.blob();
47
+ case "text":
48
+ return req.text();
49
+ case "none":
50
+ default:
51
+ return undefined;
52
+ }
53
+ }
54
+ /**
55
+ * Check Content-Length against maxSize before reading the body.
56
+ */
57
+ function enforceMaxSize(req, maxSize) {
58
+ if (!maxSize)
59
+ return;
60
+ const contentLength = req.headers.get("content-length");
61
+ if (contentLength && parseInt(contentLength) > maxSize) {
62
+ throw new HttpError(413, "Payload Too Large");
63
+ }
64
+ }
65
+ /**
66
+ * Extract all headers as a lowercased-key record.
67
+ */
68
+ export function extractHeaders(req) {
69
+ const headers = {};
70
+ req.headers.forEach((value, key) => {
71
+ headers[key] = value; // Headers API already lowercases keys
72
+ });
73
+ return headers;
74
+ }
75
+ /**
76
+ * Parse cookies from the Cookie header.
77
+ */
78
+ export function parseCookies(req) {
79
+ const header = req.headers.get("cookie") ?? "";
80
+ if (!header)
81
+ return {};
82
+ return Object.fromEntries(header
83
+ .split(";")
84
+ .map((pair) => pair.trim().split("="))
85
+ .filter(([k]) => k?.length > 0)
86
+ .map(([k, ...v]) => [k.trim(), decodeURIComponent(v.join("="))]));
87
+ }
88
+ /**
89
+ * Extract query parameters. Duplicate keys become arrays.
90
+ */
91
+ export function extractQuery(req) {
92
+ const params = {};
93
+ new URL(req.url).searchParams.forEach((value, key) => {
94
+ const existing = params[key];
95
+ if (existing === undefined) {
96
+ params[key] = value;
97
+ }
98
+ else if (Array.isArray(existing)) {
99
+ existing.push(value);
100
+ }
101
+ else {
102
+ params[key] = [existing, value];
103
+ }
104
+ });
105
+ return params;
106
+ }
@@ -0,0 +1,58 @@
1
+ import type { AnyContractBuilder, ContractShape, ErrorTypes } from "../contract/index.ts";
2
+ import type { AfterInterceptorFn, AnySchema, BasePathParams, BeforeInterceptorFn, ErrorHandlerFn, HandlerArgs, HttpMethod, InferOutput, InputKind, InputOptions, InterceptorPair, MergeParam, MiddlewareFn, RouteDefinition, RouteMeta } from "./types.ts";
3
+ /**
4
+ * Internal interface — the Router class (step 06) will implement this.
5
+ * Allows RouteBuilder to register completed routes without a circular dependency.
6
+ */
7
+ export interface RouterRef {
8
+ _registerRoute(def: RouteDefinition): void;
9
+ _getMiddlewares(): MiddlewareFn<Record<string, unknown>, Record<string, unknown>>[];
10
+ _getPrefix(): string;
11
+ }
12
+ /**
13
+ * A completed route — returned by `RouteBuilder.handle()`.
14
+ * Exposes the route definition for testing/introspection and allows `.catch()`.
15
+ */
16
+ export declare class FinishedRoute {
17
+ readonly definition: RouteDefinition;
18
+ constructor(definition: RouteDefinition);
19
+ catch(fn: ErrorHandlerFn): this;
20
+ }
21
+ /**
22
+ * Fluent route builder chain.
23
+ *
24
+ * Each method that changes generic type params returns a new instance.
25
+ * Methods that only add runtime data (.meta(), .intercept()) mutate and return `this`.
26
+ */
27
+ export declare class RouteBuilder<RouterCtx extends Record<string, unknown>, RouteCtx extends Record<string, unknown>, K extends InputKind = "none", O extends InputOptions<K> = InputOptions<K>, Params extends Record<string, unknown> = Record<string, unknown>, Output = undefined> {
28
+ #private;
29
+ constructor(router: RouterRef, method: HttpMethod, path: string, middlewares?: MiddlewareFn<Record<string, unknown>, Record<string, unknown>>[], paramSchemas?: Map<string, AnySchema>, inputKind?: InputKind, inputOptions?: InputOptions<InputKind>, outputSchema?: AnySchema, errorTypes?: ErrorTypes, meta?: RouteMeta, interceptors?: InterceptorPair<Record<string, unknown>>[]);
30
+ /** Set route metadata (title, tags, etc.) — mutates, returns this */
31
+ meta(data: RouteMeta): this;
32
+ /** Narrow/coerce a path param with a Valibot schema — returns new builder */
33
+ param<Name extends string & keyof Params, S extends AnySchema>(name: Name, schema: S): RouteBuilder<RouterCtx, RouteCtx, K, O, MergeParam<Params, Name, S>, Output>;
34
+ /** Add route-level middleware — returns new builder with extended context */
35
+ with<NewCtx extends Record<string, unknown>>(mw: MiddlewareFn<RouterCtx & RouteCtx, NewCtx>): RouteBuilder<RouterCtx, RouteCtx & NewCtx, K, O, Params, Output>;
36
+ /** Set input kind and options — returns new builder */
37
+ input<NK extends InputKind, NO extends InputOptions<NK>>(kind: NK, options?: NO): RouteBuilder<RouterCtx, RouteCtx, NK, NO extends InputOptions<NK> ? NO : InputOptions<NK>, Params, Output>;
38
+ /** Set output schema + optional error types — returns new builder */
39
+ output<S extends AnySchema>(schema: S, errors?: ErrorTypes): RouteBuilder<RouterCtx, RouteCtx, K, O, Params, InferOutput<S>>;
40
+ /** Add a before/after interceptor pair — mutates, returns this */
41
+ intercept(before?: BeforeInterceptorFn<RouterCtx & RouteCtx>, after?: AfterInterceptorFn<RouterCtx & RouteCtx>): this;
42
+ /** Terminal — registers the route on the router and returns a FinishedRoute */
43
+ handle(fn: (args: HandlerArgs<RouterCtx & RouteCtx, K, O, Params>) => [Output] extends [undefined] ? Response | Promise<Response> : Response | Output | Promise<Response | Output>): FinishedRoute;
44
+ }
45
+ /**
46
+ * Factory — creates a RouteBuilder bound to a router reference.
47
+ * Used internally by the Router class method helpers (.get(), .post(), etc.)
48
+ */
49
+ export declare function createRouteBuilder<RouterCtx extends Record<string, unknown>, P extends string>(router: RouterRef, method: HttpMethod, path: P): RouteBuilder<RouterCtx, Record<never, never>, "none", InputOptions<"none">, BasePathParams<P>, undefined>;
50
+ /**
51
+ * Factory — creates a `RouteBuilder` pre-populated from a `@fishenv/http-contract`
52
+ * endpoint (method, path, params, input, output). Used by `Router.contract()`.
53
+ *
54
+ * The returned builder carries exactly the generic shape (`K`, `O`, `Params`,
55
+ * `Output`) declared on the contract endpoint, so `.handle()` gets the same
56
+ * type safety as manually chaining `.input().output().param()`.
57
+ */
58
+ export declare function createRouteBuilderFromContract<RouterCtx extends Record<string, unknown>, C extends AnyContractBuilder>(router: RouterRef, endpoint: C): RouteBuilder<RouterCtx, Record<never, never>, ContractShape<C>["inputKind"], ContractShape<C>["inputOptions"], ContractShape<C>["params"], ContractShape<C>["output"]>;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * A completed route — returned by `RouteBuilder.handle()`.
3
+ * Exposes the route definition for testing/introspection and allows `.catch()`.
4
+ */
5
+ export class FinishedRoute {
6
+ definition;
7
+ constructor(definition) {
8
+ this.definition = definition;
9
+ }
10
+ catch(fn) {
11
+ this.definition.errorHandler = fn;
12
+ return this;
13
+ }
14
+ }
15
+ /**
16
+ * Fluent route builder chain.
17
+ *
18
+ * Each method that changes generic type params returns a new instance.
19
+ * Methods that only add runtime data (.meta(), .intercept()) mutate and return `this`.
20
+ */
21
+ export class RouteBuilder {
22
+ #router;
23
+ #method;
24
+ #path;
25
+ #middlewares;
26
+ #paramSchemas;
27
+ #inputKind;
28
+ #inputOptions;
29
+ #outputSchema;
30
+ #errorTypes;
31
+ #meta;
32
+ #interceptors;
33
+ constructor(router, method, path, middlewares = [], paramSchemas = new Map(), inputKind = "none", inputOptions = {}, outputSchema, errorTypes, meta, interceptors = []) {
34
+ this.#router = router;
35
+ this.#method = method;
36
+ this.#path = path;
37
+ this.#middlewares = middlewares;
38
+ this.#paramSchemas = paramSchemas;
39
+ this.#inputKind = inputKind;
40
+ this.#inputOptions = inputOptions;
41
+ this.#outputSchema = outputSchema;
42
+ this.#errorTypes = errorTypes;
43
+ this.#meta = meta;
44
+ this.#interceptors = interceptors;
45
+ }
46
+ /** Set route metadata (title, tags, etc.) — mutates, returns this */
47
+ meta(data) {
48
+ this.#meta = { ...this.#meta, ...data };
49
+ return this;
50
+ }
51
+ /** Narrow/coerce a path param with a Valibot schema — returns new builder */
52
+ param(name, schema) {
53
+ const newSchemas = new Map(this.#paramSchemas);
54
+ newSchemas.set(name, schema);
55
+ return new RouteBuilder(this.#router, this.#method, this.#path, [...this.#middlewares], newSchemas, this.#inputKind, this.#inputOptions, this.#outputSchema, this.#errorTypes ? [...this.#errorTypes] : undefined, this.#meta ? { ...this.#meta } : undefined, [...this.#interceptors]);
56
+ }
57
+ /** Add route-level middleware — returns new builder with extended context */
58
+ with(mw) {
59
+ return new RouteBuilder(this.#router, this.#method, this.#path, [
60
+ ...this.#middlewares,
61
+ mw,
62
+ ], new Map(this.#paramSchemas), this.#inputKind, this.#inputOptions, this.#outputSchema, this.#errorTypes ? [...this.#errorTypes] : undefined, this.#meta ? { ...this.#meta } : undefined, [...this.#interceptors]);
63
+ }
64
+ /** Set input kind and options — returns new builder */
65
+ input(kind, options) {
66
+ return new RouteBuilder(this.#router, this.#method, this.#path, [...this.#middlewares], new Map(this.#paramSchemas), kind, (options ?? {}), this.#outputSchema, this.#errorTypes ? [...this.#errorTypes] : undefined, this.#meta ? { ...this.#meta } : undefined, [...this.#interceptors]);
67
+ }
68
+ /** Set output schema + optional error types — returns new builder */
69
+ output(schema, errors) {
70
+ return new RouteBuilder(this.#router, this.#method, this.#path, [...this.#middlewares], new Map(this.#paramSchemas), this.#inputKind, this.#inputOptions, schema, errors, this.#meta ? { ...this.#meta } : undefined, [...this.#interceptors]);
71
+ }
72
+ /** Add a before/after interceptor pair — mutates, returns this */
73
+ intercept(before, after) {
74
+ this.#interceptors.push({
75
+ before: before,
76
+ after: after,
77
+ });
78
+ return this;
79
+ }
80
+ /** Terminal — registers the route on the router and returns a FinishedRoute */
81
+ handle(fn) {
82
+ const fullPath = this.#router._getPrefix()
83
+ ? `${this.#router._getPrefix()}${this.#path}`
84
+ : this.#path;
85
+ const routerMiddlewares = this.#router._getMiddlewares();
86
+ const definition = {
87
+ method: this.#method,
88
+ path: fullPath,
89
+ middlewares: [...routerMiddlewares, ...this.#middlewares],
90
+ paramSchemas: this.#paramSchemas,
91
+ inputKind: this.#inputKind,
92
+ inputOptions: this.#inputOptions,
93
+ outputSchema: this.#outputSchema,
94
+ errorTypes: this.#errorTypes,
95
+ meta: this.#meta,
96
+ interceptors: this.#interceptors,
97
+ handler: fn,
98
+ kind: "http",
99
+ };
100
+ this.#router._registerRoute(definition);
101
+ return new FinishedRoute(definition);
102
+ }
103
+ }
104
+ /**
105
+ * Factory — creates a RouteBuilder bound to a router reference.
106
+ * Used internally by the Router class method helpers (.get(), .post(), etc.)
107
+ */
108
+ export function createRouteBuilder(router, method, path) {
109
+ return new RouteBuilder(router, method, path);
110
+ }
111
+ /**
112
+ * Factory — creates a `RouteBuilder` pre-populated from a `@fishenv/http-contract`
113
+ * endpoint (method, path, params, input, output). Used by `Router.contract()`.
114
+ *
115
+ * The returned builder carries exactly the generic shape (`K`, `O`, `Params`,
116
+ * `Output`) declared on the contract endpoint, so `.handle()` gets the same
117
+ * type safety as manually chaining `.input().output().param()`.
118
+ */
119
+ export function createRouteBuilderFromContract(router, endpoint) {
120
+ const def = endpoint._definition;
121
+ return new RouteBuilder(router, def.method, def.path, [], new Map(def.paramSchemas), def.inputKind, def.inputOptions, def.outputSchema, def.errorTypes ? [...def.errorTypes] : undefined, def.meta ? { ...def.meta } : undefined, []);
122
+ }
@@ -0,0 +1,162 @@
1
+ import type { AnyContractBuilder, ContractShape } from "../contract/index.ts";
2
+ import { type CorsOptions } from "./cors.ts";
3
+ import { type RouteBuilder, type RouterRef } from "./route-builder.ts";
4
+ import type { BasePathParams, BuildEnv, DIContainer, ErrorHandlerFn, InputOptions, MiddlewareFn, NotFoundHandler, RouteDefinition } from "./types.ts";
5
+ import { type WsServerOptions } from "./upgrade.ts";
6
+ type AnyRouter = Router<any, any>;
7
+ /**
8
+ * A router mounted via `.add()`, together with the prefix and middleware of
9
+ * the parent variant it was mounted on. Applied to the child's routes at build().
10
+ */
11
+ interface MountedRouter {
12
+ router: AnyRouter;
13
+ prefix: string;
14
+ middlewares: MiddlewareFn<Record<string, unknown>, Record<string, unknown>>[];
15
+ }
16
+ /**
17
+ * Shared mutable state between a Router and all its .use() variants.
18
+ * Routes registered on any variant end up in the same store.
19
+ */
20
+ interface RouterStore {
21
+ routes: RouteDefinition[];
22
+ children: MountedRouter[];
23
+ locked: boolean;
24
+ }
25
+ /**
26
+ * Router — immutable middleware chain, mutable route registration.
27
+ *
28
+ * `.use()` returns a new Router with the extended Ctx type but the same
29
+ * backing store — routes registered on either instance are collected together.
30
+ * Route methods (.get, .post, etc.) return a RouteBuilder chain.
31
+ * Call `.build()` before `.fetch` or `serve()`.
32
+ *
33
+ * `Requires` is the context this router expects a parent to provide when it
34
+ * is mounted via `.add()`. Create one with `r<AppCtx>()`; `.add()` only
35
+ * accepts it on a parent whose `Ctx` satisfies `AppCtx`, and `serve()` only
36
+ * accepts routers with no outstanding requirements.
37
+ */
38
+ export declare class Router<Ctx extends Record<string, unknown> = Record<never, never>, Requires extends Record<string, unknown> = Record<never, never>> implements RouterRef {
39
+ #private;
40
+ /** @internal Phantom — makes `Requires` contravariant for `.add()` checks. */
41
+ readonly _requires?: (ctx: Requires) => void;
42
+ constructor(opts?: {
43
+ prefix?: string;
44
+ }, middlewares?: MiddlewareFn<Record<string, unknown>, Record<string, unknown>>[], errorHandler?: ErrorHandlerFn, notFoundHandler?: NotFoundHandler, store?: RouterStore);
45
+ _registerRoute(def: RouteDefinition): void;
46
+ _getMiddlewares(): MiddlewareFn<Record<string, unknown>, Record<string, unknown>>[];
47
+ _getPrefix(): string;
48
+ use<NewCtx extends Record<string, unknown>>(mw: MiddlewareFn<Ctx, NewCtx>): Router<Ctx & NewCtx, Requires>;
49
+ /** Create a child router and mount it on this one — shorthand for `r<Ctx>(opts)` + `.add()`. */
50
+ extend(opts?: {
51
+ prefix?: string;
52
+ }): Router<Ctx, Ctx>;
53
+ /**
54
+ * Mount routers defined elsewhere, optionally under an extra prefix:
55
+ *
56
+ * ```ts
57
+ * app.add(users, posts);
58
+ * app.add("/v1", users);
59
+ * ```
60
+ *
61
+ * Each child's routes get this router's prefix and middleware prepended at
62
+ * build(). A child created with `r<AppCtx>()` only type-checks here if this
63
+ * router's context provides `AppCtx`.
64
+ */
65
+ add(...children: Router<any, Ctx>[]): this;
66
+ add(prefix: string, ...children: Router<any, Ctx>[]): this;
67
+ get<P extends string>(path: P): RouteBuilder<Ctx, Record<never, never>, "none", InputOptions<"none">, BasePathParams<P>, undefined>;
68
+ post<P extends string>(path: P): RouteBuilder<Ctx, Record<never, never>, "none", InputOptions<"none">, BasePathParams<P>, undefined>;
69
+ put<P extends string>(path: P): RouteBuilder<Ctx, Record<never, never>, "none", InputOptions<"none">, BasePathParams<P>, undefined>;
70
+ patch<P extends string>(path: P): RouteBuilder<Ctx, Record<never, never>, "none", InputOptions<"none">, BasePathParams<P>, undefined>;
71
+ delete<P extends string>(path: P): RouteBuilder<Ctx, Record<never, never>, "none", InputOptions<"none">, BasePathParams<P>, undefined>;
72
+ options<P extends string>(path: P): RouteBuilder<Ctx, Record<never, never>, "none", InputOptions<"none">, BasePathParams<P>, undefined>;
73
+ /**
74
+ * Register a route from a `@fishenv/http-contract` endpoint definition, e.g.:
75
+ *
76
+ * ```ts
77
+ * router.contract(contract.users.create).handle(({ body }) => { ... });
78
+ * ```
79
+ *
80
+ * `.handle()` gets exactly the same type safety (body/query/params/output)
81
+ * as chaining `.input().output().param()` manually, because the contract
82
+ * endpoint and the route builder share the same shape types.
83
+ */
84
+ contract<C extends AnyContractBuilder>(endpoint: C): RouteBuilder<Ctx, Record<never, never>, ContractShape<C>["inputKind"], ContractShape<C>["inputOptions"], ContractShape<C>["params"], ContractShape<C>["output"]>;
85
+ cors(options: CorsOptions): this;
86
+ onError(handler: ErrorHandlerFn): this;
87
+ notFound(handler: NotFoundHandler): this;
88
+ /** @internal Set by serve(). For testing, call before build(). */
89
+ _setContainer(container: DIContainer): void;
90
+ /** @internal Set by serve() before build(); forwarded to route `onBuild` hooks. */
91
+ _setEnv(env: Partial<Omit<BuildEnv, "cors">>): void;
92
+ build(): void;
93
+ /**
94
+ * Entry point for request handling — available after build().
95
+ * Use directly for testing without binding a port.
96
+ */
97
+ readonly fetch: (req: Request) => Promise<Response>;
98
+ /**
99
+ * @internal Used by serve(): HEAD responses keep their body so Bun can
100
+ * derive `Content-Length` from it before dropping it (it does not drain
101
+ * streams to do so).
102
+ */
103
+ readonly _serverFetch: (req: Request) => Promise<Response>;
104
+ }
105
+ /**
106
+ * Create a router. Pass a context type to declare what a parent must provide
107
+ * when this router is mounted with `.add()`:
108
+ *
109
+ * ```ts
110
+ * // context.ts
111
+ * export const base = r().use(auth);
112
+ * export type AppCtx = CtxOf<typeof base>;
113
+ *
114
+ * // users.ts — type-only import, no runtime cycle
115
+ * export const users = r<AppCtx>({ prefix: "/users" });
116
+ *
117
+ * // main.ts
118
+ * serve(base.add(users));
119
+ * ```
120
+ */
121
+ export declare function r<Req extends Record<string, unknown> = Record<never, never>>(opts?: {
122
+ prefix?: string;
123
+ }): Router<Req, Req>;
124
+ export declare const router: typeof r;
125
+ /** The context type a router's handlers receive. */
126
+ export type CtxOf<R> = R extends Router<infer C, infer _> ? C : never;
127
+ export interface ServeOptions {
128
+ port?: number;
129
+ hostname?: string;
130
+ container?: DIContainer;
131
+ development?: boolean;
132
+ logger?: Logger;
133
+ signal?: AbortSignal;
134
+ /**
135
+ * Seconds a connection may sit idle before Bun closes it. Defaults to `0`
136
+ * (disabled) so long-lived SSE streams are not cut between keepalives.
137
+ */
138
+ idleTimeout?: number;
139
+ /** Server-wide WebSocket settings (secure defaults; see {@link WsServerOptions}). */
140
+ websocket?: WsServerOptions;
141
+ onListen?: (addr: {
142
+ hostname: string;
143
+ port: number;
144
+ }) => void;
145
+ }
146
+ /** A running server started by {@link serve}. */
147
+ export interface HttpServer {
148
+ readonly hostname: string;
149
+ readonly port: number;
150
+ /**
151
+ * Stop accepting connections; `force` also closes in-flight ones. Open
152
+ * WebSockets are always closed with `1001 Going Away` first.
153
+ */
154
+ stop(force?: boolean): Promise<void>;
155
+ }
156
+ export interface Logger {
157
+ error(message: string, ...args: unknown[]): void;
158
+ warn(message: string, ...args: unknown[]): void;
159
+ info(message: string, ...args: unknown[]): void;
160
+ }
161
+ export declare function serve(app: Router<any, Record<never, never>>, options?: ServeOptions): HttpServer;
162
+ export {};