alien-middleware 0.4.0 → 0.5.0

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.
@@ -0,0 +1,138 @@
1
+ import { AdapterRequestContext, HattipHandler } from '@hattip/core';
2
+ import { Any } from 'radashi';
3
+
4
+ type RequestPlugin = {
5
+ /**
6
+ * Define properties on the request context.
7
+ */
8
+ define?: object;
9
+ /**
10
+ * Add type-safe environment variables.
11
+ */
12
+ env?: object;
13
+ };
14
+ type Inputs<T extends MiddlewareChain> = T['$']['input'];
15
+ type Platform<T extends MiddlewareChain> = T['$']['platform'];
16
+ type InputProperties<T extends MiddlewareChain> = Inputs<T>['properties'];
17
+ type InputEnv<T extends MiddlewareChain> = Inputs<T>['env'];
18
+ type Current<T extends MiddlewareChain> = T['$']['current'];
19
+ type Properties<T extends MiddlewareChain> = Current<T>['properties'];
20
+ type Env<T extends MiddlewareChain> = Current<T>['env'];
21
+ type MiddlewareTypes<TProperties extends object = any, TEnv extends object = any> = {
22
+ properties: TProperties;
23
+ env: TEnv;
24
+ };
25
+ interface HattipContext<TPlatform, TEnv extends object> extends AdapterRequestContext<TPlatform> {
26
+ /**
27
+ * The `request.url` string parsed into a `URL` object. Parsing is performed
28
+ * on-demand and the result is cached.
29
+ */
30
+ url: URL;
31
+ env<K extends keyof TEnv>(key: Extract<K, string>): TEnv[K];
32
+ env(key: never): string | undefined;
33
+ }
34
+ /**
35
+ * Converts a type `T` to something that can be intersected with an object.
36
+ */
37
+ type Intersectable<T extends object> = [T] extends [never] ? {} : [T] extends [Any] ? Record<PropertyKey, any> : T;
38
+ type RequestContext<TProperties extends object = any, TEnv extends object = any, TPlatform = any> = HattipContext<TPlatform, TEnv> & Intersectable<TProperties>;
39
+ /**
40
+ * Extract a `RequestContext` type from a `MiddlewareChain` type.
41
+ *
42
+ * When type `T` is `never`, a default context is returned.
43
+ */
44
+ type MiddlewareContext<T extends MiddlewareChain> = [T] extends [never] ? RequestContext<{}, {}, unknown> : RequestContext<Properties<T>, Env<T>, Platform<T>>;
45
+ type Awaitable<T> = T | Promise<T>;
46
+ type RequestMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>) => Awaitable<Response | RequestPlugin | void>;
47
+ type ResponseMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>, response: Response) => Awaitable<Response | void>;
48
+ type RequestHandler<TInputs extends MiddlewareTypes = any, TCurrent extends MiddlewareTypes = any, TPlatform = any> = HattipHandler<TPlatform> & MiddlewareChain<TInputs, TCurrent, TPlatform>;
49
+ /**
50
+ * Either a request middleware or a response middleware.
51
+ *
52
+ * If your middleware declares the `response` parameter, it's treated as a
53
+ * response middleware. This means it will run *after* a `Response` is generated
54
+ * by a request middleware.
55
+ */
56
+ type Middleware<TProperties extends object = any, TEnv extends object = any, TPlatform = any> = (context: RequestContext<TProperties, TEnv, TPlatform>, response: Response) => Awaitable<Response | RequestPlugin | void>;
57
+ /**
58
+ * Extract a `Middleware` type from a `MiddlewareChain` type.
59
+ */
60
+ type ExtractMiddleware<T extends MiddlewareChain> = Middleware<Properties<T>, Env<T>, Platform<T>>;
61
+ type Merge<TSource extends object, TOverrides extends object | undefined> = {} & (TOverrides extends object ? {
62
+ [K in keyof TSource | keyof TOverrides]: K extends keyof TOverrides ? TOverrides[K] : K extends keyof TSource ? TSource[K] : never;
63
+ } : TSource);
64
+ type ApplyRequestPlugin<TParent extends MiddlewareChain, TPlugin extends RequestPlugin> = {
65
+ properties: Merge<Properties<TParent>, TPlugin['define']>;
66
+ env: Merge<Env<TParent>, TPlugin['env']>;
67
+ };
68
+ /**
69
+ * This applies a middleware to a chain. If the type `TMiddleware` is itself a
70
+ * chain, it's treated as a nested chain, which won't leak its plugins into the
71
+ * parent chain.
72
+ */
73
+ type ApplyMiddleware<TParent extends MiddlewareChain, TMiddleware> = TMiddleware extends MiddlewareChain ? RequestHandler<Inputs<TParent>, Current<TParent>, Platform<TParent>> : TMiddleware extends () => Awaitable<infer TPlugin extends RequestPlugin> ? RequestHandler<Inputs<TParent>, ApplyRequestPlugin<TParent, TPlugin>, Platform<TParent>> : RequestHandler<Inputs<TParent>, Current<TParent>, Platform<TParent>>;
74
+ type ApplyFirstMiddleware<T extends Middleware> = T extends MiddlewareChain ? T : ApplyMiddleware<MiddlewareChain<{
75
+ properties: {};
76
+ env: {};
77
+ }, {
78
+ properties: {};
79
+ env: {};
80
+ }, unknown>, T>;
81
+ type MergeMiddleware<TFirst extends MiddlewareChain, TSecond extends Middleware<Properties<TFirst>, Env<TFirst>, Platform<TFirst>>> = RequestHandler<Inputs<TFirst>, TSecond extends MiddlewareChain ? {
82
+ properties: Merge<Properties<TFirst>, Properties<TSecond>>;
83
+ env: Merge<Env<TFirst>, Env<TSecond>>;
84
+ } : TSecond extends () => Awaitable<infer TPlugin extends RequestPlugin> ? ApplyRequestPlugin<TFirst, TPlugin> : Current<TFirst>, Platform<TFirst>>;
85
+ type RouteMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD';
86
+ type RouterContext<T extends MiddlewareChain = any, TPathParams extends object = any, TMethod extends RouteMethod = RouteMethod> = MiddlewareContext<T> & {
87
+ params: TPathParams;
88
+ method: TMethod;
89
+ };
90
+ type RouteHandler<T extends MiddlewareChain = any, TPathParams extends object = any, TMethod extends RouteMethod = RouteMethod> = (context: RouterContext<T, TPathParams, TMethod>) => Awaitable<Response | void>;
91
+
92
+ declare const kRequestChain: unique symbol;
93
+ declare const kResponseChain: unique symbol;
94
+ declare class MiddlewareChain<
95
+ /** Values expected by the start of the chain. */
96
+ TInputs extends MiddlewareTypes = any,
97
+ /** Values provided by the end of the chain. */
98
+ TCurrent extends MiddlewareTypes = any,
99
+ /** Values from the host platform. */
100
+ TPlatform = any> {
101
+ /** This property won't exist at runtime. It contains type information for inference purposes. */
102
+ $: {
103
+ input: TInputs;
104
+ current: TCurrent;
105
+ platform: TPlatform;
106
+ };
107
+ /** The number of parameters when called as a function. */
108
+ readonly length: 1;
109
+ protected [kRequestChain]: RequestMiddleware[];
110
+ protected [kResponseChain]: ResponseMiddleware[];
111
+ /**
112
+ * Attach a middleware. If the `response` parameter is declared, it will be
113
+ * treated as a response middleware. Otherwise, it will be treated as a
114
+ * request middleware.
115
+ *
116
+ * @returns a new `MiddlewareChain` instance
117
+ */
118
+ use<const TMiddleware extends ExtractMiddleware<this>>(middleware: TMiddleware): ApplyMiddleware<this, TMiddleware>;
119
+ /**
120
+ * Merge two middleware chains. The middlewares from the second chain will be
121
+ * executed after the middlewares from the first chain.
122
+ *
123
+ * For ease of use, this method may be given a middleware function, which
124
+ * short-circuits to the `use` method. You should prefer using the `use`
125
+ * method directly, if possible.
126
+ */
127
+ merge<TMiddleware extends ExtractMiddleware<this>>(chain: TMiddleware): MergeMiddleware<this, TMiddleware>;
128
+ }
129
+ declare function chain<TProperties extends object = {}, TEnv extends object = {}, TPlatform = unknown>(): MiddlewareChain<{
130
+ env: TEnv;
131
+ properties: TProperties;
132
+ }, {
133
+ env: TEnv;
134
+ properties: TProperties;
135
+ }, TPlatform>;
136
+ declare function chain<const T extends Middleware = Middleware>(middleware: T): ApplyFirstMiddleware<T>;
137
+
138
+ export { type ApplyMiddleware as A, type ExtractMiddleware as E, MiddlewareChain as M, type RouteHandler as R, type MiddlewareContext as a, type RouteMethod as b, chain as c, type MergeMiddleware as d, type Middleware as e, type RequestContext as f, type RequestHandler as g, type RequestMiddleware as h, type RequestPlugin as i, type ResponseMiddleware as j };
package/dist/index.d.ts CHANGED
@@ -1,112 +1,3 @@
1
- import { AdapterRequestContext, HattipHandler } from '@hattip/core';
2
- import { Any } from 'radashi';
3
-
4
- type RequestPlugin = {
5
- /**
6
- * Define properties on the request context.
7
- */
8
- define?: object;
9
- /**
10
- * Add type-safe environment variables.
11
- */
12
- env?: object;
13
- };
14
- type Inputs<T extends MiddlewareChain> = T['$']['input'];
15
- type Platform<T extends MiddlewareChain> = T['$']['platform'];
16
- type InputProperties<T extends MiddlewareChain> = Inputs<T>['properties'];
17
- type InputEnv<T extends MiddlewareChain> = Inputs<T>['env'];
18
- type Current<T extends MiddlewareChain> = T['$']['current'];
19
- type Properties<T extends MiddlewareChain> = Current<T>['properties'];
20
- type Env<T extends MiddlewareChain> = Current<T>['env'];
21
- type MiddlewareTypes<TProperties extends object = any, TEnv extends object = any> = {
22
- properties: TProperties;
23
- env: TEnv;
24
- };
25
- interface HattipContext<TPlatform, TEnv extends object> extends AdapterRequestContext<TPlatform> {
26
- /**
27
- * The `request.url` string parsed into a `URL` object. Parsing is performed
28
- * on-demand and the result is cached.
29
- */
30
- url: URL;
31
- env<K extends keyof TEnv>(key: Extract<K, string>): TEnv[K];
32
- env(key: never): string | undefined;
33
- }
34
- /**
35
- * Converts a type `T` to something that can be intersected with an object.
36
- */
37
- type Intersectable<T extends object> = [T] extends [never] ? {} : [T] extends [Any] ? Record<PropertyKey, any> : T;
38
- type RequestContext<TProperties extends object = any, TEnv extends object = any, TPlatform = any> = HattipContext<TPlatform, TEnv> & Intersectable<TProperties>;
39
- type Awaitable<T> = T | PromiseLike<T>;
40
- type RequestMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>) => Awaitable<Response | RequestPlugin | void>;
41
- type ResponseMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>, response: Response) => Awaitable<Response | void>;
42
- type RequestHandler<TInputs extends MiddlewareTypes = any, TCurrent extends MiddlewareTypes = any, TPlatform = any> = HattipHandler<TPlatform> & MiddlewareChain<TInputs, TCurrent, TPlatform>;
43
- /**
44
- * Either a request middleware or a response middleware.
45
- *
46
- * If your middleware declares the `response` parameter, it's treated as a
47
- * response middleware. This means it will run *after* a `Response` is generated
48
- * by a request middleware.
49
- */
50
- type Middleware<TProperties extends object = any, TEnv extends object = any, TPlatform = unknown> = (context: RequestContext<TProperties, TEnv, TPlatform>, response: Response) => Awaitable<Response | RequestPlugin | void>;
51
- type Merge<TSource extends object, TOverrides extends object | undefined> = {} & (TOverrides extends object ? {
52
- [K in keyof TSource | keyof TOverrides]: K extends keyof TOverrides ? TOverrides[K] : K extends keyof TSource ? TSource[K] : never;
53
- } : TSource);
54
- type ApplyMiddleware<TChain extends MiddlewareChain, TMiddleware> = TMiddleware extends MiddlewareChain ? RequestHandler<Inputs<TChain>, Current<TChain>, Platform<TChain>> : TMiddleware extends () => Awaitable<infer TPlugin extends RequestPlugin> ? RequestHandler<Inputs<TChain>, {
55
- properties: Merge<Properties<TChain>, TPlugin['define']>;
56
- env: Merge<Env<TChain>, TPlugin['env']>;
57
- }, Platform<TChain>> : RequestHandler<Inputs<TChain>, Current<TChain>, Platform<TChain>>;
58
- type ApplyFirstMiddleware<T extends Middleware> = ApplyMiddleware<MiddlewareChain<{
59
- properties: {};
60
- env: {};
61
- }, {
62
- properties: {};
63
- env: {};
64
- }, unknown>, T>;
65
- type MergeMiddlewareChains<TFirst extends MiddlewareChain, TSecond extends MiddlewareChain<Current<TFirst>, any, Platform<TFirst>>> = RequestHandler<Inputs<TFirst>, {
66
- properties: Merge<Properties<TFirst>, Properties<TSecond>>;
67
- env: Merge<Env<TFirst>, Env<TSecond>>;
68
- }, Platform<TFirst>>;
69
-
70
- declare const kRequestChain: unique symbol;
71
- declare const kResponseChain: unique symbol;
72
- declare class MiddlewareChain<
73
- /** Values expected by the start of the chain. */
74
- TInputs extends MiddlewareTypes = any,
75
- /** Values provided by the end of the chain. */
76
- TCurrent extends MiddlewareTypes = any,
77
- /** Values from the host platform. */
78
- TPlatform = any> {
79
- /** This property won't exist at runtime. It contains type information for inference purposes. */
80
- $: {
81
- input: TInputs;
82
- current: TCurrent;
83
- platform: TPlatform;
84
- };
85
- /** The number of parameters when called as a function. */
86
- readonly length: 1;
87
- protected [kRequestChain]: RequestMiddleware[];
88
- protected [kResponseChain]: ResponseMiddleware[];
89
- /**
90
- * Attach a middleware. If the `response` paramter is declared, it will be
91
- * treated as a response middleware. Otherwise, it will be treated as a
92
- * request middleware.
93
- *
94
- * @returns a new `MiddlewareChain` instance
95
- */
96
- use<const TMiddleware extends Middleware<TCurrent['properties'], TCurrent['env'], TPlatform>>(middleware: TMiddleware): ApplyMiddleware<this, TMiddleware>;
97
- /**
98
- * Merge two middleware chains. The middlewares from the second chain will be
99
- * executed after the middlewares from the first chain.
100
- */
101
- merge<TChain extends MiddlewareChain<TCurrent, any, TPlatform>>(chain: TChain): MergeMiddlewareChains<this, TChain>;
102
- }
103
- declare function chain<TProperties extends object = {}, TEnv extends object = {}, TPlatform = unknown>(): MiddlewareChain<{
104
- env: TEnv;
105
- properties: TProperties;
106
- }, {
107
- env: TEnv;
108
- properties: TProperties;
109
- }, TPlatform>;
110
- declare function chain<const T extends Middleware = Middleware>(middleware: T): ApplyFirstMiddleware<T>;
111
-
112
- export { type Middleware, MiddlewareChain, type RequestContext, type RequestHandler, type RequestMiddleware, type RequestPlugin, type ResponseMiddleware, chain };
1
+ export { A as ApplyMiddleware, E as ExtractMiddleware, d as MergeMiddleware, e as Middleware, M as MiddlewareChain, a as MiddlewareContext, f as RequestContext, g as RequestHandler, h as RequestMiddleware, i as RequestPlugin, j as ResponseMiddleware, c as chain } from './index-DQDr9Gkb.js';
2
+ import '@hattip/core';
3
+ import 'radashi';
package/dist/index.js CHANGED
@@ -1,15 +1,3 @@
1
- // node_modules/.pnpm/radashi@12.5.0-beta.6d5c035/node_modules/radashi/dist/radashi.js
2
- var asyncIteratorSymbol = (
3
- /* c8 ignore next */
4
- Symbol.asyncIterator || Symbol.for("Symbol.asyncIterator")
5
- );
6
- function isFunction(value) {
7
- return typeof value === "function";
8
- }
9
- function isPromise(value) {
10
- return !!value && isFunction(value.then);
11
- }
12
-
13
1
  // src/index.ts
