@chidchanun/bcp 0.1.19 → 0.1.21
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/README.md +35 -4
- package/docs/README.md +70 -7
- package/docs/error-handling.md +457 -0
- package/docs/hydration.md +190 -0
- package/docs/releases/0.1.20.md +124 -0
- package/docs/releases/0.1.21.md +92 -0
- package/package.json +5 -1
- package/packages/bundler/src/index.ts +46 -33
- package/packages/client/src/http-error.ts +516 -0
- package/packages/client/src/server.ts +22 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# BCP Framework
|
|
2
2
|
|
|
3
|
-
BCP Framework is a React full-stack framework with file-based routing, SSR, client navigation, API routes, middleware, metadata, client islands, cache/revalidation, security defaults and standalone production builds.
|
|
3
|
+
BCP Framework is a React full-stack framework with file-based routing, SSR, client navigation, API routes, middleware, metadata, client islands, cache/revalidation, validation, structured errors, authentication, database primitives, security defaults and standalone production builds.
|
|
4
4
|
|
|
5
|
-
> Current release target: `0.1.
|
|
5
|
+
> Current release target: `0.1.21`. BCP is still pre-1.0 and validates each release candidate before the manual npm publish step.
|
|
6
6
|
|
|
7
7
|
## Quick start
|
|
8
8
|
|
|
@@ -86,6 +86,30 @@ See [Application Modules](docs/application-modules.md) for the complete boundary
|
|
|
86
86
|
|
|
87
87
|
Generated Tailwind projects compile to `public/bcp.css`. BCP 0.1.6 inlines that stylesheet into SSR HTML when it is 8 KiB or smaller, removing the stylesheet request from the initial render-critical path. Larger stylesheets remain external so the browser can cache them normally. If the application's Content Security Policy does not allow inline styles, BCP automatically keeps the external stylesheet link.
|
|
88
88
|
|
|
89
|
+
## Development hydration parity
|
|
90
|
+
|
|
91
|
+
BCP 0.1.21 fixes a development-only hydration mismatch where the SSR transform and the React Refresh client transform could assign different semantic values to the same multiline JSX attribute.
|
|
92
|
+
|
|
93
|
+
For example, this is supported application code:
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
<div
|
|
97
|
+
className="
|
|
98
|
+
min-h-screen
|
|
99
|
+
bg-white
|
|
100
|
+
text-slate-950
|
|
101
|
+
"
|
|
102
|
+
/>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
BCP 0.1.20 already normalized Windows `CRLF` and standalone `CR` source line endings to `LF`, but Babel's JSX transform could still collapse the multiline quoted attribute to a single-space-separated string while SSR preserved the original line breaks and indentation.
|
|
106
|
+
|
|
107
|
+
In 0.1.21, Babel remains responsible for TypeScript stripping and React Refresh registration, but JSX is preserved until esbuild compiles it with the development JSX runtime. This keeps static JSX attribute semantics aligned between SSR and the development client bundle.
|
|
108
|
+
|
|
109
|
+
Applications should not need to rewrite multiline classes to one line or use `suppressHydrationWarning` to work around framework transform differences. Genuine runtime mismatches caused by values such as `Date.now()`, `Math.random()`, browser-only initial state, locale differences or changing external data still need to be fixed in application code.
|
|
110
|
+
|
|
111
|
+
See [Hydration and deterministic rendering](docs/hydration.md) for the transform pipeline and troubleshooting guidance.
|
|
112
|
+
|
|
89
113
|
## Commands
|
|
90
114
|
|
|
91
115
|
```bash
|
|
@@ -117,7 +141,7 @@ bcp update --dry-run
|
|
|
117
141
|
Or select a published version/dist-tag explicitly:
|
|
118
142
|
|
|
119
143
|
```bash
|
|
120
|
-
bcp update 0.1.
|
|
144
|
+
bcp update 0.1.21
|
|
121
145
|
bcp update next
|
|
122
146
|
```
|
|
123
147
|
|
|
@@ -777,12 +801,19 @@ No real npm publish command is run automatically by the repository.
|
|
|
777
801
|
- [Server Data Loaders](docs/server-data-loaders.md)
|
|
778
802
|
- [Protected Route Guards](docs/route-guards.md)
|
|
779
803
|
- [Form Actions and Server Mutations](docs/form-actions.md)
|
|
804
|
+
- [Validation](docs/validation.md)
|
|
805
|
+
- [Error Handling](docs/error-handling.md)
|
|
806
|
+
- [Authentication](docs/authentication.md)
|
|
807
|
+
- [Auth Route Guards](docs/auth-route-guards.md)
|
|
780
808
|
- [JWT Cookie Sessions](docs/session-auth.md)
|
|
809
|
+
- [Database](docs/database.md)
|
|
810
|
+
- [Database Migrations](docs/database-migrations.md)
|
|
811
|
+
- [Middleware](docs/middleware.md)
|
|
812
|
+
- [Hydration and deterministic rendering](docs/hydration.md)
|
|
781
813
|
- [Updating BCP Framework](docs/updating.md)
|
|
782
814
|
- [Routing](docs/routing.md)
|
|
783
815
|
- [Configuration](docs/configuration.md)
|
|
784
816
|
- [Caching](docs/caching.md)
|
|
785
|
-
- [Middleware](docs/middleware.md)
|
|
786
817
|
- [Security](docs/security.md)
|
|
787
818
|
- [Deployment](docs/deployment.md)
|
|
788
819
|
- [Releasing](docs/releasing.md)
|
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.21`
|
|
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 transform parity, deterministic rendering and Windows line endings |
|
|
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
|
-
/releases/0.1.18
|
|
78
78
|
/releases/0.1.19
|
|
79
|
+
/releases/0.1.20
|
|
80
|
+
/releases/0.1.21
|
|
79
81
|
```
|
|
80
82
|
|
|
81
83
|
The newest release should also be available from `/releases`.
|
|
@@ -95,6 +97,8 @@ Recommended feature groups:
|
|
|
95
97
|
- Loading and error UI
|
|
96
98
|
- API routes
|
|
97
99
|
- Client islands / partial hydration
|
|
100
|
+
- Deterministic development hydration across server/client transform pipelines
|
|
101
|
+
- LF/CRLF-safe development source handling
|
|
98
102
|
|
|
99
103
|
### Server data and mutations
|
|
100
104
|
|
|
@@ -106,6 +110,9 @@ Recommended feature groups:
|
|
|
106
110
|
- `bcp/validation`
|
|
107
111
|
- Typed form/API validation
|
|
108
112
|
- Field and form error output
|
|
113
|
+
- `bcp/error`
|
|
114
|
+
- Structured HTTP error responses
|
|
115
|
+
- Safe unknown-error fallback responses
|
|
109
116
|
|
|
110
117
|
### Database
|
|
111
118
|
|
|
@@ -157,7 +164,8 @@ Suggested content, not a required exact copy:
|
|
|
157
164
|
```text
|
|
158
165
|
BCP Framework
|
|
159
166
|
A React full-stack framework for building server-rendered applications with routing,
|
|
160
|
-
data loading, server mutations, authentication, databases, validation,
|
|
167
|
+
data loading, server mutations, authentication, databases, validation, structured errors,
|
|
168
|
+
middleware and production tooling.
|
|
161
169
|
```
|
|
162
170
|
|
|
163
171
|
Primary actions:
|
|
@@ -188,6 +196,7 @@ Core
|
|
|
188
196
|
Route Guards
|
|
189
197
|
Form Actions
|
|
190
198
|
Validation
|
|
199
|
+
Error Handling
|
|
191
200
|
|
|
192
201
|
Authentication
|
|
193
202
|
Authentication Core
|
|
@@ -200,6 +209,7 @@ Database
|
|
|
200
209
|
|
|
201
210
|
Runtime
|
|
202
211
|
Middleware
|
|
212
|
+
Hydration
|
|
203
213
|
Caching
|
|
204
214
|
Security
|
|
205
215
|
|
|
@@ -208,9 +218,9 @@ Production
|
|
|
208
218
|
Development Logging
|
|
209
219
|
|
|
210
220
|
Releases
|
|
221
|
+
0.1.21
|
|
222
|
+
0.1.20
|
|
211
223
|
0.1.19
|
|
212
|
-
0.1.18
|
|
213
|
-
0.1.17
|
|
214
224
|
```
|
|
215
225
|
|
|
216
226
|
## Suggested documentation page layout
|
|
@@ -259,6 +269,11 @@ import {
|
|
|
259
269
|
validateFormData,
|
|
260
270
|
} from "bcp/validation";
|
|
261
271
|
|
|
272
|
+
import {
|
|
273
|
+
badRequest,
|
|
274
|
+
unauthorized,
|
|
275
|
+
} from "bcp/error";
|
|
276
|
+
|
|
262
277
|
import {
|
|
263
278
|
type MiddlewarePipelineHandler,
|
|
264
279
|
} from "bcp/middleware";
|
|
@@ -322,7 +337,7 @@ See `middleware.md` for the full API and examples.
|
|
|
322
337
|
|
|
323
338
|
## Validation documentation priority
|
|
324
339
|
|
|
325
|
-
BCP 0.1.19
|
|
340
|
+
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
341
|
|
|
327
342
|
Important concepts to surface:
|
|
328
343
|
|
|
@@ -335,6 +350,54 @@ Important concepts to surface:
|
|
|
335
350
|
|
|
336
351
|
See `validation.md` for the complete API and examples.
|
|
337
352
|
|
|
353
|
+
## Error Handling documentation priority
|
|
354
|
+
|
|
355
|
+
BCP 0.1.20 introduced the public `bcp/error` entrypoint and a standard HTTP error envelope.
|
|
356
|
+
|
|
357
|
+
The docs website should clearly distinguish:
|
|
358
|
+
|
|
359
|
+
```text
|
|
360
|
+
notFound() = activate page 404 UI behavior
|
|
361
|
+
notFoundResponse() = return a JSON 404 HTTP response
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Important concepts to surface:
|
|
365
|
+
|
|
366
|
+
- `HttpError` and `createHttpError()`
|
|
367
|
+
- `errorResponse()` for any 4xx/5xx status
|
|
368
|
+
- `toErrorResponse()` for safe catch boundaries
|
|
369
|
+
- `badRequest()`, `unauthorized()`, `forbidden()`, `conflict()` and other convenience helpers
|
|
370
|
+
- standard `{ error: { status, code, message, details? } }` payload
|
|
371
|
+
- error responses default to `Cache-Control: no-store`
|
|
372
|
+
- unknown exceptions become a generic 500 response without exposing the original exception message
|
|
373
|
+
- normal field validation still belongs to `bcp/validation`
|
|
374
|
+
|
|
375
|
+
See `error-handling.md` for the complete API and examples.
|
|
376
|
+
|
|
377
|
+
## Hydration documentation priority
|
|
378
|
+
|
|
379
|
+
BCP 0.1.20 added line-ending normalization for the development React Refresh path. That removed the Windows `CRLF` versus `LF` mismatch, but a second issue remained when Babel compiled multiline quoted JSX attributes and collapsed their whitespace differently from SSR.
|
|
380
|
+
|
|
381
|
+
BCP 0.1.21 completes this fix by keeping Babel responsible for TypeScript removal and React Refresh instrumentation while leaving JSX untransformed. esbuild then performs the development JSX transform with the development JSX runtime enabled.
|
|
382
|
+
|
|
383
|
+
The docs website should make these points clear:
|
|
384
|
+
|
|
385
|
+
- multiline quoted JSX attributes are supported,
|
|
386
|
+
- multiline template-literal JSX expressions are supported,
|
|
387
|
+
- developers should not need to rewrite multiline `className` values as one-line strings to avoid framework hydration warnings,
|
|
388
|
+
- `CRLF` and standalone `CR` source line endings are normalized to `LF` before development instrumentation,
|
|
389
|
+
- React Refresh instrumentation must not change the semantic value of JSX attributes,
|
|
390
|
+
- 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,
|
|
391
|
+
- `suppressHydrationWarning` is not a general-purpose solution.
|
|
392
|
+
|
|
393
|
+
See `hydration.md` for the full explanation, transform pipeline and troubleshooting steps.
|
|
394
|
+
|
|
395
|
+
## Release roadmap adjustment
|
|
396
|
+
|
|
397
|
+
`0.1.21` is reserved for the hydration parity hotfix because `0.1.20` was already published before the remaining Babel JSX whitespace mismatch was isolated.
|
|
398
|
+
|
|
399
|
+
The previously planned Developer Tools release moves to `0.1.22`, and later roadmap items should shift forward accordingly unless they are regrouped into a larger milestone.
|
|
400
|
+
|
|
338
401
|
## Release workflow for documentation
|
|
339
402
|
|
|
340
403
|
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.
|