@rhythmjs/router 0.0.8 → 0.0.10
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 +17 -11
- package/dist/adapters/aws-lambda.d.ts +4 -3
- package/dist/adapters/aws-lambda.js +4 -3
- package/dist/adapters/bun.d.ts +4 -4
- package/dist/adapters/bun.js +2 -4
- package/dist/adapters/cloudflare.d.ts +4 -4
- package/dist/adapters/cloudflare.js +10 -17
- package/dist/adapters/deno.d.ts +4 -4
- package/dist/adapters/deno.js +2 -4
- package/dist/adapters/netlify.d.ts +4 -4
- package/dist/adapters/netlify.js +7 -14
- package/dist/adapters/node.d.ts +6 -4
- package/dist/adapters/node.js +5 -13
- package/dist/adapters/service-worker.d.ts +5 -3
- package/dist/adapters/service-worker.js +4 -3
- package/dist/adapters/vercel.d.ts +4 -4
- package/dist/adapters/vercel.js +2 -4
- package/dist/base-BMFEzT_g.js +26 -0
- package/dist/base-DwJhCkwZ.d.ts +14 -0
- package/dist/context-ChHlG-q0.d.ts +24 -0
- package/dist/context.d.ts +2 -20
- package/dist/context.js +4 -1
- package/dist/fetch.d.ts +1 -1
- package/dist/rhythm-router.d.ts +2 -1
- package/dist/rhythm-router.js +11 -7
- package/dist/serve.d.ts +1 -1
- package/package.json +4 -7
- package/dist/radix-tree.d.ts +0 -18
- package/dist/radix-tree.js +0 -150
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
|
|
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
|
-
|
|
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,16 +29,16 @@ 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
|
-
- **
|
|
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
|
|
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.
|
|
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
|
|
39
|
-
- `.
|
|
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.
|
|
40
42
|
- `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`).
|
|
41
43
|
- `toFetchHandler(app)` — bridges a `Rhythm` app to a Web-standard `(Request) => Promise<Response>` handler.
|
|
42
44
|
|
|
@@ -77,15 +79,19 @@ Everything above (`RhythmRouter`, `ctx.response`, etc.) is runtime-agnostic. Two
|
|
|
77
79
|
|
|
78
80
|
## Deploying per runtime
|
|
79
81
|
|
|
80
|
-
One app definition; each runtime has its own adapter under `@rhythmjs/router/adapters/*`, hono-style: import
|
|
82
|
+
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.
|
|
83
|
+
|
|
84
|
+
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`.
|
|
85
|
+
|
|
86
|
+
Fetch-shaped adapters also accept `{ websocket }` — anything with a `handleUpgrade(request, ...extra)` method, such as a [crossws](https://crossws.h3.dev) instance or an `@rhythmjs/ws` adapter. Requests carrying an `upgrade: websocket` header divert to it (with the runtime's extra arguments — Bun's `server`, Deno's `info`, Cloudflare's `env`/`ctx` — forwarded) before the app runs; every other request is untouched.
|
|
81
87
|
|
|
82
|
-
**Node**
|
|
88
|
+
**Node** — `getRequestListener` returns a listener for your own `node:http` server:
|
|
83
89
|
|
|
84
90
|
```ts
|
|
85
91
|
import { createServer } from "node:http";
|
|
86
|
-
import {
|
|
92
|
+
import { getRequestListener } from "@rhythmjs/router/adapters/node";
|
|
87
93
|
|
|
88
|
-
createServer(
|
|
94
|
+
createServer(getRequestListener(app, { maxRequestBodySize: 1024 * 1024 })).listen(3000);
|
|
89
95
|
```
|
|
90
96
|
|
|
91
97
|
(Or skip the adapter entirely and use `serve(app, { port: 3000 })`.)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { RhythmHttpContext } from "../context.js";
|
|
1
|
+
import { t as RhythmHttpContext } from "../context-ChHlG-q0.js";
|
|
2
|
+
import { t as AdapterOptions } from "../base-DwJhCkwZ.js";
|
|
2
3
|
import { AWSLambdaHandler } from "srvx/aws-lambda";
|
|
3
4
|
import { Rhythm } from "@rhythmjs/rhythm";
|
|
4
5
|
//#region src/adapters/aws-lambda.d.ts
|
|
5
|
-
export declare function handle<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders
|
|
6
|
+
export declare function handle<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>, options?: AdapterOptions): AWSLambdaHandler;
|
|
6
7
|
//#endregion
|
|
7
|
-
export type { AWSLambdaHandler };
|
|
8
|
+
export type { AWSLambdaHandler, AdapterOptions };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as createFetchAdapter } from "../base-BMFEzT_g.js";
|
|
2
2
|
import { toLambdaHandler } from "srvx/aws-lambda";
|
|
3
3
|
//#region src/adapters/aws-lambda.ts
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const fetchHandle = createFetchAdapter();
|
|
5
|
+
function handle(app, options = {}) {
|
|
6
|
+
return toLambdaHandler({ fetch: fetchHandle(app, options) });
|
|
6
7
|
}
|
|
7
8
|
//#endregion
|
|
8
9
|
export { handle };
|
package/dist/adapters/bun.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Rhythm } from "@rhythmjs/rhythm";
|
|
1
|
+
import { n as FetchAdapter, t as AdapterOptions } from "../base-DwJhCkwZ.js";
|
|
3
2
|
//#region src/adapters/bun.d.ts
|
|
4
|
-
export declare
|
|
5
|
-
//#endregion
|
|
3
|
+
export declare const handle: FetchAdapter<[server?: unknown]>;
|
|
4
|
+
//#endregion
|
|
5
|
+
export type { AdapterOptions };
|
package/dist/adapters/bun.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as createFetchAdapter } from "../base-BMFEzT_g.js";
|
|
2
2
|
//#region src/adapters/bun.ts
|
|
3
|
-
|
|
4
|
-
return toFetchHandler(app);
|
|
5
|
-
}
|
|
3
|
+
const handle = createFetchAdapter();
|
|
6
4
|
//#endregion
|
|
7
5
|
export { handle };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Rhythm } from "@rhythmjs/rhythm";
|
|
1
|
+
import { n as FetchAdapter, t as AdapterOptions } from "../base-DwJhCkwZ.js";
|
|
3
2
|
//#region src/adapters/cloudflare.d.ts
|
|
4
|
-
export declare
|
|
5
|
-
//#endregion
|
|
3
|
+
export declare const handle: FetchAdapter<[env?: unknown, context?: unknown]>;
|
|
4
|
+
//#endregion
|
|
5
|
+
export type { AdapterOptions };
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as setRuntime, t as createFetchAdapter } from "../base-BMFEzT_g.js";
|
|
2
2
|
//#region src/adapters/cloudflare.ts
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
configurable: true
|
|
15
|
-
});
|
|
16
|
-
return handler(request);
|
|
17
|
-
};
|
|
18
|
-
}
|
|
3
|
+
const handle = createFetchAdapter((request, env, context) => {
|
|
4
|
+
if (env !== void 0 || context !== void 0) setRuntime(request, {
|
|
5
|
+
name: "cloudflare",
|
|
6
|
+
cloudflare: {
|
|
7
|
+
env,
|
|
8
|
+
context
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
});
|
|
19
12
|
//#endregion
|
|
20
13
|
export { handle };
|
package/dist/adapters/deno.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Rhythm } from "@rhythmjs/rhythm";
|
|
1
|
+
import { n as FetchAdapter, t as AdapterOptions } from "../base-DwJhCkwZ.js";
|
|
3
2
|
//#region src/adapters/deno.d.ts
|
|
4
|
-
export declare
|
|
5
|
-
//#endregion
|
|
3
|
+
export declare const handle: FetchAdapter<[info?: unknown]>;
|
|
4
|
+
//#endregion
|
|
5
|
+
export type { AdapterOptions };
|
package/dist/adapters/deno.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as createFetchAdapter } from "../base-BMFEzT_g.js";
|
|
2
2
|
//#region src/adapters/deno.ts
|
|
3
|
-
|
|
4
|
-
return toFetchHandler(app);
|
|
5
|
-
}
|
|
3
|
+
const handle = createFetchAdapter();
|
|
6
4
|
//#endregion
|
|
7
5
|
export { handle };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Rhythm } from "@rhythmjs/rhythm";
|
|
1
|
+
import { n as FetchAdapter, t as AdapterOptions } from "../base-DwJhCkwZ.js";
|
|
3
2
|
//#region src/adapters/netlify.d.ts
|
|
4
|
-
export declare
|
|
5
|
-
//#endregion
|
|
3
|
+
export declare const handle: FetchAdapter<[context?: unknown]>;
|
|
4
|
+
//#endregion
|
|
5
|
+
export type { AdapterOptions };
|
package/dist/adapters/netlify.js
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as setRuntime, t as createFetchAdapter } from "../base-BMFEzT_g.js";
|
|
2
2
|
//#region src/adapters/netlify.ts
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
netlify: { context }
|
|
10
|
-
},
|
|
11
|
-
configurable: true
|
|
12
|
-
});
|
|
13
|
-
return handler(request);
|
|
14
|
-
};
|
|
15
|
-
}
|
|
3
|
+
const handle = createFetchAdapter((request, context) => {
|
|
4
|
+
if (context !== void 0) setRuntime(request, {
|
|
5
|
+
name: "netlify",
|
|
6
|
+
netlify: { context }
|
|
7
|
+
});
|
|
8
|
+
});
|
|
16
9
|
//#endregion
|
|
17
10
|
export { handle };
|
package/dist/adapters/node.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { RhythmHttpContext } from "../context.js";
|
|
2
|
-
import {
|
|
1
|
+
import { t as RhythmHttpContext } from "../context-ChHlG-q0.js";
|
|
2
|
+
import { t as AdapterOptions } from "../base-DwJhCkwZ.js";
|
|
3
|
+
import { NodeHttp1Handler, NodeHttp2Handler } from "srvx";
|
|
3
4
|
import { Rhythm } from "@rhythmjs/rhythm";
|
|
4
5
|
//#region src/adapters/node.d.ts
|
|
5
|
-
export interface NodeHandlerOptions {
|
|
6
|
+
export interface NodeHandlerOptions extends AdapterOptions {
|
|
6
7
|
maxRequestBodySize?: number;
|
|
7
8
|
}
|
|
8
|
-
export
|
|
9
|
+
export type NodeHandler = NodeHttp1Handler & NodeHttp2Handler;
|
|
10
|
+
export declare function getRequestListener<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>, options?: NodeHandlerOptions): NodeHandler;
|
|
9
11
|
//#endregion
|
package/dist/adapters/node.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { errorToResponse } from "../serve.js";
|
|
1
|
+
import { t as createFetchAdapter } from "../base-BMFEzT_g.js";
|
|
3
2
|
import { toNodeHandler } from "srvx/node";
|
|
4
3
|
//#region src/adapters/node.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
return await handler(request);
|
|
10
|
-
} catch (error) {
|
|
11
|
-
return errorToResponse(error);
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
return toNodeHandler(safeHandler, options);
|
|
4
|
+
const fetchHandle = createFetchAdapter();
|
|
5
|
+
function getRequestListener(app, options = {}) {
|
|
6
|
+
return toNodeHandler(fetchHandle(app, options), options);
|
|
15
7
|
}
|
|
16
8
|
//#endregion
|
|
17
|
-
export {
|
|
9
|
+
export { getRequestListener };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { RhythmHttpContext } from "../context.js";
|
|
1
|
+
import { t as RhythmHttpContext } from "../context-ChHlG-q0.js";
|
|
2
|
+
import { t as AdapterOptions } from "../base-DwJhCkwZ.js";
|
|
2
3
|
import { Rhythm } from "@rhythmjs/rhythm";
|
|
3
4
|
//#region src/adapters/service-worker.d.ts
|
|
4
5
|
interface FetchEventLike {
|
|
5
6
|
readonly request: Request;
|
|
6
7
|
respondWith(response: Response | Promise<Response>): void;
|
|
7
8
|
}
|
|
8
|
-
export declare function handle<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders
|
|
9
|
-
//#endregion
|
|
9
|
+
export declare function handle<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>, options?: AdapterOptions): (event: FetchEventLike) => void;
|
|
10
|
+
//#endregion
|
|
11
|
+
export type { AdapterOptions };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as createFetchAdapter } from "../base-BMFEzT_g.js";
|
|
2
2
|
//#region src/adapters/service-worker.ts
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
const fetchHandle = createFetchAdapter();
|
|
4
|
+
function handle(app, options = {}) {
|
|
5
|
+
const handler = fetchHandle(app, options);
|
|
5
6
|
return (event) => {
|
|
6
7
|
event.respondWith(handler(event.request));
|
|
7
8
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Rhythm } from "@rhythmjs/rhythm";
|
|
1
|
+
import { n as FetchAdapter, t as AdapterOptions } from "../base-DwJhCkwZ.js";
|
|
3
2
|
//#region src/adapters/vercel.d.ts
|
|
4
|
-
export declare
|
|
5
|
-
//#endregion
|
|
3
|
+
export declare const handle: FetchAdapter;
|
|
4
|
+
//#endregion
|
|
5
|
+
export type { AdapterOptions };
|
package/dist/adapters/vercel.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as createFetchAdapter } from "../base-BMFEzT_g.js";
|
|
2
2
|
//#region src/adapters/vercel.ts
|
|
3
|
-
|
|
4
|
-
return toFetchHandler(app);
|
|
5
|
-
}
|
|
3
|
+
const handle = createFetchAdapter();
|
|
6
4
|
//#endregion
|
|
7
5
|
export { handle };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { toFetchHandler } from "./fetch.js";
|
|
2
|
+
import { errorToResponse } from "./serve.js";
|
|
3
|
+
//#region src/adapters/base.ts
|
|
4
|
+
function setRuntime(request, runtime) {
|
|
5
|
+
Object.defineProperty(request, "runtime", {
|
|
6
|
+
value: runtime,
|
|
7
|
+
configurable: true
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function createFetchAdapter(enrich) {
|
|
11
|
+
return (app, options = {}) => {
|
|
12
|
+
const handler = toFetchHandler(app);
|
|
13
|
+
const onError = options.onError ?? errorToResponse;
|
|
14
|
+
return async (request, ...extra) => {
|
|
15
|
+
if (options.websocket && request.headers.get("upgrade")?.toLowerCase() === "websocket") return options.websocket.handleUpgrade(request, ...extra);
|
|
16
|
+
try {
|
|
17
|
+
enrich?.(request, ...extra);
|
|
18
|
+
return await handler(request);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return onError(error);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { setRuntime as n, createFetchAdapter as t };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { t as RhythmHttpContext } from "./context-ChHlG-q0.js";
|
|
2
|
+
import { ErrorHandler, ServerRuntimeContext } from "srvx";
|
|
3
|
+
import { Rhythm } from "@rhythmjs/rhythm";
|
|
4
|
+
//#region src/adapters/base.d.ts
|
|
5
|
+
interface WebSocketUpgrader {
|
|
6
|
+
handleUpgrade(request: Request, ...extra: unknown[]): Response | undefined | Promise<Response | undefined>;
|
|
7
|
+
}
|
|
8
|
+
interface AdapterOptions {
|
|
9
|
+
onError?: ErrorHandler;
|
|
10
|
+
websocket?: WebSocketUpgrader;
|
|
11
|
+
}
|
|
12
|
+
type FetchAdapter<TExtra extends unknown[] = []> = <TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>, options?: AdapterOptions) => (request: Request, ...extra: TExtra) => Promise<Response>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { FetchAdapter as n, AdapterOptions as t };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ServerRuntimeContext } from "srvx";
|
|
2
|
+
//#region src/context.d.ts
|
|
3
|
+
type RhythmRuntime = ServerRuntimeContext;
|
|
4
|
+
declare function getRuntime(request: Request): RhythmRuntime | undefined;
|
|
5
|
+
type RhythmResponseBody = string | ArrayBuffer | Uint8Array | Blob | FormData | URLSearchParams | ReadableStream<Uint8Array> | null;
|
|
6
|
+
declare class RhythmResponse {
|
|
7
|
+
status: number;
|
|
8
|
+
statusText: string | undefined;
|
|
9
|
+
headers: Headers;
|
|
10
|
+
body: RhythmResponseBody;
|
|
11
|
+
}
|
|
12
|
+
interface RhythmHttpContext {
|
|
13
|
+
readonly request: Request;
|
|
14
|
+
readonly response: RhythmResponse;
|
|
15
|
+
json(data: unknown, status?: number): void;
|
|
16
|
+
text(body: string, status?: number): void;
|
|
17
|
+
html(body: string, status?: number): void;
|
|
18
|
+
error(status: number, message?: string): void;
|
|
19
|
+
redirect(url: string, status?: number): void;
|
|
20
|
+
}
|
|
21
|
+
declare function createHttpContext(request: Request): RhythmHttpContext;
|
|
22
|
+
declare function toResponse(response: RhythmResponse): Response;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { createHttpContext as a, RhythmRuntime as i, RhythmResponse as n, getRuntime as o, RhythmResponseBody as r, toResponse as s, RhythmHttpContext as t };
|
package/dist/context.d.ts
CHANGED
|
@@ -1,20 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
export declare class RhythmResponse {
|
|
4
|
-
status: number;
|
|
5
|
-
statusText: string | undefined;
|
|
6
|
-
headers: Headers;
|
|
7
|
-
body: RhythmResponseBody;
|
|
8
|
-
}
|
|
9
|
-
export interface RhythmHttpContext {
|
|
10
|
-
readonly request: Request;
|
|
11
|
-
readonly response: RhythmResponse;
|
|
12
|
-
json(data: unknown, status?: number): void;
|
|
13
|
-
text(body: string, status?: number): void;
|
|
14
|
-
html(body: string, status?: number): void;
|
|
15
|
-
error(status: number, message?: string): void;
|
|
16
|
-
redirect(url: string, status?: number): void;
|
|
17
|
-
}
|
|
18
|
-
export declare function createHttpContext(request: Request): RhythmHttpContext;
|
|
19
|
-
export declare function toResponse(response: RhythmResponse): Response;
|
|
20
|
-
//#endregion
|
|
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 };
|
package/dist/context.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
//#region src/context.ts
|
|
2
|
+
function getRuntime(request) {
|
|
3
|
+
return request.runtime;
|
|
4
|
+
}
|
|
2
5
|
var RhythmResponse = class {
|
|
3
6
|
status = 200;
|
|
4
7
|
statusText = void 0;
|
|
@@ -63,4 +66,4 @@ function toResponse(response) {
|
|
|
63
66
|
});
|
|
64
67
|
}
|
|
65
68
|
//#endregion
|
|
66
|
-
export { RhythmResponse, createHttpContext, toResponse };
|
|
69
|
+
export { RhythmResponse, createHttpContext, getRuntime, toResponse };
|
package/dist/fetch.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RhythmHttpContext } from "./context.js";
|
|
1
|
+
import { t as RhythmHttpContext } from "./context-ChHlG-q0.js";
|
|
2
2
|
import { Rhythm } from "@rhythmjs/rhythm";
|
|
3
3
|
//#region src/fetch.d.ts
|
|
4
4
|
export declare function toFetchHandler<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>): (request: Request) => Promise<Response>;
|
package/dist/rhythm-router.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RhythmHttpContext } from "./context.js";
|
|
1
|
+
import { t as RhythmHttpContext } from "./context-ChHlG-q0.js";
|
|
2
2
|
import { DeriveMiddleware, Middleware } from "@rhythmjs/rhythm/types";
|
|
3
3
|
//#region src/rhythm-router.d.ts
|
|
4
4
|
export interface RhythmRouterContext {
|
|
@@ -18,6 +18,7 @@ export type RouterEntry = {
|
|
|
18
18
|
readonly handlers: readonly Middleware<any>[];
|
|
19
19
|
};
|
|
20
20
|
type RouteHandler<TContext> = Middleware<TContext & RhythmRouterContext>;
|
|
21
|
+
export declare function joinPath(prefix: string, path: string): string;
|
|
21
22
|
export declare class RhythmRouter<TContext extends RhythmHttpContext = RhythmHttpContext> {
|
|
22
23
|
#private;
|
|
23
24
|
constructor(options?: RhythmRouterOptions);
|
package/dist/rhythm-router.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { createNode, insertRoute, joinPath, lookupRoute } from "./radix-tree.js";
|
|
2
1
|
import { compose } from "@rhythmjs/rhythm/compose";
|
|
2
|
+
import { addRoute, createRouter, findRoute } from "rou3";
|
|
3
3
|
//#region src/rhythm-router.ts
|
|
4
|
+
function joinPath(prefix, path) {
|
|
5
|
+
if (!prefix) return path;
|
|
6
|
+
return `${prefix.endsWith("/") ? prefix.slice(0, -1) : prefix}${path.startsWith("/") ? path : `/${path}`}`;
|
|
7
|
+
}
|
|
4
8
|
var RhythmRouter = class {
|
|
5
9
|
#options;
|
|
6
10
|
#entries = [];
|
|
@@ -48,14 +52,14 @@ var RhythmRouter = class {
|
|
|
48
52
|
#compile() {
|
|
49
53
|
const dispatchFor = (tree) => {
|
|
50
54
|
return async (ctx, next) => {
|
|
51
|
-
const match =
|
|
55
|
+
const match = findRoute(tree, ctx.request.method, new URL(ctx.request.url).pathname);
|
|
52
56
|
if (!match) {
|
|
53
57
|
await next();
|
|
54
58
|
return;
|
|
55
59
|
}
|
|
56
|
-
await match.
|
|
60
|
+
await match.data({
|
|
57
61
|
...ctx,
|
|
58
|
-
params: match.params
|
|
62
|
+
params: match.params ?? {}
|
|
59
63
|
}, next);
|
|
60
64
|
};
|
|
61
65
|
};
|
|
@@ -68,11 +72,11 @@ var RhythmRouter = class {
|
|
|
68
72
|
i++;
|
|
69
73
|
continue;
|
|
70
74
|
}
|
|
71
|
-
const tree =
|
|
75
|
+
const tree = createRouter();
|
|
72
76
|
while (i < this.#entries.length) {
|
|
73
77
|
const route = this.#entries[i];
|
|
74
78
|
if (route.kind !== "route") break;
|
|
75
|
-
|
|
79
|
+
addRoute(tree, route.method, route.path, compose(route.handlers));
|
|
76
80
|
i++;
|
|
77
81
|
}
|
|
78
82
|
stack.push(dispatchFor(tree));
|
|
@@ -87,4 +91,4 @@ var RhythmRouter = class {
|
|
|
87
91
|
}
|
|
88
92
|
};
|
|
89
93
|
//#endregion
|
|
90
|
-
export { RhythmRouter };
|
|
94
|
+
export { RhythmRouter, joinPath };
|
package/dist/serve.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RhythmHttpContext } from "./context.js";
|
|
1
|
+
import { t as RhythmHttpContext } from "./context-ChHlG-q0.js";
|
|
2
2
|
import { ErrorHandler, Server, ServerMiddleware, ServerOptions, ServerPlugin } from "srvx";
|
|
3
3
|
import { Rhythm } from "@rhythmjs/rhythm";
|
|
4
4
|
//#region src/serve.d.ts
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhythmjs/router",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.10",
|
|
4
|
+
"description": "HTTP router for the Rhythm middleware kernel, matching with rou3, with Bun, Node, and Deno adapters.",
|
|
5
5
|
"homepage": "https://rhythm.js.org/router",
|
|
6
6
|
"license": "ISC",
|
|
7
7
|
"repository": {
|
|
@@ -19,10 +19,6 @@
|
|
|
19
19
|
"types": "./dist/rhythm-router.d.ts",
|
|
20
20
|
"default": "./dist/rhythm-router.js"
|
|
21
21
|
},
|
|
22
|
-
"./radix-tree": {
|
|
23
|
-
"types": "./dist/radix-tree.d.ts",
|
|
24
|
-
"default": "./dist/radix-tree.js"
|
|
25
|
-
},
|
|
26
22
|
"./context": {
|
|
27
23
|
"types": "./dist/context.d.ts",
|
|
28
24
|
"default": "./dist/context.js"
|
|
@@ -77,8 +73,9 @@
|
|
|
77
73
|
"access": "public"
|
|
78
74
|
},
|
|
79
75
|
"dependencies": {
|
|
76
|
+
"rou3": "^0.11.0",
|
|
80
77
|
"srvx": "^1.0.5",
|
|
81
|
-
"@rhythmjs/rhythm": "0.0.
|
|
78
|
+
"@rhythmjs/rhythm": "0.0.10"
|
|
82
79
|
},
|
|
83
80
|
"devDependencies": {
|
|
84
81
|
"@types/node": "^26.6.3",
|
package/dist/radix-tree.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
//#region src/radix-tree.d.ts
|
|
2
|
-
export declare class TreeNode<TMethod extends string = string, TPayload = unknown> {
|
|
3
|
-
path: string;
|
|
4
|
-
indices: string;
|
|
5
|
-
children: TreeNode<TMethod, TPayload>[];
|
|
6
|
-
paramChild: TreeNode<TMethod, TPayload> | null;
|
|
7
|
-
paramName: string;
|
|
8
|
-
wildcardChild: TreeNode<TMethod, TPayload> | null;
|
|
9
|
-
methods: Map<TMethod, TPayload> | null;
|
|
10
|
-
}
|
|
11
|
-
export declare function createNode<TMethod extends string = string, TPayload = unknown>(): TreeNode<TMethod, TPayload>;
|
|
12
|
-
export declare function insertRoute<TMethod extends string, TPayload>(root: TreeNode<TMethod, TPayload>, method: TMethod, path: string, payload: TPayload): void;
|
|
13
|
-
export declare function lookupRoute<TMethod extends string, TPayload>(root: TreeNode<TMethod, TPayload>, method: string, pathname: string): {
|
|
14
|
-
payload: TPayload;
|
|
15
|
-
params: Record<string, string>;
|
|
16
|
-
} | null;
|
|
17
|
-
export declare function joinPath(prefix: string, path: string): string;
|
|
18
|
-
//#endregion
|
package/dist/radix-tree.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
//#region src/radix-tree.ts
|
|
2
|
-
var TreeNode = class {
|
|
3
|
-
path = "";
|
|
4
|
-
indices = "";
|
|
5
|
-
children = [];
|
|
6
|
-
paramChild = null;
|
|
7
|
-
paramName = "";
|
|
8
|
-
wildcardChild = null;
|
|
9
|
-
methods = null;
|
|
10
|
-
};
|
|
11
|
-
function createNode() {
|
|
12
|
-
return new TreeNode();
|
|
13
|
-
}
|
|
14
|
-
function commonPrefixLength(a, b) {
|
|
15
|
-
const max = a.length < b.length ? a.length : b.length;
|
|
16
|
-
let i = 0;
|
|
17
|
-
while (i < max && a.charCodeAt(i) === b.charCodeAt(i)) i++;
|
|
18
|
-
return i;
|
|
19
|
-
}
|
|
20
|
-
function splitChild(node, at) {
|
|
21
|
-
const tail = new TreeNode();
|
|
22
|
-
tail.path = node.path.slice(at);
|
|
23
|
-
tail.indices = node.indices;
|
|
24
|
-
tail.children = node.children;
|
|
25
|
-
tail.paramChild = node.paramChild;
|
|
26
|
-
tail.paramName = node.paramName;
|
|
27
|
-
tail.wildcardChild = node.wildcardChild;
|
|
28
|
-
tail.methods = node.methods;
|
|
29
|
-
node.path = node.path.slice(0, at);
|
|
30
|
-
node.children = [tail];
|
|
31
|
-
node.indices = tail.path.charAt(0);
|
|
32
|
-
node.paramChild = null;
|
|
33
|
-
node.paramName = "";
|
|
34
|
-
node.wildcardChild = null;
|
|
35
|
-
node.methods = null;
|
|
36
|
-
}
|
|
37
|
-
function insertAt(node, path, method, payload) {
|
|
38
|
-
if (path.length === 0) {
|
|
39
|
-
if (!node.methods) node.methods = /* @__PURE__ */ new Map();
|
|
40
|
-
node.methods.set(method, payload);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (path.charCodeAt(0) === 58) {
|
|
44
|
-
const slashIndex = path.indexOf("/");
|
|
45
|
-
const name = slashIndex === -1 ? path.slice(1) : path.slice(1, slashIndex);
|
|
46
|
-
const rest = slashIndex === -1 ? "" : path.slice(slashIndex);
|
|
47
|
-
if (!node.paramChild) {
|
|
48
|
-
node.paramChild = new TreeNode();
|
|
49
|
-
node.paramChild.paramName = name;
|
|
50
|
-
}
|
|
51
|
-
insertAt(node.paramChild, rest, method, payload);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (path.charCodeAt(0) === 42) {
|
|
55
|
-
if (path.length !== 1) throw new Error("wildcard \"*\" is only allowed at the end of a path");
|
|
56
|
-
if (!node.wildcardChild) node.wildcardChild = new TreeNode();
|
|
57
|
-
if (!node.wildcardChild.methods) node.wildcardChild.methods = /* @__PURE__ */ new Map();
|
|
58
|
-
node.wildcardChild.methods.set(method, payload);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const colonIndex = path.indexOf(":");
|
|
62
|
-
const starIndex = path.indexOf("*");
|
|
63
|
-
let staticEnd = path.length;
|
|
64
|
-
if (colonIndex !== -1) staticEnd = colonIndex;
|
|
65
|
-
if (starIndex !== -1 && starIndex < staticEnd) staticEnd = starIndex;
|
|
66
|
-
const staticPart = path.slice(0, staticEnd);
|
|
67
|
-
const firstChar = path.charAt(0);
|
|
68
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
69
|
-
if (node.indices.charAt(i) !== firstChar) continue;
|
|
70
|
-
const child = node.children[i];
|
|
71
|
-
const cpl = commonPrefixLength(staticPart, child.path);
|
|
72
|
-
if (cpl === 0) continue;
|
|
73
|
-
if (cpl < child.path.length) splitChild(child, cpl);
|
|
74
|
-
insertAt(child, path.slice(cpl), method, payload);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const child = new TreeNode();
|
|
78
|
-
child.path = staticPart;
|
|
79
|
-
node.children.push(child);
|
|
80
|
-
node.indices += firstChar;
|
|
81
|
-
insertAt(child, path.slice(staticPart.length), method, payload);
|
|
82
|
-
}
|
|
83
|
-
function expandOptionalParams(path) {
|
|
84
|
-
if (!path.includes("?")) return [path];
|
|
85
|
-
const segments = path.split("/");
|
|
86
|
-
let firstOptional = -1;
|
|
87
|
-
for (let i = 0; i < segments.length; i++) {
|
|
88
|
-
const segment = segments[i];
|
|
89
|
-
const isOptional = segment.length > 2 && segment.charCodeAt(0) === 58 && segment.endsWith("?");
|
|
90
|
-
if (!isOptional && segment.includes("?")) throw new Error(`"?" is only allowed to mark an optional param like ":name?" (in "${path}")`);
|
|
91
|
-
if (firstOptional !== -1 && !isOptional) throw new Error(`only optional params may follow an optional param (in "${path}")`);
|
|
92
|
-
if (isOptional && firstOptional === -1) firstOptional = i;
|
|
93
|
-
}
|
|
94
|
-
const variants = [];
|
|
95
|
-
for (let end = firstOptional; end <= segments.length; end++) {
|
|
96
|
-
const parts = segments.slice(0, end).map((segment) => segment.endsWith("?") ? segment.slice(0, -1) : segment);
|
|
97
|
-
variants.push(parts.join("/") || "/");
|
|
98
|
-
}
|
|
99
|
-
return variants;
|
|
100
|
-
}
|
|
101
|
-
function insertRoute(root, method, path, payload) {
|
|
102
|
-
for (const variant of expandOptionalParams(path)) insertAt(root, variant, method, payload);
|
|
103
|
-
}
|
|
104
|
-
function search(node, method, path) {
|
|
105
|
-
if (path.length === 0) {
|
|
106
|
-
const payload = node.methods?.get(method);
|
|
107
|
-
if (payload !== void 0) return {
|
|
108
|
-
payload,
|
|
109
|
-
params: {}
|
|
110
|
-
};
|
|
111
|
-
} else {
|
|
112
|
-
const firstChar = path.charAt(0);
|
|
113
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
114
|
-
if (node.indices.charAt(i) !== firstChar) continue;
|
|
115
|
-
const child = node.children[i];
|
|
116
|
-
if (!path.startsWith(child.path)) continue;
|
|
117
|
-
const result = search(child, method, path.slice(child.path.length));
|
|
118
|
-
if (result) return result;
|
|
119
|
-
}
|
|
120
|
-
if (node.paramChild) {
|
|
121
|
-
const slashIndex = path.indexOf("/");
|
|
122
|
-
const value = slashIndex === -1 ? path : path.slice(0, slashIndex);
|
|
123
|
-
if (value.length > 0) {
|
|
124
|
-
const rest = slashIndex === -1 ? "" : path.slice(slashIndex);
|
|
125
|
-
const result = search(node.paramChild, method, rest);
|
|
126
|
-
if (result) {
|
|
127
|
-
result.params[node.paramChild.paramName] = decodeURIComponent(value);
|
|
128
|
-
return result;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (node.wildcardChild) {
|
|
134
|
-
const payload = node.wildcardChild.methods?.get(method);
|
|
135
|
-
if (payload !== void 0) return {
|
|
136
|
-
payload,
|
|
137
|
-
params: { "*": decodeURIComponent(path) }
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
function lookupRoute(root, method, pathname) {
|
|
143
|
-
return search(root, method, pathname);
|
|
144
|
-
}
|
|
145
|
-
function joinPath(prefix, path) {
|
|
146
|
-
if (!prefix) return path;
|
|
147
|
-
return `${prefix.endsWith("/") ? prefix.slice(0, -1) : prefix}${path.startsWith("/") ? path : `/${path}`}`;
|
|
148
|
-
}
|
|
149
|
-
//#endregion
|
|
150
|
-
export { TreeNode, createNode, insertRoute, joinPath, lookupRoute };
|