alien-middleware 0.5.0 → 0.6.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.
@@ -1,16 +1,22 @@
1
1
  import { AdapterRequestContext, HattipHandler } from '@hattip/core';
2
2
  import { Any } from 'radashi';
3
3
 
4
- type RequestPlugin = {
4
+ type RequestEnvPlugin = {
5
5
  /**
6
- * Define properties on the request context.
7
- */
8
- define?: object;
9
- /**
10
- * Add type-safe environment variables.
6
+ * Add type-safe environment variables. These are accessed with the `env()`
7
+ * method on the request context.
11
8
  */
12
9
  env?: object;
13
10
  };
11
+ /**
12
+ * The object returned by a request middleware that is merged into the request
13
+ * context. The same context property cannot be defined by two different
14
+ * plugins, or an error will be thrown at runtime.
15
+ *
16
+ * May contain special properties:
17
+ * - `env`: Add type-safe environment variables.
18
+ */
19
+ type RequestPlugin = Record<string, unknown> & RequestEnvPlugin;
14
20
  type Inputs<T extends MiddlewareChain> = T['$']['input'];
15
21
  type Platform<T extends MiddlewareChain> = T['$']['platform'];
16
22
  type InputProperties<T extends MiddlewareChain> = Inputs<T>['properties'];
@@ -35,7 +41,14 @@ interface HattipContext<TPlatform, TEnv extends object> extends AdapterRequestCo
35
41
  * Converts a type `T` to something that can be intersected with an object.
36
42
  */
37
43
  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>;
44
+ /**
45
+ * An extensible Hattip context object.
46
+ *
47
+ * NOTE: When using this type on the right side of an `extends` clause, you
48
+ * should prefer `RequestContext<any>` over `RequestContext` (no type
49
+ * parameters), as the default type is stricter.
50
+ */
51
+ type RequestContext<TProperties extends object = never, TEnv extends object = any, TPlatform = any> = HattipContext<TPlatform, TEnv> & Intersectable<TProperties>;
39
52
  /**
40
53
  * Extract a `RequestContext` type from a `MiddlewareChain` type.
41
54
  *
@@ -62,7 +75,7 @@ type Merge<TSource extends object, TOverrides extends object | undefined> = {} &
62
75
  [K in keyof TSource | keyof TOverrides]: K extends keyof TOverrides ? TOverrides[K] : K extends keyof TSource ? TSource[K] : never;
63
76
  } : TSource);
64
77
  type ApplyRequestPlugin<TParent extends MiddlewareChain, TPlugin extends RequestPlugin> = {
65
- properties: Merge<Properties<TParent>, TPlugin['define']>;
78
+ properties: Merge<Properties<TParent>, Omit<TPlugin, keyof RequestEnvPlugin>>;
66
79
  env: Merge<Env<TParent>, TPlugin['env']>;
67
80
  };
68
81
  /**
@@ -71,13 +84,14 @@ type ApplyRequestPlugin<TParent extends MiddlewareChain, TPlugin extends Request
71
84
  * parent chain.
72
85
  */
73
86
  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<{
87
+ type EmptyMiddlewareChain = MiddlewareChain<{
75
88
  properties: {};
76
89
  env: {};
77
90
  }, {
78
91
  properties: {};
79
92
  env: {};
80
- }, unknown>, T>;
93
+ }, unknown>;
94
+ type ApplyFirstMiddleware<T extends Middleware> = T extends MiddlewareChain ? T : ApplyMiddleware<EmptyMiddlewareChain, T>;
81
95
  type MergeMiddleware<TFirst extends MiddlewareChain, TSecond extends Middleware<Properties<TFirst>, Env<TFirst>, Platform<TFirst>>> = RequestHandler<Inputs<TFirst>, TSecond extends MiddlewareChain ? {
82
96
  properties: Merge<Properties<TFirst>, Properties<TSecond>>;
83
97
  env: Merge<Env<TFirst>, Env<TSecond>>;
@@ -135,4 +149,4 @@ declare function chain<TProperties extends object = {}, TEnv extends object = {}
135
149
  }, TPlatform>;
136
150
  declare function chain<const T extends Middleware = Middleware>(middleware: T): ApplyFirstMiddleware<T>;
137
151
 
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 };
152
+ export { type ApplyMiddleware as A, type EmptyMiddlewareChain as E, MiddlewareChain as M, type RouteHandler as R, type MiddlewareContext as a, type RouteMethod as b, type RouterContext as c, chain as d, type ExtractMiddleware as e, type MergeMiddleware as f, type Middleware as g, type RequestContext as h, type RequestHandler as i, type RequestMiddleware as j, type RequestPlugin as k, type ResponseMiddleware as l };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
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';
1
+ export { A as ApplyMiddleware, e as ExtractMiddleware, f as MergeMiddleware, g as Middleware, M as MiddlewareChain, a as MiddlewareContext, h as RequestContext, i as RequestHandler, j as RequestMiddleware, k as RequestPlugin, l as ResponseMiddleware, d as chain } from './index-CuxbHrbf.js';
2
2
  import '@hattip/core';
3
3
  import 'radashi';
package/dist/index.js CHANGED
@@ -73,18 +73,16 @@ function createHandler(requestChain, responseChain) {
73
73
  response = result;
74
74
  break;
75
75
  }
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
- });
76
+ for (const key in result) {
77
+ const descriptor = Object.getOwnPropertyDescriptor(result, key);
78
+ if (key === "env") {
79
+ env ||= createExtendedEnv(context);
80
+ Object.defineProperty(env, key, descriptor);
81
+ } else {
82
+ descriptor.configurable = false;
83
+ Object.defineProperty(context, key, descriptor);
82
84
  }
83
85
  }
