@rhythmjs/router 0.0.9 → 0.0.11

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 CHANGED
@@ -1,8 +1,10 @@
1
1
  # @rhythmjs/router
2
2
 
3
- Web-standard HTTP routing on top of `@rhythmjs/rhythm`. `RhythmRouter` matches routes with a compressed radix tree (a static segment always wins over a `:param` segment, regardless of registration order), supports prefixes and nested routers, and mounts flat into a parent `Rhythm` app via `.use(router.routes())`, so an unmatched request correctly falls through to whatever's registered after it.
3
+ Web-standard HTTP routing on top of `@rhythmjs/rhythm`. `RhythmRouter` matches routes with [rou3](https://github.com/h3js/rou3), the router that powers h3 (a static segment always wins over a `:param` segment, regardless of registration order), supports prefixes and nested routers, and mounts flat into a parent `Rhythm` app via `.use(router.middleware())`, so an unmatched request correctly falls through to whatever's registered after it.
4
4
 
5
- `RhythmRouter` is not an app and does not extend `Rhythm` — it is a controller that compiles routes and middleware down to a single middleware (`.routes()`). It shares the core middleware contract (`compose`, `Middleware`, `next(extra)`), but has no `provide()` or `register()`, and it can't be served on its own: a `Rhythm` app is always the host that owns the lifecycle and the adapters.
5
+ Route patterns follow rou3's conventions: `:name` params (`:name?` optional, `:id(\\d+)` regex-constrained), `*` for one unnamed segment (captured as `params["0"]`), and `**` for the rest of the path (`params._`, or `params.name` with `**:name`). Param values are the raw path segments, undecoded.
6
+
7
+ `RhythmRouter` is not an app and does not extend `Rhythm` — it is a controller that compiles routes and middleware down to a single middleware (`.middleware()`). It shares the core middleware contract (`compose`, `Middleware`, `next(extra)`), but has no `provide()` or `register()`, and it can't be served on its own: a `Rhythm` app is always the host that owns the lifecycle and the adapters.
6
8
 
7
9
  ## Example
8
10
 
@@ -27,31 +29,44 @@ A fuller runnable version, including nested prefixes and a fallback route, is at
27
29
  - **`ctx.response`** is a plain mutable object (`status`, `statusText`, `headers`, `body`) — set it directly rather than constructing a `Response` yourself. The adapter converts it to a real `Response` at the end.
28
30
  - **Response helpers** — `ctx.json(data, status?)`, `ctx.text(body, status?)`, `ctx.html(body, status?)`, `ctx.error(status, message?)`, and `ctx.redirect(url, status = 302)` set the content type, body, and status on `ctx.response` in one call. `error()` defaults the message from the status code (`ctx.error(404)` → `"Not Found"`). They're sugar over `ctx.response`, so mixing both styles is fine, and later writes win.
29
31
  - **`ctx.params`** — captured `:name` path segments, added once a route matches.
30
- - **Prefixes compose across nesting** — a child router mounted into a prefixed parent via `.use(child)` gets the parent's prefix joined onto every one of its routes, at any nesting depth. Mounting copies the child's routes and middleware at that moment; routes added to the child afterwards don't appear in the parent, and the child keeps working standalone.
31
- - **Registration order is execution order** — a `.use()` middleware wraps only the routes registered after it; routes registered before it are untouched, and a matched route that doesn't call `next()` returns without reaching anything registered later. Consecutive routes share one radix tree lookup; an unmatched request falls through, entry by entry, to the outer `next()`.
32
- - **A router is a controller, not a module** — it has no `provide()` or `register()`, and it cannot be `register()`ed into a `Rhythm` app either; `register()` composes `Rhythm` modules only. A router mounts into an app exactly one way: koa-style, via `.use(router.routes())`.
32
+ - **Nesting is `.use(child.middleware())`** — a router mounts into another router (or into the app) as a compiled middleware. The mount is opaque, so the parent's prefix is **not** applied to the child's routes: the child carries its own absolute prefix (`new RhythmRouter({ prefix: "/api/users" })`). On a miss the child falls through to `next()`, so the parent's later middleware and routes still run, and the child keeps working standalone.
33
+ - **Registration order is execution order** — a `.use()` middleware wraps only the routes registered after it; routes registered before it are untouched, and a matched route that doesn't call `next()` returns without reaching anything registered later. Consecutive routes share one rou3 lookup; an unmatched request falls through, entry by entry, to the outer `next()`.
34
+ - **A router is a controller, not a module** — it has no `provide()` or `register()`, and it cannot be `register()`ed into a `Rhythm` app either; `register()` composes `Rhythm` modules only. A router mounts into an app exactly one way: koa-style, via `.use(router.middleware())`.
33
35
 
34
36
  ## API
35
37
 
36
38
  - `new RhythmRouter(options?)` — `options.prefix`.
37
39
  - `.get/.post/.put/.patch/.delete(path, ...handlers)` — register a route; `path` may contain `:param` segments.
38
- - `.use(fn)` — plain middleware. `.use(child)` — mount a nested `RhythmRouter` (prefixes compose).
39
- - `.routes()` — this router as a plain middleware, for mounting into a `Rhythm` app via `.use()`; the router's only way onto a server. Note: mounting a _router_ into a _router_ must use `.use(child)`, not `.use(child.routes())` — an opaque middleware can't have the parent's prefix applied to its routes.
40
- - `ctx.json/.text/.html(body, status?)`, `ctx.error(status, message?)`, `ctx.redirect(url, status?)` — response helpers built into the context by the adapters (`createHttpContext` in `@rhythmjs/router/context`).
40
+ - `.use(fn)` — plain middleware; it takes only functions, so a nested router mounts as `.use(child.middleware())`.
41
+ - `.middleware()` — this router compiled to a plain middleware: the one form that mounts anywhere, into a `Rhythm` app or into another router. Because the compiled form is opaque, the mounting router's prefix is not applied to it — give the child its full prefix.
42
+ - `ctx.json/.text/.html(body, status?)`, `ctx.error(status, message?)`, `ctx.redirect(url, status?)` — response helpers built into the context (`createHttpContext` in `@rhythmjs/router/context`).
41
43
  - `toFetchHandler(app)` — bridges a `Rhythm` app to a Web-standard `(Request) => Promise<Response>` handler.
42
44
 
43
45
  ## Serving
44
46
 
45
- Everything above (`RhythmRouter`, `ctx.response`, etc.) is runtime-agnostic. Two primitives turn an app into a server, both built on [srvx](https://srvx.h3.dev):
47
+ Two primitives turn an app into a server, both coupled to [Bun](https://bun.com) on purpose:
48
+
49
+ - **`serve(app, options)`** (`@rhythmjs/router/serve`) — starts the app on `Bun.serve` and returns Bun's `Server` (`server.port`, `server.url`, `server.publish`, `server.stop()`).
50
+ - **`toFetchHandler(app)`** (`@rhythmjs/router/fetch`) — the raw `(Request) => Promise<Response>` handler, for composing and testing without a listener.
51
+
52
+ `serve()` passes the server fields through to `Bun.serve` — `port`, `hostname`, `unix`, `tls`, `reusePort`, `idleTimeout`, `development`, `maxRequestBodySize` (an over-limit body is rejected without crashing) — plus the extension points below. Every request gets a lazy `request.ip` (from `server.requestIP()`), the field `@rhythmjs/security`'s rate limit and `@rhythmjs/http`'s proxy key off. Errors thrown in the middleware chain are answered with `500` (or the error's own `status`) without crashing the process; override the mapping with `options.error`.
53
+
54
+ ### Static assets
46
55
 
47
- - **`serve(app, options)`** (`@rhythmjs/router/serve`) — starts a server on Node, Bun, or Deno with one identical call; srvx picks the runtime implementation via conditional exports. On Node, requests are lazy: method, url, headers, and body materialize only when middleware touches them.
48
- - **`toFetchHandler(app)`** (`@rhythmjs/router/fetch`) — the universal `(Request) => Promise<Response>` handler, for platforms that invoke you per request instead of letting you own a listener.
56
+ The `static` option serves files with `Bun.file` before the app runs; a request no folder answers falls through to your routes. It takes one folder config or an array — folders are probed in order, first match wins:
57
+
58
+ ```ts
59
+ serve(app, {
60
+ port: 3000,
61
+ static: [{ dir: "dist/client", maxAge: 31536000, immutable: true }, { dir: "public" }],
62
+ });
63
+ ```
49
64
 
50
- `serve()` accepts every srvx `ServerOptions` field except `fetch`: `port`, `hostname`, `tls` (HTTPS/HTTP2), `maxRequestBodySize` (an over-limit body read is answered with `413`), `reusePort`, `gracefulShutdown`, plus the three extension points below. Errors thrown in the middleware chain are answered with `500` (or the error's own `status`) without crashing the process; override the mapping with `options.error`.
65
+ Each entry (`StaticMiddlewareOptions`, also exported from `@rhythmjs/router/static`) takes `dir`, `prefix` (mount point, default the site root), `index` (default `index.html`, served for directory paths), `maxAge`/`immutable` cache control, and `ETag` revalidation with `304`s (`etag`, on by default). Content types come from `Bun.file`. Path traversal is normalized away. `middleware` you pass runs before and wraps the static handlers, so CORS or logging cover asset responses too.
51
66
 
52
67
  ### Extending: CORS, WebSockets, and similar
53
68
 
54
- - **`middleware`** — srvx middlewares (`(request, next) => Response`) run around the whole app, the natural place for CORS, logging, or auth gates:
69
+ - **`middleware`** — serve middlewares (`(request, next) => Response`) run around the whole app, the natural place for CORS, logging, or auth gates:
55
70
 
56
71
  ```ts
57
72
  serve(app, {
@@ -67,88 +82,14 @@ Everything above (`RhythmRouter`, `ctx.response`, etc.) is runtime-agnostic. Two
67
82
  });
68
83
  ```
69
84
 
70
- - **`plugins`** — a plugin receives the live srvx `Server`, the hook for anything below fetch, WebSockets included ([crossws](https://crossws.h3.dev) attaches here; on Node the raw server is at `server.node.server`):
85
+ - **`upgrade` + `websocket`** — the seams for Bun's native WebSockets, shaped for [`@rhythmjs/ws`](https://github.com/rhythmjs/ws). Requests with an `upgrade: websocket` header divert to `upgrade(request, server)` before the app runs (return a `Response` to reject, or `undefined` after `server.upgrade()`); `websocket` is Bun's behavior object, passed through:
71
86
 
72
87
  ```ts
73
- serve(app, { plugins: [(server) => wireWebSockets(server)] });
88
+ import { websocket } from "@rhythmjs/ws";
89
+
90
+ serve(app, { upgrade: ws.upgrade, websocket: websocket() });
74
91
  ```
75
92
 
76
93
  - **`error`** — replace the default error-to-response mapping.
77
94
 
78
- ## Deploying per runtime
79
-
80
- One app definition; each runtime has its own adapter under `@rhythmjs/router/adapters/*`, hono-style: import from your target's adapter and export what the platform expects. Runtimes that speak web-standard `Request`/`Response` natively (Bun, Deno, Vercel) can equally use `toFetchHandler(app)` from `@rhythmjs/router/fetch` — their adapters are aliases for it.
81
-
82
- Every adapter maps errors thrown in the middleware chain to a response (`500`, or the error's own `status`) instead of crashing, and accepts `{ onError }` to replace that mapping: `handle(app, { onError: (error) => new Response("down", { status: 503 }) })`. Platform metadata (Cloudflare `env`/`ctx`, the Netlify context) is readable in middleware via `getRuntime(ctx.request)` from `@rhythmjs/router/context`.
83
-
84
- **Node** — `getRequestListener` returns a listener for your own `node:http` server:
85
-
86
- ```ts
87
- import { createServer } from "node:http";
88
- import { getRequestListener } from "@rhythmjs/router/adapters/node";
89
-
90
- createServer(getRequestListener(app, { maxRequestBodySize: 1024 * 1024 })).listen(3000);
91
- ```
92
-
93
- (Or skip the adapter entirely and use `serve(app, { port: 3000 })`.)
94
-
95
- **Bun**
96
-
97
- ```ts
98
- import { handle } from "@rhythmjs/router/adapters/bun";
99
-
100
- Bun.serve({ port: 3000, fetch: handle(app) });
101
- ```
102
-
103
- **Deno**
104
-
105
- ```ts
106
- import { handle } from "@rhythmjs/router/adapters/deno";
107
-
108
- Deno.serve({ port: 3000 }, handle(app));
109
- ```
110
-
111
- **Vercel** — in a catch-all route file:
112
-
113
- ```ts
114
- import { handle } from "@rhythmjs/router/adapters/vercel";
115
-
116
- const handler = handle(app);
117
- export const GET = handler;
118
- export const POST = handler; // …and the other methods you serve
119
- ```
120
-
121
- **Cloudflare Workers** — `env` and `ctx` are exposed to middleware as `ctx.request.runtime.cloudflare`:
122
-
123
- ```ts
124
- import { handle } from "@rhythmjs/router/adapters/cloudflare";
125
-
126
- export default { fetch: handle(app) };
127
- ```
128
-
129
- **AWS Lambda** — API Gateway v1/v2 events, translated by srvx:
130
-
131
- ```ts
132
- import { handle } from "@rhythmjs/router/adapters/aws-lambda";
133
-
134
- export const lambda = handle(app);
135
- ```
136
-
137
- **Netlify Edge Functions** — the Netlify context is exposed as `ctx.request.runtime.netlify`:
138
-
139
- ```ts
140
- import { handle } from "@rhythmjs/router/adapters/netlify";
141
-
142
- export default handle(app);
143
- export const config = { path: "/*" };
144
- ```
145
-
146
- **Service workers**
147
-
148
- ```ts
149
- import { handle } from "@rhythmjs/router/adapters/service-worker";
150
-
151
- addEventListener("fetch", handle(app));
152
- ```
153
-
154
- Any other fetch-based runtime works with the raw primitive: `toFetchHandler(app)` from `@rhythmjs/router/fetch`.
95
+ Anything else fetch-shaped composes with the raw primitive: `toFetchHandler(app)` from `@rhythmjs/router/fetch`.
package/dist/context.d.ts CHANGED
@@ -1,2 +1,18 @@
1
- import { a as createHttpContext, i as RhythmRuntime, n as RhythmResponse, o as getRuntime, r as RhythmResponseBody, s as toResponse, t as RhythmHttpContext } from "./context-ChHlG-q0.js";
2
- export { RhythmHttpContext, RhythmResponse, RhythmResponseBody, RhythmRuntime, createHttpContext, getRuntime, toResponse };
1
+ export type RhythmResponseBody = string | ArrayBuffer | Uint8Array | Blob | FormData | URLSearchParams | ReadableStream<Uint8Array> | null;
2
+ export declare class RhythmResponse {
3
+ status: number;
4
+ statusText: string | undefined;
5
+ headers: Headers;
6
+ body: RhythmResponseBody;
7
+ }
8
+ export interface RhythmHttpContext {
9
+ readonly request: Request;
10
+ readonly response: RhythmResponse;
11
+ json(data: unknown, status?: number): void;
12
+ text(body: string, status?: number): void;
13
+ html(body: string, status?: number): void;
14
+ error(status: number, message?: string): void;
15
+ redirect(url: string, status?: number): void;
16
+ }
17
+ export declare function createHttpContext(request: Request): RhythmHttpContext;
18
+ export declare function toResponse(response: RhythmResponse): Response;
package/dist/context.js CHANGED
@@ -1,69 +1,11 @@
1
- //#region src/context.ts
2
- function getRuntime(request) {
3
- return request.runtime;
4
- }
5
- var RhythmResponse = class {
6
- status = 200;
7
- statusText = void 0;
8
- headers = new Headers();
9
- body = null;
1
+ // @bun
2
+ import {
3
+ RhythmResponse2,
4
+ createHttpContext2,
5
+ toResponse2
6
+ } from "./rhythm-router-yj4ffyyg.js";
7
+ export {
8
+ RhythmResponse2 as RhythmResponse,
9
+ createHttpContext2 as createHttpContext,
10
+ toResponse2 as toResponse
10
11
  };
11
- const STATUS_TEXT = {
12
- 400: "Bad Request",
13
- 401: "Unauthorized",
14
- 403: "Forbidden",
15
- 404: "Not Found",
16
- 405: "Method Not Allowed",
17
- 409: "Conflict",
18
- 410: "Gone",
19
- 413: "Payload Too Large",
20
- 415: "Unsupported Media Type",
21
- 422: "Unprocessable Entity",
22
- 429: "Too Many Requests",
23
- 500: "Internal Server Error",
24
- 501: "Not Implemented",
25
- 502: "Bad Gateway",
26
- 503: "Service Unavailable",
27
- 504: "Gateway Timeout"
28
- };
29
- function createHttpContext(request) {
30
- const response = new RhythmResponse();
31
- return {
32
- request,
33
- response,
34
- json(data, status) {
35
- if (status !== void 0) response.status = status;
36
- response.headers.set("content-type", "application/json; charset=utf-8");
37
- response.body = JSON.stringify(data);
38
- },
39
- text(body, status) {
40
- if (status !== void 0) response.status = status;
41
- response.headers.set("content-type", "text/plain; charset=utf-8");
42
- response.body = body;
43
- },
44
- html(body, status) {
45
- if (status !== void 0) response.status = status;
46
- response.headers.set("content-type", "text/html; charset=utf-8");
47
- response.body = body;
48
- },
49
- error(status, message) {
50
- response.status = status;
51
- response.headers.set("content-type", "text/plain; charset=utf-8");
52
- response.body = message ?? STATUS_TEXT[status] ?? `Error ${status}`;
53
- },
54
- redirect(url, status = 302) {
55
- response.status = status;
56
- response.headers.set("location", url);
57
- response.body = null;
58
- }
59
- };
60
- }
61
- function toResponse(response) {
62
- return new Response(response.body, {
63
- status: response.status,
64
- statusText: response.statusText,
65
- headers: response.headers
66
- });
67
- }
68
- //#endregion
69
- export { RhythmResponse, createHttpContext, getRuntime, toResponse };
package/dist/fetch.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { t as RhythmHttpContext } from "./context-ChHlG-q0.js";
2
- import { Rhythm } from "@rhythmjs/rhythm";
3
- //#region src/fetch.d.ts
1
+ import type { Rhythm } from "@rhythmjs/rhythm";
2
+ import { type RhythmHttpContext } from "./context";
4
3
  export declare function toFetchHandler<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>): (request: Request) => Promise<Response>;
5
- //#endregion
package/dist/fetch.js CHANGED
@@ -1,16 +1,8 @@
1
- import { createHttpContext } from "./context.js";
2
- import { FastResponse } from "srvx";
3
- //#region src/fetch.ts
4
- function toFetchHandler(app) {
5
- const run = app.callback();
6
- return async (request) => {
7
- const response = (await run(createHttpContext(request))).response;
8
- return new FastResponse(response.body, {
9
- status: response.status,
10
- statusText: response.statusText,
11
- headers: response.headers
12
- });
13
- };
14
- }
15
- //#endregion
16
- export { toFetchHandler };
1
+ // @bun
2
+ import {
3
+ toFetchHandler2
4
+ } from "./rhythm-router-88dtq5mx.js";
5
+ import"./rhythm-router-yj4ffyyg.js";
6
+ export {
7
+ toFetchHandler2 as toFetchHandler
8
+ };
@@ -0,0 +1,20 @@
1
+ // @bun
2
+ import {
3
+ createHttpContext2
4
+ } from "./rhythm-router-yj4ffyyg.js";
5
+
6
+ // src/fetch.ts
7
+ function toFetchHandler2(app) {
8
+ const run = app.callback();
9
+ return async (request) => {
10
+ const ctx = await run(createHttpContext2(request));
11
+ const response = ctx.response;
12
+ return new Response(response.body, {
13
+ status: response.status,
14
+ statusText: response.statusText,
15
+ headers: response.headers
16
+ });
17
+ };
18
+ }
19
+
20
+ export { toFetchHandler2 };
@@ -0,0 +1,53 @@
1
+ // @bun
2
+ // src/static.ts
3
+ import { join, normalize, sep } from "path";
4
+ function normalizePrefix(prefix) {
5
+ const withLeading = prefix.startsWith("/") ? prefix : `/${prefix}`;
6
+ return withLeading.endsWith("/") ? withLeading.slice(0, -1) : withLeading;
7
+ }
8
+ function staticMiddleware2(options) {
9
+ const prefix = normalizePrefix(options.prefix ?? "/");
10
+ const index = options.index ?? "index.html";
11
+ const useEtag = options.etag ?? true;
12
+ const cacheControl = options.maxAge === undefined ? undefined : `max-age=${options.maxAge}${options.immutable ? ", immutable" : ""}`;
13
+ return async (request, next) => {
14
+ if (request.method !== "GET" && request.method !== "HEAD")
15
+ return next();
16
+ let pathname;
17
+ try {
18
+ pathname = decodeURIComponent(new URL(request.url).pathname);
19
+ } catch {
20
+ return next();
21
+ }
22
+ if (prefix !== "" && pathname !== prefix && !pathname.startsWith(`${prefix}/`))
23
+ return next();
24
+ const relative = normalize(pathname.slice(prefix.length));
25
+ if (relative.includes("\x00") || relative === ".." || relative.startsWith(`..${sep}`))
26
+ return next();
27
+ const base = join(options.dir, relative);
28
+ const candidates = pathname.endsWith("/") ? [join(base, index)] : [base, join(base, index)];
29
+ let file;
30
+ for (const candidate of candidates) {
31
+ const found = Bun.file(candidate);
32
+ if (await found.exists()) {
33
+ file = found;
34
+ break;
35
+ }
36
+ }
37
+ if (file === undefined)
38
+ return next();
39
+ const headers = new Headers;
40
+ if (cacheControl !== undefined)
41
+ headers.set("cache-control", cacheControl);
42
+ if (useEtag) {
43
+ const etag = `"${file.size.toString(16)}-${Math.floor(file.lastModified).toString(16)}"`;
44
+ headers.set("etag", etag);
45
+ if (request.headers.get("if-none-match") === etag) {
46
+ return new Response(null, { status: 304, headers });
47
+ }
48
+ }
49
+ return new Response(file, { headers });
50
+ };
51
+ }
52
+
53
+ export { staticMiddleware2 };
@@ -0,0 +1,70 @@
1
+ // @bun
2
+ // src/context.ts
3
+ class RhythmResponse2 {
4
+ status = 200;
5
+ statusText = undefined;
6
+ headers = new Headers;
7
+ body = null;
8
+ }
9
+ var STATUS_TEXT = {
10
+ 400: "Bad Request",
11
+ 401: "Unauthorized",
12
+ 403: "Forbidden",
13
+ 404: "Not Found",
14
+ 405: "Method Not Allowed",
15
+ 409: "Conflict",
16
+ 410: "Gone",
17
+ 413: "Payload Too Large",
18
+ 415: "Unsupported Media Type",
19
+ 422: "Unprocessable Entity",
20
+ 429: "Too Many Requests",
21
+ 500: "Internal Server Error",
22
+ 501: "Not Implemented",
23
+ 502: "Bad Gateway",
24
+ 503: "Service Unavailable",
25
+ 504: "Gateway Timeout"
26
+ };
27
+ function createHttpContext2(request) {
28
+ const response = new RhythmResponse2;
29
+ return {
30
+ request,
31
+ response,
32
+ json(data, status) {
33
+ if (status !== undefined)
34
+ response.status = status;
35
+ response.headers.set("content-type", "application/json; charset=utf-8");
36
+ response.body = JSON.stringify(data);
37
+ },
38
+ text(body, status) {
39
+ if (status !== undefined)
40
+ response.status = status;
41
+ response.headers.set("content-type", "text/plain; charset=utf-8");
42
+ response.body = body;
43
+ },
44
+ html(body, status) {
45
+ if (status !== undefined)
46
+ response.status = status;
47
+ response.headers.set("content-type", "text/html; charset=utf-8");
48
+ response.body = body;
49
+ },
50
+ error(status, message) {
51
+ response.status = status;
52
+ response.headers.set("content-type", "text/plain; charset=utf-8");
53
+ response.body = message ?? STATUS_TEXT[status] ?? `Error ${status}`;
54
+ },
55
+ redirect(url, status = 302) {
56
+ response.status = status;
57
+ response.headers.set("location", url);
58
+ response.body = null;
59
+ }
60
+ };
61
+ }
62
+ function toResponse2(response) {
63
+ return new Response(response.body, {
64
+ status: response.status,
65
+ statusText: response.statusText,
66
+ headers: response.headers
67
+ });
68
+ }
69
+
70
+ export { RhythmResponse2, createHttpContext2, toResponse2 };
@@ -1,39 +1,39 @@
1
- import { t as RhythmHttpContext } from "./context-ChHlG-q0.js";
2
- import { DeriveMiddleware, Middleware } from "@rhythmjs/rhythm/types";
3
- //#region src/rhythm-router.d.ts
1
+ import type { DeriveMiddleware, Middleware } from "@rhythmjs/rhythm/types";
2
+ import type { RhythmHttpContext } from "./context";
4
3
  export interface RhythmRouterContext {
5
- readonly params: Readonly<Record<string, string>>;
4
+ readonly params: Readonly<Record<string, string>>;
6
5
  }
7
6
  export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
8
7
  export interface RhythmRouterOptions {
9
- prefix?: string;
8
+ prefix?: string;
10
9
  }
11
10
  export type RouterEntry = {
12
- readonly kind: "middleware";
13
- readonly fn: Middleware<any>;
11
+ readonly kind: "middleware";
12
+ readonly fn: Middleware<any>;
14
13
  } | {
15
- readonly kind: "route";
16
- readonly method: HttpMethod;
17
- readonly path: string;
18
- readonly handlers: readonly Middleware<any>[];
14
+ readonly kind: "route";
15
+ readonly method: HttpMethod;
16
+ readonly path: string;
17
+ readonly handlers: readonly Middleware<any>[];
19
18
  };
20
19
  type RouteHandler<TContext> = Middleware<TContext & RhythmRouterContext>;
20
+ export declare function joinPath(prefix: string, path: string): string;
21
21
  export declare class RhythmRouter<TContext extends RhythmHttpContext = RhythmHttpContext> {
22
- #private;
23
- constructor(options?: RhythmRouterOptions);
24
- get entries(): readonly RouterEntry[];
25
- use<TExtra extends object>(fn: DeriveMiddleware<TContext, TExtra>): RhythmRouter<TContext & TExtra>;
26
- use(fn: Middleware<TContext>): this;
27
- get<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
28
- get(path: string, ...handlers: RouteHandler<TContext>[]): this;
29
- post<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
30
- post(path: string, ...handlers: RouteHandler<TContext>[]): this;
31
- put<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
32
- put(path: string, ...handlers: RouteHandler<TContext>[]): this;
33
- patch<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
34
- patch(path: string, ...handlers: RouteHandler<TContext>[]): this;
35
- delete<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
36
- delete(path: string, ...handlers: RouteHandler<TContext>[]): this;
37
- middleware(): Middleware<TContext>;
22
+ #private;
23
+ constructor(options?: RhythmRouterOptions);
24
+ get entries(): readonly RouterEntry[];
25
+ use<TExtra extends object>(fn: DeriveMiddleware<TContext, TExtra>): RhythmRouter<TContext & TExtra>;
26
+ use(fn: Middleware<TContext>): this;
27
+ get<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
28
+ get(path: string, ...handlers: RouteHandler<TContext>[]): this;
29
+ post<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
30
+ post(path: string, ...handlers: RouteHandler<TContext>[]): this;
31
+ put<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
32
+ put(path: string, ...handlers: RouteHandler<TContext>[]): this;
33
+ patch<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
34
+ patch(path: string, ...handlers: RouteHandler<TContext>[]): this;
35
+ delete<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
36
+ delete(path: string, ...handlers: RouteHandler<TContext>[]): this;
37
+ middleware(): Middleware<TContext>;
38
38
  }
39
- //#endregion
39
+ export {};