@chidchanun/bcp 0.1.17 → 0.1.18
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/docs/README.md +325 -0
- package/docs/middleware.md +486 -24
- package/docs/releases/0.1.18.md +119 -0
- package/package.json +1 -1
- package/packages/server/src/middleware-loader.ts +34 -7
- package/packages/server/src/middleware-proxy.ts +325 -159
- package/packages/server/src/middleware.ts +544 -104
package/docs/middleware.md
CHANGED
|
@@ -1,58 +1,520 @@
|
|
|
1
1
|
# Middleware
|
|
2
2
|
|
|
3
|
+
BCP Middleware runs before application routing and can inspect requests, rewrite URLs, redirect requests, change request headers, return responses directly, and add response headers.
|
|
4
|
+
|
|
5
|
+
BCP 0.1.18 adds Middleware System v2 while keeping the v1 API backward-compatible.
|
|
6
|
+
|
|
7
|
+
## Create middleware
|
|
8
|
+
|
|
3
9
|
Create `middleware.ts` in the project root.
|
|
4
10
|
|
|
11
|
+
```text
|
|
12
|
+
my-app/
|
|
13
|
+
├─ app/
|
|
14
|
+
├─ middleware.ts
|
|
15
|
+
├─ bcp.config.ts
|
|
16
|
+
└─ package.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Import middleware APIs from `bcp/middleware`.
|
|
20
|
+
|
|
21
|
+
## Middleware System v2
|
|
22
|
+
|
|
23
|
+
Middleware v2 uses an onion-style pipeline. A handler can run code before the next handler/application, call `await next()`, then inspect or change the final `Response` on the way back out.
|
|
24
|
+
|
|
5
25
|
```ts
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
redirect,
|
|
9
|
-
rewrite,
|
|
10
|
-
type MiddlewareConfig,
|
|
11
|
-
type MiddlewareRequest,
|
|
26
|
+
import type {
|
|
27
|
+
MiddlewarePipelineHandler,
|
|
12
28
|
} from "bcp/middleware";
|
|
13
29
|
|
|
14
|
-
export const
|
|
30
|
+
export const middleware:
|
|
31
|
+
MiddlewarePipelineHandler =
|
|
32
|
+
async (
|
|
33
|
+
request,
|
|
34
|
+
next,
|
|
35
|
+
context
|
|
36
|
+
) => {
|
|
37
|
+
const started =
|
|
38
|
+
performance.now();
|
|
39
|
+
|
|
40
|
+
context.state.requestPath =
|
|
41
|
+
request.nextUrl.pathname;
|
|
42
|
+
|
|
43
|
+
const response =
|
|
44
|
+
await next();
|
|
45
|
+
|
|
46
|
+
response.headers.set(
|
|
47
|
+
"X-Response-Time",
|
|
48
|
+
`${Math.round(
|
|
49
|
+
performance.now() -
|
|
50
|
+
started
|
|
51
|
+
)}ms`
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return response;
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`await next()` executes the rest of the middleware chain and then the BCP application. The returned value is the real downstream Web `Response`.
|
|
59
|
+
|
|
60
|
+
This makes response interception possible without creating an extra API abstraction.
|
|
61
|
+
|
|
62
|
+
## Middleware chains
|
|
63
|
+
|
|
64
|
+
Export an array to compose several middleware handlers.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import type {
|
|
68
|
+
MiddlewarePipelineHandler,
|
|
69
|
+
} from "bcp/middleware";
|
|
70
|
+
|
|
71
|
+
const logger:
|
|
72
|
+
MiddlewarePipelineHandler =
|
|
73
|
+
async (
|
|
74
|
+
request,
|
|
75
|
+
next
|
|
76
|
+
) => {
|
|
77
|
+
console.log(
|
|
78
|
+
"request",
|
|
79
|
+
request.method,
|
|
80
|
+
request.nextUrl.pathname
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const response =
|
|
84
|
+
await next();
|
|
85
|
+
|
|
86
|
+
console.log(
|
|
87
|
+
"response",
|
|
88
|
+
response.status
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
return response;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const responseHeaders:
|
|
95
|
+
MiddlewarePipelineHandler =
|
|
96
|
+
async (
|
|
97
|
+
_request,
|
|
98
|
+
next
|
|
99
|
+
) => {
|
|
100
|
+
const response =
|
|
101
|
+
await next();
|
|
102
|
+
|
|
103
|
+
response.headers.set(
|
|
104
|
+
"X-App",
|
|
105
|
+
"BCP"
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return response;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const middleware = [
|
|
112
|
+
logger,
|
|
113
|
+
responseHeaders,
|
|
114
|
+
];
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Execution order is:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
Request
|
|
121
|
+
↓
|
|
122
|
+
logger before
|
|
123
|
+
↓
|
|
124
|
+
responseHeaders before
|
|
125
|
+
↓
|
|
126
|
+
BCP router / page / API
|
|
127
|
+
↑
|
|
128
|
+
responseHeaders after
|
|
129
|
+
↑
|
|
130
|
+
logger after
|
|
131
|
+
↓
|
|
132
|
+
Response
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
A middleware handler may call `next()` only once. Calling it more than once throws an error because a request body and downstream application execution cannot safely be replayed automatically.
|
|
136
|
+
|
|
137
|
+
## Shared request state
|
|
138
|
+
|
|
139
|
+
The third argument is a `MiddlewareContext` shared by every handler in one request.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import type {
|
|
143
|
+
MiddlewarePipelineHandler,
|
|
144
|
+
} from "bcp/middleware";
|
|
145
|
+
|
|
146
|
+
const identify:
|
|
147
|
+
MiddlewarePipelineHandler =
|
|
148
|
+
async (
|
|
149
|
+
request,
|
|
150
|
+
next,
|
|
151
|
+
context
|
|
152
|
+
) => {
|
|
153
|
+
context.state.requestSource =
|
|
154
|
+
request.headers.get(
|
|
155
|
+
"X-Request-Source"
|
|
156
|
+
) ??
|
|
157
|
+
"web";
|
|
158
|
+
|
|
159
|
+
return next();
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const observe:
|
|
163
|
+
MiddlewarePipelineHandler =
|
|
164
|
+
async (
|
|
165
|
+
_request,
|
|
166
|
+
next,
|
|
167
|
+
context
|
|
168
|
+
) => {
|
|
169
|
+
console.log(
|
|
170
|
+
context.state.requestSource
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return next();
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const middleware = [
|
|
177
|
+
identify,
|
|
178
|
+
observe,
|
|
179
|
+
];
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`context.state` starts empty for every request and is not shared across concurrent requests.
|
|
183
|
+
|
|
184
|
+
Use it for lightweight request-scoped information such as timing metadata, correlation IDs, feature flags, or values computed by an earlier middleware handler.
|
|
185
|
+
|
|
186
|
+
Do not treat it as persistent storage.
|
|
187
|
+
|
|
188
|
+
## Matchers
|
|
189
|
+
|
|
190
|
+
Use `config.matcher` to limit the routes that execute middleware.
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import type {
|
|
194
|
+
MiddlewareConfig,
|
|
195
|
+
} from "bcp/middleware";
|
|
196
|
+
|
|
197
|
+
export const config:
|
|
198
|
+
MiddlewareConfig = {
|
|
15
199
|
matcher: [
|
|
16
200
|
"/dashboard/:path*",
|
|
17
201
|
"/api/private/:path*",
|
|
18
202
|
],
|
|
19
203
|
};
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
String matchers support:
|
|
207
|
+
|
|
208
|
+
```text
|
|
209
|
+
/dashboard
|
|
210
|
+
/users/:id
|
|
211
|
+
/dashboard/:path*
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
A JavaScript `RegExp` is also supported.
|
|
215
|
+
|
|
216
|
+
## Request data
|
|
217
|
+
|
|
218
|
+
The middleware request contains:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
request.url
|
|
222
|
+
request.method
|
|
223
|
+
request.headers
|
|
224
|
+
request.nextUrl
|
|
225
|
+
request.cookies
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Example:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
export async function middleware(
|
|
232
|
+
request,
|
|
233
|
+
next
|
|
234
|
+
) {
|
|
235
|
+
const theme =
|
|
236
|
+
request.cookies.get(
|
|
237
|
+
"theme"
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
console.log(
|
|
241
|
+
request.method,
|
|
242
|
+
request.nextUrl.pathname,
|
|
243
|
+
theme
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
return next();
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Response interception
|
|
251
|
+
|
|
252
|
+
Because `next()` returns the downstream `Response`, middleware can add or change headers after a page or API route finishes.
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
export async function middleware(
|
|
256
|
+
request,
|
|
257
|
+
next
|
|
258
|
+
) {
|
|
259
|
+
const response =
|
|
260
|
+
await next();
|
|
261
|
+
|
|
262
|
+
response.headers.set(
|
|
263
|
+
"X-Route",
|
|
264
|
+
request.nextUrl.pathname
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
return response;
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Middleware may also replace the response completely:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
export async function middleware(
|
|
275
|
+
request,
|
|
276
|
+
next
|
|
277
|
+
) {
|
|
278
|
+
const response =
|
|
279
|
+
await next();
|
|
280
|
+
|
|
281
|
+
if (
|
|
282
|
+
response.status === 404 &&
|
|
283
|
+
request.nextUrl.pathname
|
|
284
|
+
.startsWith("/api/")
|
|
285
|
+
) {
|
|
286
|
+
return Response.json(
|
|
287
|
+
{
|
|
288
|
+
error:
|
|
289
|
+
"API endpoint not found",
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
status: 404,
|
|
293
|
+
}
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return response;
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Rewrite
|
|
302
|
+
|
|
303
|
+
`rewrite()` keeps the browser URL but changes the route processed by BCP.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import {
|
|
307
|
+
rewrite,
|
|
308
|
+
} from "bcp/middleware";
|
|
309
|
+
|
|
310
|
+
export function middleware(
|
|
311
|
+
request
|
|
312
|
+
) {
|
|
313
|
+
if (
|
|
314
|
+
request.nextUrl.pathname ===
|
|
315
|
+
"/old-dashboard"
|
|
316
|
+
) {
|
|
317
|
+
return rewrite(
|
|
318
|
+
"/dashboard"
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Rewrites must remain on the same origin.
|
|
325
|
+
|
|
326
|
+
In a v2 chain, return `rewrite()` before calling `next()`.
|
|
327
|
+
|
|
328
|
+
## Redirect
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import {
|
|
332
|
+
redirect,
|
|
333
|
+
} from "bcp/middleware";
|
|
334
|
+
|
|
335
|
+
export function middleware(
|
|
336
|
+
request
|
|
337
|
+
) {
|
|
338
|
+
if (
|
|
339
|
+
!request.cookies.has(
|
|
340
|
+
"session"
|
|
341
|
+
)
|
|
342
|
+
) {
|
|
343
|
+
return redirect(
|
|
344
|
+
"/login",
|
|
345
|
+
{
|
|
346
|
+
status: 303,
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Supported redirect statuses are `301`, `302`, `303`, `307`, and `308`.
|
|
354
|
+
|
|
355
|
+
Middleware redirects continue to work with direct document requests and BCP SPA navigation.
|
|
356
|
+
|
|
357
|
+
## Request and response headers
|
|
358
|
+
|
|
359
|
+
The existing v1 helpers continue to work inside a v2 chain.
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
import {
|
|
363
|
+
next,
|
|
364
|
+
} from "bcp/middleware";
|
|
365
|
+
|
|
366
|
+
export function middleware() {
|
|
367
|
+
return next({
|
|
368
|
+
request: {
|
|
369
|
+
headers: {
|
|
370
|
+
"X-Internal-Request":
|
|
371
|
+
"true",
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
response: {
|
|
375
|
+
headers: {
|
|
376
|
+
"X-Powered-By-App":
|
|
377
|
+
"BCP",
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Request headers are visible to later middleware and the application. Response headers are applied after downstream execution.
|
|
385
|
+
|
|
386
|
+
## Direct responses
|
|
387
|
+
|
|
388
|
+
A middleware handler can stop the chain by returning a Web `Response` without calling `next()`.
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
export function middleware(
|
|
392
|
+
request
|
|
393
|
+
) {
|
|
394
|
+
if (
|
|
395
|
+
request.nextUrl.pathname ===
|
|
396
|
+
"/maintenance"
|
|
397
|
+
) {
|
|
398
|
+
return new Response(
|
|
399
|
+
"Maintenance",
|
|
400
|
+
{
|
|
401
|
+
status: 503,
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## Error middleware pattern
|
|
409
|
+
|
|
410
|
+
A v2 handler can wrap downstream execution with `try/catch`.
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
export async function middleware(
|
|
414
|
+
request,
|
|
415
|
+
next
|
|
416
|
+
) {
|
|
417
|
+
try {
|
|
418
|
+
return await next();
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.error(
|
|
421
|
+
"Request failed",
|
|
422
|
+
request.nextUrl.pathname,
|
|
423
|
+
error
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
return Response.json(
|
|
427
|
+
{
|
|
428
|
+
error:
|
|
429
|
+
"Internal Server Error",
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
status: 500,
|
|
433
|
+
}
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Use this for application-specific error translation. Framework security and server error boundaries still have their own responsibilities.
|
|
440
|
+
|
|
441
|
+
## v1 compatibility
|
|
442
|
+
|
|
443
|
+
BCP 0.1.18 does not require existing middleware to be rewritten.
|
|
444
|
+
|
|
445
|
+
A v1 handler remains valid:
|
|
446
|
+
|
|
447
|
+
```ts
|
|
448
|
+
import {
|
|
449
|
+
next,
|
|
450
|
+
redirect,
|
|
451
|
+
} from "bcp/middleware";
|
|
20
452
|
|
|
21
453
|
export function middleware(
|
|
22
|
-
request
|
|
454
|
+
request
|
|
23
455
|
) {
|
|
24
456
|
if (
|
|
25
|
-
|
|
457
|
+
request.nextUrl.pathname ===
|
|
458
|
+
"/private" &&
|
|
459
|
+
!request.cookies.has(
|
|
460
|
+
"session"
|
|
461
|
+
)
|
|
26
462
|
) {
|
|
27
|
-
return redirect(
|
|
463
|
+
return redirect(
|
|
464
|
+
"/login"
|
|
465
|
+
);
|
|
28
466
|
}
|
|
29
467
|
|
|
30
468
|
return next();
|
|
31
469
|
}
|
|
32
470
|
```
|
|
33
471
|
|
|
34
|
-
|
|
472
|
+
The important difference is:
|
|
35
473
|
|
|
36
|
-
|
|
474
|
+
| Capability | v1 | v2 |
|
|
475
|
+
| --- | --- | --- |
|
|
476
|
+
| Inspect request | Yes | Yes |
|
|
477
|
+
| Match routes | Yes | Yes |
|
|
478
|
+
| Redirect | Yes | Yes |
|
|
479
|
+
| Rewrite | Yes | Yes |
|
|
480
|
+
| Request/response header helpers | Yes | Yes |
|
|
481
|
+
| Return direct `Response` | Yes | Yes |
|
|
482
|
+
| Multiple middleware handlers | No | Yes |
|
|
483
|
+
| `await next()` executes downstream | No | Yes |
|
|
484
|
+
| Inspect final route response | No | Yes |
|
|
485
|
+
| Shared request context | No | Yes |
|
|
486
|
+
| Before/after onion flow | No | Yes |
|
|
37
487
|
|
|
38
|
-
-
|
|
39
|
-
- rewrite to another same-origin route with `rewrite()`
|
|
40
|
-
- redirect to an HTTP(S) URL with `redirect()`
|
|
41
|
-
- return a `Response` directly
|
|
42
|
-
- mutate request and response headers through middleware options
|
|
488
|
+
Existing v1 middleware can be migrated gradually. A project may mix legacy one-argument handlers and v2 handlers in the same exported array.
|
|
43
489
|
|
|
44
|
-
##
|
|
490
|
+
## Execution model
|
|
45
491
|
|
|
46
|
-
|
|
492
|
+
At runtime, middleware sits in front of the BCP application.
|
|
47
493
|
|
|
48
494
|
```text
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
495
|
+
Browser
|
|
496
|
+
↓
|
|
497
|
+
BCP outer gateways
|
|
498
|
+
↓
|
|
499
|
+
Middleware v2 pipeline
|
|
500
|
+
↓
|
|
501
|
+
Actions / Guards / Loaders / Pages / APIs
|
|
502
|
+
↑
|
|
503
|
+
Middleware v2 response path
|
|
504
|
+
↓
|
|
505
|
+
Browser
|
|
52
506
|
```
|
|
53
507
|
|
|
508
|
+
Development and standalone production use the same middleware pipeline semantics.
|
|
509
|
+
|
|
510
|
+
## Performance note
|
|
511
|
+
|
|
512
|
+
Middleware response interception crosses a Web `Response` boundary so BCP can expose the completed downstream response to user middleware. Avoid unnecessary body replacement for large responses when only a header change is needed.
|
|
513
|
+
|
|
54
514
|
## Security constraints
|
|
55
515
|
|
|
56
|
-
Rewrites must stay on the same origin. Redirects and rewrites reject unsupported URL protocols and embedded URL credentials.
|
|
516
|
+
Rewrites must stay on the same origin. Redirects and rewrites reject unsupported URL protocols and embedded URL credentials.
|
|
517
|
+
|
|
518
|
+
Framework-internal `/_bcp/*` requests bypass user middleware where required by the runtime. BCP SPA navigation still evaluates middleware against the intended destination URL rather than exposing internal navigation transport details to application policy.
|
|
57
519
|
|
|
58
|
-
|
|
520
|
+
Do not use middleware as a replacement for route authorization when authorization is page-specific. Use route guards and `bcp/auth` helpers such as `requireAuth()` or `requireRole()` for protected application areas.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# BCP Framework 0.1.18
|
|
2
|
+
|
|
3
|
+
BCP Framework 0.1.18 introduces Middleware System v2 while preserving existing Middleware v1 applications.
|
|
4
|
+
|
|
5
|
+
## Middleware System v2
|
|
6
|
+
|
|
7
|
+
Middleware can now execute as an onion-style chain around the application.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
export async function middleware(
|
|
11
|
+
request,
|
|
12
|
+
next,
|
|
13
|
+
context
|
|
14
|
+
) {
|
|
15
|
+
const started =
|
|
16
|
+
performance.now();
|
|
17
|
+
|
|
18
|
+
const response =
|
|
19
|
+
await next();
|
|
20
|
+
|
|
21
|
+
response.headers.set(
|
|
22
|
+
"X-Response-Time",
|
|
23
|
+
`${Math.round(
|
|
24
|
+
performance.now() -
|
|
25
|
+
started
|
|
26
|
+
)}ms`
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return response;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`await next()` runs the remaining middleware and the real downstream BCP route, then returns the resulting Web `Response`.
|
|
34
|
+
|
|
35
|
+
## Middleware chains
|
|
36
|
+
|
|
37
|
+
Applications may export an array of handlers:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
export const middleware = [
|
|
41
|
+
logger,
|
|
42
|
+
requestContext,
|
|
43
|
+
responseHeaders,
|
|
44
|
+
];
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Handlers execute from outer to inner before the application, then unwind from inner to outer after the application returns.
|
|
48
|
+
|
|
49
|
+
## Shared request context
|
|
50
|
+
|
|
51
|
+
The third handler argument exposes request-scoped state:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
context.state.requestId =
|
|
55
|
+
"request-123";
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The same state object is visible to later middleware in that request and is recreated for the next request.
|
|
59
|
+
|
|
60
|
+
## Response interception
|
|
61
|
+
|
|
62
|
+
Middleware can now inspect and modify the actual route response after page/API processing finishes.
|
|
63
|
+
|
|
64
|
+
This supports patterns such as:
|
|
65
|
+
|
|
66
|
+
- request/response timing
|
|
67
|
+
- response metadata
|
|
68
|
+
- application-specific response headers
|
|
69
|
+
- error translation
|
|
70
|
+
- response status observation
|
|
71
|
+
- composable logging
|
|
72
|
+
|
|
73
|
+
## Compatibility
|
|
74
|
+
|
|
75
|
+
Middleware v1 remains supported.
|
|
76
|
+
|
|
77
|
+
Existing code such as:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
export function middleware(
|
|
81
|
+
request
|
|
82
|
+
) {
|
|
83
|
+
return next();
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
continues to work without modification.
|
|
88
|
+
|
|
89
|
+
Legacy `next()`, `rewrite()`, `redirect()`, matchers, request headers, response headers, cookie reads and direct `Response` returns remain available.
|
|
90
|
+
|
|
91
|
+
A project can also mix v1 and v2 handlers in the same middleware array.
|
|
92
|
+
|
|
93
|
+
## Runtime
|
|
94
|
+
|
|
95
|
+
The middleware proxy now exposes the real downstream application response to the v2 pipeline in both:
|
|
96
|
+
|
|
97
|
+
- development
|
|
98
|
+
- standalone production
|
|
99
|
+
|
|
100
|
+
SPA navigation keeps middleware redirect/rewrite behavior while evaluating middleware policy against the intended destination URL.
|
|
101
|
+
|
|
102
|
+
## Reliability
|
|
103
|
+
|
|
104
|
+
Added regression coverage for:
|
|
105
|
+
|
|
106
|
+
- onion execution order
|
|
107
|
+
- response interception
|
|
108
|
+
- shared request context
|
|
109
|
+
- context isolation across requests
|
|
110
|
+
- legacy handlers inside v2 chains
|
|
111
|
+
- rewrite propagation
|
|
112
|
+
- duplicate `next()` rejection
|
|
113
|
+
- middleware array validation
|
|
114
|
+
|
|
115
|
+
## Documentation
|
|
116
|
+
|
|
117
|
+
`docs/middleware.md` now contains the complete Middleware v2 guide and v1 migration reference.
|
|
118
|
+
|
|
119
|
+
`docs/README.md` provides a documentation content map, recommended navigation, feature inventory, and source-of-truth guidance for building the `bcp-docs` website.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chidchanun/bcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "BCP Framework - a React full-stack framework with file-based routing, SSR, APIs, middleware, islands, caching and standalone production builds.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|