@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
package/docs/README.md
CHANGED
|
@@ -4,7 +4,7 @@ This directory is the documentation source of truth for the BCP Framework docume
|
|
|
4
4
|
|
|
5
5
|
The recommended documentation website project name is `bcp-docs`.
|
|
6
6
|
|
|
7
|
-
> Documentation target: BCP Framework `0.1.
|
|
7
|
+
> Documentation target: BCP Framework `0.1.20`
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
10
10
|
|
|
@@ -41,6 +41,7 @@ The website should not invent API behavior that is not documented or tested in t
|
|
|
41
41
|
| `/docs/form-actions` | `form-actions.md` | Server mutations and forms |
|
|
42
42
|
| `/docs/server-request-apis` | `server-request-apis.md` | Request, cookie, redirect and response APIs |
|
|
43
43
|
| `/docs/validation` | `validation.md` | Typed form/API validation and field errors |
|
|
44
|
+
| `/docs/error-handling` | `error-handling.md` | Structured HTTP errors and standard error responses |
|
|
44
45
|
|
|
45
46
|
### Authentication
|
|
46
47
|
|
|
@@ -62,6 +63,7 @@ The website should not invent API behavior that is not documented or tested in t
|
|
|
62
63
|
| Website route | Source file | Description |
|
|
63
64
|
| --- | --- | --- |
|
|
64
65
|
| `/docs/middleware` | `middleware.md` | Middleware System v2 and v1 compatibility |
|
|
66
|
+
| `/docs/hydration` | `hydration.md` | SSR/client hydration, deterministic rendering and Windows CRLF behavior |
|
|
65
67
|
| `/docs/caching` | `caching.md` | Cache and revalidation |
|
|
66
68
|
| `/docs/security` | `security.md` | Framework security defaults |
|
|
67
69
|
| `/docs/development-logging` | `development-logging.md` | Development request logging |
|
|
@@ -73,9 +75,9 @@ Use the files under `docs/releases/` for release pages.
|
|
|
73
75
|
Recommended route format:
|
|
74
76
|
|
|
75
77
|
```text
|
|
76
|
-
/releases/0.1.17
|
|
77
78
|
/releases/0.1.18
|
|
78
79
|
/releases/0.1.19
|
|
80
|
+
/releases/0.1.20
|
|
79
81
|
```
|
|
80
82
|
|
|
81
83
|
The newest release should also be available from `/releases`.
|
|
@@ -95,6 +97,7 @@ Recommended feature groups:
|
|
|
95
97
|
- Loading and error UI
|
|
96
98
|
- API routes
|
|
97
99
|
- Client islands / partial hydration
|
|
100
|
+
- Deterministic development hydration across LF/CRLF source checkouts
|
|
98
101
|
|
|
99
102
|
### Server data and mutations
|
|
100
103
|
|
|
@@ -106,6 +109,9 @@ Recommended feature groups:
|
|
|
106
109
|
- `bcp/validation`
|
|
107
110
|
- Typed form/API validation
|
|
108
111
|
- Field and form error output
|
|
112
|
+
- `bcp/error`
|
|
113
|
+
- Structured HTTP error responses
|
|
114
|
+
- Safe unknown-error fallback responses
|
|
109
115
|
|
|
110
116
|
### Database
|
|
111
117
|
|
|
@@ -157,7 +163,8 @@ Suggested content, not a required exact copy:
|
|
|
157
163
|
```text
|
|
158
164
|
BCP Framework
|
|
159
165
|
A React full-stack framework for building server-rendered applications with routing,
|
|
160
|
-
data loading, server mutations, authentication, databases, validation,
|
|
166
|
+
data loading, server mutations, authentication, databases, validation, structured errors,
|
|
167
|
+
middleware and production tooling.
|
|
161
168
|
```
|
|
162
169
|
|
|
163
170
|
Primary actions:
|
|
@@ -188,6 +195,7 @@ Core
|
|
|
188
195
|
Route Guards
|
|
189
196
|
Form Actions
|
|
190
197
|
Validation
|
|
198
|
+
Error Handling
|
|
191
199
|
|
|
192
200
|
Authentication
|
|
193
201
|
Authentication Core
|
|
@@ -200,6 +208,7 @@ Database
|
|
|
200
208
|
|
|
201
209
|
Runtime
|
|
202
210
|
Middleware
|
|
211
|
+
Hydration
|
|
203
212
|
Caching
|
|
204
213
|
Security
|
|
205
214
|
|
|
@@ -208,9 +217,9 @@ Production
|
|
|
208
217
|
Development Logging
|
|
209
218
|
|
|
210
219
|
Releases
|
|
220
|
+
0.1.20
|
|
211
221
|
0.1.19
|
|
212
222
|
0.1.18
|
|
213
|
-
0.1.17
|
|
214
223
|
```
|
|
215
224
|
|
|
216
225
|
## Suggested documentation page layout
|
|
@@ -259,6 +268,11 @@ import {
|
|
|
259
268
|
validateFormData,
|
|
260
269
|
} from "bcp/validation";
|
|
261
270
|
|
|
271
|
+
import {
|
|
272
|
+
badRequest,
|
|
273
|
+
unauthorized,
|
|
274
|
+
} from "bcp/error";
|
|
275
|
+
|
|
262
276
|
import {
|
|
263
277
|
type MiddlewarePipelineHandler,
|
|
264
278
|
} from "bcp/middleware";
|
|
@@ -322,7 +336,7 @@ See `middleware.md` for the full API and examples.
|
|
|
322
336
|
|
|
323
337
|
## Validation documentation priority
|
|
324
338
|
|
|
325
|
-
BCP 0.1.19
|
|
339
|
+
BCP 0.1.19 introduced the public `bcp/validation` entrypoint. The docs website should show validation in both form-action and API-route examples.
|
|
326
340
|
|
|
327
341
|
Important concepts to surface:
|
|
328
342
|
|
|
@@ -335,6 +349,44 @@ Important concepts to surface:
|
|
|
335
349
|
|
|
336
350
|
See `validation.md` for the complete API and examples.
|
|
337
351
|
|
|
352
|
+
## Error Handling documentation priority
|
|
353
|
+
|
|
354
|
+
BCP 0.1.20 introduces the public `bcp/error` entrypoint and a standard HTTP error envelope.
|
|
355
|
+
|
|
356
|
+
The docs website should clearly distinguish:
|
|
357
|
+
|
|
358
|
+
```text
|
|
359
|
+
notFound() = activate page 404 UI behavior
|
|
360
|
+
notFoundResponse() = return a JSON 404 HTTP response
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Important concepts to surface:
|
|
364
|
+
|
|
365
|
+
- `HttpError` and `createHttpError()`
|
|
366
|
+
- `errorResponse()` for any 4xx/5xx status
|
|
367
|
+
- `toErrorResponse()` for safe catch boundaries
|
|
368
|
+
- `badRequest()`, `unauthorized()`, `forbidden()`, `conflict()` and other convenience helpers
|
|
369
|
+
- standard `{ error: { status, code, message, details? } }` payload
|
|
370
|
+
- error responses default to `Cache-Control: no-store`
|
|
371
|
+
- unknown exceptions become a generic 500 response without exposing the original exception message
|
|
372
|
+
- normal field validation still belongs to `bcp/validation`
|
|
373
|
+
|
|
374
|
+
See `error-handling.md` for the complete API and examples.
|
|
375
|
+
|
|
376
|
+
## Hydration documentation priority
|
|
377
|
+
|
|
378
|
+
BCP 0.1.20 also fixes a Windows development hydration mismatch caused by CRLF source line endings passing through the React Refresh/Babel transform differently from SSR.
|
|
379
|
+
|
|
380
|
+
The docs website should make these points clear:
|
|
381
|
+
|
|
382
|
+
- multiline JSX attributes such as template-literal `className` values are supported,
|
|
383
|
+
- developers should not need to rewrite them as one-line strings to avoid framework hydration warnings,
|
|
384
|
+
- BCP normalizes `CRLF` and standalone `CR` source line endings to `LF` before the development Babel transform,
|
|
385
|
+
- the framework fix does not suppress genuine hydration mismatches caused by random values, locale differences, browser-only initial branches, changing external data or invalid HTML,
|
|
386
|
+
- `suppressHydrationWarning` is not a general-purpose solution.
|
|
387
|
+
|
|
388
|
+
See `hydration.md` for the full explanation and troubleshooting steps.
|
|
389
|
+
|
|
338
390
|
## Release workflow for documentation
|
|
339
391
|
|
|
340
392
|
Before publishing a new BCP release:
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
BCP 0.1.20 adds a structured HTTP error API for API routes, form actions, route guards, server data loaders and shared service code.
|
|
4
|
+
|
|
5
|
+
Import the public helpers from `bcp/error`:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import {
|
|
9
|
+
badRequest,
|
|
10
|
+
conflict,
|
|
11
|
+
createHttpError,
|
|
12
|
+
forbidden,
|
|
13
|
+
isHttpError,
|
|
14
|
+
notFoundResponse,
|
|
15
|
+
throwHttpError,
|
|
16
|
+
tooManyRequests,
|
|
17
|
+
toErrorResponse,
|
|
18
|
+
unauthorized,
|
|
19
|
+
unprocessableEntity,
|
|
20
|
+
} from "bcp/error";
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The same helpers are also re-exported by the server-only `bcp/server` entrypoint.
|
|
24
|
+
|
|
25
|
+
## Standard error envelope
|
|
26
|
+
|
|
27
|
+
BCP error responses use one JSON shape:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"error": {
|
|
32
|
+
"status": 422,
|
|
33
|
+
"code": "invalid_input",
|
|
34
|
+
"message": "Input is invalid",
|
|
35
|
+
"details": {
|
|
36
|
+
"email": [
|
|
37
|
+
"Email is required"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`details` is optional.
|
|
45
|
+
|
|
46
|
+
HTTP error responses default to:
|
|
47
|
+
|
|
48
|
+
```http
|
|
49
|
+
Cache-Control: no-store
|
|
50
|
+
Content-Type: application/json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The no-store default prevents authentication and validation failures from being cached accidentally. A custom `Cache-Control` header can override the default when an application intentionally needs another policy.
|
|
54
|
+
|
|
55
|
+
## API routes
|
|
56
|
+
|
|
57
|
+
Return an error helper exactly like a normal Web `Response`:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import {
|
|
61
|
+
unauthorized,
|
|
62
|
+
} from "bcp/error";
|
|
63
|
+
|
|
64
|
+
export async function GET() {
|
|
65
|
+
const user = null;
|
|
66
|
+
|
|
67
|
+
if (!user) {
|
|
68
|
+
return unauthorized();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Response.json({
|
|
72
|
+
user,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Custom messages and details are supported:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import {
|
|
81
|
+
unprocessableEntity,
|
|
82
|
+
} from "bcp/error";
|
|
83
|
+
|
|
84
|
+
export async function POST() {
|
|
85
|
+
return unprocessableEntity(
|
|
86
|
+
"Input is invalid",
|
|
87
|
+
{
|
|
88
|
+
email: [
|
|
89
|
+
"Email is required",
|
|
90
|
+
],
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Form actions
|
|
97
|
+
|
|
98
|
+
BCP actions already accept a Web `Response`, so error helpers work without a separate transport convention:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import {
|
|
102
|
+
conflict,
|
|
103
|
+
} from "bcp/error";
|
|
104
|
+
|
|
105
|
+
export async function createUser(
|
|
106
|
+
formData: FormData
|
|
107
|
+
) {
|
|
108
|
+
const email =
|
|
109
|
+
String(
|
|
110
|
+
formData.get("email") ?? ""
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const exists =
|
|
114
|
+
email ===
|
|
115
|
+
"existing@example.com";
|
|
116
|
+
|
|
117
|
+
if (exists) {
|
|
118
|
+
return conflict(
|
|
119
|
+
"Email is already in use",
|
|
120
|
+
{
|
|
121
|
+
field:
|
|
122
|
+
"email",
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
ok: true,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
For normal form-field validation, prefer `bcp/validation`. Use HTTP error responses for request-level failures such as authorization, conflicts, invalid application state or service failures.
|
|
134
|
+
|
|
135
|
+
## Route guards
|
|
136
|
+
|
|
137
|
+
Route guards may stop navigation with a response:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import {
|
|
141
|
+
forbidden,
|
|
142
|
+
} from "bcp/error";
|
|
143
|
+
|
|
144
|
+
export async function guard() {
|
|
145
|
+
const role =
|
|
146
|
+
"member";
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
role !== "admin"
|
|
150
|
+
) {
|
|
151
|
+
return forbidden(
|
|
152
|
+
"Admin access required"
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
role,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
For standard authenticated route protection, `requireAuth()` and `requireRole()` from `bcp/auth` remain the preferred high-level APIs.
|
|
163
|
+
|
|
164
|
+
## Server data loaders
|
|
165
|
+
|
|
166
|
+
Loaders may return error responses before rendering a page:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import {
|
|
170
|
+
serviceUnavailable,
|
|
171
|
+
} from "bcp/error";
|
|
172
|
+
|
|
173
|
+
export async function loader() {
|
|
174
|
+
const databaseAvailable =
|
|
175
|
+
false;
|
|
176
|
+
|
|
177
|
+
if (!databaseAvailable) {
|
|
178
|
+
return serviceUnavailable();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
users: [],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## `notFound()` versus `notFoundResponse()`
|
|
188
|
+
|
|
189
|
+
BCP has two intentionally different 404 APIs.
|
|
190
|
+
|
|
191
|
+
Use `notFound()` from `bcp` when page rendering should activate the BCP `not-found.tsx` route UI:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import {
|
|
195
|
+
notFound,
|
|
196
|
+
} from "bcp";
|
|
197
|
+
|
|
198
|
+
export default function UserPage() {
|
|
199
|
+
const user = null;
|
|
200
|
+
|
|
201
|
+
if (!user) {
|
|
202
|
+
notFound();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return <main>User</main>;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Use `notFoundResponse()` from `bcp/error` when the caller needs a JSON HTTP response:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
import {
|
|
213
|
+
notFoundResponse,
|
|
214
|
+
} from "bcp/error";
|
|
215
|
+
|
|
216
|
+
export function GET() {
|
|
217
|
+
return notFoundResponse(
|
|
218
|
+
"User not found"
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
This separation keeps page UI semantics distinct from API response semantics.
|
|
224
|
+
|
|
225
|
+
## Structured `HttpError`
|
|
226
|
+
|
|
227
|
+
Shared service code may need to report a typed error without returning a Web `Response` directly.
|
|
228
|
+
|
|
229
|
+
Create one with:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import {
|
|
233
|
+
createHttpError,
|
|
234
|
+
} from "bcp/error";
|
|
235
|
+
|
|
236
|
+
export async function loadAccount(
|
|
237
|
+
id: string
|
|
238
|
+
) {
|
|
239
|
+
if (!id) {
|
|
240
|
+
throw createHttpError(
|
|
241
|
+
400,
|
|
242
|
+
{
|
|
243
|
+
code:
|
|
244
|
+
"missing_account_id",
|
|
245
|
+
message:
|
|
246
|
+
"Account id is required",
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The equivalent shorthand is:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import {
|
|
257
|
+
throwHttpError,
|
|
258
|
+
} from "bcp/error";
|
|
259
|
+
|
|
260
|
+
throwHttpError(
|
|
261
|
+
403,
|
|
262
|
+
{
|
|
263
|
+
code:
|
|
264
|
+
"billing_access_denied",
|
|
265
|
+
message:
|
|
266
|
+
"Billing access denied",
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`HttpError` contains:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
{
|
|
275
|
+
status: number;
|
|
276
|
+
code: string;
|
|
277
|
+
message: string;
|
|
278
|
+
details: unknown;
|
|
279
|
+
headers: Headers;
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
HTTP error statuses must be integers from `400` through `599`.
|
|
284
|
+
|
|
285
|
+
## Converting caught errors
|
|
286
|
+
|
|
287
|
+
Use `isHttpError()` when application code needs to branch on the error type:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import {
|
|
291
|
+
isHttpError,
|
|
292
|
+
toErrorResponse,
|
|
293
|
+
} from "bcp/error";
|
|
294
|
+
|
|
295
|
+
export async function GET() {
|
|
296
|
+
try {
|
|
297
|
+
await loadAccount("123");
|
|
298
|
+
|
|
299
|
+
return Response.json({
|
|
300
|
+
ok: true,
|
|
301
|
+
});
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (
|
|
304
|
+
isHttpError(error)
|
|
305
|
+
) {
|
|
306
|
+
console.warn(
|
|
307
|
+
error.code,
|
|
308
|
+
error.status
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return toErrorResponse(
|
|
313
|
+
error
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Known `HttpError` values preserve their status, code, message, details and headers.
|
|
320
|
+
|
|
321
|
+
Unknown exceptions are intentionally converted to:
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"error": {
|
|
326
|
+
"status": 500,
|
|
327
|
+
"code": "internal_server_error",
|
|
328
|
+
"message": "Internal Server Error"
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
BCP does not expose the original unknown exception message in that fallback response. Log internal errors on the server instead of returning stack traces, database errors, credentials or other implementation details to clients.
|
|
334
|
+
|
|
335
|
+
## Rate limiting
|
|
336
|
+
|
|
337
|
+
`tooManyRequests()` supports `Retry-After`:
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
import {
|
|
341
|
+
tooManyRequests,
|
|
342
|
+
} from "bcp/error";
|
|
343
|
+
|
|
344
|
+
return tooManyRequests(
|
|
345
|
+
"Try again later",
|
|
346
|
+
{
|
|
347
|
+
retryAfter:
|
|
348
|
+
60,
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Response:
|
|
354
|
+
|
|
355
|
+
```http
|
|
356
|
+
HTTP/1.1 429 Too Many Requests
|
|
357
|
+
Retry-After: 60
|
|
358
|
+
Cache-Control: no-store
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Generic status responses
|
|
362
|
+
|
|
363
|
+
Use `errorResponse()` when no convenience helper exists:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
import {
|
|
367
|
+
errorResponse,
|
|
368
|
+
} from "bcp/error";
|
|
369
|
+
|
|
370
|
+
return errorResponse(
|
|
371
|
+
405,
|
|
372
|
+
{
|
|
373
|
+
code:
|
|
374
|
+
"method_not_allowed",
|
|
375
|
+
message:
|
|
376
|
+
"Only POST is allowed",
|
|
377
|
+
headers: {
|
|
378
|
+
Allow:
|
|
379
|
+
"POST",
|
|
380
|
+
},
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Common built-in helpers in 0.1.20 are:
|
|
386
|
+
|
|
387
|
+
```text
|
|
388
|
+
badRequest() 400
|
|
389
|
+
unauthorized() 401
|
|
390
|
+
forbidden() 403
|
|
391
|
+
notFoundResponse() 404
|
|
392
|
+
conflict() 409
|
|
393
|
+
unprocessableEntity() 422
|
|
394
|
+
tooManyRequests() 429
|
|
395
|
+
internalServerError() 500
|
|
396
|
+
serviceUnavailable() 503
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Use `errorResponse(status, options)` for other `4xx` and `5xx` statuses.
|
|
400
|
+
|
|
401
|
+
## Validation integration
|
|
402
|
+
|
|
403
|
+
A practical API pattern combines `bcp/validation` with `bcp/error`:
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
import {
|
|
407
|
+
unprocessableEntity,
|
|
408
|
+
} from "bcp/error";
|
|
409
|
+
import {
|
|
410
|
+
v,
|
|
411
|
+
} from "bcp/validation";
|
|
412
|
+
|
|
413
|
+
const schema =
|
|
414
|
+
v.object({
|
|
415
|
+
email:
|
|
416
|
+
v.string({
|
|
417
|
+
email: true,
|
|
418
|
+
}),
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
export async function POST(
|
|
422
|
+
request: Request
|
|
423
|
+
) {
|
|
424
|
+
const result =
|
|
425
|
+
schema.safeParse(
|
|
426
|
+
await request.json()
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
if (!result.success) {
|
|
430
|
+
return unprocessableEntity(
|
|
431
|
+
"Validation failed",
|
|
432
|
+
{
|
|
433
|
+
fieldErrors:
|
|
434
|
+
result.fieldErrors,
|
|
435
|
+
formErrors:
|
|
436
|
+
result.formErrors,
|
|
437
|
+
}
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return Response.json(
|
|
442
|
+
result.data
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Error handling responsibilities
|
|
448
|
+
|
|
449
|
+
BCP separates three concerns:
|
|
450
|
+
|
|
451
|
+
```text
|
|
452
|
+
notFound() -> page 404 UI convention
|
|
453
|
+
bcp/validation -> typed input validation
|
|
454
|
+
bcp/error -> HTTP/application error responses
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Unexpected framework or programming exceptions remain server errors and should be logged. Do not convert every exception to a successful action data object; use the correct HTTP status when the failure represents an HTTP request outcome.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Hydration and deterministic rendering
|
|
2
|
+
|
|
3
|
+
BCP uses server-side rendering for the initial HTML and React hydration in the browser. The server-rendered tree and the first client-rendered tree must produce the same element attributes and text.
|
|
4
|
+
|
|
5
|
+
## Windows CRLF support
|
|
6
|
+
|
|
7
|
+
BCP Framework 0.1.20 normalizes application source line endings before the development React Refresh/Babel transform.
|
|
8
|
+
|
|
9
|
+
This fixes a Windows-specific hydration warning that could appear when a JSX attribute used a multiline template literal, for example:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
export default function Page() {
|
|
13
|
+
return (
|
|
14
|
+
<main
|
|
15
|
+
className={`
|
|
16
|
+
min-h-screen
|
|
17
|
+
bg-white
|
|
18
|
+
text-slate-950
|
|
19
|
+
`}
|
|
20
|
+
>
|
|
21
|
+
Hello
|
|
22
|
+
</main>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
On a CRLF checkout, the development client transform could previously preserve carriage-return characters differently from the SSR transform. React then compared two visually equivalent class lists whose underlying strings were different and reported a hydration mismatch.
|
|
28
|
+
|
|
29
|
+
BCP now normalizes both `CRLF` (`\r\n`) and standalone `CR` (`\r`) source line endings to `LF` (`\n`) before Babel processes development application modules. Developers do not need to rewrite multiline `className` values as one line to work around this framework issue.
|
|
30
|
+
|
|
31
|
+
## What BCP fixes automatically
|
|
32
|
+
|
|
33
|
+
The line-ending fix addresses deterministic source transformation. It does not hide genuine hydration differences caused by application behavior.
|
|
34
|
+
|
|
35
|
+
BCP applications should still avoid producing different initial values on the server and client from code such as:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
const value = Date.now();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
const value = Math.random();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
or:
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
const browserOnly =
|
|
49
|
+
typeof window !== "undefined";
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
when those values directly change the initial rendered markup.
|
|
53
|
+
|
|
54
|
+
Other common application-level causes include:
|
|
55
|
+
|
|
56
|
+
- locale-dependent formatting that differs between server and browser,
|
|
57
|
+
- data that changes between SSR and hydration without a serialized snapshot,
|
|
58
|
+
- invalid HTML nesting,
|
|
59
|
+
- browser extensions that modify the DOM before React hydrates it.
|
|
60
|
+
|
|
61
|
+
## Recommended pattern for browser-only state
|
|
62
|
+
|
|
63
|
+
If a value genuinely depends on the browser, initialize the server/client render deterministically and update it after mount.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
"use client";
|
|
67
|
+
|
|
68
|
+
import {
|
|
69
|
+
useEffect,
|
|
70
|
+
useState,
|
|
71
|
+
} from "react";
|
|
72
|
+
|
|
73
|
+
export default function BrowserValue() {
|
|
74
|
+
const [ready, setReady] =
|
|
75
|
+
useState(false);
|
|
76
|
+
|
|
77
|
+
useEffect(
|
|
78
|
+
() => {
|
|
79
|
+
setReady(true);
|
|
80
|
+
},
|
|
81
|
+
[]
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<span>
|
|
86
|
+
{ready
|
|
87
|
+
? "Browser ready"
|
|
88
|
+
: "Loading"}
|
|
89
|
+
</span>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Development and production
|
|
95
|
+
|
|
96
|
+
The Windows CRLF issue was specific to the development React Refresh/Babel path. Production client compilation uses the production esbuild pipeline. The 0.1.20 fix makes development source handling deterministic before Babel so development hydration matches the SSR semantics.
|
|
97
|
+
|
|
98
|
+
## Troubleshooting
|
|
99
|
+
|
|
100
|
+
If React still reports a hydration mismatch after upgrading to a BCP release containing this fix:
|
|
101
|
+
|
|
102
|
+
1. stop the BCP dev server,
|
|
103
|
+
2. remove `.bcp-framework/`,
|
|
104
|
+
3. start `bcp dev` again,
|
|
105
|
+
4. inspect the first differing server/client value in the React hydration warning,
|
|
106
|
+
5. check for request-time, random, locale, browser-only or externally changing values.
|
|
107
|
+
|
|
108
|
+
Do not use `suppressHydrationWarning` as a general fix. It should only be used when a difference is intentional and understood.
|
|
109
|
+
|
|
110
|
+
## Regression coverage
|
|
111
|
+
|
|
112
|
+
The framework test suite contains a Windows-style CRLF fixture with a multiline JSX `className`. The development client bundle is required to normalize source line endings before the React Refresh Babel transform.
|