alien-middleware 0.1.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/LICENSE +21 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +106 -0
- package/package.json +41 -0
- package/readme.md +203 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Alec Larson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { AdapterRequestContext, HattipHandler } from '@hattip/core';
|
|
2
|
+
|
|
3
|
+
type RequestPlugin = {
|
|
4
|
+
/**
|
|
5
|
+
* Define properties on the request context.
|
|
6
|
+
*/
|
|
7
|
+
define?: object;
|
|
8
|
+
/**
|
|
9
|
+
* Add type-safe environment variables.
|
|
10
|
+
*/
|
|
11
|
+
env?: object;
|
|
12
|
+
};
|
|
13
|
+
type Inputs<T extends MiddlewareChain> = T['$']['input'];
|
|
14
|
+
type Platform<T extends MiddlewareChain> = T['$']['platform'];
|
|
15
|
+
type InputProperties<T extends MiddlewareChain> = Inputs<T>['properties'];
|
|
16
|
+
type InputEnv<T extends MiddlewareChain> = Inputs<T>['env'];
|
|
17
|
+
type Current<T extends MiddlewareChain> = T['$']['current'];
|
|
18
|
+
type Properties<T extends MiddlewareChain> = Current<T>['properties'];
|
|
19
|
+
type Env<T extends MiddlewareChain> = Current<T>['env'];
|
|
20
|
+
type MiddlewareTypes<TProperties extends object = any, TEnv extends object = any> = {
|
|
21
|
+
properties: TProperties;
|
|
22
|
+
env: TEnv;
|
|
23
|
+
};
|
|
24
|
+
interface HattipContext<TPlatform, TEnv extends object> extends AdapterRequestContext<TPlatform> {
|
|
25
|
+
env<K extends keyof TEnv>(key: K): TEnv[K];
|
|
26
|
+
env(key: never): string | undefined;
|
|
27
|
+
}
|
|
28
|
+
type CastNever<T, U> = [T] extends [never] ? U : T;
|
|
29
|
+
type RequestContext<TProperties extends object = {}, TEnv extends object = {}, TPlatform = unknown> = HattipContext<TPlatform, TEnv> & CastNever<TProperties, {}>;
|
|
30
|
+
type Awaitable<T> = T | PromiseLike<T>;
|
|
31
|
+
type RequestMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>) => Awaitable<Response | RequestPlugin | void>;
|
|
32
|
+
type ResponseMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>, response: Response) => Awaitable<Response | void>;
|
|
33
|
+
type RequestHandler<TInputs extends MiddlewareTypes, TCurrent extends MiddlewareTypes, TPlatform> = HattipHandler<TPlatform> & MiddlewareChain<TInputs, TCurrent, TPlatform>;
|
|
34
|
+
/**
|
|
35
|
+
* Either a request middleware or a response middleware.
|
|
36
|
+
*
|
|
37
|
+
* If your middleware declares the `response` parameter, it's treated as a
|
|
38
|
+
* response middleware. This means it will run *after* a `Response` is generated
|
|
39
|
+
* by a request middleware.
|
|
40
|
+
*/
|
|
41
|
+
type Middleware<TProperties extends object = {}, TEnv extends object = {}, TPlatform = unknown> = (context: RequestContext<TProperties, TEnv, TPlatform>, response: Response) => Awaitable<Response | RequestPlugin | void>;
|
|
42
|
+
type Merge<TSource extends object, TOverrides extends object | undefined> = {} & (TOverrides extends object ? {
|
|
43
|
+
[K in keyof TSource | keyof TOverrides]: K extends keyof TOverrides ? TOverrides[K] : K extends keyof TSource ? TSource[K] : never;
|
|
44
|
+
} : TSource);
|
|
45
|
+
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>, {
|
|
46
|
+
properties: Merge<Properties<TChain>, TPlugin['define']>;
|
|
47
|
+
env: Merge<Env<TChain>, TPlugin['env']>;
|
|
48
|
+
}, Platform<TChain>> : RequestHandler<Inputs<TChain>, Current<TChain>, Platform<TChain>>;
|
|
49
|
+
type ApplyFirstMiddleware<T extends Middleware> = ApplyMiddleware<MiddlewareChain<{
|
|
50
|
+
properties: {};
|
|
51
|
+
env: {};
|
|
52
|
+
}, {
|
|
53
|
+
properties: {};
|
|
54
|
+
env: {};
|
|
55
|
+
}, unknown>, T>;
|
|
56
|
+
|
|
57
|
+
declare const kRequestChain: unique symbol;
|
|
58
|
+
declare const kResponseChain: unique symbol;
|
|
59
|
+
declare class MiddlewareChain<
|
|
60
|
+
/** Values expected by the start of the chain. */
|
|
61
|
+
TInputs extends MiddlewareTypes = any,
|
|
62
|
+
/** Values provided by the end of the chain. */
|
|
63
|
+
TCurrent extends MiddlewareTypes = any,
|
|
64
|
+
/** Values from the host platform. */
|
|
65
|
+
TPlatform = any> {
|
|
66
|
+
/** This property won't exist at runtime. It contains type information for inference purposes. */
|
|
67
|
+
$: {
|
|
68
|
+
input: TInputs;
|
|
69
|
+
current: TCurrent;
|
|
70
|
+
platform: TPlatform;
|
|
71
|
+
};
|
|
72
|
+
/** The number of parameters when called as a function. */
|
|
73
|
+
readonly length: 1;
|
|
74
|
+
protected [kRequestChain]: RequestMiddleware[];
|
|
75
|
+
protected [kResponseChain]: ResponseMiddleware[];
|
|
76
|
+
/**
|
|
77
|
+
* Attach a middleware. If the `response` paramter is declared, it will be
|
|
78
|
+
* treated as a response middleware. Otherwise, it will be treated as a
|
|
79
|
+
* request middleware.
|
|
80
|
+
*
|
|
81
|
+
* @returns a new `MiddlewareChain` instance
|
|
82
|
+
*/
|
|
83
|
+
use<const TMiddleware extends Middleware<TCurrent['properties'], TCurrent['env'], TPlatform>>(middleware: TMiddleware): ApplyMiddleware<this, TMiddleware>;
|
|
84
|
+
}
|
|
85
|
+
declare function chain<TPlatform = unknown, TEnv extends object = {}, TProperties extends object = {}>(): MiddlewareChain<{
|
|
86
|
+
env: TEnv;
|
|
87
|
+
properties: TProperties;
|
|
88
|
+
}, {
|
|
89
|
+
env: TEnv;
|
|
90
|
+
properties: TProperties;
|
|
91
|
+
}, TPlatform>;
|
|
92
|
+
declare function chain<const T extends Middleware = Middleware>(middleware: T): ApplyFirstMiddleware<T>;
|
|
93
|
+
|
|
94
|
+
export { MiddlewareChain, chain };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
// src/index.ts
|
|
14
|
+
var kRequestChain = Symbol("requestChain");
|
|
15
|
+
var kResponseChain = Symbol("responseChain");
|
|
16
|
+
var kIgnoreNotFound = Symbol("ignoreNotFound");
|
|
17
|
+
var kMiddlewareCache = Symbol("middlewareCache");
|
|
18
|
+
var MiddlewareChain = class _MiddlewareChain {
|
|
19
|
+
[kRequestChain] = [];
|
|
20
|
+
[kResponseChain] = [];
|
|
21
|
+
/**
|
|
22
|
+
* Attach a middleware. If the `response` paramter is declared, it will be
|
|
23
|
+
* treated as a response middleware. Otherwise, it will be treated as a
|
|
24
|
+
* request middleware.
|
|
25
|
+
*
|
|
26
|
+
* @returns a new `MiddlewareChain` instance
|
|
27
|
+
*/
|
|
28
|
+
use(middleware) {
|
|
29
|
+
let requestChain = this[kRequestChain];
|
|
30
|
+
let responseChain = this[kResponseChain];
|
|
31
|
+
if (middleware.length < 2) {
|
|
32
|
+
requestChain = [...requestChain, middleware];
|
|
33
|
+
} else {
|
|
34
|
+
responseChain = [...responseChain, middleware];
|
|
35
|
+
}
|
|
36
|
+
async function handler(parentContext) {
|
|
37
|
+
const context = Object.create(parentContext);
|
|
38
|
+
context[kIgnoreNotFound] = true;
|
|
39
|
+
const cache = context[kMiddlewareCache] ||= /* @__PURE__ */ new Set();
|
|
40
|
+
let response;
|
|
41
|
+
let env;
|
|
42
|
+
for (const middleware2 of requestChain) {
|
|
43
|
+
if (cache.has(middleware2)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
cache.add(middleware2);
|
|
47
|
+
let result = middleware2(context);
|
|
48
|
+
if (isPromise(result)) {
|
|
49
|
+
result = await result;
|
|
50
|
+
}
|
|
51
|
+
if (result) {
|
|
52
|
+
if (result instanceof Response) {
|
|
53
|
+
response = result;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
if (result.define) {
|
|
57
|
+
Object.assign(context, result.define);
|
|
58
|
+
}
|
|
59
|
+
if (result.env) {
|
|
60
|
+
env ||= createExtendedEnv(context);
|
|
61
|
+
Object.assign(env, result.env);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!response) {
|
|
66
|
+
if (parentContext[kIgnoreNotFound]) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
return new Response("Not Found", { status: 404 });
|
|
70
|
+
}
|
|
71
|
+
for (const middleware2 of responseChain) {
|
|
72
|
+
if (cache.has(middleware2)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
cache.add(middleware2);
|
|
76
|
+
let result = middleware2(context, response);
|
|
77
|
+
if (isPromise(result)) {
|
|
78
|
+
result = await result;
|
|
79
|
+
}
|
|
80
|
+
if (result && result instanceof Response) {
|
|
81
|
+
response = result;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return response;
|
|
86
|
+
}
|
|
87
|
+
Object.setPrototypeOf(handler, _MiddlewareChain.prototype);
|
|
88
|
+
handler[kRequestChain] = requestChain;
|
|
89
|
+
handler[kResponseChain] = responseChain;
|
|
90
|
+
return handler;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function createExtendedEnv(context) {
|
|
94
|
+
const env = /* @__PURE__ */ Object.create(null);
|
|
95
|
+
const superEnv = context.env;
|
|
96
|
+
context.env = (key) => env[key] ?? superEnv(key);
|
|
97
|
+
return env;
|
|
98
|
+
}
|
|
99
|
+
function chain(middleware) {
|
|
100
|
+
const handler = new MiddlewareChain();
|
|
101
|
+
return middleware ? handler.use(middleware) : handler;
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
MiddlewareChain,
|
|
105
|
+
chain
|
|
106
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "alien-middleware",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"exports": {
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"default": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "Alec Larson",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/alloc/alien-middleware.git"
|
|
17
|
+
},
|
|
18
|
+
"prettier": "@alloc/prettier-config",
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@hattip/core": "*"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@alloc/prettier-config": "^1.0.0",
|
|
24
|
+
"@hattip/core": "^0.0.49",
|
|
25
|
+
"@types/node": "^22.15.3",
|
|
26
|
+
"prettier": "^3.5.3",
|
|
27
|
+
"radashi": "12.5.0-beta.6d5c035",
|
|
28
|
+
"rimraf": "^6.0.1",
|
|
29
|
+
"tsc-lint": "^0.1.9",
|
|
30
|
+
"tsup": "^8.4.0",
|
|
31
|
+
"typescript": "^5.8.3",
|
|
32
|
+
"vitest": "^3.1.2"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"dev": "rimraf dist && tsup --sourcemap --watch",
|
|
36
|
+
"build": "rimraf dist && tsup",
|
|
37
|
+
"format": "prettier --write .",
|
|
38
|
+
"lint": "tsc-lint",
|
|
39
|
+
"test": "vitest"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# alien-middleware
|
|
2
|
+
|
|
3
|
+
Reusable middleware chains with top-notch TypeScript support. Built upon [Hattip](https://github.com/hattipjs/hattip) to avoid vendor lock-in using Web Standard APIs.
|
|
4
|
+
|
|
5
|
+
## Philosophy
|
|
6
|
+
|
|
7
|
+
By default, middlewares in `alien-middleware` are **synchronous** or **promise-based**. There is no `next()` function to call. If a middleware returns a `Response`, the chain is terminated. If a middleware wants to extend the request context, it returns an object implementing the `RequestPlugin` interface.
|
|
8
|
+
|
|
9
|
+
Middlewares are either **request-oriented** (the default) or **response-oriented**. Response-oriented middlewares run _after_ a `Response` has been generated. They're allowed to return a new `Response`, but cannot return a `RequestPlugin` object.
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
First, add the package to your project:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add alien-middleware
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Creating a Chain
|
|
20
|
+
|
|
21
|
+
Import the `chain` function and initialize a middleware chain. You can optionally provide an initial middleware directly to `chain`.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { chain } from 'alien-middleware'
|
|
25
|
+
|
|
26
|
+
// Create an empty chain
|
|
27
|
+
const app = chain()
|
|
28
|
+
|
|
29
|
+
// Or create a chain with an initial middleware
|
|
30
|
+
const appWithInitial = chain(ctx => {
|
|
31
|
+
console.log('Initial middleware running for:', ctx.request.url)
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Adding Middleware with `.use()`
|
|
36
|
+
|
|
37
|
+
Use the `.use()` method to add middleware functions to the chain. Each call to `.use()` returns a _new_, immutable chain instance.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import type { RequestContext } from 'alien-middleware'
|
|
41
|
+
|
|
42
|
+
const firstMiddleware = (ctx: RequestContext) => {
|
|
43
|
+
console.log('First middleware')
|
|
44
|
+
// Doesn't return anything, so the chain continues
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const secondMiddleware = (ctx: RequestContext) => {
|
|
48
|
+
console.log('Second middleware')
|
|
49
|
+
return new Response('Hello from middleware!', { status: 200 })
|
|
50
|
+
// Returns a Response, terminating the request-phase chain
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Add middleware sequentially
|
|
54
|
+
const finalApp = app.use(firstMiddleware).use(secondMiddleware)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> [!NOTE]
|
|
58
|
+
> Middleware chains are immutable. Each call to `.use()` returns a new chain instance.
|
|
59
|
+
|
|
60
|
+
### Executing the Chain
|
|
61
|
+
|
|
62
|
+
To run the middleware chain, call the chain instance itself with a context object. This object typically comes from an adapter like Hattip's [Node adapter](https://www.npmjs.com/package/@hattip/adapter-node).
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Simplified context for demonstration (requires necessary imports like 'noop' if run directly)
|
|
66
|
+
const context: AdapterRequestContext = {
|
|
67
|
+
request: new Request('http://localhost/test'),
|
|
68
|
+
ip: '127.0.0.1',
|
|
69
|
+
platform: {},
|
|
70
|
+
waitUntil: (promise: Promise<any>) => {},
|
|
71
|
+
passThrough: () => {},
|
|
72
|
+
env: (key: string) => undefined, // Basic env function
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Execute the chain
|
|
76
|
+
const response = await finalApp(context) // Assuming finalApp from previous example
|
|
77
|
+
|
|
78
|
+
console.log(await response.text()) // Output: Hello from middleware!
|
|
79
|
+
console.log(response.status) // Output: 200
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
> [!NOTE]
|
|
83
|
+
> If no middleware in the chain returns a `Response`, a `404 Not Found` response
|
|
84
|
+
> is automatically returned, except for nested chains.
|
|
85
|
+
|
|
86
|
+
### Request Middleware
|
|
87
|
+
|
|
88
|
+
Request middleware runs sequentially before a `Response` is generated.
|
|
89
|
+
|
|
90
|
+
- **Terminating the Chain:** Return a `Response` object to stop processing subsequent request middleware.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const earlyResponder = (ctx: RequestContext) => {
|
|
94
|
+
if (ctx.request.url.endsWith('/forbidden')) {
|
|
95
|
+
return new Response('Forbidden', { status: 403 })
|
|
96
|
+
}
|
|
97
|
+
// Otherwise, continue the chain
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- **Extending Context:** Return an object with a `define` property to add properties to the context for _downstream_ middleware.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const addUser = (ctx: RequestContext) => {
|
|
105
|
+
// In a real app, you might look up a user based on a token
|
|
106
|
+
const user = { id: 1, name: 'Alice' }
|
|
107
|
+
|
|
108
|
+
return { define: { user } }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const greetUser = (
|
|
112
|
+
ctx: RequestContext<{ user: { id: number; name: string } }>
|
|
113
|
+
) => {
|
|
114
|
+
// The `user` property is now available thanks to `addUser`
|
|
115
|
+
return new Response(`Hello, ${ctx.user.name}!`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const userApp = app.use(addUser).use(greetUser)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
> [!NOTE]
|
|
122
|
+
> If you're wondering why you need to return a `{ define: { … } }` object
|
|
123
|
+
> (rather than using simple assignment), it's because TypeScript is unable to
|
|
124
|
+
> infer the type of the context object downstream if you don't do this.
|
|
125
|
+
>
|
|
126
|
+
> Another thing to note is you don't typically define middlewares outside the
|
|
127
|
+
> `.use(…)` call expression, since that requires you to unnecessarily declare
|
|
128
|
+
> the type of the context object. It's better to define them inline.
|
|
129
|
+
|
|
130
|
+
- **Extending Environment:** Return an object with an `env` property to add environment variables accessible via `ctx.env()`.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const addApiKey = (ctx: RequestContext) => {
|
|
134
|
+
return { env: { API_KEY: 'secret123' } }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const useApiKey = (ctx: RequestContext<{}, { API_KEY: string }>) => {
|
|
138
|
+
const key = ctx.env('API_KEY')
|
|
139
|
+
console.log('API Key:', key) // Output: API Key: secret123
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const envApp = app.use(addApiKey).use(useApiKey)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Response Middleware
|
|
146
|
+
|
|
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
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const addHeader = (ctx: RequestContext, response: Response) => {
|
|
151
|
+
response.headers.set('X-Powered-By', 'alien-middleware')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const mainHandler = (ctx: RequestContext) => {
|
|
155
|
+
return new Response('Main content')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// `addHeader` runs after `mainHandler` generates a response
|
|
159
|
+
const headerApp = app.use(mainHandler).use(addHeader)
|
|
160
|
+
|
|
161
|
+
// Assuming `context` is defined as in the "Executing the Chain" example
|
|
162
|
+
const responseWithHeader = await headerApp(context)
|
|
163
|
+
console.log(responseWithHeader.headers.get('X-Powered-By')) // Output: alien-middleware
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
> [!NOTE]
|
|
167
|
+
> Response middleware cannot extend the context using `{ define }` or `{ env }`.
|
|
168
|
+
> They can only inspect the `Response` or replace it by returning a new
|
|
169
|
+
> `Response`.
|
|
170
|
+
|
|
171
|
+
### Nesting Chains
|
|
172
|
+
|
|
173
|
+
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.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const innerChain = chain((ctx: RequestContext) => {
|
|
177
|
+
console.log('Inner chain start')
|
|
178
|
+
return { define: { innerData: 'secret' } } // Only available inside innerChain
|
|
179
|
+
}).use((ctx: RequestContext<{ innerData: string }>) => {
|
|
180
|
+
console.log('Accessing inner data:', ctx.innerData)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const outerMiddleware = (ctx: RequestContext) => {
|
|
184
|
+
// ctx.innerData is not accessible here
|
|
185
|
+
console.log('Outer middleware after inner chain')
|
|
186
|
+
if (!('innerData' in ctx)) {
|
|
187
|
+
console.log('innerData is correctly scoped.')
|
|
188
|
+
}
|
|
189
|
+
return new Response('Finished')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const nestedApp = app.use(innerChain).use(outerMiddleware)
|
|
193
|
+
|
|
194
|
+
// Assuming `context` is defined as in the "Executing the Chain" example
|
|
195
|
+
await nestedApp(context)
|
|
196
|
+
// Output:
|
|
197
|
+
// Inner chain start
|
|
198
|
+
// Accessing inner data: secret
|
|
199
|
+
// Outer middleware after inner chain
|
|
200
|
+
// innerData is correctly scoped.
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
If a nested chain does not return a `Response`, execution continues with the next middleware in the outer chain.
|