@b9g/router 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # @b9g/router
2
2
 
3
- Universal request router built on web standards with cache-aware routing and middleware support.
3
+ **Universal request router for ServiceWorker applications. Built on web standards with generator-based middleware.**
4
4
 
5
5
  ## Features
6
6
 
7
- - **Web Standards Based**: Built on URLPattern, Request, Response, and Cache APIs
8
- - **Cache-Aware Routing**: First-class cache integration with automatic population
9
- - **Middleware Support**: Global and route-specific middleware with `next()` pattern
10
- - **Method Routing**: HTTP method shortcuts (get, post, put, delete, etc.)
7
+ - **ServiceWorker Compatible**: Designed for ServiceWorker `fetch` event handling
8
+ - **Generator Middleware**: Uses `yield` for flow control (no Express-style `next()`)
9
+ - **Web Standards**: Built on URLPattern, Request, and Response APIs
11
10
  - **Universal**: Same code runs in browsers, Node.js, Bun, and edge platforms
11
+ - **Simple Context**: Route parameters and middleware-extensible context
12
12
 
13
13
  ## Installation
14
14
 
@@ -36,49 +36,90 @@ router.get('/posts/:id', (request, context) => {
36
36
  const response = await router.handler(request);
37
37
  ```
38
38
 
39
- ## Cache-Aware Routing
39
+ ## Middleware
40
+
41
+ The router supports generator-based middleware with `yield` for clean flow control:
40
42
 
41
43
  ```javascript
42
- import { Router } from '@b9g/router';
43
- import { CacheStorage, MemoryCache } from '@b9g/cache';
44
+ // Global middleware using generator pattern
45
+ router.use(async function* (request, context) {
46
+ console.log(`${request.method} ${request.url}`);
47
+ const response = yield request;
48
+ console.log(`${response.status}`);
49
+ return response;
50
+ });
51
+
52
+ // Function middleware (can short-circuit)
53
+ router.use(async (request, context) => {
54
+ if (!request.headers.get('Authorization')) {
55
+ return new Response('Unauthorized', { status: 401 });
56
+ }
57
+ // Return null/undefined to continue to next middleware
58
+ return null;
59
+ });
60
+ ```
44
61
 
45
- // Setup cache storage
46
- const caches = new CacheStorage();
47
- caches.register('posts', () => new MemoryCache('posts'));
62
+ ## Caching
48
63
 
49
- const router = new Router({ caches });
64
+ The router doesn't provide built-in cache integration. For caching, use the global `caches` API directly in your handlers:
65
+
66
+ ```javascript
67
+ router.get('/api/posts/:id', async (request, context) => {
68
+ // Use global caches API
69
+ const cache = await caches.open('api-v1');
70
+
71
+ const cached = await cache.match(request);
72
+ if (cached) return cached;
50
73
 
51
- // Route with cache declaration
52
- router.route({
53
- pattern: '/api/posts/:id',
54
- cache: { name: 'posts' }
55
- }).get(async (request, context) => {
56
- // context.cache is the opened 'posts' cache
57
74
  const post = await db.posts.get(context.params.id);
58
- return Response.json(post);
75
+ const response = Response.json(post);
76
+
77
+ await cache.put(request, response.clone());
78
+ return response;
59
79
  });
60
80
  ```
61
81
 
62
- ## Middleware
82
+ Or implement caching as middleware:
63
83
 
64
84
  ```javascript
65
- // Global middleware
66
- router.use(async (request, context, next) => {
67
- console.log(`${request.method} ${request.url}`);
68
- const response = await next();
69
- return response;
70
- });
85
+ // Cache middleware
86
+ async function* cacheMiddleware(request, context) {
87
+ if (request.method !== 'GET') {
88
+ return yield request;
89
+ }
90
+
91
+ const cache = await caches.open('pages-v1');
92
+ const cached = await cache.match(request);
93
+ if (cached) return cached;
71
94
 
72
- // Route-specific middleware
73
- router.route('/admin/*')
74
- .use(authMiddleware)
75
- .use(rateLimitMiddleware)
76
- .get(adminHandler);
95
+ const response = yield request;
77
96
 
78
- // Pattern-based middleware
79
- router.use('/api/*', corsMiddleware);
97
+ if (response.ok) {
98
+ await cache.put(request, response.clone());
99
+ }
100
+
101
+ return response;
102
+ }
103
+
104
+ router.use(cacheMiddleware);
80
105
  ```
81
106
 
107
+ ## Exports
108
+
109
+ ### Classes
110
+
111
+ - `Router` - Request router with pattern matching and middleware support
112
+
113
+ ### Types
114
+
115
+ - `RouteContext` - Context object passed to handlers with params and middleware-added properties
116
+ - `Handler` - Route handler function type `(request, context) => Response | Promise<Response>`
117
+ - `GeneratorMiddleware` - Generator-based middleware type using `yield`
118
+ - `FunctionMiddleware` - Simple function middleware type
119
+ - `Middleware` - Union of GeneratorMiddleware | FunctionMiddleware
120
+ - `HttpMethod` - HTTP method string literal type
121
+ - `RouteConfig` - Route configuration object
122
+
82
123
  ## API Reference
83
124
 
84
125
  ### Router
@@ -86,22 +127,20 @@ router.use('/api/*', corsMiddleware);
86
127
  #### Constructor
87
128
 
88
129
  ```javascript
89
- new Router(options?)
130
+ new Router()
90
131
  ```
91
132
 
92
- Options:
93
- - `caches`: CacheStorage instance for cache-aware routing
94
-
95
133
  #### Methods
96
134
 
97
- ##### `route(pattern, options?)`
135
+ ##### `route(pattern)`
98
136
 
99
137
  Create a route builder for the given pattern.
100
138
 
101
139
  ```javascript
102
- router.route('/api/posts/:id', { cache: { name: 'posts' } })
103
- .use(middleware)
104
- .get(handler);
140
+ router.route('/api/posts/:id')
141
+ .get(handler)
142
+ .post(handler)
143
+ .delete(handler);
105
144
  ```
106
145
 
107
146
  ##### HTTP Method Shortcuts
@@ -116,14 +155,22 @@ router.head(pattern, handler)
116
155
  router.options(pattern, handler)
117
156
  ```
118
157
 
119
- ##### `use(pattern?, middleware)`
158
+ ##### `use(middleware)`
159
+
160
+ Add global middleware.
120
161
 
121
- Add middleware globally or for specific patterns.
162
+ ```javascript
163
+ router.use(loggingMiddleware);
164
+ ```
122
165
 
123
166
  ##### `handler(request): Promise<Response>`
124
167
 
125
168
  Bound handler function for processing requests.
126
169
 
170
+ ```javascript
171
+ const response = await router.handler(request);
172
+ ```
173
+
127
174
  ### Context Object
128
175
 
129
176
  Handler and middleware functions receive a context object:
@@ -131,12 +178,19 @@ Handler and middleware functions receive a context object:
131
178
  ```javascript
132
179
  {
133
180
  params: Record<string, string>, // URL parameters
134
- cache?: Cache, // Opened cache for this route
135
- caches?: CacheStorage, // All available caches
136
- // ... additional context
181
+ // Middleware can add arbitrary properties
137
182
  }
138
183
  ```
139
184
 
185
+ Middleware can extend context with custom properties:
186
+
187
+ ```javascript
188
+ router.use(async function* (request, context) {
189
+ context.user = await authenticate(request);
190
+ return yield request;
191
+ });
192
+ ```
193
+
140
194
  ## Examples
141
195
 
142
196
  ### Basic API Router
@@ -144,7 +198,7 @@ Handler and middleware functions receive a context object:
144
198
  ```javascript
145
199
  const router = new Router();
146
200
 
147
- router.get('/api/health', () =>
201
+ router.get('/api/health', () =>
148
202
  Response.json({ status: 'ok' })
149
203
  );
150
204
 
@@ -160,40 +214,43 @@ router.post('/api/posts', async (request) => {
160
214
  });
161
215
  ```
162
216
 
163
- ### With Caching
217
+ ### Authentication Middleware
164
218
 
165
219
  ```javascript
166
- import { Router } from '@b9g/router';
167
- import { CacheStorage, MemoryCache } from '@b9g/cache';
168
-
169
- const caches = new CacheStorage();
170
- caches.register('api', () => new MemoryCache('api'));
171
-
172
- const router = new Router({ caches });
220
+ const router = new Router();
173
221
 
174
- // Cache-aware middleware
175
- router.use(async (request, context, next) => {
176
- if (request.method === 'GET' && context.cache) {
177
- const cached = await context.cache.match(request);
178
- if (cached) return cached;
222
+ // Add user to context
223
+ router.use(async function* (request, context) {
224
+ const token = request.headers.get('Authorization')?.replace('Bearer ', '');
225
+ if (token) {
226
+ context.user = await verifyToken(token);
179
227
  }
180
-
181
- const response = await next();
182
-
183
- if (request.method === 'GET' && context.cache && response.ok) {
184
- await context.cache.put(request, response.clone());
228
+ return yield request;
229
+ });
230
+
231
+ // Protected route
232
+ router.get('/api/profile', async (request, context) => {
233
+ if (!context.user) {
234
+ return new Response('Unauthorized', { status: 401 });
185
235
  }
186
-
187
- return response;
236
+ return Response.json(context.user);
188
237
  });
238
+ ```
239
+
240
+ ### Subrouter Mounting
189
241
 
190
- router.route('/api/posts/:id', { cache: { name: 'api' } })
191
- .get(async (request, context) => {
192
- const post = await db.posts.get(context.params.id);
193
- return Response.json(post);
194
- });
242
+ ```javascript
243
+ // API subrouter
244
+ const apiRouter = new Router();
245
+ apiRouter.get('/users', getUsersHandler);
246
+ apiRouter.get('/posts', getPostsHandler);
247
+
248
+ // Main router
249
+ const mainRouter = new Router();
250
+ mainRouter.mount('/api/v1', apiRouter);
251
+ // Routes become: /api/v1/users, /api/v1/posts
195
252
  ```
196
253
 
197
254
  ## License
198
255
 
199
- MIT
256
+ MIT
package/package.json CHANGED
@@ -1,8 +1,19 @@
1
1
  {
2
2
  "name": "@b9g/router",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
+ "description": "Universal request router for ServiceWorker applications. Built on web standards with cache-aware routing and generator-based middleware.",
5
+ "keywords": [
6
+ "router",
7
+ "serviceworker",
8
+ "middleware",
9
+ "cache",
10
+ "universal",
11
+ "web-standards",
12
+ "urlpattern",
13
+ "shovel"
14
+ ],
4
15
  "dependencies": {
5
- "@b9g/match-pattern": "^0.1.6"
16
+ "@b9g/match-pattern": "^0.1.7"
6
17
  },
7
18
  "devDependencies": {
8
19
  "@b9g/libuild": "^0.1.11",
@@ -24,14 +35,6 @@
24
35
  "types": "./src/index.d.ts",
25
36
  "import": "./src/index.js"
26
37
  },
27
- "./router": {
28
- "types": "./src/router.d.ts",
29
- "import": "./src/router.js"
30
- },
31
- "./router.js": {
32
- "types": "./src/router.d.ts",
33
- "import": "./src/router.js"
34
- },
35
38
  "./package.json": "./package.json"
36
39
  }
37
40
  }
package/src/index.d.ts CHANGED
@@ -3,10 +3,188 @@
3
3
  *
4
4
  * Features:
5
5
  * - Pure Request/Response routing (works anywhere)
6
- * - Middleware chain with next() continuation
7
6
  * - Chainable route builder API
8
- * - Integration with @b9g/match-pattern for enhanced URL matching
9
- * - Prepared for future cache-first architecture
7
+ * - Generator-based middleware with yield continuation
8
+ * - Integration with URLPattern and MatchPattern for enhanced URL matching
9
+ * - Cache-aware routing
10
+ * TODO:
11
+ * - Portable param matching
12
+ * - Typechecking
10
13
  */
11
- export { Router } from "./router.js";
12
- export type { Handler, Middleware, RouteContext, HttpMethod, RouterOptions, RouteConfig, RouteCacheConfig, } from "./_types.js";
14
+ /**
15
+ * Context object passed to handlers and middleware
16
+ * Contains route parameters extracted from URL pattern matching
17
+ */
18
+ export interface RouteContext {
19
+ /** Route parameters extracted from URL pattern matching */
20
+ params: Record<string, string>;
21
+ /** Middleware can add arbitrary properties for context sharing */
22
+ [key: string]: any;
23
+ }
24
+ /**
25
+ * Handler function signature - terminal response producer
26
+ * Handlers are terminal - must return a Response
27
+ */
28
+ export type Handler = (request: Request, context: RouteContext) => Response | Promise<Response>;
29
+ /**
30
+ * Generator middleware signature - uses yield for continuation
31
+ * Provides clean syntax and eliminates control flow bugs
32
+ */
33
+ export type GeneratorMiddleware = (request: Request, context: RouteContext) => AsyncGenerator<Request, Response | null | undefined, Response>;
34
+ /**
35
+ * Function middleware signature - supports short-circuiting
36
+ * Can modify request and context, and can return a Response to short-circuit
37
+ * - Return Response: short-circuits, skipping remaining middleware and handler
38
+ * - Return null/undefined: continues to next middleware (fallthrough)
39
+ */
40
+ export type FunctionMiddleware = (request: Request, context: RouteContext) => null | undefined | Response | Promise<null | undefined | Response>;
41
+ /**
42
+ * Union type for all supported middleware types
43
+ * Framework automatically detects type and executes appropriately
44
+ */
45
+ export type Middleware = GeneratorMiddleware | FunctionMiddleware;
46
+ /**
47
+ * HTTP methods supported by the router
48
+ */
49
+ export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
50
+ /**
51
+ * Route configuration options
52
+ */
53
+ export interface RouteConfig {
54
+ /** URL pattern for the route */
55
+ pattern: string;
56
+ }
57
+ /**
58
+ * Internal route entry stored by the router
59
+ */
60
+ interface RouteEntry {
61
+ pattern: import("@b9g/match-pattern").MatchPattern;
62
+ method: string;
63
+ handler: Handler;
64
+ }
65
+ /**
66
+ * Internal middleware entry stored by the router
67
+ */
68
+ interface MiddlewareEntry {
69
+ middleware: Middleware;
70
+ }
71
+ /**
72
+ * RouteBuilder provides a chainable API for defining routes with multiple HTTP methods
73
+ *
74
+ * Example:
75
+ * router.route('/api/users/:id')
76
+ * .get(getUserHandler)
77
+ * .put(updateUserHandler)
78
+ * .delete(deleteUserHandler);
79
+ */
80
+ declare class RouteBuilder {
81
+ #private;
82
+ constructor(router: Router, pattern: string);
83
+ /**
84
+ * Register a GET handler for this route pattern
85
+ */
86
+ get(handler: Handler): RouteBuilder;
87
+ /**
88
+ * Register a POST handler for this route pattern
89
+ */
90
+ post(handler: Handler): RouteBuilder;
91
+ /**
92
+ * Register a PUT handler for this route pattern
93
+ */
94
+ put(handler: Handler): RouteBuilder;
95
+ /**
96
+ * Register a DELETE handler for this route pattern
97
+ */
98
+ delete(handler: Handler): RouteBuilder;
99
+ /**
100
+ * Register a PATCH handler for this route pattern
101
+ */
102
+ patch(handler: Handler): RouteBuilder;
103
+ /**
104
+ * Register a HEAD handler for this route pattern
105
+ */
106
+ head(handler: Handler): RouteBuilder;
107
+ /**
108
+ * Register an OPTIONS handler for this route pattern
109
+ */
110
+ options(handler: Handler): RouteBuilder;
111
+ /**
112
+ * Register a handler for all HTTP methods on this route pattern
113
+ */
114
+ all(handler: Handler): RouteBuilder;
115
+ }
116
+ /**
117
+ * Router provides Request/Response routing with middleware support
118
+ * Designed to work universally across all JavaScript runtimes
119
+ */
120
+ export declare class Router {
121
+ #private;
122
+ constructor();
123
+ /**
124
+ * Register middleware that applies to all routes
125
+ * Middleware executes in the order it was registered
126
+ */
127
+ use(middleware: Middleware): void;
128
+ /**
129
+ * Register a handler for a specific pattern
130
+ */
131
+ use(pattern: string, handler: Handler): void;
132
+ /**
133
+ * Create a route builder for the given pattern
134
+ * Returns a chainable interface for registering HTTP method handlers
135
+ *
136
+ * Example:
137
+ * router.route('/api/users/:id')
138
+ * .get(getUserHandler)
139
+ * .put(updateUserHandler);
140
+ */
141
+ route(pattern: string): RouteBuilder;
142
+ route(config: RouteConfig): RouteBuilder;
143
+ /**
144
+ * Internal method called by RouteBuilder to register routes
145
+ * Public for RouteBuilder access, but not intended for direct use
146
+ */
147
+ addRoute(method: HttpMethod, pattern: string, handler: Handler): void;
148
+ /**
149
+ * Handle a request - main entrypoint for ServiceWorker usage
150
+ * Returns a response or throws if no route matches
151
+ */
152
+ handler: (request: Request) => Promise<Response>;
153
+ /**
154
+ * Match a request against registered routes and execute the handler chain
155
+ * Returns the response from the matched handler, or null if no route matches
156
+ * Note: Global middleware executes even if no route matches
157
+ */
158
+ match(request: Request): Promise<Response | null>;
159
+ /**
160
+ * Get registered routes for debugging/introspection
161
+ */
162
+ getRoutes(): RouteEntry[];
163
+ /**
164
+ * Get registered middleware for debugging/introspection
165
+ */
166
+ getMiddlewares(): MiddlewareEntry[];
167
+ /**
168
+ * Mount a subrouter at a specific path prefix
169
+ * All routes from the subrouter will be prefixed with the mount path
170
+ *
171
+ * Example:
172
+ * const apiRouter = new Router();
173
+ * apiRouter.route('/users').get(getUsersHandler);
174
+ * apiRouter.route('/users/:id').get(getUserHandler);
175
+ *
176
+ * const mainRouter = new Router();
177
+ * mainRouter.mount('/api/v1', apiRouter);
178
+ * // Routes become: /api/v1/users, /api/v1/users/:id
179
+ */
180
+ mount(mountPath: string, subrouter: Router): void;
181
+ /**
182
+ * Get route statistics
183
+ */
184
+ getStats(): {
185
+ routeCount: number;
186
+ middlewareCount: number;
187
+ compiled: boolean;
188
+ };
189
+ }
190
+ export {};