@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 +84 -7
- package/docs/error-handling.md +457 -0
- package/docs/hydration.md +112 -0
- package/docs/releases/0.1.19.md +106 -0
- package/docs/releases/0.1.20.md +124 -0
- package/docs/validation.md +342 -0
- package/package.json +9 -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/packages/client/src/validation.ts +1118 -0
|
@@ -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.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# BCP Framework 0.1.19
|
|
2
|
+
|
|
3
|
+
BCP Framework 0.1.19 adds the first built-in Validation System.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- new `bcp/validation` public entrypoint
|
|
8
|
+
- universal validation API for server and client code
|
|
9
|
+
- `v.object`, `v.string`, `v.number`, `v.boolean`, `v.literal`, `v.enum`, `v.array` and `v.union`
|
|
10
|
+
- `.optional()`, `.nullable()` and `.refine()` composition
|
|
11
|
+
- `safeParse()` and throwing `parse()` modes
|
|
12
|
+
- typed validation output through `InferValidator`
|
|
13
|
+
- `validateFormData()` for BCP form actions
|
|
14
|
+
- repeated FormData keys become arrays
|
|
15
|
+
- string/number/boolean coercion options for HTML forms
|
|
16
|
+
- field errors flattened into dot paths
|
|
17
|
+
- serializable validation failure objects
|
|
18
|
+
- unknown object fields are stripped by default
|
|
19
|
+
- no new external runtime dependency
|
|
20
|
+
|
|
21
|
+
## Form action example
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import {
|
|
25
|
+
validateFormData,
|
|
26
|
+
v,
|
|
27
|
+
} from "bcp/validation";
|
|
28
|
+
|
|
29
|
+
const schema =
|
|
30
|
+
v.object({
|
|
31
|
+
email:
|
|
32
|
+
v.string({
|
|
33
|
+
trim: true,
|
|
34
|
+
email: true,
|
|
35
|
+
}),
|
|
36
|
+
age:
|
|
37
|
+
v.number({
|
|
38
|
+
coerce: true,
|
|
39
|
+
min: 18,
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export async function saveUser(
|
|
44
|
+
formData: FormData
|
|
45
|
+
) {
|
|
46
|
+
const result =
|
|
47
|
+
validateFormData(
|
|
48
|
+
schema,
|
|
49
|
+
formData
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
success: true,
|
|
58
|
+
user: result.data,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API example
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const result =
|
|
67
|
+
schema.safeParse(
|
|
68
|
+
await request.json()
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (!result.success) {
|
|
72
|
+
return Response.json(
|
|
73
|
+
result,
|
|
74
|
+
{
|
|
75
|
+
status: 422,
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Error shape
|
|
82
|
+
|
|
83
|
+
Validation failures use the same shape in form actions, API routes and direct validation calls:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
{
|
|
87
|
+
success: false,
|
|
88
|
+
issues: [
|
|
89
|
+
{
|
|
90
|
+
path: ["email"],
|
|
91
|
+
message:
|
|
92
|
+
"Must be a valid email address.",
|
|
93
|
+
code:
|
|
94
|
+
"invalid_email",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
fieldErrors: {
|
|
98
|
+
email: [
|
|
99
|
+
"Must be a valid email address.",
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
formErrors: [],
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
See `docs/validation.md` for the full API.
|
|
@@ -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.
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# Validation
|
|
2
|
+
|
|
3
|
+
BCP Framework 0.1.19 adds a built-in validation system through `bcp/validation`.
|
|
4
|
+
|
|
5
|
+
The module is universal and can be used in server actions, API routes, loaders and client-side code.
|
|
6
|
+
|
|
7
|
+
## Basic schema
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
v,
|
|
12
|
+
} from "bcp/validation";
|
|
13
|
+
|
|
14
|
+
const userSchema =
|
|
15
|
+
v.object({
|
|
16
|
+
name:
|
|
17
|
+
v.string({
|
|
18
|
+
trim: true,
|
|
19
|
+
minLength: 2,
|
|
20
|
+
}),
|
|
21
|
+
email:
|
|
22
|
+
v.string({
|
|
23
|
+
trim: true,
|
|
24
|
+
email: true,
|
|
25
|
+
}),
|
|
26
|
+
age:
|
|
27
|
+
v.number({
|
|
28
|
+
coerce: true,
|
|
29
|
+
integer: true,
|
|
30
|
+
min: 18,
|
|
31
|
+
}),
|
|
32
|
+
role:
|
|
33
|
+
v.enum([
|
|
34
|
+
"admin",
|
|
35
|
+
"user",
|
|
36
|
+
]),
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Safe parsing
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const result =
|
|
44
|
+
userSchema.safeParse(input);
|
|
45
|
+
|
|
46
|
+
if (!result.success) {
|
|
47
|
+
console.log(
|
|
48
|
+
result.fieldErrors
|
|
49
|
+
);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log(result.data);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Successful results have:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
{
|
|
60
|
+
success: true,
|
|
61
|
+
data: value,
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Failures have:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
{
|
|
69
|
+
success: false,
|
|
70
|
+
issues: [...],
|
|
71
|
+
fieldErrors: {
|
|
72
|
+
email: [
|
|
73
|
+
"Must be a valid email address.",
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
formErrors: [],
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Nested field paths are flattened with dot notation, for example `profile.email` and `items.0.name`.
|
|
81
|
+
|
|
82
|
+
## Form actions
|
|
83
|
+
|
|
84
|
+
`validateFormData()` converts repeated FormData keys into arrays and validates the resulting object.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import {
|
|
88
|
+
validateFormData,
|
|
89
|
+
v,
|
|
90
|
+
} from "bcp/validation";
|
|
91
|
+
|
|
92
|
+
const schema =
|
|
93
|
+
v.object({
|
|
94
|
+
email:
|
|
95
|
+
v.string({
|
|
96
|
+
trim: true,
|
|
97
|
+
email: true,
|
|
98
|
+
}),
|
|
99
|
+
age:
|
|
100
|
+
v.number({
|
|
101
|
+
coerce: true,
|
|
102
|
+
min: 18,
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
export async function saveUser(
|
|
107
|
+
formData: FormData
|
|
108
|
+
) {
|
|
109
|
+
const result =
|
|
110
|
+
validateFormData(
|
|
111
|
+
schema,
|
|
112
|
+
formData
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (!result.success) {
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// result.data is typed and validated.
|
|
120
|
+
// await db.execute(...)
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
success: true,
|
|
124
|
+
user: result.data,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The failure object is serializable and can be returned from a BCP form action directly.
|
|
130
|
+
|
|
131
|
+
## API routes
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import {
|
|
135
|
+
v,
|
|
136
|
+
} from "bcp/validation";
|
|
137
|
+
|
|
138
|
+
const schema =
|
|
139
|
+
v.object({
|
|
140
|
+
email:
|
|
141
|
+
v.string({
|
|
142
|
+
email: true,
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
export async function POST(
|
|
147
|
+
request: Request
|
|
148
|
+
) {
|
|
149
|
+
const input =
|
|
150
|
+
await request.json();
|
|
151
|
+
const result =
|
|
152
|
+
schema.safeParse(
|
|
153
|
+
input
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (!result.success) {
|
|
157
|
+
return Response.json(
|
|
158
|
+
result,
|
|
159
|
+
{
|
|
160
|
+
status: 422,
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return Response.json({
|
|
166
|
+
user: result.data,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Throwing parse
|
|
172
|
+
|
|
173
|
+
Use `parse()` when invalid input should throw a `ValidationError`.
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import {
|
|
177
|
+
ValidationError,
|
|
178
|
+
parse,
|
|
179
|
+
} from "bcp/validation";
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const user =
|
|
183
|
+
parse(
|
|
184
|
+
userSchema,
|
|
185
|
+
input
|
|
186
|
+
);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (
|
|
189
|
+
error instanceof
|
|
190
|
+
ValidationError
|
|
191
|
+
) {
|
|
192
|
+
console.log(
|
|
193
|
+
error.fieldErrors
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Available validators
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
v.string()
|
|
203
|
+
v.number()
|
|
204
|
+
v.boolean()
|
|
205
|
+
v.literal("active")
|
|
206
|
+
v.enum(["admin", "user"])
|
|
207
|
+
v.array(v.string())
|
|
208
|
+
v.object({ ... })
|
|
209
|
+
v.union([ ... ])
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Optional and nullable values:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
v.string().optional()
|
|
216
|
+
v.string().nullable()
|
|
217
|
+
|
|
218
|
+
v.optional(v.string())
|
|
219
|
+
v.nullable(v.string())
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## String validation
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
v.string({
|
|
226
|
+
trim: true,
|
|
227
|
+
minLength: 2,
|
|
228
|
+
maxLength: 100,
|
|
229
|
+
email: true,
|
|
230
|
+
pattern: /^[A-Z]/,
|
|
231
|
+
})
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Number validation
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
v.number({
|
|
238
|
+
coerce: true,
|
|
239
|
+
integer: true,
|
|
240
|
+
min: 1,
|
|
241
|
+
max: 100,
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`coerce: true` is useful for HTML forms because FormData values are strings.
|
|
246
|
+
|
|
247
|
+
## Boolean validation
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
v.boolean({
|
|
251
|
+
coerce: true,
|
|
252
|
+
})
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Boolean coercion recognizes common form values including `true`, `false`, `1`, `0`, `on`, `off`, `yes` and `no`.
|
|
256
|
+
|
|
257
|
+
## Custom rules
|
|
258
|
+
|
|
259
|
+
Use `refine()` for application-specific rules.
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
const password =
|
|
263
|
+
v.string({
|
|
264
|
+
minLength: 8,
|
|
265
|
+
}).refine(
|
|
266
|
+
(value) =>
|
|
267
|
+
/\d/.test(value),
|
|
268
|
+
"Password must contain a number.",
|
|
269
|
+
"password_number"
|
|
270
|
+
);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Unknown object fields
|
|
274
|
+
|
|
275
|
+
Object validation strips fields that are not declared in the schema by default.
|
|
276
|
+
|
|
277
|
+
This is useful for API and form input because untrusted extra fields do not automatically pass into database writes.
|
|
278
|
+
|
|
279
|
+
To preserve unknown fields explicitly:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
v.object(
|
|
283
|
+
{
|
|
284
|
+
name: v.string(),
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
allowUnknown: true,
|
|
288
|
+
}
|
|
289
|
+
)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Validation paths and errors
|
|
293
|
+
|
|
294
|
+
Each issue contains:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
{
|
|
298
|
+
path: [
|
|
299
|
+
"profile",
|
|
300
|
+
"email",
|
|
301
|
+
],
|
|
302
|
+
message:
|
|
303
|
+
"Must be a valid email address.",
|
|
304
|
+
code:
|
|
305
|
+
"invalid_email",
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Use `getFieldError()` when only the first message for a field is needed.
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
import {
|
|
313
|
+
getFieldError,
|
|
314
|
+
} from "bcp/validation";
|
|
315
|
+
|
|
316
|
+
const emailError =
|
|
317
|
+
getFieldError(
|
|
318
|
+
result,
|
|
319
|
+
"email"
|
|
320
|
+
);
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Type inference
|
|
324
|
+
|
|
325
|
+
Use `InferValidator` when application code needs the TypeScript output type of a schema.
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
import type {
|
|
329
|
+
InferValidator,
|
|
330
|
+
} from "bcp/validation";
|
|
331
|
+
|
|
332
|
+
type UserInput =
|
|
333
|
+
InferValidator<
|
|
334
|
+
typeof userSchema
|
|
335
|
+
>;
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## External schema libraries
|
|
339
|
+
|
|
340
|
+
BCP 0.1.19 does not require Zod, Valibot or another validation dependency. The built-in API keeps framework validation dependency-free.
|
|
341
|
+
|
|
342
|
+
Adapters for external schema libraries can be added in future releases without changing the validation result model used by actions and API routes.
|
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",
|
|
@@ -43,6 +43,14 @@
|
|
|
43
43
|
"types": "./packages/client/src/config.ts",
|
|
44
44
|
"default": "./packages/client/src/config.ts"
|
|
45
45
|
},
|
|
46
|
+
"./validation": {
|
|
47
|
+
"types": "./packages/client/src/validation.ts",
|
|
48
|
+
"default": "./packages/client/src/validation.ts"
|
|
49
|
+
},
|
|
50
|
+
"./error": {
|
|
51
|
+
"types": "./packages/client/src/http-error.ts",
|
|
52
|
+
"default": "./packages/client/src/http-error.ts"
|
|
53
|
+
},
|
|
46
54
|
"./database": {
|
|
47
55
|
"types": "./packages/client/src/database.ts",
|
|
48
56
|
"browser": "./packages/client/src/server-only.browser.mjs",
|