14
2
  var kRequestChain = Symbol("requestChain");
15
3
  var kResponseChain = Symbol("responseChain");
@@ -23,11 +11,11 @@ var urlDescriptor = {
23
11
  return url;
24
12
  }
25
13
  };
26
- var MiddlewareChain = class {
14
+ var MiddlewareChain = class _MiddlewareChain {
27
15
  [kRequestChain] = [];
28
16
  [kResponseChain] = [];
29
17
  /**
30
- * Attach a middleware. If the `response` paramter is declared, it will be
18
+ * Attach a middleware. If the `response` parameter is declared, it will be
31
19
  * treated as a response middleware. Otherwise, it will be treated as a
32
20
  * request middleware.
33
21
  *
@@ -46,12 +34,19 @@ var MiddlewareChain = class {
46
34
  /**
47
35
  * Merge two middleware chains. The middlewares from the second chain will be
48
36
  * executed after the middlewares from the first chain.
37
+ *
38
+ * For ease of use, this method may be given a middleware function, which
39
+ * short-circuits to the `use` method. You should prefer using the `use`
40
+ * method directly, if possible.
49
41
  */
50
42
  merge(chain2) {
51
- return createHandler(
52
- [...this[kRequestChain], ...chain2[kRequestChain]],
53
- [...this[kResponseChain], ...chain2[kResponseChain]]
54
- );
43
+ if (chain2 instanceof _MiddlewareChain) {
44
+ return createHandler(
45
+ [...this[kRequestChain], ...chain2[kRequestChain]],
46
+ [...this[kResponseChain], ...chain2[kResponseChain]]
47
+ );
48
+ }
49
+ return this.use(chain2);
55
50
  }
56
51
  };
57
52
  function createHandler(requestChain, responseChain) {
@@ -70,7 +65,7 @@ function createHandler(requestChain, responseChain) {
70
65
  }
71
66
  cache.add(middleware);
72
67
  let result = middleware(context);
73
- if (isPromise(result)) {
68
+ if (result instanceof Promise) {
74
69
  result = await result;
75
70
  }
76
71
  if (result) {
@@ -104,7 +99,7 @@ function createHandler(requestChain, responseChain) {
104
99
  }
105
100
  cache.add(middleware);
106
101
  let result = middleware(context, response);
107
- if (isPromise(result)) {
102
+ if (result instanceof Promise) {
108
103
  result = await result;
109
104
  }
110
105
  if (result && result instanceof Response) {
@@ -126,6 +121,9 @@ function createExtendedEnv(context) {
126
121
  return env;
127
122
  }
128
123
  function chain(middleware) {
124
+ if (middleware instanceof MiddlewareChain) {
125
+ return middleware;
126
+ }
129
127
  const handler = new MiddlewareChain();
130
128
  return middleware ? handler.use(middleware) : handler;
131
129
  }
@@ -0,0 +1,16 @@
1
+ import { InferParams } from 'pathic';
2
+ import { M as MiddlewareChain, a as MiddlewareContext, R as RouteHandler, b as RouteMethod } from './index-DQDr9Gkb.js';
3
+ import '@hattip/core';
4
+ import 'radashi';
5
+
6
+ type OneOrMany<T> = T | readonly T[];
7
+ type Router<T extends MiddlewareChain = any> = ReturnType<typeof routes<T>>;
8
+ declare function routes<T extends MiddlewareChain>(middlewares?: T): {
9
+ (context: MiddlewareContext<T>): void | Response | Promise<void | Response> | undefined;
10
+ use: {
11
+ <TPath extends string>(path: TPath, handler: RouteHandler<T, InferParams<TPath>>): /*elided*/ any;
12
+ <TPath extends string, TMethod extends RouteMethod = RouteMethod>(method: OneOrMany<TMethod> | "*", path: TPath, handler: RouteHandler<T, InferParams<TPath>, TMethod>): /*elided*/ any;
13
+ };
14
+ };
15
+
16
+ export { type Router, routes };
package/dist/router.js ADDED
@@ -0,0 +1,50 @@
1
+ // src/router.ts
2
+ import { compilePaths } from "pathic";
3
+
4
+ // node_modules/.pnpm/radashi@12.5.0-beta.6d5c035/node_modules/radashi/dist/radashi.js
5
+ var isArray = /* @__PURE__ */ (() => Array.isArray)();
6
+ var asyncIteratorSymbol = (
7
+ /* c8 ignore next */
8
+ Symbol.asyncIterator || Symbol.for("Symbol.asyncIterator")
9
+ );
10
+ function isFunction(value) {
11
+ return typeof value === "function";
12
+ }
13
+
14
+ // src/router.ts
15
+ function routes(middlewares) {
16
+ const paths = [];
17
+ const filters = [];
18
+ const handlers = [];
19
+ function use(method, path, handler) {
20
+ if (isFunction(path)) {
21
+ paths.push(method);
22
+ filters.push(null);
23
+ handlers.push(path);
24
+ } else {
25
+ paths.push(path);
26
+ filters.push(
27
+ method === "*" ? null : isArray(method) ? (m) => method.includes(m) : (m) => method === m
28
+ );
29
+ handlers.push(handler);
30
+ }
31
+ return router;
32
+ }
33
+ let matcher;
34
+ function router(context) {
35
+ matcher ||= compilePaths(paths);
36
+ const method = context.request.method;
37
+ return matcher(context.request.path, (index, params) => {
38
+ if (!filters[index] || filters[index](method)) {
39
+ context.method = method;
40
+ context.params = params;
41
+ return middlewares ? middlewares.use(handlers[index])(context) : handlers[index](context);
42
+ }
43
+ });
44
+ }
45
+ router.use = use;
46
+ return router;
47
+ }
48
+ export {
49
+ routes
50
+ };
package/package.json CHANGED
@@ -1,10 +1,16 @@
1
1
  {
2
2
  "name": "alien-middleware",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "exports": {
6
- "types": "./dist/index.d.ts",
7
- "default": "./dist/index.js"
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "default": "./dist/index.js"
9
+ },
10
+ "./router": {
11
+ "types": "./dist/router.d.ts",
12
+ "default": "./dist/router.js"
13
+ }
8
14
  },
9
15
  "files": [
10
16
  "dist"
@@ -31,6 +37,9 @@
31
37
  "typescript": "^5.8.3",
32
38
  "vitest": "^3.1.2"
33
39
  },
40
+ "dependencies": {
41
+ "pathic": "^0.1.6"
42
+ },
34
43
  "scripts": {
35
44
  "dev": "rimraf dist && tsup --sourcemap --watch",
36
45
  "build": "rimraf dist && tsup",
package/readme.md CHANGED
@@ -253,3 +253,110 @@ const myMiddleware = (context: RequestContext<any, Env>) => {
253
253
  ```
254
254
 
255
255
  In both examples, we skip declaring any additional context properties (the first type parameter) because we're not using any. The second type parameter is for environment variables. The third is for the special `context.platform` property, whose value is provided by the host platform (e.g. Node.js, Deno, Bun, etc). On principle, a middleware should avoid using the `context.platform` property, since that could make it non-portable unless you write extra fallback logic for other hosts.
256
+
257
+ ## Router
258
+
259
+ The `routes` function provides a way to create a router instance for handling different paths and HTTP methods.
260
+
261
+ ```typescript
262
+ import { routes } from 'alien-middleware/router'
263
+
264
+ const router = routes()
265
+ ```
266
+
267
+ ### Path Parameter Type Inference
268
+
269
+ The `routes` function leverages `pathic` to provide type inference for path parameters.
270
+
271
+ ```typescript
272
+ import { routes } from 'alien-middleware/router'
273
+
274
+ const router = routes()
275
+
276
+ router.use('/users/:userId', context => {
277
+ // context.params.userId is automatically typed as string
278
+ const userId = context.params.userId
279
+ return new Response(`User ID: ${userId}`)
280
+ })
281
+ ```
282
+
283
+ ### Handling Specific HTTP Methods
284
+
285
+ You can specify one or more HTTP methods for a route by providing the method(s) as the first argument to `.use()`.
286
+
287
+ ```typescript
288
+ import { routes } from 'alien-middleware/router'
289
+
290
+ const router = routes()
291
+
292
+ // This handler will only run for GET requests to /api/items
293
+ router.use('GET', '/api/items', context => {
294
+ return new Response('List of items')
295
+ })
296
+
297
+ // This handler will only run for POST requests to /api/items
298
+ router.use('POST', '/api/items', context => {
299
+ return new Response('Create a new item', { status: 201 })
300
+ })
301
+
302
+ // This handler will run for both PUT and PATCH requests to /api/items/:id
303
+ router.use(['PUT', 'PATCH'], '/api/items/:id', context => {
304
+ const itemId = context.params.id
305
+ return new Response(`Update item ${itemId}`)
306
+ })
307
+
308
+ // This handler will run for any method to /status
309
+ router.use('/status', context => {
310
+ return new Response('Status: OK')
311
+ })
312
+ ```
313
+
314
+ > [!NOTE]
315
+ > Your routes don't need to be in any particular order, unless their path
316
+ > patterns are exactly the same. The `pathic` library will match the most
317
+ > specific path first. This allows you to split your routes into multiple files
318
+ > for better organization.
319
+
320
+ ### Type-Safe Middleware with `routes()`
321
+
322
+ You can pass a middleware chain to the `routes()` function to apply middleware specifically to the routes defined by that router instance. This provides type safety for context extensions within the router.
323
+
324
+ ```typescript
325
+ import {
326
+ chain,
327
+ type RequestContext,
328
+ type RequestPlugin,
329
+ } from 'alien-middleware'
330
+ import { routes } from 'alien-middleware/router'
331
+
332
+ // Define a middleware that adds a user to the context
333
+ const addUserMiddleware = (context: RequestContext): RequestPlugin => {
334
+ const user = { id: 123, name: 'Alice' }
335
+ return { define: { user } }
336
+ }
337
+
338
+ // Create a chain with the middleware
339
+ const authMiddlewares = chain(addUserMiddleware)
340
+
341
+ // Pass the chain to the routes function
342
+ const authenticatedRouter = routes(authMiddlewares)
343
+
344
+ // Define a route that uses the context property added by the middleware
345
+ authenticatedRouter.use('/profile', context => {
346
+ // context.user is now type-safe and available
347
+ return new Response(`Welcome, ${context.user.name}!`)
348
+ })
349
+
350
+ // Routes defined on a router without a chain won't have the user property
351
+ const publicRouter = routes()
352
+
353
+ publicRouter.use('/public', context => {
354
+ // context.user is not available here
355
+ // @ts-expect-error - user is not defined on this context
356
+ console.log(context.user)
357
+ return new Response('Public content')
358
+ })
359
+
360
+ // You can combine routers using .use()
361
+ const app = chain().use(authenticatedRouter).use(publicRouter)
362
+ ```