alien-middleware 0.3.1 → 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,103 +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
-
66
- declare const kRequestChain: unique symbol;
67
- declare const kResponseChain: unique symbol;
68
- declare class MiddlewareChain<
69
- /** Values expected by the start of the chain. */
70
- TInputs extends MiddlewareTypes = any,
71
- /** Values provided by the end of the chain. */
72
- TCurrent extends MiddlewareTypes = any,
73
- /** Values from the host platform. */
74
- TPlatform = any> {
75
- /** This property won't exist at runtime. It contains type information for inference purposes. */
76
- $: {
77
- input: TInputs;
78
- current: TCurrent;
79
- platform: TPlatform;
80
- };
81
- /** The number of parameters when called as a function. */
82
- readonly length: 1;
83
- protected [kRequestChain]: RequestMiddleware[];
84
- protected [kResponseChain]: ResponseMiddleware[];
85
- /**
86
- * Attach a middleware. If the `response` paramter is declared, it will be
87
- * treated as a response middleware. Otherwise, it will be treated as a
88
- * request middleware.
89
- *
90
- * @returns a new `MiddlewareChain` instance
91
- */
92
- use<const TMiddleware extends Middleware<TCurrent['properties'], TCurrent['env'], TPlatform>>(middleware: TMiddleware): ApplyMiddleware<this, TMiddleware>;
93
- }
94
- declare function chain<TProperties extends object = {}, TEnv extends object = {}, TPlatform = unknown>(): MiddlewareChain<{
95
- env: TEnv;
96
- properties: TProperties;
97
- }, {
98
- env: TEnv;
99
- properties: TProperties;
100
- }, TPlatform>;
101
- declare function chain<const T extends Middleware = Middleware>(middleware: T): ApplyFirstMiddleware<T>;
102
-
103
- 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");
@@ -27,7 +15,7 @@ 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
  *
@@ -41,66 +29,91 @@ var MiddlewareChain = class _MiddlewareChain {
41
29
  } else {
42
30
  responseChain = [...responseChain, middleware];
43
31
  }
