@rhythmjs/router 0.0.20 → 0.0.22

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  The HTTP layer of Rhythm, the Bun-native backend framework: web-standard (`Request`/`Response`) routing on top of the `@rhythmjs/rhythm` kernel, served on `Bun.serve`. `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).
4
4
 
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 URI-decoded; a malformed escape does not match.
5
+ Route patterns follow rou3's conventions: `:name` params (`:name?` optional, `:id(\\d+)` regex-constrained), `*` for an unnamed wildcard (captured as `params["0"]`; with the installed rou3 it spans several segments, so use `:name` for exactly one segment), and `**` for the rest of the path (`params._`, or `params.name` with `**:name`). Param values are URI-decoded; a malformed escape does not match.
6
6
 
7
7
  `RhythmRouter` is a `Pipeline`, like `Rhythm`: it has request-time `use()` middleware and routes, but no startup phase. Startup work (`register`, `decorate`, `include`) belongs to the `Rhythm` app that mounts it with `mount(router)`.
8
8
 
@@ -118,4 +118,8 @@ Directory routes (`{ dir }`, path must end in `/*`) come with content types, `La
118
118
 
119
119
  ### WebSockets
120
120
 
121
- [`@rhythmjs/ws`](https://github.com/rhythmjs/ws) plugs into the same hand-wired `fetch`: its `upgrade()` returns `null` synchronously for non-websocket requests, so it composes as `ws.upgrade(request, srv) ?? handler(request)`, with `websocket: ws.websocket` on the same `Bun.serve` call.
121
+ [`@rhythmjs/ws`](https://github.com/rhythmjs/ws) is a pipeline like the router: mount it beside your routers with `mount(ws)`, and pass `websocket` to the same `Bun.serve` call. Its routes call Bun's `ctx.server.upgrade()`, which is why the fetch handler must be given the server (`fetch: toFetchHandler(app)`).
122
+
123
+ ```ts
124
+ Bun.serve({ fetch: toFetchHandler(new Rhythm().use(mount(router)).use(mount(ws))), websocket });
125
+ ```
package/dist/path.js CHANGED
@@ -1,4 +1,8 @@
1
1
  // @bun
2
+ import {
3
+ pathnameOf
4
+ } from "./rhythm-router-t7qys9ar.js";
5
+
2
6
  // src/path.ts
3
7
  import { addRoute, createRouter, findRoute } from "rou3";
4
8
  function pathIs(pattern) {
@@ -6,7 +10,7 @@ function pathIs(pattern) {
6
10
  throw new TypeError(`path must start with "/", got "${pattern}"`);
7
11
  const router = createRouter();
8
12
  addRoute(router, "", pattern, true);
9
- return (ctx) => findRoute(router, ctx.request.method, new URL(ctx.request.url).pathname) !== undefined;
13
+ return (ctx) => findRoute(router, ctx.request.method, pathnameOf(ctx.request.url)) !== undefined;
10
14
  }
11
15
  export {
12
16
  pathIs
@@ -0,0 +1 @@
1
+ export declare function pathnameOf(url: string): string;
@@ -0,0 +1,20 @@
1
+ // @bun
2
+ // src/pathname.ts
3
+ var NEEDS_PARSING = /[\\\u0000- \u007f-\uFFFF"<>^`{|}]|\/\.|%2e/i;
4
+ function pathnameOf(url) {
5
+ const authority = url.indexOf("://") + 3;
6
+ const start = url.indexOf("/", authority);
7
+ if (authority < 3 || start === -1)
8
+ return new URL(url).pathname;
9
+ let end = url.length;
10
+ const query = url.indexOf("?", start);
11
+ if (query !== -1)
12
+ end = query;
13
+ const hash = url.indexOf("#", start);
14
+ if (hash !== -1 && hash < end)
15
+ end = hash;
16
+ const pathname = url.slice(start, end);
17
+ return NEEDS_PARSING.test(pathname) ? new URL(url).pathname : pathname;
18
+ }
19
+
20
+ export { pathnameOf };
@@ -1,4 +1,8 @@
1
1
  // @bun
2
+ import {
3
+ pathnameOf
4
+ } from "./rhythm-router-t7qys9ar.js";
5
+
2
6
  // src/rhythm-router.ts
3
7
  import { addRoute, createRouter, findRoute } from "rou3";
4
8
  import {
@@ -7,9 +11,12 @@ import {
7
11
  } from "@rhythmjs/rhythm";
8
12
  function decodeParams(params) {
9
13
  const decoded = {};
14
+ if (!params)
15
+ return decoded;
10
16
  try {
11
- for (const [key, value] of Object.entries(params ?? {})) {
12
- decoded[key] = decodeURIComponent(value);
17
+ for (const key in params) {
18
+ const value = params[key];
19
+ decoded[key] = value.includes("%") ? decodeURIComponent(value) : value;
13
20
  }
14
21
  } catch {
15
22
  return;
@@ -42,18 +49,21 @@ class RhythmRouter extends Pipeline {
42
49
  }
43
50
  callback() {
44
51
  const run = this.chain();
45
- return async (input) => {
52
+ return (input) => {
46
53
  const ctx = input;
47
- const match = this.#match(ctx);
48
- if (!match)
49
- return ctx;
50
- Object.assign(ctx, { params: match.params });
51
- await run(ctx, () => match.route(ctx));
52
- return ctx;
54
+ try {
55
+ const match = this.#match(ctx);
56
+ if (!match)
57
+ return Promise.resolve(ctx);
58
+ Object.assign(ctx, { params: match.params });
59
+ return run(ctx, () => match.route(ctx)).then(() => ctx);
60
+ } catch (error) {
61
+ return Promise.reject(error);
62
+ }
53
63
  };
54
64
  }
55
65
  #match(ctx) {
56
- const found = findRoute(this.#routes, ctx.request.method, new URL(ctx.request.url).pathname);
66
+ const found = findRoute(this.#routes, ctx.request.method, pathnameOf(ctx.request.url));
57
67
  if (!found)
58
68
  return;
59
69
  const params = decodeParams(found.params);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhythmjs/router",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "description": "Bun-native HTTP router for Rhythm: RhythmRouter plus a fetch handler for Bun.serve, matching with rou3.",
5
5
  "homepage": "https://rhythm.js.org/router",
6
6
  "license": "ISC",
@@ -36,8 +36,16 @@
36
36
  "publishConfig": {
37
37
  "access": "public"
38
38
  },
39
+ "scripts": {
40
+ "clean": "rm -rf dist",
41
+ "prebuild": "bun run clean",
42
+ "build": "bun build src/rhythm-router.ts src/context.ts src/fetch.ts src/path.ts --outdir dist --root src --format esm --target bun --packages external --splitting && tsc -p tsconfig.build.json",
43
+ "prepublishOnly": "bun run typecheck && bun test && bun run build",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "bun test"
46
+ },
39
47
  "dependencies": {
40
- "@rhythmjs/rhythm": "0.0.20",
48
+ "@rhythmjs/rhythm": "workspace:*",
41
49
  "rou3": "^1.0.0"
42
50
  },
43
51
  "devDependencies": {
@@ -47,10 +55,5 @@
47
55
  },
48
56
  "engines": {
49
57
  "bun": ">=1.2.0"
50
- },
51
- "scripts": {
52
- "build": "rm -rf dist && bun build src/rhythm-router.ts src/context.ts src/fetch.ts src/path.ts --outdir dist --root src --format esm --target bun --packages external --splitting && tsc -p tsconfig.build.json",
53
- "typecheck": "tsc --noEmit",
54
- "test": "bun test"
55
58
  }
56
- }
59
+ }