@chidchanun/bcp 0.1.18 → 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 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.18`
7
+ > Documentation target: BCP Framework `0.1.20`
8
8
 
9
9
  ## Purpose
10
10
 
@@ -40,6 +40,8 @@ The website should not invent API behavior that is not documented or tested in t
40
40
  | `/docs/route-guards` | `route-guards.md` | Scoped page authorization |
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
+ | `/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 |
43
45
 
44
46
  ### Authentication
45
47
 
@@ -61,6 +63,7 @@ The website should not invent API behavior that is not documented or tested in t
61
63
  | Website route | Source file | Description |
62
64
  | --- | --- | --- |
63
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 |
64
67
  | `/docs/caching` | `caching.md` | Cache and revalidation |
65
68
  | `/docs/security` | `security.md` | Framework security defaults |
66
69
  | `/docs/development-logging` | `development-logging.md` | Development request logging |
@@ -72,9 +75,9 @@ Use the files under `docs/releases/` for release pages.
72
75
  Recommended route format:
73
76
 
74
77
  ```text
75
- /releases/0.1.16
76
- /releases/0.1.17
77
78
  /releases/0.1.18
79
+ /releases/0.1.19
80
+ /releases/0.1.20
78
81
  ```
79
82
 
80
83
  The newest release should also be available from `/releases`.
@@ -94,6 +97,7 @@ Recommended feature groups:
94
97
  - Loading and error UI
95
98
  - API routes
96
99
  - Client islands / partial hydration
100
+ - Deterministic development hydration across LF/CRLF source checkouts
97
101
 
98
102
  ### Server data and mutations
99
103
 
@@ -102,6 +106,12 @@ Recommended feature groups:
102
106
  - Form actions
103
107
  - Request-scoped server APIs
104
108
  - Cookies and redirects
109
+ - `bcp/validation`
110
+ - Typed form/API validation
111
+ - Field and form error output
112
+ - `bcp/error`
113
+ - Structured HTTP error responses
114
+ - Safe unknown-error fallback responses
105
115
 
106
116
  ### Database
107
117
 
@@ -153,7 +163,8 @@ Suggested content, not a required exact copy:
153
163
  ```text
154
164
  BCP Framework
155
165
  A React full-stack framework for building server-rendered applications with routing,
156
- data loading, server mutations, authentication, databases, middleware and production tooling.
166
+ data loading, server mutations, authentication, databases, validation, structured errors,
167
+ middleware and production tooling.
157
168
  ```
158
169
 
159
170
  Primary actions:
@@ -183,6 +194,8 @@ Core
183
194
  Server Data Loaders
184
195
  Route Guards
185
196
  Form Actions
197
+ Validation
198
+ Error Handling
186
199
 
187
200
  Authentication
188
201
  Authentication Core
@@ -195,6 +208,7 @@ Database
195
208
 
196
209
  Runtime
197
210
  Middleware
211
+ Hydration
198
212
  Caching
199
213
  Security
200
214
 
@@ -203,9 +217,9 @@ Production
203
217
  Development Logging
204
218
 
205
219
  Releases
220
+ 0.1.20
221
+ 0.1.19
206
222
  0.1.18
207
- 0.1.17
208
- 0.1.16
209
223
  ```
210
224
 
211
225
  ## Suggested documentation page layout
@@ -249,6 +263,16 @@ import {
249
263
  db,
250
264
  } from "bcp/database";
251
265
 
266
+ import {
267
+ v,
268
+ validateFormData,
269
+ } from "bcp/validation";
270
+
271
+ import {
272
+ badRequest,
273
+ unauthorized,
274
+ } from "bcp/error";
275
+
252
276
  import {
253
277
  type MiddlewarePipelineHandler,
254
278
  } from "bcp/middleware";
@@ -297,7 +321,7 @@ npx create-bcp-app@latest my-app
297
321
 
298
322
  ## Middleware v2 documentation priority
299
323
 
300
- BCP 0.1.18 introduces a meaningful middleware architecture change, so the `bcp-docs` website should make the distinction between v1 and v2 clear.
324
+ BCP 0.1.18 introduced a meaningful middleware architecture change, so the `bcp-docs` website should make the distinction between v1 and v2 clear.
301
325
 
302
326
  The middleware page should prominently explain:
303
327
 
@@ -310,6 +334,59 @@ Existing v1 code remains supported, so migration documentation should describe v
310
334
 
311
335
  See `middleware.md` for the full API and examples.
312
336
 
337
+ ## Validation documentation priority
338
+
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.
340
+
341
+ Important concepts to surface:
342
+
343
+ - `v.object`, `v.string`, `v.number`, `v.boolean`, `v.array`
344
+ - FormData coercion with `validateFormData()`
345
+ - `success/data` versus `issues/fieldErrors/formErrors`
346
+ - object fields are stripped unless `allowUnknown: true`
347
+ - `refine()` for custom application rules
348
+ - no external validation dependency is required
349
+
350
+ See `validation.md` for the complete API and examples.
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
+
313
390
  ## Release workflow for documentation
314
391
 
315
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.