@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.
@@ -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.19",
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
- fs.readFileSync(
612
- runtimeOutput
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
- await fs.promises
1715
- .readFile(
1716
- args.path,
1717
- "utf8"
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(