@chidchanun/bcp 0.1.19 → 0.1.20
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 +57 -5
- package/docs/error-handling.md +457 -0
- package/docs/hydration.md +112 -0
- package/docs/releases/0.1.20.md +124 -0
- package/package.json +5 -1
- package/packages/bundler/src/index.ts +24 -13
- package/packages/client/src/http-error.ts +516 -0
- package/packages/client/src/server.ts +22 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# BCP Framework 0.1.20
|
|
2
|
+
|
|
3
|
+
BCP 0.1.20 introduces the Error Handling System: structured HTTP errors and consistent JSON error responses for APIs, form actions, guards, loaders and shared service code. It also hardens development hydration on Windows by normalizing source line endings before the React Refresh/Babel transform.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- Added the public `bcp/error` entrypoint.
|
|
8
|
+
- Added `HttpError`, `createHttpError()`, `throwHttpError()` and `isHttpError()`.
|
|
9
|
+
- Added `errorResponse()` and `toErrorResponse()`.
|
|
10
|
+
- Added convenience response helpers for common statuses:
|
|
11
|
+
- `badRequest()` — 400
|
|
12
|
+
- `unauthorized()` — 401
|
|
13
|
+
- `forbidden()` — 403
|
|
14
|
+
- `notFoundResponse()` — 404
|
|
15
|
+
- `conflict()` — 409
|
|
16
|
+
- `unprocessableEntity()` — 422
|
|
17
|
+
- `tooManyRequests()` — 429
|
|
18
|
+
- `internalServerError()` — 500
|
|
19
|
+
- `serviceUnavailable()` — 503
|
|
20
|
+
- Standardized the HTTP error payload around `status`, `code`, `message` and optional `details`.
|
|
21
|
+
- Error responses default to `Cache-Control: no-store`.
|
|
22
|
+
- `tooManyRequests()` can emit `Retry-After`.
|
|
23
|
+
- Unknown exceptions passed to `toErrorResponse()` become a safe generic 500 response without exposing the original exception message.
|
|
24
|
+
- Re-exported the HTTP error helpers through the server-only `bcp/server` entrypoint.
|
|
25
|
+
- Kept `notFound()` and `notFoundResponse()` intentionally separate: page 404 UI versus JSON HTTP 404 response.
|
|
26
|
+
- Fixed development hydration mismatches on Windows when CRLF source files contain multiline JSX/template-literal attributes such as multiline `className` values.
|
|
27
|
+
- Development application source now normalizes `CRLF` and standalone `CR` to `LF` before the React Refresh Babel transform, matching SSR source semantics.
|
|
28
|
+
- Added a CRLF development bundle regression fixture so the line-ending mismatch cannot silently return.
|
|
29
|
+
- Added unit and publish-surface regression coverage.
|
|
30
|
+
- Added the Error Handling and Hydration guides and updated the `bcp-docs` source map.
|
|
31
|
+
|
|
32
|
+
## API example
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import {
|
|
36
|
+
unauthorized,
|
|
37
|
+
} from "bcp/error";
|
|
38
|
+
|
|
39
|
+
export async function GET() {
|
|
40
|
+
const user = null;
|
|
41
|
+
|
|
42
|
+
if (!user) {
|
|
43
|
+
return unauthorized();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return Response.json({
|
|
47
|
+
user,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Validation example
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import {
|
|
56
|
+
unprocessableEntity,
|
|
57
|
+
} from "bcp/error";
|
|
58
|
+
import {
|
|
59
|
+
v,
|
|
60
|
+
} from "bcp/validation";
|
|
61
|
+
|
|
62
|
+
const schema =
|
|
63
|
+
v.object({
|
|
64
|
+
email:
|
|
65
|
+
v.string({
|
|
66
|
+
email: true,
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export async function POST(
|
|
71
|
+
request: Request
|
|
72
|
+
) {
|
|
73
|
+
const result =
|
|
74
|
+
schema.safeParse(
|
|
75
|
+
await request.json()
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (!result.success) {
|
|
79
|
+
return unprocessableEntity(
|
|
80
|
+
"Validation failed",
|
|
81
|
+
{
|
|
82
|
+
fieldErrors:
|
|
83
|
+
result.fieldErrors,
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return Response.json(
|
|
89
|
+
result.data
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Windows hydration fix
|
|
95
|
+
|
|
96
|
+
The following pattern is valid BCP/React code and no longer requires a one-line workaround on CRLF checkouts:
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
export default function Page() {
|
|
100
|
+
return (
|
|
101
|
+
<main
|
|
102
|
+
className={`
|
|
103
|
+
min-h-screen
|
|
104
|
+
bg-white
|
|
105
|
+
text-slate-950
|
|
106
|
+
`}
|
|
107
|
+
>
|
|
108
|
+
Hello
|
|
109
|
+
</main>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Before the fix, the development Babel/React Refresh path could preserve carriage-return characters differently from the SSR transform. React then saw different attribute strings during hydration even though the Tailwind class list looked identical. BCP now normalizes the development source before Babel.
|
|
115
|
+
|
|
116
|
+
This does not suppress genuine hydration errors from `Date.now()`, `Math.random()`, browser-only initial branches, locale differences, changing external data or invalid HTML.
|
|
117
|
+
|
|
118
|
+
See `docs/hydration.md` for details and troubleshooting.
|
|
119
|
+
|
|
120
|
+
## Compatibility
|
|
121
|
+
|
|
122
|
+
0.1.20 is additive. Existing `Response`, `Response.json()`, `bcp/server` helpers, `notFound()`, route guards and form actions continue to work.
|
|
123
|
+
|
|
124
|
+
Applications do not need to migrate existing error handling immediately. New code can adopt `bcp/error` incrementally.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chidchanun/bcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
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",
|
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
"types": "./packages/client/src/validation.ts",
|
|
48
48
|
"default": "./packages/client/src/validation.ts"
|
|
49
49
|
},
|
|
50
|
+
"./error": {
|
|
51
|
+
"types": "./packages/client/src/http-error.ts",
|
|
52
|
+
"default": "./packages/client/src/http-error.ts"
|
|
53
|
+
},
|
|
50
54
|
"./database": {
|
|
51
55
|
"types": "./packages/client/src/database.ts",
|
|
52
56
|
"browser": "./packages/client/src/server-only.browser.mjs",
|
|
@@ -608,9 +608,9 @@ if (!globalThis.__BCP_HMR_SOURCE__) {
|
|
|
608
608
|
runtimeOutput,
|
|
609
609
|
|
|
610
610
|
content:
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
611
|
+
fs.readFileSync(
|
|
612
|
+
runtimeOutput
|
|
613
|
+
),
|
|
614
614
|
|
|
615
615
|
contentType:
|
|
616
616
|
"text/javascript; charset=utf-8",
|
|
@@ -1114,7 +1114,7 @@ export class DevClientBundler {
|
|
|
1114
1114
|
/*
|
|
1115
1115
|
* =================================
|
|
1116
1116
|
* Module Graph
|
|
1117
|
-
*
|
|
1117
|
+
* =====================================
|
|
1118
1118
|
*/
|
|
1119
1119
|
|
|
1120
1120
|
private rebuildModuleGraph() {
|
|
@@ -1155,7 +1155,7 @@ export class DevClientBundler {
|
|
|
1155
1155
|
/*
|
|
1156
1156
|
* =================================
|
|
1157
1157
|
* Find Affected Routes
|
|
1158
|
-
*
|
|
1158
|
+
* =====================================
|
|
1159
1159
|
*/
|
|
1160
1160
|
|
|
1161
1161
|
findAffectedRoutes(
|
|
@@ -1201,7 +1201,7 @@ export class DevClientBundler {
|
|
|
1201
1201
|
/*
|
|
1202
1202
|
* =================================
|
|
1203
1203
|
* Debug Graph
|
|
1204
|
-
*
|
|
1204
|
+
* =====================================
|
|
1205
1205
|
*/
|
|
1206
1206
|
|
|
1207
1207
|
printAffectedRoutes(
|
|
@@ -1243,7 +1243,7 @@ export class DevClientBundler {
|
|
|
1243
1243
|
/*
|
|
1244
1244
|
* =================================
|
|
1245
1245
|
* Bundles
|
|
1246
|
-
*
|
|
1246
|
+
* =====================================
|
|
1247
1247
|
*/
|
|
1248
1248
|
|
|
1249
1249
|
getBundles():
|
|
@@ -1259,7 +1259,7 @@ export class DevClientBundler {
|
|
|
1259
1259
|
/*
|
|
1260
1260
|
* =================================
|
|
1261
1261
|
* Dispose
|
|
1262
|
-
*
|
|
1262
|
+
* =====================================
|
|
1263
1263
|
*/
|
|
1264
1264
|
|
|
1265
1265
|
async dispose() {
|
|
@@ -1649,6 +1649,15 @@ function createReactVendorPlugin():
|
|
|
1649
1649
|
};
|
|
1650
1650
|
}
|
|
1651
1651
|
|
|
1652
|
+
export function normalizeSourceLineEndings(
|
|
1653
|
+
source: string
|
|
1654
|
+
): string {
|
|
1655
|
+
return source.replace(
|
|
1656
|
+
/\r\n?/g,
|
|
1657
|
+
"\n"
|
|
1658
|
+
);
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1652
1661
|
/*
|
|
1653
1662
|
* =====================================
|
|
1654
1663
|
* React Refresh Plugin
|
|
@@ -1711,11 +1720,13 @@ function createReactRefreshPlugin(
|
|
|
1711
1720
|
}
|
|
1712
1721
|
|
|
1713
1722
|
const source =
|
|
1714
|
-
|
|
1715
|
-
.
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1723
|
+
normalizeSourceLineEndings(
|
|
1724
|
+
await fs.promises
|
|
1725
|
+
.readFile(
|
|
1726
|
+
args.path,
|
|
1727
|
+
"utf8"
|
|
1728
|
+
)
|
|
1729
|
+
);
|
|
1719
1730
|
|
|
1720
1731
|
const extension =
|
|
1721
1732
|
path.extname(
|