84
- if (result.env) {
85
- env ||= createExtendedEnv(context);
86
- Object.assign(env, result.env);
87
- }
88
86
  }
89
87
  }
90
88
  if (!response) {
package/dist/router.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { InferParams } from 'pathic';
2
- import { M as MiddlewareChain, a as MiddlewareContext, R as RouteHandler, b as RouteMethod } from './index-DQDr9Gkb.js';
2
+ import { M as MiddlewareChain, E as EmptyMiddlewareChain, a as MiddlewareContext, R as RouteHandler, b as RouteMethod } from './index-CuxbHrbf.js';
3
+ export { c as RouterContext } from './index-CuxbHrbf.js';
3
4
  import '@hattip/core';
4
5
  import 'radashi';
5
6
 
6
7
  type OneOrMany<T> = T | readonly T[];
7
8
  type Router<T extends MiddlewareChain = any> = ReturnType<typeof routes<T>>;
8
- declare function routes<T extends MiddlewareChain>(middlewares?: T): {
9
+ declare function routes<T extends MiddlewareChain = EmptyMiddlewareChain>(middlewares?: T): {
9
10
  (context: MiddlewareContext<T>): void | Response | Promise<void | Response> | undefined;
10
11
  use: {
11
12
  <TPath extends string>(path: TPath, handler: RouteHandler<T, InferParams<TPath>>): /*elided*/ any;
@@ -13,4 +14,4 @@ declare function routes<T extends MiddlewareChain>(middlewares?: T): {
13
14
  };
14
15
  };
15
16
 
16
- export { type Router, routes };
17
+ export { RouteHandler, type Router, routes };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alien-middleware",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.6.0",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
package/readme.md CHANGED
@@ -97,6 +97,8 @@ Request middleware runs sequentially before a `Response` is generated.
97
97
  - **Terminating the Chain:** Return a `Response` object to stop processing subsequent request middleware.
98
98
 
99
99
  ```typescript
100
+ import type { RequestContext } from 'alien-middleware'
101
+
100
102
  const earlyResponder = (context: RequestContext) => {
101
103
  if (context.request.url.endsWith('/forbidden')) {
102
104
  return new Response('Forbidden', { status: 403 })
@@ -105,19 +107,21 @@ Request middleware runs sequentially before a `Response` is generated.
105
107
  }
106
108
  ```
107
109
 
108
- - **Extending Context:** Return an object with a `define` property to add properties to the context for _downstream_ middleware.
110
+ - **Extending Context:** Return an object (known as a "request plugin") to add its properties to the context for _downstream_ middleware. Getter syntax is supported.
109
111
 
110
112
  ```typescript
113
+ import type { RequestContext } from 'alien-middleware'
114
+
115
+ type User = { id: number; name: string }
116
+
111
117
  const addUser = (context: RequestContext) => {
112
118
  // In a real app, you might look up a user based on a token
113
- const user = { id: 1, name: 'Alice' }
119
+ const user: User = { id: 1, name: 'Alice' }
114
120
 
115
- return { define: { user } }
121
+ return { user }
116
122
  }
117
123
 
118
- const greetUser = (
119
- context: RequestContext<{ user: { id: number; name: string } }>
120
- ) => {
124
+ const greetUser = (context: RequestContext<{ user: User }>) => {
121
125
  // The `user` property is now available thanks to `addUser`
122
126
  return new Response(`Hello, ${context.user.name}!`)
123
127
  }
@@ -125,14 +129,16 @@ Request middleware runs sequentially before a `Response` is generated.
125
129
  const app = chain().use(addUser).use(greetUser)
126
130
  ```
127
131
 
128
- - **Extending Environment:** Return an object with an `env` property to add environment variables accessible via `context.env()`.
132
+ - **Extending Environment:** Request plugins may have an `env` property to add environment variables accessible via `context.env()`.
129
133
 
130
134
  ```typescript
135
+ import type { RequestContext } from 'alien-middleware'
136
+
131
137
  const addApiKey = (context: RequestContext) => {
132
138
  return { env: { API_KEY: 'secret123' } }
133
139
  }
134
140
 
135
- const useApiKey = (context: RequestContext<{}, { API_KEY: string }>) => {
141
+ const useApiKey = (context: RequestContext<never, { API_KEY: string }>) => {
136
142
  const key = context.env('API_KEY')
137
143
  console.log('API Key:', key) // Output: API Key: secret123
138
144
  }
@@ -141,7 +147,7 @@ Request middleware runs sequentially before a `Response` is generated.
141
147
  ```
142
148
 
143
149
  > [!NOTE]
144
- > If you're wondering why you need to return a `{ define: { … } }` object
150
+ > If you're wondering why you need to return an object to define properties
145
151
  > (rather than simply assigning to the context object), it's because TypeScript
146
152
  > is unable to infer the type of the context object downstream if you don't do
147
153
  > it like this.
@@ -182,7 +188,7 @@ another chain, since the outer chain will still have a chance to return a
182
188
 
183
189
  ### Nesting Chains
184
190
 
185
- You can compose middleware by nesting chains using `.use()`. Context modifications (`define`, `env`) within a nested chain are scoped to that chain and do not affect middleware outside of it.
191
+ You can compose middleware by nesting chains using `.use()`. _Request plugins_ within a nested chain are scoped to that chain and do not affect middleware outside of it.
186
192
 
187
193
  ```typescript
188
194
  const innerChain = chain((context: RequestContext) => {