alien-middleware 0.2.0 → 0.3.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.
- package/dist/index.d.ts +15 -6
- package/dist/index.js +11 -0
- package/package.json +1 -1
- package/readme.md +54 -21
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AdapterRequestContext, HattipHandler } from '@hattip/core';
|
|
2
|
+
import { Any } from 'radashi';
|
|
2
3
|
|
|
3
4
|
type RequestPlugin = {
|
|
4
5
|
/**
|
|
@@ -22,11 +23,19 @@ type MiddlewareTypes<TProperties extends object = any, TEnv extends object = any
|
|
|
22
23
|
env: TEnv;
|
|
23
24
|
};
|
|
24
25
|
interface HattipContext<TPlatform, TEnv extends object> extends AdapterRequestContext<TPlatform> {
|
|
25
|
-
|
|
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];
|
|
26
32
|
env(key: never): string | undefined;
|
|
27
33
|
}
|
|
28
|
-
|
|
29
|
-
|
|
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>;
|
|
30
39
|
type Awaitable<T> = T | PromiseLike<T>;
|
|
31
40
|
type RequestMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>) => Awaitable<Response | RequestPlugin | void>;
|
|
32
41
|
type ResponseMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>, response: Response) => Awaitable<Response | void>;
|
|
@@ -38,7 +47,7 @@ type RequestHandler<TInputs extends MiddlewareTypes, TCurrent extends Middleware
|
|
|
38
47
|
* response middleware. This means it will run *after* a `Response` is generated
|
|
39
48
|
* by a request middleware.
|
|
40
49
|
*/
|
|
41
|
-
type Middleware<TProperties extends object =
|
|
50
|
+
type Middleware<TProperties extends object = any, TEnv extends object = any, TPlatform = unknown> = (context: RequestContext<TProperties, TEnv, TPlatform>, response: Response) => Awaitable<Response | RequestPlugin | void>;
|
|
42
51
|
type Merge<TSource extends object, TOverrides extends object | undefined> = {} & (TOverrides extends object ? {
|
|
43
52
|
[K in keyof TSource | keyof TOverrides]: K extends keyof TOverrides ? TOverrides[K] : K extends keyof TSource ? TSource[K] : never;
|
|
44
53
|
} : TSource);
|
|
@@ -82,7 +91,7 @@ TPlatform = any> {
|
|
|
82
91
|
*/
|
|
83
92
|
use<const TMiddleware extends Middleware<TCurrent['properties'], TCurrent['env'], TPlatform>>(middleware: TMiddleware): ApplyMiddleware<this, TMiddleware>;
|
|
84
93
|
}
|
|
85
|
-
declare function chain<
|
|
94
|
+
declare function chain<TProperties extends object = {}, TEnv extends object = {}, TPlatform = unknown>(): MiddlewareChain<{
|
|
86
95
|
env: TEnv;
|
|
87
96
|
properties: TProperties;
|
|
88
97
|
}, {
|
|
@@ -91,4 +100,4 @@ declare function chain<TPlatform = unknown, TEnv extends object = {}, TPropertie
|
|
|
91
100
|
}, TPlatform>;
|
|
92
101
|
declare function chain<const T extends Middleware = Middleware>(middleware: T): ApplyFirstMiddleware<T>;
|
|
93
102
|
|
|
94
|
-
export { MiddlewareChain, chain };
|
|
103
|
+
export { type Middleware, MiddlewareChain, type RequestContext, type RequestHandler, type RequestMiddleware, type RequestPlugin, type ResponseMiddleware, chain };
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,14 @@ var kRequestChain = Symbol("requestChain");
|
|
|
15
15
|
var kResponseChain = Symbol("responseChain");
|
|
16
16
|
var kIgnoreNotFound = Symbol("ignoreNotFound");
|
|
17
17
|
var kMiddlewareCache = Symbol("middlewareCache");
|
|
18
|
+
var urlDescriptor = {
|
|
19
|
+
configurable: true,
|
|
20
|
+
get() {
|
|
21
|
+
const url = new URL(this.request.url);
|
|
22
|
+
Object.defineProperty(this, "url", { value: url });
|
|
23
|
+
return url;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
18
26
|
var MiddlewareChain = class _MiddlewareChain {
|
|
19
27
|
[kRequestChain] = [];
|
|
20
28
|
[kResponseChain] = [];
|
|
@@ -36,6 +44,9 @@ var MiddlewareChain = class _MiddlewareChain {
|
|
|
36
44
|
async function handler(parentContext) {
|
|
37
45
|
const context = Object.create(parentContext);
|
|
38
46
|
context[kIgnoreNotFound] = true;
|
|
47
|
+
if (!("url" in context)) {
|
|
48
|
+
Object.defineProperty(context, "url", urlDescriptor);
|
|
49
|
+
}
|
|
39
50
|
const cache = context[kMiddlewareCache] ||= /* @__PURE__ */ new Set();
|
|
40
51
|
let response;
|
|
41
52
|
let env;
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -27,8 +27,8 @@ import { chain } from 'alien-middleware'
|
|
|
27
27
|
const app = chain()
|
|
28
28
|
|
|
29
29
|
// Or create a chain with an initial middleware
|
|
30
|
-
const appWithInitial = chain(
|
|
31
|
-
console.log('Initial middleware running for:',
|
|
30
|
+
const appWithInitial = chain(context => {
|
|
31
|
+
console.log('Initial middleware running for:', context.request.url)
|
|
32
32
|
})
|
|
33
33
|
```
|
|
34
34
|
|
|
@@ -39,12 +39,12 @@ Use the `.use()` method to add middleware functions to the chain. Each call to `
|
|
|
39
39
|
```typescript
|
|
40
40
|
import type { RequestContext } from 'alien-middleware'
|
|
41
41
|
|
|
42
|
-
const firstMiddleware = (
|
|
42
|
+
const firstMiddleware = (context: RequestContext) => {
|
|
43
43
|
console.log('First middleware')
|
|
44
44
|
// Doesn't return anything, so the chain continues
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const secondMiddleware = (
|
|
47
|
+
const secondMiddleware = (context: RequestContext) => {
|
|
48
48
|
console.log('Second middleware')
|
|
49
49
|
return new Response('Hello from middleware!', { status: 200 })
|
|
50
50
|
// Returns a Response, terminating the request-phase chain
|
|
@@ -89,8 +89,8 @@ Request middleware runs sequentially before a `Response` is generated.
|
|
|
89
89
|
- **Terminating the Chain:** Return a `Response` object to stop processing subsequent request middleware.
|
|
90
90
|
|
|
91
91
|
```typescript
|
|
92
|
-
const earlyResponder = (
|
|
93
|
-
if (
|
|
92
|
+
const earlyResponder = (context: RequestContext) => {
|
|
93
|
+
if (context.request.url.endsWith('/forbidden')) {
|
|
94
94
|
return new Response('Forbidden', { status: 403 })
|
|
95
95
|
}
|
|
96
96
|
// Otherwise, continue the chain
|
|
@@ -100,7 +100,7 @@ Request middleware runs sequentially before a `Response` is generated.
|
|
|
100
100
|
- **Extending Context:** Return an object with a `define` property to add properties to the context for _downstream_ middleware.
|
|
101
101
|
|
|
102
102
|
```typescript
|
|
103
|
-
const addUser = (
|
|
103
|
+
const addUser = (context: RequestContext) => {
|
|
104
104
|
// In a real app, you might look up a user based on a token
|
|
105
105
|
const user = { id: 1, name: 'Alice' }
|
|
106
106
|
|
|
@@ -108,24 +108,24 @@ Request middleware runs sequentially before a `Response` is generated.
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
const greetUser = (
|
|
111
|
-
|
|
111
|
+
context: RequestContext<{ user: { id: number; name: string } }>
|
|
112
112
|
) => {
|
|
113
113
|
// The `user` property is now available thanks to `addUser`
|
|
114
|
-
return new Response(`Hello, ${
|
|
114
|
+
return new Response(`Hello, ${context.user.name}!`)
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
const app = chain().use(addUser).use(greetUser)
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
- **Extending Environment:** Return an object with an `env` property to add environment variables accessible via `
|
|
120
|
+
- **Extending Environment:** Return an object with an `env` property to add environment variables accessible via `context.env()`.
|
|
121
121
|
|
|
122
122
|
```typescript
|
|
123
|
-
const addApiKey = (
|
|
123
|
+
const addApiKey = (context: RequestContext) => {
|
|
124
124
|
return { env: { API_KEY: 'secret123' } }
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
const useApiKey = (
|
|
128
|
-
const key =
|
|
127
|
+
const useApiKey = (context: RequestContext<{}, { API_KEY: string }>) => {
|
|
128
|
+
const key = context.env('API_KEY')
|
|
129
129
|
console.log('API Key:', key) // Output: API Key: secret123
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -147,11 +147,11 @@ Request middleware runs sequentially before a `Response` is generated.
|
|
|
147
147
|
Response middleware runs _after_ a `Response` has been generated by a request middleware or the final handler. It receives both the context and the generated `Response`.
|
|
148
148
|
|
|
149
149
|
```typescript
|
|
150
|
-
const poweredByMiddleware = (
|
|
150
|
+
const poweredByMiddleware = (context: RequestContext, response: Response) => {
|
|
151
151
|
response.headers.set('X-Powered-By', 'alien-middleware')
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
const mainHandler = (
|
|
154
|
+
const mainHandler = (context: RequestContext) => {
|
|
155
155
|
return new Response('Main content')
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -177,17 +177,17 @@ another chain, since the outer chain will still have a chance to return a
|
|
|
177
177
|
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.
|
|
178
178
|
|
|
179
179
|
```typescript
|
|
180
|
-
const innerChain = chain((
|
|
180
|
+
const innerChain = chain((context: RequestContext) => {
|
|
181
181
|
console.log('Inner chain start')
|
|
182
182
|
return { define: { innerData: 'secret' } } // Only available inside innerChain
|
|
183
|
-
}).use((
|
|
184
|
-
console.log('Accessing inner data:',
|
|
183
|
+
}).use((context: RequestContext<{ innerData: string }>) => {
|
|
184
|
+
console.log('Accessing inner data:', context.innerData)
|
|
185
185
|
})
|
|
186
186
|
|
|
187
|
-
const outerMiddleware = (
|
|
188
|
-
//
|
|
187
|
+
const outerMiddleware = (context: RequestContext) => {
|
|
188
|
+
// context.innerData is not accessible here
|
|
189
189
|
console.log('Outer middleware after inner chain')
|
|
190
|
-
if (!('innerData' in
|
|
190
|
+
if (!('innerData' in context)) {
|
|
191
191
|
console.log('innerData is correctly scoped.')
|
|
192
192
|
}
|
|
193
193
|
return new Response('Finished')
|
|
@@ -202,3 +202,36 @@ const finalApp = chain().use(innerChain).use(outerMiddleware)
|
|
|
202
202
|
```
|
|
203
203
|
|
|
204
204
|
If a nested chain does not return a `Response`, execution continues with the next middleware in the outer chain.
|
|
205
|
+
|
|
206
|
+
### Safe Environment Variables
|
|
207
|
+
|
|
208
|
+
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.
|
|
209
|
+
|
|
210
|
+
With alien-middleware, you **must** declare an environment variable's type in order to use it.
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { chain } from 'alien-middleware'
|
|
214
|
+
|
|
215
|
+
// A common pattern is to declare a dedicated type for the environment variables.
|
|
216
|
+
type Env = {
|
|
217
|
+
API_KEY: string
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const app = chain<any, Env>().use(context => {
|
|
221
|
+
const key = context.env('API_KEY')
|
|
222
|
+
// ^? string
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
When defining a middleware, you can declare env types that the middleware expects to use.
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import type { RequestContext } from 'alien-middleware'
|
|
230
|
+
|
|
231
|
+
// Assuming `Env` is defined like in the previous example.
|
|
232
|
+
const myMiddleware = (context: RequestContext<any, Env>) => {
|
|
233
|
+
const key = context.env('API_KEY')
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
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.
|