44
- async function handler(parentContext) {
45
- const context = Object.create(parentContext);
46
- context[kIgnoreNotFound] = true;
47
- if (!("url" in context)) {
48
- Object.defineProperty(context, "url", urlDescriptor);
32
+ return createHandler(requestChain, responseChain);
33
+ }
34
+ /**
35
+ * Merge two middleware chains. The middlewares from the second chain will be
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.
41
+ */
42
+ merge(chain2) {
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);
50
+ }
51
+ };
52
+ function createHandler(requestChain, responseChain) {
53
+ async function handler(parentContext) {
54
+ const context = Object.create(parentContext);
55
+ context[kIgnoreNotFound] = true;
56
+ if (!("url" in context)) {
57
+ Object.defineProperty(context, "url", urlDescriptor);
58
+ }
59
+ const cache = context[kMiddlewareCache] ||= /* @__PURE__ */ new Set();
60
+ let response;
61
+ let env;
62
+ for (const middleware of requestChain) {
63
+ if (cache.has(middleware)) {
64
+ continue;
49
65
  }
50
- const cache = context[kMiddlewareCache] ||= /* @__PURE__ */ new Set();
51
- let response;
52
- let env;
53
- for (const middleware2 of requestChain) {
54
- if (cache.has(middleware2)) {
55
- continue;
56
- }
57
- cache.add(middleware2);
58
- let result = middleware2(context);
59
- if (isPromise(result)) {
60
- result = await result;
66
+ cache.add(middleware);
67
+ let result = middleware(context);
68
+ if (result instanceof Promise) {
69
+ result = await result;
70
+ }
71
+ if (result) {
72
+ if (result instanceof Response) {
73
+ response = result;
74
+ break;
61
75
  }
62
- if (result) {
63
- if (result instanceof Response) {
64
- response = result;
65
- break;
66
- }
67
- if (result.define) {
68
- Object.assign(context, result.define);
69
- }
70
- if (result.env) {
71
- env ||= createExtendedEnv(context);
72
- Object.assign(env, result.env);
76
+ if (result.define) {
77
+ for (const key in result.define) {
78
+ Object.defineProperty(context, key, {
79
+ ...Object.getOwnPropertyDescriptor(result.define, key),
80
+ configurable: false
81
+ });
73
82
  }
74
83
  }
75
- }
76
- if (!response) {
77
- if (parentContext[kIgnoreNotFound]) {
78
- return;
84
+ if (result.env) {
85
+ env ||= createExtendedEnv(context);
86
+ Object.assign(env, result.env);
79
87
  }
80
- response = new Response("Not Found", { status: 404 });
81
88
  }
82
- for (const middleware2 of responseChain) {
83
- if (cache.has(middleware2)) {
84
- continue;
85
- }
86
- cache.add(middleware2);
87
- let result = middleware2(context, response);
88
- if (isPromise(result)) {
89
- result = await result;
90
- }
91
- if (result && result instanceof Response) {
92
- response = result;
93
- continue;
94
- }
89
+ }
90
+ if (!response) {
91
+ if (parentContext[kIgnoreNotFound]) {
92
+ return;
95
93
  }
96
- return response;
94
+ response = new Response("Not Found", { status: 404 });
97
95
  }
98
- Object.setPrototypeOf(handler, _MiddlewareChain.prototype);
99
- handler[kRequestChain] = requestChain;
100
- handler[kResponseChain] = responseChain;
101
- return handler;
96
+ for (const middleware of responseChain) {
97
+ if (cache.has(middleware)) {
98
+ continue;
99
+ }
100
+ cache.add(middleware);
101
+ let result = middleware(context, response);
102
+ if (result instanceof Promise) {
103
+ result = await result;
104
+ }
105
+ if (result && result instanceof Response) {
106
+ response = result;
107
+ continue;
108
+ }
109
+ }
110
+ return response;
102
111
  }
103
- };
112
+ Object.setPrototypeOf(handler, MiddlewareChain.prototype);
113
+ handler[kRequestChain] = requestChain;
114
+ handler[kResponseChain] = responseChain;
115
+ return handler;
116
+ }
104
117
  function createExtendedEnv(context) {
105
118
  const env = /* @__PURE__ */ Object.create(null);
106
119
  const superEnv = context.env;
@@ -108,6 +121,9 @@ function createExtendedEnv(context) {
108
121
  return env;
109
122
  }
110
123
  function chain(middleware) {
124
+ if (middleware instanceof MiddlewareChain) {
125
+ return middleware;
126
+ }
111
127
  const handler = new MiddlewareChain();
112
128
  return middleware ? handler.use(middleware) : handler;
113
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.3.1",
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
@@ -82,6 +82,14 @@ server.listen(3000, () => {
82
82
  > If no middleware in the chain returns a `Response`, a `404 Not Found` response
83
83
  > is automatically returned.
84
84
 
85
+ #### Middlewares are deduplicated.
86
+
87
+ If you add the same middleware multiple times, it will only run once. This is a safety measure that allows you to use the same middleware in different places without worrying about it running multiple times.
88
+
89
+ ```typescript
90
+ const app = chain().use(myMiddleware).use(myMiddleware)
91
+ ```
92
+
85
93
  ### Request Middleware
86
94
 
87
95
  Request middleware runs sequentially before a `Response` is generated.
@@ -203,6 +211,16 @@ const finalApp = chain().use(innerChain).use(outerMiddleware)
203
211
 
204
212
  If a nested chain does not return a `Response`, execution continues with the next middleware in the outer chain.
205
213
 
214
+ ### Merging Chains
215
+
216
+ You can merge two middleware chains using the `merge` method. This is useful if you want to combine middleware from multiple files or modules.
217
+
218
+ ```typescript
219
+ const mergedApp = chain().use(middleware1).merge(chain().use(middleware2))
220
+ // …is equivalent to…
221
+ const mergedApp = chain().use(middleware1).use(middleware2)
222
+ ```
223
+
206
224
  ### Safe Environment Variables
207
225
 
208
226
  When writing a Hattip handler without this package, the `context.env()` method is inherently unsafe. Its return type is always `string | undefined`, which means you either need to write defensive checks or use type assertions. Neither is ideal.
@@ -235,3 +253,110 @@ const myMiddleware = (context: RequestContext<any, Env>) => {
235
253
  ```
236
254
 
237
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
+ ```