@rhythmjs/router 0.0.14 → 0.0.16

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
@@ -4,7 +4,7 @@ The HTTP layer of Rhythm, the Bun-native backend framework: web-standard (`Reque
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
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`, `derive`; `next()` takes no arguments, extend the context with `derive()`), but has no a startup `context` 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.
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`, `derive`; `next()` takes no arguments, extend the context with `derive()`), but has no startup `context` 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.
8
8
 
9
9
  ## Example
10
10
 
@@ -31,7 +31,8 @@ A fuller runnable version, including nested prefixes and a fallback route, is at
31
31
  - **`ctx.params`**: captured `:name` path segments, added once a route matches.
32
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
33
  - **Registration order is execution order**: a `.use()` middleware wraps only the routes registered after it, and runs only when one of them matches the request's method and path (so a guard in a `/projects` router never answers `/docs`); a mounted router (`.use(child.middleware())`) always runs; 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 a startup `context` 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())`.
34
+ - **Conditional middleware**: `.use(fn, condition)` also needs `condition(ctx)` to return true (sync or async); the second callback only narrows, so a middleware registered after every route still never runs. `@rhythmjs/router/match` provides ready-made predicates: `matches("/api/**")` (patterns, `exclude`, `methods`) and `routed(module)` (true only for requests the module's own routers serve), e.g. `app.use(audit(), routed(usersModule))`.
35
+ - **A router is a controller, not a module**: it has no startup `context` 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())`.
35
36
 
36
37
  ## API
37
38
 
@@ -0,0 +1,14 @@
1
+ import type { HttpMethod } from "./rhythm-router";
2
+ export type RoutePattern = string | readonly string[];
3
+ export interface RouteMatchOptions {
4
+ include?: RoutePattern;
5
+ exclude?: RoutePattern;
6
+ methods?: readonly HttpMethod[];
7
+ }
8
+ export type RequestPredicate = (ctx: {
9
+ readonly request: Request;
10
+ }) => boolean;
11
+ export declare function matches(spec: RoutePattern | RouteMatchOptions): RequestPredicate;
12
+ export declare function routed(module: {
13
+ readonly sources: readonly object[];
14
+ }): RequestPredicate;
package/dist/match.js ADDED
@@ -0,0 +1,53 @@
1
+ // @bun
2
+ import {
3
+ mountedRoutes2,
4
+ RhythmRouter2
5
+ } from "./rhythm-router-wmzh62d5.js";
6
+
7
+ // src/match.ts
8
+ import { addRoute, createRouter, findRoute } from "rou3";
9
+ var toList = (pattern) => pattern === undefined ? [] : typeof pattern === "string" ? [pattern] : pattern;
10
+ var pathnameOf = (request) => new URL(request.url).pathname;
11
+ function treeOf(patterns, methods) {
12
+ const tree = createRouter();
13
+ for (const pattern of patterns) {
14
+ if (methods && methods.length > 0)
15
+ for (const method of methods)
16
+ addRoute(tree, method, pattern, true);
17
+ else
18
+ addRoute(tree, undefined, pattern, true);
19
+ }
20
+ return tree;
21
+ }
22
+ function matches(spec) {
23
+ const options = typeof spec === "string" || Array.isArray(spec) ? { include: spec } : spec;
24
+ const include = toList(options.include);
25
+ const includeTree = include.length > 0 ? treeOf(include, options.methods) : undefined;
26
+ const excludeTree = toList(options.exclude).length > 0 ? treeOf(toList(options.exclude), options.methods) : undefined;
27
+ const methodsOnly = !includeTree && options.methods && options.methods.length > 0 ? new Set(options.methods) : undefined;
28
+ return ({ request }) => {
29
+ const path = pathnameOf(request);
30
+ if (excludeTree && findRoute(excludeTree, request.method, path, { params: false }))
31
+ return false;
32
+ if (includeTree)
33
+ return !!findRoute(includeTree, request.method, path, { params: false });
34
+ return methodsOnly ? methodsOnly.has(request.method) : true;
35
+ };
36
+ }
37
+ function routed(module) {
38
+ let tree;
39
+ return ({ request }) => {
40
+ if (!tree) {
41
+ tree = createRouter();
42
+ const seen = new Set;
43
+ for (const source of module.sources)
44
+ if (source instanceof RhythmRouter2)
45
+ mountedRoutes2(source, tree, seen);
46
+ }
47
+ return !!findRoute(tree, request.method, pathnameOf(request), { params: false });
48
+ };
49
+ }
50
+ export {
51
+ matches,
52
+ routed
53
+ };
@@ -0,0 +1,133 @@
1
+ // @bun
2
+ // src/rhythm-router.ts
3
+ import { compose, gate } from "@rhythmjs/rhythm/compose";
4
+ import { sourceOf, withSource } from "@rhythmjs/rhythm/source";
5
+ import { addRoute, createRouter, findRoute } from "rou3";
6
+ function joinPath2(prefix, path) {
7
+ if (!prefix)
8
+ return path;
9
+ if (path === "/" || path === "")
10
+ return prefix;
11
+ const trimmedPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
12
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
13
+ return `${trimmedPrefix}${normalizedPath}`;
14
+ }
15
+ function mountedRoutes2(router, tree, seen = new Set) {
16
+ if (seen.has(router))
17
+ return;
18
+ seen.add(router);
19
+ for (const entry of router.entries) {
20
+ if (entry.kind === "route")
21
+ addRoute(tree, entry.method, entry.path, true);
22
+ else {
23
+ const child = sourceOf(entry.fn);
24
+ if (child instanceof RhythmRouter2)
25
+ mountedRoutes2(child, tree, seen);
26
+ }
27
+ }
28
+ }
29
+
30
+ class RhythmRouter2 {
31
+ #options;
32
+ #entries = [];
33
+ constructor(options = {}) {
34
+ this.#options = options;
35
+ }
36
+ get #prefixPath() {
37
+ return this.#options.prefix ?? "";
38
+ }
39
+ get entries() {
40
+ return [...this.#entries];
41
+ }
42
+ use(fn, when) {
43
+ if (typeof fn !== "function")
44
+ throw new TypeError("middleware must be a function!");
45
+ if (when !== undefined && typeof when !== "function")
46
+ throw new TypeError("when must be a function!");
47
+ const source = sourceOf(fn);
48
+ const wrapped = when ? gate(fn, when) : fn;
49
+ this.#entries.push({ kind: "middleware", fn: when && source ? withSource(wrapped, source) : wrapped });
50
+ return this;
51
+ }
52
+ #route(method, path, handlers) {
53
+ this.#entries.push({ kind: "route", method, path: joinPath2(this.#prefixPath, path), handlers });
54
+ return this;
55
+ }
56
+ get(path, ...handlers) {
57
+ return this.#route("GET", path, handlers);
58
+ }
59
+ post(path, ...handlers) {
60
+ return this.#route("POST", path, handlers);
61
+ }
62
+ put(path, ...handlers) {
63
+ return this.#route("PUT", path, handlers);
64
+ }
65
+ patch(path, ...handlers) {
66
+ return this.#route("PATCH", path, handlers);
67
+ }
68
+ delete(path, ...handlers) {
69
+ return this.#route("DELETE", path, handlers);
70
+ }
71
+ #compile() {
72
+ const dispatchFor = (tree) => {
73
+ return async (ctx, next) => {
74
+ const match = findRoute(tree, ctx.request.method, new URL(ctx.request.url).pathname);
75
+ if (!match) {
76
+ await next();
77
+ return;
78
+ }
79
+ await match.data({ ...ctx, params: match.params ?? {} }, next);
80
+ };
81
+ };
82
+ const trees = [];
83
+ const reaches = (ctx, from) => {
84
+ const method = ctx.request.method;
85
+ const pathname = new URL(ctx.request.url).pathname;
86
+ for (let t = from;t < trees.length; t++)
87
+ if (findRoute(trees[t], method, pathname))
88
+ return true;
89
+ return false;
90
+ };
91
+ const stack = [];
92
+ let i = 0;
93
+ while (i < this.#entries.length) {
94
+ const entry = this.#entries[i];
95
+ if (entry.kind === "middleware") {
96
+ const { fn } = entry;
97
+ const source = sourceOf(fn);
98
+ if (source) {
99
+ if (source instanceof RhythmRouter2) {
100
+ const tree = createRouter();
101
+ mountedRoutes2(source, tree);
102
+ trees.push(tree);
103
+ }
104
+ stack.push(fn);
105
+ } else {
106
+ const from = trees.length;
107
+ stack.push((ctx, next) => reaches(ctx, from) ? fn(ctx, next) : next());
108
+ }
109
+ i++;
110
+ continue;
111
+ }
112
+ const tree = createRouter();
113
+ while (i < this.#entries.length) {
114
+ const route = this.#entries[i];
115
+ if (route.kind !== "route")
116
+ break;
117
+ addRoute(tree, route.method, route.path, compose(route.handlers));
118
+ i++;
119
+ }
120
+ trees.push(tree);
121
+ stack.push(dispatchFor(tree));
122
+ }
123
+ return compose(stack);
124
+ }
125
+ middleware() {
126
+ const fn = this.#compile();
127
+ return withSource(async (ctx, next) => {
128
+ await fn(ctx, next);
129
+ }, this);
130
+ }
131
+ }
132
+
133
+ export { joinPath2, mountedRoutes2, RhythmRouter2 };
@@ -0,0 +1,133 @@
1
+ // @bun
2
+ // src/rhythm-router.ts
3
+ import { compose, gate } from "@rhythmjs/rhythm/compose";
4
+ import { sourceOf, withSource } from "@rhythmjs/rhythm/source";
5
+ import { addRoute, createRouter, findRoute } from "rou3";
6
+ function joinPath2(prefix, path) {
7
+ if (!prefix)
8
+ return path;
9
+ if (path === "/" || path === "")
10
+ return prefix;
11
+ const trimmedPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
12
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
13
+ return `${trimmedPrefix}${normalizedPath}`;
14
+ }
15
+ function mountedRoutes2(router, tree, seen = new Set) {
16
+ if (seen.has(router))
17
+ return;
18
+ seen.add(router);
19
+ for (const entry of router.entries) {
20
+ if (entry.kind === "route")
21
+ addRoute(tree, entry.method, entry.path, true);
22
+ else {
23
+ const child = sourceOf(entry.fn);
24
+ if (child instanceof RhythmRouter2)
25
+ mountedRoutes2(child, tree, seen);
26
+ }
27
+ }
28
+ }
29
+
30
+ class RhythmRouter2 {
31
+ #options;
32
+ #entries = [];
33
+ constructor(options = {}) {
34
+ this.#options = options;
35
+ }
36
+ get #prefixPath() {
37
+ return this.#options.prefix ?? "";
38
+ }
39
+ get entries() {
40
+ return [...this.#entries];
41
+ }
42
+ use(fn, condition) {
43
+ if (typeof fn !== "function")
44
+ throw new TypeError("middleware must be a function!");
45
+ if (condition !== undefined && typeof condition !== "function")
46
+ throw new TypeError("condition must be a function!");
47
+ const source = sourceOf(fn);
48
+ const wrapped = condition ? gate(fn, condition) : fn;
49
+ this.#entries.push({ kind: "middleware", fn: condition && source ? withSource(wrapped, source) : wrapped });
50
+ return this;
51
+ }
52
+ #route(method, path, handlers) {
53
+ this.#entries.push({ kind: "route", method, path: joinPath2(this.#prefixPath, path), handlers });
54
+ return this;
55
+ }
56
+ get(path, ...handlers) {
57
+ return this.#route("GET", path, handlers);
58
+ }
59
+ post(path, ...handlers) {
60
+ return this.#route("POST", path, handlers);
61
+ }
62
+ put(path, ...handlers) {
63
+ return this.#route("PUT", path, handlers);
64
+ }
65
+ patch(path, ...handlers) {
66
+ return this.#route("PATCH", path, handlers);
67
+ }
68
+ delete(path, ...handlers) {
69
+ return this.#route("DELETE", path, handlers);
70
+ }
71
+ #compile() {
72
+ const dispatchFor = (tree) => {
73
+ return async (ctx, next) => {
74
+ const match = findRoute(tree, ctx.request.method, new URL(ctx.request.url).pathname);
75
+ if (!match) {
76
+ await next();
77
+ return;
78
+ }
79
+ await match.data({ ...ctx, params: match.params ?? {} }, next);
80
+ };
81
+ };
82
+ const trees = [];
83
+ const reaches = (ctx, from) => {
84
+ const method = ctx.request.method;
85
+ const pathname = new URL(ctx.request.url).pathname;
86
+ for (let t = from;t < trees.length; t++)
87
+ if (findRoute(trees[t], method, pathname))
88
+ return true;
89
+ return false;
90
+ };
91
+ const stack = [];
92
+ let i = 0;
93
+ while (i < this.#entries.length) {
94
+ const entry = this.#entries[i];
95
+ if (entry.kind === "middleware") {
96
+ const { fn } = entry;
97
+ const source = sourceOf(fn);
98
+ if (source) {
99
+ if (source instanceof RhythmRouter2) {
100
+ const tree = createRouter();
101
+ mountedRoutes2(source, tree);
102
+ trees.push(tree);
103
+ }
104
+ stack.push(fn);
105
+ } else {
106
+ const from = trees.length;
107
+ stack.push((ctx, next) => reaches(ctx, from) ? fn(ctx, next) : next());
108
+ }
109
+ i++;
110
+ continue;
111
+ }
112
+ const tree = createRouter();
113
+ while (i < this.#entries.length) {
114
+ const route = this.#entries[i];
115
+ if (route.kind !== "route")
116
+ break;
117
+ addRoute(tree, route.method, route.path, compose(route.handlers));
118
+ i++;
119
+ }
120
+ trees.push(tree);
121
+ stack.push(dispatchFor(tree));
122
+ }
123
+ return compose(stack);
124
+ }
125
+ middleware() {
126
+ const fn = this.#compile();
127
+ return withSource(async (ctx, next) => {
128
+ await fn(ctx, next);
129
+ }, this);
130
+ }
131
+ }
132
+
133
+ export { joinPath2, mountedRoutes2, RhythmRouter2 };
@@ -0,0 +1,132 @@
1
+ // @bun
2
+ // src/rhythm-router.ts
3
+ import { compose, gate } from "@rhythmjs/rhythm/compose";
4
+ import { sourceOf, withSource } from "@rhythmjs/rhythm/source";
5
+ import { addRoute, createRouter, findRoute } from "rou3";
6
+ function joinPath2(prefix, path) {
7
+ if (!prefix)
8
+ return path;
9
+ if (path === "/" || path === "")
10
+ return prefix;
11
+ const trimmedPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
12
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
13
+ return `${trimmedPrefix}${normalizedPath}`;
14
+ }
15
+ function mountedRoutes2(router, tree, seen = new Set) {
16
+ if (seen.has(router))
17
+ return;
18
+ seen.add(router);
19
+ for (const entry of router.entries) {
20
+ if (entry.kind === "route")
21
+ addRoute(tree, entry.method, entry.path, true);
22
+ else {
23
+ const child = sourceOf(entry.fn);
24
+ if (child instanceof RhythmRouter2)
25
+ mountedRoutes2(child, tree, seen);
26
+ }
27
+ }
28
+ }
29
+
30
+ class RhythmRouter2 {
31
+ #options;
32
+ #entries = [];
33
+ constructor(options = {}) {
34
+ this.#options = options;
35
+ }
36
+ get #prefixPath() {
37
+ return this.#options.prefix ?? "";
38
+ }
39
+ get entries() {
40
+ return [...this.#entries];
41
+ }
42
+ use(fn, when) {
43
+ if (typeof fn !== "function")
44
+ throw new TypeError("middleware must be a function!");
45
+ if (when !== undefined && typeof when !== "function")
46
+ throw new TypeError("when must be a function!");
47
+ this.#entries.push(when ? { kind: "middleware", fn, when } : { kind: "middleware", fn });
48
+ return this;
49
+ }
50
+ #route(method, path, handlers) {
51
+ this.#entries.push({ kind: "route", method, path: joinPath2(this.#prefixPath, path), handlers });
52
+ return this;
53
+ }
54
+ get(path, ...handlers) {
55
+ return this.#route("GET", path, handlers);
56
+ }
57
+ post(path, ...handlers) {
58
+ return this.#route("POST", path, handlers);
59
+ }
60
+ put(path, ...handlers) {
61
+ return this.#route("PUT", path, handlers);
62
+ }
63
+ patch(path, ...handlers) {
64
+ return this.#route("PATCH", path, handlers);
65
+ }
66
+ delete(path, ...handlers) {
67
+ return this.#route("DELETE", path, handlers);
68
+ }
69
+ #compile() {
70
+ const dispatchFor = (tree) => {
71
+ return async (ctx, next) => {
72
+ const match = findRoute(tree, ctx.request.method, new URL(ctx.request.url).pathname);
73
+ if (!match) {
74
+ await next();
75
+ return;
76
+ }
77
+ await match.data({ ...ctx, params: match.params ?? {} }, next);
78
+ };
79
+ };
80
+ const trees = [];
81
+ const reaches = (ctx, from) => {
82
+ const method = ctx.request.method;
83
+ const pathname = new URL(ctx.request.url).pathname;
84
+ for (let t = from;t < trees.length; t++)
85
+ if (findRoute(trees[t], method, pathname))
86
+ return true;
87
+ return false;
88
+ };
89
+ const stack = [];
90
+ let i = 0;
91
+ while (i < this.#entries.length) {
92
+ const entry = this.#entries[i];
93
+ if (entry.kind === "middleware") {
94
+ const { fn, when } = entry;
95
+ const guarded = when ? gate(fn, when) : fn;
96
+ const source = sourceOf(fn);
97
+ if (source) {
98
+ if (source instanceof RhythmRouter2) {
99
+ const tree = createRouter();
100
+ mountedRoutes2(source, tree);
101
+ trees.push(tree);
102
+ }
103
+ stack.push(guarded);
104
+ } else {
105
+ const from = trees.length;
106
+ stack.push((ctx, next) => reaches(ctx, from) ? guarded(ctx, next) : next());
107
+ }
108
+ i++;
109
+ continue;
110
+ }
111
+ const tree = createRouter();
112
+ while (i < this.#entries.length) {
113
+ const route = this.#entries[i];
114
+ if (route.kind !== "route")
115
+ break;
116
+ addRoute(tree, route.method, route.path, compose(route.handlers));
117
+ i++;
118
+ }
119
+ trees.push(tree);
120
+ stack.push(dispatchFor(tree));
121
+ }
122
+ return compose(stack);
123
+ }
124
+ middleware() {
125
+ const fn = this.#compile();
126
+ return withSource(async (ctx, next) => {
127
+ await fn(ctx, next);
128
+ }, this);
129
+ }
130
+ }
131
+
132
+ export { joinPath2, mountedRoutes2, RhythmRouter2 };
@@ -1,4 +1,5 @@
1
- import type { DeriveMiddleware, Middleware } from "@rhythmjs/rhythm/types";
1
+ import type { Condition, DeriveMiddleware, Middleware } from "@rhythmjs/rhythm/types";
2
+ import { type RouterContext } from "rou3";
2
3
  import type { RhythmHttpContext } from "./context";
3
4
  export interface RhythmRouterContext {
4
5
  readonly params: Readonly<Record<string, string>>;
@@ -18,12 +19,13 @@ export type RouterEntry = {
18
19
  };
19
20
  type RouteHandler<TContext> = Middleware<TContext & RhythmRouterContext>;
20
21
  export declare function joinPath(prefix: string, path: string): string;
22
+ export declare function mountedRoutes(router: RhythmRouter<any, any>, tree: RouterContext<any>, seen?: Set<object>): void;
21
23
  export declare class RhythmRouter<TContext extends RhythmHttpContext = RhythmHttpContext, TInput extends RhythmHttpContext = TContext> {
22
24
  #private;
23
25
  constructor(options?: RhythmRouterOptions);
24
26
  get entries(): readonly RouterEntry[];
25
27
  use<TExtra extends object>(fn: DeriveMiddleware<TContext, TExtra>): RhythmRouter<TContext & TExtra, TInput>;
26
- use(fn: Middleware<TContext>): this;
28
+ use(fn: Middleware<TContext>, condition?: Condition<TContext>): this;
27
29
  get<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
28
30
  get(path: string, ...handlers: RouteHandler<TContext>[]): this;
29
31
  post<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
@@ -1,131 +1,11 @@
1
1
  // @bun
2
- // src/rhythm-router.ts
3
- import { compose } from "@rhythmjs/rhythm/compose";
4
- import { sourceOf, withSource } from "@rhythmjs/rhythm/source";
5
- import { addRoute, createRouter, findRoute } from "rou3";
6
- function joinPath(prefix, path) {
7
- if (!prefix)
8
- return path;
9
- if (path === "/" || path === "")
10
- return prefix;
11
- const trimmedPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
12
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
13
- return `${trimmedPrefix}${normalizedPath}`;
14
- }
15
- function mountedRoutes(router, tree, seen = new Set) {
16
- if (seen.has(router))
17
- return;
18
- seen.add(router);
19
- for (const entry of router.entries) {
20
- if (entry.kind === "route")
21
- addRoute(tree, entry.method, entry.path, true);
22
- else {
23
- const child = sourceOf(entry.fn);
24
- if (child instanceof RhythmRouter)
25
- mountedRoutes(child, tree, seen);
26
- }
27
- }
28
- }
29
-
30
- class RhythmRouter {
31
- #options;
32
- #entries = [];
33
- constructor(options = {}) {
34
- this.#options = options;
35
- }
36
- get #prefixPath() {
37
- return this.#options.prefix ?? "";
38
- }
39
- get entries() {
40
- return [...this.#entries];
41
- }
42
- use(fn) {
43
- if (typeof fn !== "function")
44
- throw new TypeError("middleware must be a function!");
45
- this.#entries.push({ kind: "middleware", fn });
46
- return this;
47
- }
48
- #route(method, path, handlers) {
49
- this.#entries.push({ kind: "route", method, path: joinPath(this.#prefixPath, path), handlers });
50
- return this;
51
- }
52
- get(path, ...handlers) {
53
- return this.#route("GET", path, handlers);
54
- }
55
- post(path, ...handlers) {
56
- return this.#route("POST", path, handlers);
57
- }
58
- put(path, ...handlers) {
59
- return this.#route("PUT", path, handlers);
60
- }
61
- patch(path, ...handlers) {
62
- return this.#route("PATCH", path, handlers);
63
- }
64
- delete(path, ...handlers) {
65
- return this.#route("DELETE", path, handlers);
66
- }
67
- #compile() {
68
- const dispatchFor = (tree) => {
69
- return async (ctx, next) => {
70
- const match = findRoute(tree, ctx.request.method, new URL(ctx.request.url).pathname);
71
- if (!match) {
72
- await next();
73
- return;
74
- }
75
- await match.data({ ...ctx, params: match.params ?? {} }, next);
76
- };
77
- };
78
- const trees = [];
79
- const reaches = (ctx, from) => {
80
- const method = ctx.request.method;
81
- const pathname = new URL(ctx.request.url).pathname;
82
- for (let t = from;t < trees.length; t++)
83
- if (findRoute(trees[t], method, pathname))
84
- return true;
85
- return false;
86
- };
87
- const stack = [];
88
- let i = 0;
89
- while (i < this.#entries.length) {
90
- const entry = this.#entries[i];
91
- if (entry.kind === "middleware") {
92
- const { fn } = entry;
93
- const source = sourceOf(fn);
94
- if (source) {
95
- if (source instanceof RhythmRouter) {
96
- const tree = createRouter();
97
- mountedRoutes(source, tree);
98
- trees.push(tree);
99
- }
100
- stack.push(fn);
101
- } else {
102
- const from = trees.length;
103
- stack.push((ctx, next) => reaches(ctx, from) ? fn(ctx, next) : next());
104
- }
105
- i++;
106
- continue;
107
- }
108
- const tree = createRouter();
109
- while (i < this.#entries.length) {
110
- const route = this.#entries[i];
111
- if (route.kind !== "route")
112
- break;
113
- addRoute(tree, route.method, route.path, compose(route.handlers));
114
- i++;
115
- }
116
- trees.push(tree);
117
- stack.push(dispatchFor(tree));
118
- }
119
- return compose(stack);
120
- }
121
- middleware() {
122
- const fn = this.#compile();
123
- return withSource(async (ctx, next) => {
124
- await fn(ctx, next);
125
- }, this);
126
- }
127
- }
2
+ import {
3
+ joinPath2,
4
+ mountedRoutes2,
5
+ RhythmRouter2
6
+ } from "./rhythm-router-wmzh62d5.js";
128
7
  export {
129
- RhythmRouter,
130
- joinPath
8
+ RhythmRouter2 as RhythmRouter,
9
+ joinPath2 as joinPath,
10
+ mountedRoutes2 as mountedRoutes
131
11
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhythmjs/router",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "Bun-native HTTP router for the Rhythm middleware kernel, matching with rou3 and serving with Bun.serve.",
5
5
  "homepage": "https://rhythm.js.org/router",
6
6
  "license": "ISC",
@@ -27,6 +27,10 @@
27
27
  "types": "./dist/fetch.d.ts",
28
28
  "default": "./dist/fetch.js"
29
29
  },
30
+ "./match": {
31
+ "types": "./dist/match.d.ts",
32
+ "default": "./dist/match.js"
33
+ },
30
34
  "./adapters/context": {
31
35
  "types": "./dist/context.d.ts",
32
36
  "default": "./dist/context.js"
@@ -38,7 +42,7 @@
38
42
  },
39
43
  "dependencies": {
40
44
  "rou3": "^0.11.0",
41
- "@rhythmjs/rhythm": "0.0.15"
45
+ "@rhythmjs/rhythm": "0.0.16"
42
46
  },
43
47
  "devDependencies": {
44
48
  "@types/bun": "^1.2.0",
@@ -49,7 +53,7 @@
49
53
  "bun": ">=1.2.0"
50
54
  },
51
55
  "scripts": {
52
- "build": "bun build src/rhythm-router.ts src/context.ts src/fetch.ts --outdir dist --root src --format esm --target bun --packages external --splitting && tsc -p tsconfig.build.json",
56
+ "build": "bun build src/rhythm-router.ts src/context.ts src/fetch.ts src/match.ts --outdir dist --root src --format esm --target bun --packages external --splitting && tsc -p tsconfig.build.json",
53
57
  "typecheck": "tsc --noEmit",
54
58
  "test": "bun test"
55
59
  }