@rhythmjs/router 0.0.6 → 0.0.8
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 +113 -15
- package/dist/adapters/aws-lambda.d.ts +7 -0
- package/dist/adapters/aws-lambda.js +8 -0
- package/dist/adapters/bun.d.ts +5 -2
- package/dist/adapters/bun.js +7 -2
- package/dist/adapters/cloudflare.d.ts +5 -0
- package/dist/adapters/cloudflare.js +20 -0
- package/dist/adapters/deno.d.ts +5 -2
- package/dist/adapters/deno.js +7 -2
- package/dist/adapters/netlify.d.ts +5 -0
- package/dist/adapters/netlify.js +17 -0
- package/dist/adapters/node.d.ts +5 -8
- package/dist/adapters/node.js +11 -111
- package/dist/adapters/service-worker.d.ts +9 -0
- package/dist/adapters/service-worker.js +10 -0
- package/dist/adapters/vercel.d.ts +5 -0
- package/dist/adapters/vercel.js +7 -0
- package/dist/context.d.ts +20 -0
- package/dist/{adapters/context.js → context.js} +1 -1
- package/dist/{adapters/web-std.d.ts → fetch.d.ts} +2 -2
- package/dist/fetch.js +16 -0
- package/dist/rhythm-router.d.ts +1 -1
- package/dist/serve.d.ts +9 -0
- package/dist/serve.js +21 -0
- package/package.json +39 -10
- package/dist/adapters/context.d.ts +0 -2
- package/dist/adapters/web-std.js +0 -11
- package/dist/context-B0711qPV.d.ts +0 -21
package/README.md
CHANGED
|
@@ -9,15 +9,15 @@ Web-standard HTTP routing on top of `@rhythmjs/rhythm`. `RhythmRouter` matches r
|
|
|
9
9
|
```ts
|
|
10
10
|
import { Rhythm } from "@rhythmjs/rhythm";
|
|
11
11
|
import { RhythmRouter } from "@rhythmjs/router";
|
|
12
|
-
import {
|
|
13
|
-
import type { RhythmHttpContext } from "@rhythmjs/router/
|
|
12
|
+
import { serve } from "@rhythmjs/router/serve";
|
|
13
|
+
import type { RhythmHttpContext } from "@rhythmjs/router/context";
|
|
14
14
|
|
|
15
15
|
const usersRouter = new RhythmRouter({ prefix: "/users" }).get("/:id", (ctx) => {
|
|
16
16
|
ctx.json({ id: ctx.params.id });
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
const app = new Rhythm<RhythmHttpContext>().use(usersRouter.
|
|
20
|
-
|
|
19
|
+
const app = new Rhythm<RhythmHttpContext>().use(usersRouter.middleware());
|
|
20
|
+
serve(app, { port: 3000 });
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
A fuller runnable version, including nested prefixes and a fallback route, is at [`examples/router`](../../examples/router).
|
|
@@ -37,18 +37,116 @@ A fuller runnable version, including nested prefixes and a fallback route, is at
|
|
|
37
37
|
- `.get/.post/.put/.patch/.delete(path, ...handlers)` — register a route; `path` may contain `:param` segments.
|
|
38
38
|
- `.use(fn)` — plain middleware. `.use(child)` — mount a nested `RhythmRouter` (prefixes compose).
|
|
39
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
|
|
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`).
|
|
41
41
|
- `toFetchHandler(app)` — bridges a `Rhythm` app to a Web-standard `(Request) => Promise<Response>` handler.
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## Serving
|
|
44
44
|
|
|
45
|
-
Everything above (`RhythmRouter`, `ctx.response`, etc.) is runtime-agnostic
|
|
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):
|
|
46
46
|
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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.
|
|
49
|
+
|
|
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`.
|
|
51
|
+
|
|
52
|
+
### Extending: CORS, WebSockets, and similar
|
|
53
|
+
|
|
54
|
+
- **`middleware`** — srvx middlewares (`(request, next) => Response`) run around the whole app, the natural place for CORS, logging, or auth gates:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
serve(app, {
|
|
58
|
+
middleware: [
|
|
59
|
+
async (request, next) => {
|
|
60
|
+
if (request.method === "OPTIONS")
|
|
61
|
+
return new Response(null, { status: 204, headers: { "access-control-allow-origin": "*" } });
|
|
62
|
+
const response = await next();
|
|
63
|
+
response.headers.set("access-control-allow-origin", "*");
|
|
64
|
+
return response;
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
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`):
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
serve(app, { plugins: [(server) => wireWebSockets(server)] });
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- **`error`** — replace the default error-to-response mapping.
|
|
77
|
+
|
|
78
|
+
## Deploying per runtime
|
|
79
|
+
|
|
80
|
+
One app definition; each runtime has its own adapter under `@rhythmjs/router/adapters/*`, hono-style: import `handle` from your target's adapter and export what the platform expects.
|
|
81
|
+
|
|
82
|
+
**Node**
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { createServer } from "node:http";
|
|
86
|
+
import { handle } from "@rhythmjs/router/adapters/node";
|
|
87
|
+
|
|
88
|
+
createServer(handle(app, { maxRequestBodySize: 1024 * 1024 })).listen(3000);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
(Or skip the adapter entirely and use `serve(app, { port: 3000 })`.)
|
|
92
|
+
|
|
93
|
+
**Bun**
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { handle } from "@rhythmjs/router/adapters/bun";
|
|
97
|
+
|
|
98
|
+
Bun.serve({ port: 3000, fetch: handle(app) });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Deno**
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { handle } from "@rhythmjs/router/adapters/deno";
|
|
105
|
+
|
|
106
|
+
Deno.serve({ port: 3000 }, handle(app));
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Vercel** — in a catch-all route file:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { handle } from "@rhythmjs/router/adapters/vercel";
|
|
113
|
+
|
|
114
|
+
const handler = handle(app);
|
|
115
|
+
export const GET = handler;
|
|
116
|
+
export const POST = handler; // …and the other methods you serve
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Cloudflare Workers** — `env` and `ctx` are exposed to middleware as `ctx.request.runtime.cloudflare`:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { handle } from "@rhythmjs/router/adapters/cloudflare";
|
|
123
|
+
|
|
124
|
+
export default { fetch: handle(app) };
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**AWS Lambda** — API Gateway v1/v2 events, translated by srvx:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { handle } from "@rhythmjs/router/adapters/aws-lambda";
|
|
131
|
+
|
|
132
|
+
export const lambda = handle(app);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Netlify Edge Functions** — the Netlify context is exposed as `ctx.request.runtime.netlify`:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { handle } from "@rhythmjs/router/adapters/netlify";
|
|
139
|
+
|
|
140
|
+
export default handle(app);
|
|
141
|
+
export const config = { path: "/*" };
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Service workers**
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { handle } from "@rhythmjs/router/adapters/service-worker";
|
|
148
|
+
|
|
149
|
+
addEventListener("fetch", handle(app));
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Any other fetch-based runtime works with the raw primitive: `toFetchHandler(app)` from `@rhythmjs/router/fetch`.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { RhythmHttpContext } from "../context.js";
|
|
2
|
+
import { AWSLambdaHandler } from "srvx/aws-lambda";
|
|
3
|
+
import { Rhythm } from "@rhythmjs/rhythm";
|
|
4
|
+
//#region src/adapters/aws-lambda.d.ts
|
|
5
|
+
export declare function handle<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>): AWSLambdaHandler;
|
|
6
|
+
//#endregion
|
|
7
|
+
export type { AWSLambdaHandler };
|
package/dist/adapters/bun.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { RhythmHttpContext } from "../context.js";
|
|
2
|
+
import { Rhythm } from "@rhythmjs/rhythm";
|
|
3
|
+
//#region src/adapters/bun.d.ts
|
|
4
|
+
export declare function handle<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>): (request: Request) => Promise<Response>;
|
|
5
|
+
//#endregion
|
package/dist/adapters/bun.js
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { RhythmHttpContext } from "../context.js";
|
|
2
|
+
import { Rhythm } from "@rhythmjs/rhythm";
|
|
3
|
+
//#region src/adapters/cloudflare.d.ts
|
|
4
|
+
export declare function handle<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>): (request: Request, env?: unknown, context?: unknown) => Promise<Response>;
|
|
5
|
+
//#endregion
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { toFetchHandler } from "../fetch.js";
|
|
2
|
+
//#region src/adapters/cloudflare.ts
|
|
3
|
+
function handle(app) {
|
|
4
|
+
const handler = toFetchHandler(app);
|
|
5
|
+
return (request, env, context) => {
|
|
6
|
+
if (env !== void 0 || context !== void 0) Object.defineProperty(request, "runtime", {
|
|
7
|
+
value: {
|
|
8
|
+
name: "cloudflare",
|
|
9
|
+
cloudflare: {
|
|
10
|
+
env,
|
|
11
|
+
context
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
configurable: true
|
|
15
|
+
});
|
|
16
|
+
return handler(request);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { handle };
|
package/dist/adapters/deno.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { RhythmHttpContext } from "../context.js";
|
|
2
|
+
import { Rhythm } from "@rhythmjs/rhythm";
|
|
3
|
+
//#region src/adapters/deno.d.ts
|
|
4
|
+
export declare function handle<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>): (request: Request) => Promise<Response>;
|
|
5
|
+
//#endregion
|
package/dist/adapters/deno.js
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { RhythmHttpContext } from "../context.js";
|
|
2
|
+
import { Rhythm } from "@rhythmjs/rhythm";
|
|
3
|
+
//#region src/adapters/netlify.d.ts
|
|
4
|
+
export declare function handle<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>): (request: Request, context?: unknown) => Promise<Response>;
|
|
5
|
+
//#endregion
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { toFetchHandler } from "../fetch.js";
|
|
2
|
+
//#region src/adapters/netlify.ts
|
|
3
|
+
function handle(app) {
|
|
4
|
+
const handler = toFetchHandler(app);
|
|
5
|
+
return (request, context) => {
|
|
6
|
+
if (context !== void 0) Object.defineProperty(request, "runtime", {
|
|
7
|
+
value: {
|
|
8
|
+
name: "netlify",
|
|
9
|
+
netlify: { context }
|
|
10
|
+
},
|
|
11
|
+
configurable: true
|
|
12
|
+
});
|
|
13
|
+
return handler(request);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { handle };
|
package/dist/adapters/node.d.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RhythmHttpContext } from "../context.js";
|
|
2
|
+
import { NodeHttpHandler } from "srvx";
|
|
2
3
|
import { Rhythm } from "@rhythmjs/rhythm";
|
|
3
|
-
import { IncomingMessage, ServerResponse } from "node:http";
|
|
4
4
|
//#region src/adapters/node.d.ts
|
|
5
|
-
export
|
|
6
|
-
|
|
5
|
+
export interface NodeHandlerOptions {
|
|
6
|
+
maxRequestBodySize?: number;
|
|
7
7
|
}
|
|
8
|
-
export
|
|
9
|
-
bodyLimit?: number | false;
|
|
10
|
-
}
|
|
11
|
-
export declare function toNodeHandler<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>, options?: RhythmNodeHandlerOptions): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
8
|
+
export declare function handle<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>, options?: NodeHandlerOptions): NodeHttpHandler;
|
|
12
9
|
//#endregion
|
package/dist/adapters/node.js
CHANGED
|
@@ -1,117 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { toFetchHandler } from "../fetch.js";
|
|
2
|
+
import { errorToResponse } from "../serve.js";
|
|
3
|
+
import { toNodeHandler } from "srvx/node";
|
|
3
4
|
//#region src/adapters/node.ts
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
super(`request body exceeds the ${limit} byte limit`);
|
|
8
|
-
this.name = "RhythmBodyTooLargeError";
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
function readRawBody(req, limit) {
|
|
12
|
-
return new Promise((resolve, reject) => {
|
|
13
|
-
const chunks = [];
|
|
14
|
-
let received = 0;
|
|
15
|
-
let tooLarge = false;
|
|
16
|
-
let settled = false;
|
|
17
|
-
req.on("data", (chunk) => {
|
|
18
|
-
received += chunk.length;
|
|
19
|
-
if (received > limit) {
|
|
20
|
-
if (!tooLarge) {
|
|
21
|
-
tooLarge = true;
|
|
22
|
-
settled = true;
|
|
23
|
-
reject(new RhythmBodyTooLargeError(limit));
|
|
24
|
-
}
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
chunks.push(chunk);
|
|
28
|
-
});
|
|
29
|
-
req.on("end", () => {
|
|
30
|
-
if (settled) return;
|
|
31
|
-
settled = true;
|
|
32
|
-
resolve(Buffer.concat(chunks));
|
|
33
|
-
});
|
|
34
|
-
req.on("error", (err) => {
|
|
35
|
-
if (settled) return;
|
|
36
|
-
settled = true;
|
|
37
|
-
reject(err);
|
|
38
|
-
});
|
|
39
|
-
req.on("aborted", () => {
|
|
40
|
-
if (settled) return;
|
|
41
|
-
settled = true;
|
|
42
|
-
reject(/* @__PURE__ */ new Error("request aborted"));
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
async function toWebRequest(req, bodyLimit) {
|
|
47
|
-
const method = req.method ?? "GET";
|
|
48
|
-
const url = `http://${req.headers.host ?? "localhost"}${req.url ?? "/"}`;
|
|
49
|
-
const headers = new Headers();
|
|
50
|
-
for (const [key, value] of Object.entries(req.headers)) {
|
|
51
|
-
if (value === void 0) continue;
|
|
52
|
-
if (Array.isArray(value)) for (const entry of value) headers.append(key, entry);
|
|
53
|
-
else headers.set(key, value);
|
|
54
|
-
}
|
|
55
|
-
if (!(method !== "GET" && method !== "HEAD")) return new Request(url, {
|
|
56
|
-
method,
|
|
57
|
-
headers
|
|
58
|
-
});
|
|
59
|
-
if (bodyLimit === false) return new Request(url, {
|
|
60
|
-
method,
|
|
61
|
-
headers,
|
|
62
|
-
body: Readable.toWeb(req),
|
|
63
|
-
duplex: "half"
|
|
64
|
-
});
|
|
65
|
-
const body = await readRawBody(req, bodyLimit);
|
|
66
|
-
return new Request(url, {
|
|
67
|
-
method,
|
|
68
|
-
headers,
|
|
69
|
-
body
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
function toNodeHeaders(headers) {
|
|
73
|
-
const result = {};
|
|
74
|
-
const setCookie = typeof headers.getSetCookie === "function" ? headers.getSetCookie() : [];
|
|
75
|
-
if (setCookie.length > 0) result["set-cookie"] = setCookie;
|
|
76
|
-
for (const [key, value] of headers) {
|
|
77
|
-
if (key === "set-cookie") continue;
|
|
78
|
-
result[key] = value;
|
|
79
|
-
}
|
|
80
|
-
return result;
|
|
81
|
-
}
|
|
82
|
-
async function writeWebResponse(response, res) {
|
|
83
|
-
res.writeHead(response.status, toNodeHeaders(response.headers));
|
|
84
|
-
if (!response.body) {
|
|
85
|
-
res.end();
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
await new Promise((resolve, reject) => {
|
|
89
|
-
const nodeStream = Readable.fromWeb(response.body);
|
|
90
|
-
nodeStream.on("error", reject);
|
|
91
|
-
res.on("finish", resolve);
|
|
92
|
-
nodeStream.pipe(res);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
function toNodeHandler(app, options = {}) {
|
|
96
|
-
const run = app.callback();
|
|
97
|
-
const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT;
|
|
98
|
-
return async (req, res) => {
|
|
5
|
+
function handle(app, options = {}) {
|
|
6
|
+
const handler = toFetchHandler(app);
|
|
7
|
+
const safeHandler = async (request) => {
|
|
99
8
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
} catch (err) {
|
|
104
|
-
if (!(err instanceof RhythmBodyTooLargeError)) console.error(err);
|
|
105
|
-
if (!res.headersSent) {
|
|
106
|
-
const status = err instanceof RhythmBodyTooLargeError ? 413 : 500;
|
|
107
|
-
const message = err instanceof RhythmBodyTooLargeError ? err.message : "Internal Server Error";
|
|
108
|
-
const headers = { "content-type": "text/plain; charset=utf-8" };
|
|
109
|
-
if (err instanceof RhythmBodyTooLargeError) headers.connection = "close";
|
|
110
|
-
res.writeHead(status, headers);
|
|
111
|
-
res.end(message);
|
|
112
|
-
} else res.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
9
|
+
return await handler(request);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
return errorToResponse(error);
|
|
113
12
|
}
|
|
114
13
|
};
|
|
14
|
+
return toNodeHandler(safeHandler, options);
|
|
115
15
|
}
|
|
116
16
|
//#endregion
|
|
117
|
-
export {
|
|
17
|
+
export { handle };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { RhythmHttpContext } from "../context.js";
|
|
2
|
+
import { Rhythm } from "@rhythmjs/rhythm";
|
|
3
|
+
//#region src/adapters/service-worker.d.ts
|
|
4
|
+
interface FetchEventLike {
|
|
5
|
+
readonly request: Request;
|
|
6
|
+
respondWith(response: Response | Promise<Response>): void;
|
|
7
|
+
}
|
|
8
|
+
export declare function handle<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>): (event: FetchEventLike) => void;
|
|
9
|
+
//#endregion
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { toFetchHandler } from "../fetch.js";
|
|
2
|
+
//#region src/adapters/service-worker.ts
|
|
3
|
+
function handle(app) {
|
|
4
|
+
const handler = toFetchHandler(app);
|
|
5
|
+
return (event) => {
|
|
6
|
+
event.respondWith(handler(event.request));
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { handle };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { RhythmHttpContext } from "../context.js";
|
|
2
|
+
import { Rhythm } from "@rhythmjs/rhythm";
|
|
3
|
+
//#region src/adapters/vercel.d.ts
|
|
4
|
+
export declare function handle<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>): (request: Request) => Promise<Response>;
|
|
5
|
+
//#endregion
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/context.d.ts
|
|
2
|
+
export type RhythmResponseBody = string | ArrayBuffer | Uint8Array | Blob | FormData | URLSearchParams | ReadableStream<Uint8Array> | null;
|
|
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,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RhythmHttpContext } from "./context.js";
|
|
2
2
|
import { Rhythm } from "@rhythmjs/rhythm";
|
|
3
|
-
//#region src/
|
|
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>;
|
|
5
5
|
//#endregion
|
package/dist/fetch.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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 };
|
package/dist/rhythm-router.d.ts
CHANGED
package/dist/serve.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { RhythmHttpContext } from "./context.js";
|
|
2
|
+
import { ErrorHandler, Server, ServerMiddleware, ServerOptions, ServerPlugin } from "srvx";
|
|
3
|
+
import { Rhythm } from "@rhythmjs/rhythm";
|
|
4
|
+
//#region src/serve.d.ts
|
|
5
|
+
export type RhythmServeOptions = Omit<ServerOptions, "fetch">;
|
|
6
|
+
export declare const errorToResponse: ErrorHandler;
|
|
7
|
+
export declare function serve<TContext extends RhythmHttpContext, TProviders extends object = {}>(app: Rhythm<RhythmHttpContext, TContext, TProviders>, options?: RhythmServeOptions): Server;
|
|
8
|
+
//#endregion
|
|
9
|
+
export type { ErrorHandler, Server, ServerMiddleware, ServerPlugin };
|
package/dist/serve.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { toFetchHandler } from "./fetch.js";
|
|
2
|
+
import { serve as serve$1 } from "srvx";
|
|
3
|
+
//#region src/serve.ts
|
|
4
|
+
const errorToResponse = (error) => {
|
|
5
|
+
const status = error.status ?? error.statusCode ?? 500;
|
|
6
|
+
const message = status >= 500 ? "Internal Server Error" : error instanceof Error ? error.message : String(error);
|
|
7
|
+
if (status >= 500) console.error(error);
|
|
8
|
+
return new Response(message, {
|
|
9
|
+
status,
|
|
10
|
+
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
function serve(app, options = {}) {
|
|
14
|
+
return serve$1({
|
|
15
|
+
error: errorToResponse,
|
|
16
|
+
...options,
|
|
17
|
+
fetch: toFetchHandler(app)
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { errorToResponse, serve };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhythmjs/router",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Radix-tree HTTP router for the Rhythm middleware kernel, with Bun, Node, and Deno adapters.",
|
|
5
5
|
"homepage": "https://rhythm.js.org/router",
|
|
6
6
|
"license": "ISC",
|
|
@@ -23,9 +23,21 @@
|
|
|
23
23
|
"types": "./dist/radix-tree.d.ts",
|
|
24
24
|
"default": "./dist/radix-tree.js"
|
|
25
25
|
},
|
|
26
|
-
"./
|
|
27
|
-
"types": "./dist/
|
|
28
|
-
"default": "./dist/
|
|
26
|
+
"./context": {
|
|
27
|
+
"types": "./dist/context.d.ts",
|
|
28
|
+
"default": "./dist/context.js"
|
|
29
|
+
},
|
|
30
|
+
"./fetch": {
|
|
31
|
+
"types": "./dist/fetch.d.ts",
|
|
32
|
+
"default": "./dist/fetch.js"
|
|
33
|
+
},
|
|
34
|
+
"./serve": {
|
|
35
|
+
"types": "./dist/serve.d.ts",
|
|
36
|
+
"default": "./dist/serve.js"
|
|
37
|
+
},
|
|
38
|
+
"./adapters/node": {
|
|
39
|
+
"types": "./dist/adapters/node.d.ts",
|
|
40
|
+
"default": "./dist/adapters/node.js"
|
|
29
41
|
},
|
|
30
42
|
"./adapters/bun": {
|
|
31
43
|
"types": "./dist/adapters/bun.d.ts",
|
|
@@ -35,13 +47,29 @@
|
|
|
35
47
|
"types": "./dist/adapters/deno.d.ts",
|
|
36
48
|
"default": "./dist/adapters/deno.js"
|
|
37
49
|
},
|
|
38
|
-
"./adapters/
|
|
39
|
-
"types": "./dist/adapters/
|
|
40
|
-
"default": "./dist/adapters/
|
|
50
|
+
"./adapters/vercel": {
|
|
51
|
+
"types": "./dist/adapters/vercel.d.ts",
|
|
52
|
+
"default": "./dist/adapters/vercel.js"
|
|
53
|
+
},
|
|
54
|
+
"./adapters/netlify": {
|
|
55
|
+
"types": "./dist/adapters/netlify.d.ts",
|
|
56
|
+
"default": "./dist/adapters/netlify.js"
|
|
57
|
+
},
|
|
58
|
+
"./adapters/cloudflare": {
|
|
59
|
+
"types": "./dist/adapters/cloudflare.d.ts",
|
|
60
|
+
"default": "./dist/adapters/cloudflare.js"
|
|
61
|
+
},
|
|
62
|
+
"./adapters/aws-lambda": {
|
|
63
|
+
"types": "./dist/adapters/aws-lambda.d.ts",
|
|
64
|
+
"default": "./dist/adapters/aws-lambda.js"
|
|
65
|
+
},
|
|
66
|
+
"./adapters/service-worker": {
|
|
67
|
+
"types": "./dist/adapters/service-worker.d.ts",
|
|
68
|
+
"default": "./dist/adapters/service-worker.js"
|
|
41
69
|
},
|
|
42
70
|
"./adapters/context": {
|
|
43
|
-
"types": "./dist/
|
|
44
|
-
"default": "./dist/
|
|
71
|
+
"types": "./dist/context.d.ts",
|
|
72
|
+
"default": "./dist/context.js"
|
|
45
73
|
},
|
|
46
74
|
"./package.json": "./package.json"
|
|
47
75
|
},
|
|
@@ -49,7 +77,8 @@
|
|
|
49
77
|
"access": "public"
|
|
50
78
|
},
|
|
51
79
|
"dependencies": {
|
|
52
|
-
"
|
|
80
|
+
"srvx": "^1.0.5",
|
|
81
|
+
"@rhythmjs/rhythm": "0.0.8"
|
|
53
82
|
},
|
|
54
83
|
"devDependencies": {
|
|
55
84
|
"@types/node": "^26.6.3",
|
package/dist/adapters/web-std.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { createHttpContext, toResponse } from "./context.js";
|
|
2
|
-
//#region src/adapters/web-std.ts
|
|
3
|
-
function toFetchHandler(app) {
|
|
4
|
-
const run = app.callback();
|
|
5
|
-
return async (request) => {
|
|
6
|
-
const ctx = await run(createHttpContext(request));
|
|
7
|
-
return toResponse(ctx.response);
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
//#endregion
|
|
11
|
-
export { toFetchHandler };
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
//#region src/adapters/context.d.ts
|
|
2
|
-
type RhythmResponseBody = string | ArrayBuffer | Uint8Array | Blob | FormData | URLSearchParams | ReadableStream<Uint8Array> | null;
|
|
3
|
-
declare class RhythmResponse {
|
|
4
|
-
status: number;
|
|
5
|
-
statusText: string | undefined;
|
|
6
|
-
headers: Headers;
|
|
7
|
-
body: RhythmResponseBody;
|
|
8
|
-
}
|
|
9
|
-
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
|
-
declare function createHttpContext(request: Request): RhythmHttpContext;
|
|
19
|
-
declare function toResponse(response: RhythmResponse): Response;
|
|
20
|
-
//#endregion
|
|
21
|
-
export { toResponse as a, createHttpContext as i, RhythmResponse as n, RhythmResponseBody as r, RhythmHttpContext as t };
|