alien-middleware 0.9.1 → 0.10.1

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.
@@ -21,19 +21,25 @@ function defineParsedURL(context) {
21
21
  }
22
22
  }
23
23
 
24
+ // src/middleware/filterPlatform.ts
25
+ function filterPlatform(name) {
26
+ return function(ctx) {
27
+ if (ctx.platform.name !== name) {
28
+ return ctx.passThrough();
29
+ }
30
+ return null;
31
+ };
32
+ }
33
+
24
34
  // src/index.ts
25
35
  var kRequestChain = Symbol("requestChain");
26
- var kResponseChain = Symbol("responseChain");
27
36
  var kIgnoreNotFound = Symbol("ignoreNotFound");
28
37
  var kMiddlewareCache = Symbol("middlewareCache");
29
38
  var kResponseHeaders = Symbol("responseHeaders");
30
39
  var MiddlewareChain = class _MiddlewareChain {
31
40
  [kRequestChain] = [];
32
- [kResponseChain] = [];
33
41
  /**
34
- * Attach a middleware. If the `response` parameter is declared, it will be
35
- * treated as a response middleware. Otherwise, it will be treated as a
36
- * request middleware.
42
+ * Add a request middleware to the end of the chain.
37
43
  *
38
44
  * If a middleware chain is given, its middlewares will be executed after any
39
45
  * existing middlewares in this chain.
@@ -41,20 +47,9 @@ var MiddlewareChain = class _MiddlewareChain {
41
47
  * @returns a new `MiddlewareChain` instance
42
48
  */
43
49
  use(middleware) {
44
- if (middleware instanceof _MiddlewareChain) {
45
- return createHandler(
46
- [...this[kRequestChain], ...middleware[kRequestChain]],
47
- [...this[kResponseChain], ...middleware[kResponseChain]]
48
- );
49
- }
50
- let requestChain = this[kRequestChain];
51
- let responseChain = this[kResponseChain];
52
- if (middleware.length < 2) {
53
- requestChain = [...requestChain, middleware];
54
- } else {
55
- responseChain = [...responseChain, middleware];
56
- }
57
- return createHandler(requestChain, responseChain);
50
+ return createHandler(
51
+ middleware instanceof _MiddlewareChain ? [...this[kRequestChain], ...middleware[kRequestChain]] : [...this[kRequestChain], middleware]
52
+ );
58
53
  }
59
54
  /**
60
55
  * Create a middleware function that encapsulates this middleware chain, so
@@ -64,7 +59,7 @@ var MiddlewareChain = class _MiddlewareChain {
64
59
  return isFunction(this) ? (ctx) => this(ctx) : noop;
65
60
  }
66
61
  };
67
- function createHandler(requestChain, responseChain) {
62
+ function createHandler(requestChain) {
68
63
  async function handler(parentContext) {
69
64
  const context = Object.create(parentContext);
70
65
  context[kIgnoreNotFound] = true;
@@ -80,7 +75,13 @@ function createHandler(requestChain, responseChain) {
80
75
  }
81
76
  context[kResponseHeaders].set(name, value);
82
77
  };
83
- const cache = context[kMiddlewareCache] ||= /* @__PURE__ */ new Set();
78
+ const responseChain = [];
79
+ context.onResponse = (callback) => {
80
+ responseChain.push(callback);
81
+ };
82
+ const cache = context[kMiddlewareCache] = new Set(
83
+ parentContext[kMiddlewareCache]
84
+ );
84
85
  let response;
85
86
  let env;
86
87
  for (const middleware of requestChain) {
@@ -102,11 +103,17 @@ function createHandler(requestChain, responseChain) {
102
103
  }
103
104
  for (const key in result) {
104
105
  if (key === "env") {
105
- env ||= createExtendedEnv(context);
106
- Object.defineProperties(
107
- env,
108
- Object.getOwnPropertyDescriptors(result.env)
109
- );
106
+ if (result.env) {
107
+ env ||= createExtendedEnv(context);
108
+ Object.defineProperties(
109
+ env,
110
+ Object.getOwnPropertyDescriptors(result.env)
111
+ );
112
+ }
113
+ } else if (key === "onResponse") {
114
+ if (result.onResponse) {
115
+ responseChain.push(result.onResponse);
116
+ }
110
117
  } else {
111
118
  const descriptor = Object.getOwnPropertyDescriptor(result, key);
112
119
  descriptor.configurable = false;
@@ -131,16 +138,12 @@ function createHandler(requestChain, responseChain) {
131
138
  response.headers.set(name, value);
132
139
  });
133
140
  context.setHeader = null;
134
- for (const middleware of responseChain) {
135
- if (cache.has(middleware)) {
136
- continue;
137
- }
138
- cache.add(middleware);
139
- let result = middleware(context, response);
141
+ for (const plugin of responseChain) {
142
+ let result = plugin(response);
140
143
  if (result instanceof Promise) {
141
144
  result = await result;
142
145
  }
143
- if (result && result instanceof Response) {
146
+ if (result) {
144
147
  response = result;
145
148
  continue;
146
149
  }
@@ -149,7 +152,6 @@ function createHandler(requestChain, responseChain) {
149
152
  }
150
153
  Object.setPrototypeOf(handler, MiddlewareChain.prototype);
151
154
  handler[kRequestChain] = requestChain;
152
- handler[kResponseChain] = responseChain;
153
155
  return handler;
154
156
  }
155
157
  function createExtendedEnv(context) {
@@ -170,6 +172,7 @@ export {
170
172
  isArray,
171
173
  isFunction,
172
174
  defineParsedURL,
175
+ filterPlatform,
173
176
  MiddlewareChain,
174
177
  chain
175
178
  };
@@ -21,12 +21,16 @@ type Merge<TSource extends object, TOverrides extends object | undefined> = Eval
21
21
  [K in Keys<TOverrides>]: TOverrides extends any ? MergeProperty<CastNever<TSource, {}>, TOverrides, K> : never;
22
22
  }>;
23
23
 
24
- type RequestEnvPlugin = {
24
+ type ReservedProperties = {
25
25
  /**
26
26
  * Add type-safe environment variables. These are accessed with the `env()`
27
27
  * method on the request context.
28
28
  */
29
29
  env?: object;
30
+ /**
31
+ * Intercept the response before it's sent to the client.
32
+ */
33
+ onResponse?: ResponseCallback;
30
34
  };
31
35
  /**
32
36
  * The object returned by a request middleware that is merged into the request
@@ -36,7 +40,7 @@ type RequestEnvPlugin = {
36
40
  * May contain special properties:
37
41
  * - `env`: Add type-safe environment variables.
38
42
  */
39
- type RequestPlugin = Record<string, unknown> & RequestEnvPlugin;
43
+ type RequestPlugin = Record<string, unknown> & ReservedProperties;
40
44
  type MiddlewareTypes = {
41
45
  /** Values expected by the start of the chain. */
42
46
  initial: {
@@ -70,20 +74,31 @@ type Current<T extends AnyMiddlewareChain> = T['$MiddlewareChain']['current'];
70
74
  type Properties<T extends AnyMiddlewareChain> = Current<T>['properties'];
71
75
  type Env<T extends AnyMiddlewareChain> = Current<T>['env'];
72
76
  type Platform<T extends AnyMiddlewareChain> = T['$MiddlewareChain']['platform'];
77
+ /**
78
+ * The `context.env` method used to access environment variables.
79
+ */
80
+ type EnvAccessor<TEnv extends object> = {
81
+ <K extends keyof TEnv>(key: Extract<K, string>): TEnv[K];
82
+ (key: never): string | undefined;
83
+ };
73
84
  interface HattipContext<TPlatform, TEnv extends object> extends AdapterRequestContext<TPlatform> {
85
+ env: EnvAccessor<TEnv>;
86
+ passThrough(): never;
74
87
  /**
75
88
  * The `request.url` string parsed into a `URL` object. Parsing is performed
76
89
  * on-demand and the result is cached.
77
90
  */
78
91
  url: URL;
79
- env<K extends keyof TEnv>(key: Extract<K, string>): TEnv[K];
80
- env(key: never): string | undefined;
81
92
  /**
82
93
  * Set a response header from a request middleware.
83
94
  *
84
95
  * Response middlewares should use `response.headers.set()` instead.
85
96
  */
86
97
  setHeader(name: string, value: string): void;
98
+ /**
99
+ * Add a callback to be called when a response is generated.
100
+ */
101
+ onResponse(callback: ResponseCallback): void;
87
102
  }
88
103
  /**
89
104
  * An extensible Hattip context object.
@@ -98,10 +113,12 @@ type RequestContext<TEnv extends object = any, TProperties extends object = neve
98
113
  *
99
114
  * When type `T` is `never`, a default context is returned.
100
115
  */
101
- type MiddlewareContext<T extends MiddlewareChain> = [T] extends [never] ? RequestContext<{}, never, unknown> : RequestContext<Env<T>, Properties<T>, Platform<T>>;
116
+ type MiddlewareContext<T extends MiddlewareChain | Middleware[]> = [
117
+ T
118
+ ] extends [never] ? RequestContext<{}, never, unknown> : T extends MiddlewareChain ? RequestContext<Env<T>, Properties<T>, Platform<T>> : T extends Middleware[] ? MiddlewareContext<ApplyMiddlewares<T>> : never;
102
119
  type IsolatedContext<T extends MiddlewareChain> = RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>;
103
120
  type RequestMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>) => Awaitable<Response | RequestPlugin | void>;
104
- type ResponseMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>, response: Response) => Awaitable<Response | void>;
121
+ type ResponseCallback = (response: Response) => Awaitable<Response | void>;
105
122
  interface RequestHandler<T extends MiddlewareTypes = any> extends HattipHandler<T['platform']>, MiddlewareChain<T> {
106
123
  }
107
124
  /**
@@ -112,7 +129,7 @@ interface RequestHandler<T extends MiddlewareTypes = any> extends HattipHandler<
112
129
  * by a request middleware.
113
130
  */
114
131
  type Middleware<TEnv extends object = any, TProperties extends object = any, TPlatform = any> = {
115
- (context: RequestContext<TEnv, TProperties, TPlatform>, response: Response): Awaitable<Response | RequestPlugin | void>;
132
+ (context: RequestContext<TEnv, TProperties, TPlatform>): Awaitable<Response | RequestPlugin | void>;
116
133
  /** This property won't exist at runtime. It contains type information for inference purposes. */
117
134
  $Middleware?: MiddlewareTypes & {
118
135
  initial: {
@@ -132,12 +149,12 @@ type ApplyMiddlewareResult<TParent extends MiddlewareChain, TResult> = Eval<{
132
149
  env: Merge<Env<TParent>, TResult extends {
133
150
  env: infer TEnv extends object | undefined;
134
151
  } ? TEnv : undefined>;
135
- properties: Merge<Properties<TParent>, TResult extends RequestPlugin ? Omit<TResult, keyof RequestEnvPlugin> : undefined>;
152
+ properties: Merge<Properties<TParent>, TResult extends RequestPlugin ? Omit<TResult, keyof ReservedProperties> : undefined>;
136
153
  }>;
137
154
  type ApplyMiddlewareOutputs<TFirst extends MiddlewareChain, TSecond extends Middleware> = TSecond extends MiddlewareChain ? {
138
155
  env: Merge<Env<TFirst>, Env<TSecond>>;
139
156
  properties: Merge<Properties<TFirst>, Properties<TSecond>>;
140
- } : TSecond extends (...args: any[]) => Awaitable<infer TResult> ? ApplyMiddlewareResult<TFirst, TResult> : Current<TFirst>;
157
+ } : TSecond extends (...args: any[]) => Awaitable<infer TResult> ? ApplyMiddlewareResult<TFirst, Exclude<TResult, Response>> : Current<TFirst>;
141
158
  type MiddlewareInputs<T extends Middleware> = T extends Middleware<infer TEnv, infer TProperties> ? {
142
159
  env: TEnv;
143
160
  properties: TProperties;
@@ -156,6 +173,13 @@ type ApplyMiddleware<TFirst extends MiddlewareChain, TSecond extends Middleware>
156
173
  current: TCurrent;
157
174
  platform: CastNever<Platform<TFirst>, MiddlewarePlatform<TSecond>>;
158
175
  }> : never;
176
+ /**
177
+ * Apply a list of middlewares to an empty middleware chain.
178
+ */
179
+ type ApplyMiddlewares<T extends Middleware[]> = T extends [
180
+ ...infer TRest extends Middleware[],
181
+ infer TLast extends Middleware
182
+ ] ? ApplyMiddleware<ApplyMiddlewares<TRest>, TLast> : ApplyFirstMiddleware<T[0]>;
159
183
  type EmptyMiddlewareChain<TPlatform = unknown> = MiddlewareChain<{
160
184
  initial: {
161
185
  env: {};
@@ -184,19 +208,21 @@ interface Router<T extends MiddlewareChain = any> extends RouterTypes<T> {
184
208
  use<TPath extends string, TMethod extends RouteMethod = RouteMethod>(method: OneOrMany<TMethod> | '*', path: TPath, handler: RouteHandler<this, InferParams<TPath>, TMethod>): Router;
185
209
  }
186
210
 
211
+ declare function filterPlatform<TPlatform extends {
212
+ name: string;
213
+ }>(name: TPlatform['name']): (ctx: RequestContext) => {
214
+ platform: TPlatform;
215
+ };
216
+
187
217
  declare const kRequestChain: unique symbol;
188
- declare const kResponseChain: unique symbol;
189
218
  declare class MiddlewareChain<T extends MiddlewareTypes = any> {
190
219
  /** This property won't exist at runtime. It contains type information for inference purposes. */
191
220
  $MiddlewareChain: T;
192
221
  /** The number of parameters when called as a function. */
193
222
  readonly length: 1;
194
223
  protected [kRequestChain]: RequestMiddleware[];
195
- protected [kResponseChain]: ResponseMiddleware[];
196
224
  /**
197
- * Attach a middleware. If the `response` parameter is declared, it will be
198
- * treated as a response middleware. Otherwise, it will be treated as a
199
- * request middleware.
225
+ * Add a request middleware to the end of the chain.
200
226
  *
201
227
  * If a middleware chain is given, its middlewares will be executed after any
202
228
  * existing middlewares in this chain.
@@ -223,4 +249,4 @@ declare function chain<TEnv extends object = {}, TProperties extends object = {}
223
249
  }>;
224
250
  declare function chain<const T extends Middleware = Middleware<{}, {}, unknown>>(middleware: T): ApplyFirstMiddleware<T>;
225
251
 
226
- export { type ApplyMiddleware as A, type EmptyMiddlewareChain as E, type MiddlewareContext as M, type Router as R, MiddlewareChain as a, type RouteContext as b, type RouteHandler as c, chain as d, type ExtractMiddleware as e, type Middleware as f, type RequestContext as g, type RequestHandler as h, type RequestMiddleware as i, type RequestPlugin as j, type ResponseMiddleware as k };
252
+ export { type ApplyMiddleware as A, type EmptyMiddlewareChain as E, type MiddlewareContext as M, type Router as R, MiddlewareChain as a, type RouteContext as b, type RouteHandler as c, chain as d, type ApplyMiddlewares as e, filterPlatform as f, type EnvAccessor as g, type ExtractMiddleware as h, type Middleware as i, type RequestContext as j, type RequestHandler as k, type RequestMiddleware as l, type RequestPlugin as m, type ResponseCallback as n };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { A as ApplyMiddleware, e as ExtractMiddleware, f as Middleware, a as MiddlewareChain, M as MiddlewareContext, g as RequestContext, h as RequestHandler, i as RequestMiddleware, j as RequestPlugin, k as ResponseMiddleware, d as chain } from './index-B5T_qCSA.js';
1
+ export { A as ApplyMiddleware, e as ApplyMiddlewares, g as EnvAccessor, h as ExtractMiddleware, i as Middleware, a as MiddlewareChain, M as MiddlewareContext, j as RequestContext, k as RequestHandler, l as RequestMiddleware, m as RequestPlugin, n as ResponseCallback, d as chain, f as filterPlatform } from './index-BRjj7Wf2.js';
2
2
  import '@hattip/core';
3
3
  import 'pathic';
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  MiddlewareChain,
3
- chain
4
- } from "./chunk-OH7VM54L.js";
3
+ chain,
4
+ filterPlatform
5
+ } from "./chunk-BBMRIHZ5.js";
5
6
  export {
6
7
  MiddlewareChain,
7
- chain
8
+ chain,
9
+ filterPlatform
8
10
  };
package/dist/router.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as Router, M as MiddlewareContext, a as MiddlewareChain, E as EmptyMiddlewareChain } from './index-B5T_qCSA.js';
2
- export { b as RouteContext, c as RouteHandler } from './index-B5T_qCSA.js';
1
+ import { R as Router, M as MiddlewareContext, a as MiddlewareChain, E as EmptyMiddlewareChain } from './index-BRjj7Wf2.js';
2
+ export { b as RouteContext, c as RouteHandler } from './index-BRjj7Wf2.js';
3
3
  import '@hattip/core';
4
4
  import 'pathic';
5
5
 
package/dist/router.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  defineParsedURL,
4
4
  isArray,
5
5
  isFunction
6
- } from "./chunk-OH7VM54L.js";
6
+ } from "./chunk-BBMRIHZ5.js";
7
7
 
8
8
  // src/router.ts
9
9
  import { compilePaths } from "pathic";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alien-middleware",
3
3
  "type": "module",
4
- "version": "0.9.1",
4
+ "version": "0.10.1",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
package/readme.md CHANGED
@@ -4,9 +4,17 @@ Reusable middleware chains with top-notch TypeScript support. Built upon [Hattip
4
4
 
5
5
  ## Philosophy
6
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.
7
+ alien-middleware is built on a few key principles:
8
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.
9
+ 1. **Best-in-class TypeScript support** - Type safety is a first-class citizen. The library provides strong type inference for middleware chains, context extensions, and environment variables.
10
+
11
+ 2. **Web Standards** - Built on standard Web APIs like `Request` and `Response`, allowing you to write idiomatic code that follows established patterns and conventions.
12
+
13
+ 3. **No vendor lock-in** - Thanks to Hattip's adapter system, your middleware can run anywhere: Node.js, Deno, Bun, Cloudflare Workers, and more.
14
+
15
+ 4. **Linear middleware flow** - Unlike Express-style middleware, there's no `next()` function to call. Middleware either returns a `Response` (ending the chain) or doesn't (continuing to the next middleware). This makes the flow easier to reason about and eliminates common bugs like forgetting to call `next()`.
16
+
17
+ 5. **Immutable chains** - Middleware chains are immutable, making them easier to compose, extend, and reason about.
10
18
 
11
19
  ## Quick Start
12
20
 
@@ -34,7 +42,7 @@ const appWithInitial = chain(context => {
34
42
 
35
43
  ### Adding Middleware with `.use()`
36
44
 
37
- Use the `.use()` method to add middleware functions to the chain. Each call to `.use()` returns a _new_, immutable chain instance.
45
+ Use the `.use()` method to add middleware functions to the chain.
38
46
 
39
47
  ```typescript
40
48
  import type { RequestContext } from 'alien-middleware'
@@ -146,6 +154,14 @@ Request middleware runs sequentially before a `Response` is generated.
146
154
  const app = chain().use(addApiKey).use(useApiKey)
147
155
  ```
148
156
 
157
+ - **Setting Response Headers:** Call the `context.setHeader()` method to set a response header.
158
+
159
+ ```typescript
160
+ const app = chain().use(context => {
161
+ context.setHeader('X-Powered-By', 'alien-middleware')
162
+ })
163
+ ```
164
+
149
165
  > [!NOTE]
150
166
  > If you're wondering why you need to return an object to define properties
151
167
  > (rather than simply assigning to the context object), it's because TypeScript
@@ -156,35 +172,63 @@ Request middleware runs sequentially before a `Response` is generated.
156
172
  > `.use(…)` call expression, since that requires you to unnecessarily declare
157
173
  > the type of the context object. It's better to define them inline.
158
174
 
159
- ### Response Middleware
175
+ ### Response Callbacks
176
+
177
+ Request middleware can register a _response callback_ to receive the `Response` object. This is done by either returning an `onResponse` method or by calling `context.onResponse(callback)`. Response callbacks may return a new `Response` object.
160
178
 
161
- 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`.
179
+ Response callbacks are called even if none of your middlewares generate a `Response`. In this case, they receive the default `404 Not Found` response. Note that [isolated middleware chains](#isolating-a-middleware-chain) are an exception to this rule, since a default response is not generated for them.
162
180
 
163
181
  ```typescript
164
- const poweredByMiddleware = (context: RequestContext, response: Response) => {
165
- response.headers.set('X-Powered-By', 'alien-middleware')
166
- }
182
+ // Approach 1: Returning an `onResponse` method
183
+ const poweredByMiddleware = (context: RequestContext) => ({
184
+ onResponse(response) {
185
+ response.headers.set('X-Powered-By', 'alien-middleware')
186
+ },
187
+ })
167
188
 
168
189
  const mainHandler = (context: RequestContext) => {
190
+ // Approach 2: Calling `context.onResponse(callback)`
191
+ context.onResponse(response => {
192
+ assert(response instanceof Response)
193
+ })
194
+
169
195
  return new Response('Main content')
170
196
  }
171
197
 
172
- // `poweredByMiddleware` runs after `mainHandler` generates a response
173
- const app = chain().use(mainHandler).use(poweredByMiddleware)
198
+ const app = chain().use(poweredByMiddleware).use(mainHandler)
174
199
 
175
200
  const response = await app({…})
176
201
  console.log(response.headers.get('X-Powered-By')) // Output: alien-middleware
177
202
  ```
178
203
 
179
204
  > [!NOTE]
180
- > Response middleware cannot extend the context using `{ define }` or `{ env }`.
181
- > They can only inspect the `Response` or replace it by returning a new
182
- > `Response`.
183
-
184
- Your response middlewares will run even if no `Response` is generated by the
185
- request middlewares, **except** when the middleware chain is nested inside
186
- another chain, since the outer chain will still have a chance to return a
187
- `Response`.
205
+ > Remember that request middlewares may not be called if a previous middleware
206
+ > returns a `Response`. In that case, any response callbacks added by the
207
+ > uncalled middleware will not be executed. Therefore, order your middlewares
208
+ > carefully.
209
+
210
+ #### Modifying Response Headers
211
+
212
+ Even if a middleware returns an immutable `Response` (e.g. from a `fetch()` call), your _response callback_ can still modify the headers. We make sure to clone the response before processing it with any response callbacks.
213
+
214
+ #### Non-Blocking Response Callbacks
215
+
216
+ To ensure the client receives a response as soon as possible, your response callbacks should avoid using `await` unless absolutely necessary. Prefer using `context.waitUntil()` to register independent promises that shouldn't block the response from being sent.
217
+
218
+ ```typescript
219
+ const app = chain().use(context => {
220
+ context.onResponse(async response => {
221
+ // ❌ Bad! This blocks the response from being sent.
222
+ await myLoggingService.logResponse(response)
223
+
224
+ // ❌ Bad! This may be interrupted by serverless runtimes.
225
+ myLoggingService.logResponse(response).catch(console.error)
226
+
227
+ // ✅ Good! This doesn't block the response from being sent.
228
+ context.waitUntil(myLoggingService.logResponse(response))
229
+ })
230
+ })
231
+ ```
188
232
 
189
233
  ### Merging a Middleware Chain
190
234