@chidchanun/bcp 0.1.23 → 0.1.24
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 +241 -640
- package/docs/README.md +113 -368
- package/docs/auth-route-guards.md +14 -0
- package/docs/developer-tools.md +45 -22
- package/docs/file-upload.md +280 -0
- package/docs/releases/0.1.24.md +166 -0
- package/package.json +1 -1
- package/packages/bundler/src/server-production-actions.ts +15 -0
- package/packages/bundler/src/server-production-guards.ts +15 -0
- package/packages/client/src/server.ts +16 -0
- package/packages/server/src/dev-route-graph.ts +91 -0
- package/packages/server/src/middleware-dev-server.ts +217 -9
- package/packages/server/src/upload.ts +556 -0
package/README.md
CHANGED
|
@@ -1,28 +1,53 @@
|
|
|
1
1
|
# BCP Framework
|
|
2
2
|
|
|
3
|
-
BCP Framework is a React full-stack framework with file-based routing, SSR,
|
|
3
|
+
BCP Framework is a React full-stack framework with file-based routing, SSR, SPA navigation, API routes, middleware, loaders, guards, form actions, authentication, MySQL primitives, validation, structured errors, logging, file uploads and standalone production builds.
|
|
4
4
|
|
|
5
|
-
> Current release target: `0.1.
|
|
5
|
+
> Current release target: `0.1.24`. BCP is pre-1.0 and each release candidate is validated before the manual npm publish step.
|
|
6
6
|
|
|
7
7
|
## Quick start
|
|
8
8
|
|
|
9
|
-
After the packages are published:
|
|
10
|
-
|
|
11
9
|
```bash
|
|
12
|
-
npx create-bcp-app my-app
|
|
10
|
+
npx create-bcp-app@latest my-app
|
|
13
11
|
cd my-app
|
|
14
12
|
npm run dev
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
Default development URL:
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
```text
|
|
18
|
+
http://localhost:3000
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## CLI
|
|
20
22
|
|
|
21
23
|
```bash
|
|
22
|
-
|
|
24
|
+
bcp dev
|
|
25
|
+
bcp routes
|
|
26
|
+
bcp build
|
|
27
|
+
bcp start
|
|
28
|
+
bcp doctor
|
|
29
|
+
bcp doctor --json
|
|
30
|
+
bcp inspect
|
|
31
|
+
bcp inspect --json
|
|
32
|
+
bcp update
|
|
33
|
+
bcp version
|
|
34
|
+
|
|
35
|
+
bcp db create create_users
|
|
36
|
+
bcp db migrate
|
|
37
|
+
bcp db status
|
|
38
|
+
bcp db rollback
|
|
23
39
|
```
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
Microsoft SQL Server also installs a Windows executable named `bcp.exe`. BCP Framework therefore also publishes the collision-free alias:
|
|
42
|
+
|
|
43
|
+
```powershell
|
|
44
|
+
bcp-framework doctor
|
|
45
|
+
bcp-framework inspect
|
|
46
|
+
bcp-framework dev
|
|
47
|
+
bcp-framework build
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Normal project npm scripts can continue using `bcp` because npm places `node_modules/.bin` first on the script PATH.
|
|
26
51
|
|
|
27
52
|
## Project structure
|
|
28
53
|
|
|
@@ -30,19 +55,17 @@ This creates package tarballs under `.package/artifacts` and verifies that `crea
|
|
|
30
55
|
app/
|
|
31
56
|
├─ layout.tsx
|
|
32
57
|
├─ page.tsx
|
|
33
|
-
├─ loading.tsx
|
|
34
|
-
├─ error.tsx
|
|
35
|
-
├─ not-found.tsx
|
|
36
58
|
├─ dashboard/
|
|
37
59
|
│ ├─ guard.ts
|
|
38
|
-
│ ├─ page.tsx
|
|
39
60
|
│ └─ users/
|
|
40
61
|
│ └─ [id]/
|
|
41
62
|
│ ├─ loader.ts
|
|
42
63
|
│ ├─ actions.ts
|
|
43
64
|
│ └─ page.tsx
|
|
44
65
|
└─ api/
|
|
45
|
-
|
|
66
|
+
├─ hello/
|
|
67
|
+
│ └─ route.ts
|
|
68
|
+
└─ upload/
|
|
46
69
|
└─ route.ts
|
|
47
70
|
|
|
48
71
|
lib/
|
|
@@ -52,146 +75,8 @@ package.json
|
|
|
52
75
|
tsconfig.json
|
|
53
76
|
```
|
|
54
77
|
|
|
55
|
-
## Application imports and boundaries
|
|
56
|
-
|
|
57
|
-
Generated applications include the project-root `@/` alias:
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
import {
|
|
61
|
-
db,
|
|
62
|
-
} from "@/lib/database";
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Modules that use React client hooks must declare `"use client"`:
|
|
66
|
-
|
|
67
|
-
```tsx
|
|
68
|
-
"use client";
|
|
69
|
-
|
|
70
|
-
import {
|
|
71
|
-
useState,
|
|
72
|
-
} from "react";
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Server-only modules can declare:
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
import "bcp/server-only";
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Database helpers generated by `create-bcp-app` include the server-only marker automatically. A server-only helper must not be imported from a hydrated page/client graph. Use an API route or a server-only route primitive such as `loader.ts`, `guard.ts` or `actions.ts`.
|
|
82
|
-
|
|
83
|
-
See [Application Modules](docs/application-modules.md) for the complete boundary model and examples.
|
|
84
|
-
|
|
85
|
-
## Tailwind and critical CSS
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
## Developer diagnostics
|
|
114
|
-
|
|
115
|
-
BCP 0.1.22 adds dedicated developer tooling for diagnosing application setup without starting the development server.
|
|
116
|
-
|
|
117
|
-
Run a project health check:
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
bcp doctor
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
The doctor checks the Node.js runtime, project structure, installed BCP/React packages, React and React DOM version parity, duplicate React package roots, environment/config loading, route conflicts and client/server boundaries. Blocking failures produce a non-zero process exit code.
|
|
124
|
-
|
|
125
|
-
Inspect the resolved project inputs BCP sees:
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
bcp inspect
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
This prints development env filenames, public environment variable names, resolved BCP configuration, dependency versions and discovered page/API routes.
|
|
132
|
-
|
|
133
|
-
Both commands support JSON output:
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
bcp doctor --json
|
|
137
|
-
bcp inspect --json
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
The duplicate React checks are especially useful when verifying local framework builds. Install packed `.tgz` artifacts for release testing instead of linking `.package/bcp` directly into another project, because a linked staging directory can make the application and SSR renderer resolve different React instances.
|
|
141
|
-
|
|
142
|
-
See [Developer Tools](docs/developer-tools.md) for the complete command reference.
|
|
143
|
-
|
|
144
|
-
## Commands
|
|
145
|
-
|
|
146
|
-
```bash
|
|
147
|
-
bcp dev
|
|
148
|
-
bcp routes
|
|
149
|
-
bcp build
|
|
150
|
-
bcp start
|
|
151
|
-
bcp doctor
|
|
152
|
-
bcp doctor --json
|
|
153
|
-
bcp inspect
|
|
154
|
-
bcp inspect --json
|
|
155
|
-
bcp update
|
|
156
|
-
bcp version
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
CLI server overrides are available with `--port` and `--hostname`.
|
|
160
|
-
|
|
161
|
-
## Updating an existing project
|
|
162
|
-
|
|
163
|
-
Once a project is on a BCP version that contains the updater, update to the current npm `latest` release with:
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
bcp update
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Preview an update without changing files:
|
|
170
|
-
|
|
171
|
-
```bash
|
|
172
|
-
bcp update --check
|
|
173
|
-
bcp update --dry-run
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
Or select a published version/dist-tag explicitly:
|
|
177
|
-
|
|
178
|
-
```bash
|
|
179
|
-
bcp update 0.1.22
|
|
180
|
-
bcp update next
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
Versions published before the updater do not recognize `bcp update`. Bootstrap the newest CLI once from those projects:
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
npx @chidchanun/bcp@latest update
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
The updater changes the framework dependency and package-manager lockfile, detects npm/pnpm/Yarn/Bun from the project lockfile, and restores package metadata if installation fails. It does not overwrite application source files, database schemas or authentication code. See [Updating BCP Framework](docs/updating.md) for the complete behavior and migration notes.
|
|
190
|
-
|
|
191
78
|
## Routing
|
|
192
79
|
|
|
193
|
-
BCP supports:
|
|
194
|
-
|
|
195
80
|
```text
|
|
196
81
|
app/page.tsx /
|
|
197
82
|
app/about/page.tsx /about
|
|
@@ -201,143 +86,25 @@ app/catalog/[[...slug]]/page.tsx /catalog and /catalog/*
|
|
|
201
86
|
app/(admin)/settings/page.tsx /settings
|
|
202
87
|
```
|
|
203
88
|
|
|
204
|
-
Static routes have priority over dynamic
|
|
205
|
-
|
|
206
|
-
## API routes
|
|
207
|
-
|
|
208
|
-
```ts
|
|
209
|
-
// app/api/hello/route.ts
|
|
210
|
-
export function GET() {
|
|
211
|
-
return Response.json({
|
|
212
|
-
message: "Hello",
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
Supported methods include GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS. HEAD falls back to GET when no explicit HEAD handler exists, and OPTIONS is generated automatically when appropriate.
|
|
89
|
+
Static routes have priority over dynamic and catch-all routes.
|
|
218
90
|
|
|
219
|
-
## Server
|
|
220
|
-
|
|
221
|
-
BCP 0.1.7 adds request-scoped server helpers through `bcp/server`. These helpers are server-only and are available from API handlers, server data loaders, route guards and form actions.
|
|
222
|
-
|
|
223
|
-
```ts
|
|
224
|
-
import {
|
|
225
|
-
bearerToken,
|
|
226
|
-
clientIp,
|
|
227
|
-
cookies,
|
|
228
|
-
json,
|
|
229
|
-
requestId,
|
|
230
|
-
requestMethod,
|
|
231
|
-
requestUrl,
|
|
232
|
-
} from "bcp/server";
|
|
233
|
-
|
|
234
|
-
export async function GET() {
|
|
235
|
-
const url =
|
|
236
|
-
await requestUrl();
|
|
237
|
-
|
|
238
|
-
return json({
|
|
239
|
-
pathname:
|
|
240
|
-
url.pathname,
|
|
241
|
-
method:
|
|
242
|
-
await requestMethod(),
|
|
243
|
-
requestId:
|
|
244
|
-
await requestId(),
|
|
245
|
-
bearerToken:
|
|
246
|
-
await bearerToken(),
|
|
247
|
-
clientIp:
|
|
248
|
-
await clientIp(),
|
|
249
|
-
session:
|
|
250
|
-
(
|
|
251
|
-
await cookies()
|
|
252
|
-
).get(
|
|
253
|
-
"session"
|
|
254
|
-
)?.value ?? null,
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
`requestId()` reuses a valid incoming `X-Request-Id` or generates one stable UUID for the request. `bearerToken()` extracts a Bearer credential without decoding or verifying it. `clientIp()` uses the direct socket address by default; applications behind a trusted reverse proxy can explicitly use `clientIp({ trustProxy: true })` to read validated `Forwarded`, `X-Forwarded-For` or `X-Real-IP` values.
|
|
260
|
-
|
|
261
|
-
Response cookies and redirects can be composed for login-style flows:
|
|
262
|
-
|
|
263
|
-
```ts
|
|
264
|
-
import {
|
|
265
|
-
cookies,
|
|
266
|
-
redirect,
|
|
267
|
-
} from "bcp/server";
|
|
268
|
-
|
|
269
|
-
export async function POST() {
|
|
270
|
-
const cookieStore =
|
|
271
|
-
await cookies();
|
|
272
|
-
|
|
273
|
-
cookieStore.set(
|
|
274
|
-
"session",
|
|
275
|
-
"session-token",
|
|
276
|
-
{
|
|
277
|
-
httpOnly: true,
|
|
278
|
-
secure: true,
|
|
279
|
-
sameSite: "lax",
|
|
280
|
-
path: "/",
|
|
281
|
-
maxAge: 60 * 60 * 12,
|
|
282
|
-
}
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
return redirect(
|
|
286
|
-
"/dashboard",
|
|
287
|
-
303
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
`redirect()` defaults to status `307`; supported statuses are `301`, `302`, `303`, `307` and `308`. Use `cookieStore.delete("session")` to expire a cookie. See [Server Request APIs](docs/server-request-apis.md) for proxy trust, URL handling, response helpers, request isolation, cookie options and boundary details.
|
|
91
|
+
## Server data
|
|
293
92
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
BCP 0.1.9 adds a server-only `loader.ts` convention next to `page.tsx`. The loader runs before SSR, receives route `params` plus `searchParams`, and can use `bcp/server` APIs, sessions and server-only database modules.
|
|
297
|
-
|
|
298
|
-
```text
|
|
299
|
-
app/users/[id]/
|
|
300
|
-
├─ loader.ts
|
|
301
|
-
└─ page.tsx
|
|
302
|
-
```
|
|
93
|
+
A route can load server-only data with `loader.ts`:
|
|
303
94
|
|
|
304
95
|
```ts
|
|
305
96
|
// app/users/[id]/loader.ts
|
|
306
|
-
import {
|
|
307
|
-
getSession,
|
|
308
|
-
redirect,
|
|
309
|
-
} from "bcp/server";
|
|
310
|
-
|
|
311
97
|
export async function loader({
|
|
312
98
|
params,
|
|
313
|
-
searchParams,
|
|
314
|
-
}: {
|
|
315
|
-
params: {
|
|
316
|
-
id: string;
|
|
317
|
-
};
|
|
318
|
-
searchParams: URLSearchParams;
|
|
319
99
|
}) {
|
|
320
|
-
const session =
|
|
321
|
-
await getSession();
|
|
322
|
-
|
|
323
|
-
if (!session) {
|
|
324
|
-
return redirect(
|
|
325
|
-
"/login",
|
|
326
|
-
303
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
100
|
return {
|
|
331
101
|
id:
|
|
332
102
|
params.id,
|
|
333
|
-
query:
|
|
334
|
-
searchParams.get("q"),
|
|
335
|
-
session,
|
|
336
103
|
};
|
|
337
104
|
}
|
|
338
105
|
```
|
|
339
106
|
|
|
340
|
-
|
|
107
|
+
The page consumes the serializable value with:
|
|
341
108
|
|
|
342
109
|
```tsx
|
|
343
110
|
"use client";
|
|
@@ -350,434 +117,272 @@ export default function UserPage() {
|
|
|
350
117
|
const data =
|
|
351
118
|
useLoaderData<{
|
|
352
119
|
id: string;
|
|
353
|
-
query: string | null;
|
|
354
120
|
}>();
|
|
355
121
|
|
|
356
|
-
return
|
|
357
|
-
<main>
|
|
358
|
-
User {data.id}
|
|
359
|
-
</main>
|
|
360
|
-
);
|
|
122
|
+
return <main>{data.id}</main>;
|
|
361
123
|
}
|
|
362
124
|
```
|
|
363
125
|
|
|
364
|
-
|
|
126
|
+
Protected route trees can use `guard.ts`, and route-owned mutations can use `actions.ts` with the public `<Form>` client API.
|
|
365
127
|
|
|
366
|
-
|
|
128
|
+
## Server request APIs
|
|
367
129
|
|
|
368
|
-
|
|
130
|
+
```ts
|
|
131
|
+
import {
|
|
132
|
+
bearerToken,
|
|
133
|
+
clientIp,
|
|
134
|
+
cookies,
|
|
135
|
+
headers,
|
|
136
|
+
requestId,
|
|
137
|
+
requestMethod,
|
|
138
|
+
requestUrl,
|
|
139
|
+
} from "bcp/server";
|
|
140
|
+
```
|
|
369
141
|
|
|
370
|
-
|
|
142
|
+
`requestId()` accepts a valid incoming `X-Request-Id` or generates a stable UUID for the request.
|
|
371
143
|
|
|
372
|
-
|
|
144
|
+
## Logging & Observability
|
|
373
145
|
|
|
374
|
-
|
|
375
|
-
app/dashboard/
|
|
376
|
-
├─ guard.ts
|
|
377
|
-
├─ page.tsx
|
|
378
|
-
└─ users/
|
|
379
|
-
└─ [id]/
|
|
380
|
-
├─ loader.ts
|
|
381
|
-
└─ page.tsx
|
|
382
|
-
```
|
|
146
|
+
BCP 0.1.23 introduced structured server logging:
|
|
383
147
|
|
|
384
148
|
```ts
|
|
385
|
-
// app/dashboard/guard.ts
|
|
386
149
|
import {
|
|
387
|
-
|
|
388
|
-
|
|
150
|
+
logger,
|
|
151
|
+
requestLogger,
|
|
389
152
|
} from "bcp/server";
|
|
390
153
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
role: string;
|
|
397
|
-
}>();
|
|
398
|
-
|
|
399
|
-
if (!session) {
|
|
400
|
-
return redirect(
|
|
401
|
-
"/login",
|
|
402
|
-
303
|
|
403
|
-
);
|
|
154
|
+
logger.info(
|
|
155
|
+
"Application event",
|
|
156
|
+
{
|
|
157
|
+
feature:
|
|
158
|
+
"catalog",
|
|
404
159
|
}
|
|
160
|
+
);
|
|
405
161
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
```
|
|
162
|
+
export async function loader() {
|
|
163
|
+
const log =
|
|
164
|
+
await requestLogger({
|
|
165
|
+
feature:
|
|
166
|
+
"categories",
|
|
167
|
+
});
|
|
413
168
|
|
|
414
|
-
|
|
169
|
+
log.info(
|
|
170
|
+
"Loading categories"
|
|
171
|
+
);
|
|
415
172
|
|
|
416
|
-
```ts
|
|
417
|
-
export async function loader({
|
|
418
|
-
params,
|
|
419
|
-
guardData,
|
|
420
|
-
}) {
|
|
421
173
|
return {
|
|
422
|
-
|
|
423
|
-
params.id,
|
|
424
|
-
user:
|
|
425
|
-
guardData.session,
|
|
426
|
-
role:
|
|
427
|
-
guardData.role,
|
|
174
|
+
items: [],
|
|
428
175
|
};
|
|
429
176
|
}
|
|
430
177
|
```
|
|
431
178
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
```tsx
|
|
435
|
-
"use client";
|
|
436
|
-
|
|
437
|
-
import {
|
|
438
|
-
useGuardData,
|
|
439
|
-
} from "bcp";
|
|
440
|
-
|
|
441
|
-
export default function DashboardPage() {
|
|
442
|
-
const guard =
|
|
443
|
-
useGuardData<{
|
|
444
|
-
session: {
|
|
445
|
-
email: string;
|
|
446
|
-
};
|
|
447
|
-
role: string;
|
|
448
|
-
}>();
|
|
179
|
+
Configure output with:
|
|
449
180
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
</main>
|
|
454
|
-
);
|
|
455
|
-
}
|
|
181
|
+
```env
|
|
182
|
+
BCP_LOG_LEVEL=debug
|
|
183
|
+
BCP_LOG_FORMAT=json
|
|
456
184
|
```
|
|
457
185
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
See [Protected Route Guards](docs/route-guards.md) for nested role policies, serialization rules, middleware ordering, cookie behavior and production hardening.
|
|
461
|
-
|
|
462
|
-
## Form actions and server mutations
|
|
186
|
+
Supported levels are `debug`, `info`, `warn`, `error` and `silent`. Formats are `pretty` and `json`.
|
|
463
187
|
|
|
464
|
-
|
|
188
|
+
## File Upload
|
|
465
189
|
|
|
466
|
-
|
|
467
|
-
app/users/[id]/
|
|
468
|
-
├─ guard.ts
|
|
469
|
-
├─ loader.ts
|
|
470
|
-
├─ actions.ts
|
|
471
|
-
└─ page.tsx
|
|
472
|
-
```
|
|
190
|
+
BCP 0.1.24 adds server-only multipart/file helpers through `bcp/server`:
|
|
473
191
|
|
|
474
192
|
```ts
|
|
475
|
-
// app/users/[id]/actions.ts
|
|
476
|
-
import "bcp/server-only";
|
|
477
|
-
|
|
478
193
|
import {
|
|
479
|
-
|
|
480
|
-
|
|
194
|
+
parseMultipartFormData,
|
|
195
|
+
requireUploadedFile,
|
|
196
|
+
saveUploadedFile,
|
|
481
197
|
} from "bcp/server";
|
|
482
198
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
} from "bcp/cache";
|
|
486
|
-
|
|
487
|
-
export async function saveUser(
|
|
488
|
-
formData: FormData,
|
|
489
|
-
context: PageActionContext
|
|
199
|
+
export async function POST(
|
|
200
|
+
request: Request
|
|
490
201
|
) {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
revalidatePath("/users");
|
|
499
|
-
|
|
500
|
-
if (!name) {
|
|
501
|
-
return {
|
|
502
|
-
ok: false,
|
|
503
|
-
message: "Name is required",
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
if (
|
|
508
|
-
context.searchParams.get("done") === "1"
|
|
509
|
-
) {
|
|
510
|
-
return redirect(
|
|
511
|
-
"/users",
|
|
512
|
-
303
|
|
202
|
+
const formData =
|
|
203
|
+
await parseMultipartFormData(
|
|
204
|
+
request,
|
|
205
|
+
{
|
|
206
|
+
maxBytes:
|
|
207
|
+
8 * 1024 * 1024,
|
|
208
|
+
}
|
|
513
209
|
);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
return {
|
|
517
|
-
ok: true,
|
|
518
|
-
id: context.params.id,
|
|
519
|
-
name,
|
|
520
|
-
method: context.method,
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
Client forms reference the named server action without importing it into the browser graph:
|
|
526
|
-
|
|
527
|
-
```tsx
|
|
528
|
-
"use client";
|
|
529
210
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
211
|
+
const file =
|
|
212
|
+
requireUploadedFile(
|
|
213
|
+
formData,
|
|
214
|
+
"file",
|
|
215
|
+
{
|
|
216
|
+
maxBytes:
|
|
217
|
+
5 * 1024 * 1024,
|
|
218
|
+
allowedTypes: [
|
|
219
|
+
"image/png",
|
|
220
|
+
"image/jpeg",
|
|
221
|
+
"image/webp",
|
|
222
|
+
],
|
|
223
|
+
allowedExtensions: [
|
|
224
|
+
".png",
|
|
225
|
+
".jpg",
|
|
226
|
+
".jpeg",
|
|
227
|
+
".webp",
|
|
228
|
+
],
|
|
229
|
+
}
|
|
230
|
+
);
|
|
536
231
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
>
|
|
546
|
-
{status.pending
|
|
547
|
-
? "Saving..."
|
|
548
|
-
: "Save"}
|
|
549
|
-
</button>
|
|
550
|
-
);
|
|
551
|
-
}
|
|
232
|
+
const saved =
|
|
233
|
+
await saveUploadedFile(
|
|
234
|
+
file,
|
|
235
|
+
{
|
|
236
|
+
directory:
|
|
237
|
+
"./uploads",
|
|
238
|
+
}
|
|
239
|
+
);
|
|
552
240
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
return (
|
|
563
|
-
<Form
|
|
564
|
-
action="saveUser"
|
|
565
|
-
method="patch"
|
|
566
|
-
refresh
|
|
567
|
-
>
|
|
568
|
-
<input name="name" />
|
|
569
|
-
<SubmitButton />
|
|
570
|
-
{result?.message}
|
|
571
|
-
{error?.message}
|
|
572
|
-
</Form>
|
|
573
|
-
);
|
|
241
|
+
return Response.json({
|
|
242
|
+
fileName:
|
|
243
|
+
saved.fileName,
|
|
244
|
+
size:
|
|
245
|
+
saved.size,
|
|
246
|
+
checksumSha256:
|
|
247
|
+
saved.checksumSha256,
|
|
248
|
+
});
|
|
574
249
|
}
|
|
575
250
|
```
|
|
576
251
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
See [Form Actions and Server Mutations](docs/form-actions.md) for action return values, pending/error state, progressive enhancement, guards, redirects, cookies, cache invalidation and standalone behavior.
|
|
580
|
-
|
|
581
|
-
## JWT cookie sessions
|
|
252
|
+
Upload helpers provide:
|
|
582
253
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
254
|
+
- multipart validation,
|
|
255
|
+
- total and per-file size limits,
|
|
256
|
+
- MIME/extension allowlists,
|
|
257
|
+
- optional or required file fields,
|
|
258
|
+
- safe UUID-based storage names,
|
|
259
|
+
- filename sanitization,
|
|
260
|
+
- path traversal protection,
|
|
261
|
+
- no-overwrite-by-default storage,
|
|
262
|
+
- SHA-256 checksum metadata.
|
|
588
263
|
|
|
589
|
-
|
|
264
|
+
The security gateway enforces `server.bodyLimit` / `BCP_BODY_LIMIT` before request data reaches application handlers. The default is 1 MiB, so applications accepting larger files must raise it explicitly.
|
|
590
265
|
|
|
591
266
|
```ts
|
|
592
267
|
import {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
} from "bcp/server";
|
|
596
|
-
|
|
597
|
-
export async function POST() {
|
|
598
|
-
await createSession(
|
|
599
|
-
{
|
|
600
|
-
userId: 42,
|
|
601
|
-
email: "user@example.com",
|
|
602
|
-
role: "admin",
|
|
603
|
-
},
|
|
604
|
-
{
|
|
605
|
-
issuer: "my-app",
|
|
606
|
-
audience: "my-app-users",
|
|
607
|
-
}
|
|
608
|
-
);
|
|
268
|
+
defineConfig,
|
|
269
|
+
} from "bcp/config";
|
|
609
270
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
271
|
+
export default defineConfig({
|
|
272
|
+
server: {
|
|
273
|
+
bodyLimit:
|
|
274
|
+
10 * 1024 * 1024,
|
|
275
|
+
},
|
|
276
|
+
});
|
|
614
277
|
```
|
|
615
278
|
|
|
616
|
-
|
|
279
|
+
MIME type and extension are metadata checks, not content-signature verification. Security-sensitive applications should additionally inspect file content and use malware scanning where appropriate.
|
|
617
280
|
|
|
618
|
-
|
|
619
|
-
import {
|
|
620
|
-
getSession,
|
|
621
|
-
json,
|
|
622
|
-
} from "bcp/server";
|
|
281
|
+
See [File Upload](docs/file-upload.md).
|
|
623
282
|
|
|
624
|
-
|
|
625
|
-
const session =
|
|
626
|
-
await getSession<{
|
|
627
|
-
userId: number;
|
|
628
|
-
email: string;
|
|
629
|
-
role: string;
|
|
630
|
-
}>({
|
|
631
|
-
issuer: "my-app",
|
|
632
|
-
audience: "my-app-users",
|
|
633
|
-
});
|
|
283
|
+
## Development route graph recovery
|
|
634
284
|
|
|
635
|
-
|
|
636
|
-
return json(
|
|
637
|
-
{
|
|
638
|
-
error: "Unauthorized",
|
|
639
|
-
},
|
|
640
|
-
{
|
|
641
|
-
status: 401,
|
|
642
|
-
}
|
|
643
|
-
);
|
|
644
|
-
}
|
|
285
|
+
BCP 0.1.24 fixes a development route/client-bundle synchronization issue that could produce:
|
|
645
286
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
session.userId,
|
|
649
|
-
email:
|
|
650
|
-
session.email,
|
|
651
|
-
role:
|
|
652
|
-
session.role,
|
|
653
|
-
});
|
|
654
|
-
}
|
|
287
|
+
```text
|
|
288
|
+
Client bundle was not found for route "/docs/[...slug]".
|
|
655
289
|
```
|
|
656
290
|
|
|
657
|
-
|
|
291
|
+
The problem happened when the actual page topology changed but Windows/editor filesystem events were classified as `change` instead of `add`/`unlink`. The route scanner could see a new route while the incremental client bundle graph still represented the previous route tree.
|
|
658
292
|
|
|
659
|
-
|
|
293
|
+
The dev gateway now compares the actual pathname/page/layout graph and automatically refreshes the internal development server/client bundler when topology changes. Manually deleting `.bcp-framework` should no longer be required to recover this state.
|
|
660
294
|
|
|
661
|
-
|
|
295
|
+
## Developer diagnostics
|
|
662
296
|
|
|
663
297
|
```bash
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
Interactive setup offers `None` and `JWT Cookie`. Selecting JWT Cookie creates:
|
|
668
|
-
|
|
669
|
-
```text
|
|
670
|
-
lib/auth.ts
|
|
671
|
-
app/api/auth/login/route.ts
|
|
672
|
-
app/api/auth/logout/route.ts
|
|
673
|
-
app/api/auth/me/route.ts
|
|
674
|
-
```
|
|
675
|
-
|
|
676
|
-
and adds:
|
|
677
|
-
|
|
678
|
-
```env
|
|
679
|
-
BCP_SESSION_SECRET=
|
|
298
|
+
bcp doctor
|
|
299
|
+
bcp inspect
|
|
680
300
|
```
|
|
681
301
|
|
|
682
|
-
|
|
302
|
+
`bcp doctor` checks project structure, BCP/React installations, React renderer parity, duplicate framework copies, environment/config loading, route conflicts and client/server boundaries. Blocking failures return a non-zero exit code.
|
|
683
303
|
|
|
684
|
-
|
|
304
|
+
`bcp inspect` prints resolved configuration, development env filenames, public variable names, dependency versions and discovered routes.
|
|
685
305
|
|
|
686
|
-
|
|
306
|
+
For local release verification, install packed `.tgz` artifacts under the existing `bcp` dependency key. Do not install a second `@chidchanun/bcp` copy next to `node_modules/bcp`, because separate framework copies can split React contexts and produce misleading loader/hook errors.
|
|
687
307
|
|
|
688
|
-
##
|
|
308
|
+
## Validation and errors
|
|
689
309
|
|
|
690
310
|
```ts
|
|
691
|
-
import
|
|
692
|
-
|
|
693
|
-
|
|
311
|
+
import {
|
|
312
|
+
v,
|
|
313
|
+
validateFormData,
|
|
314
|
+
} from "bcp/validation";
|
|
694
315
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
316
|
+
import {
|
|
317
|
+
badRequest,
|
|
318
|
+
unauthorized,
|
|
319
|
+
toErrorResponse,
|
|
320
|
+
} from "bcp/error";
|
|
699
321
|
```
|
|
700
322
|
|
|
701
|
-
|
|
323
|
+
Structured error responses use the common envelope:
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"error": {
|
|
328
|
+
"status": 400,
|
|
329
|
+
"code": "BAD_REQUEST",
|
|
330
|
+
"message": "Invalid request"
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
702
334
|
|
|
703
|
-
##
|
|
335
|
+
## Database
|
|
704
336
|
|
|
705
337
|
```ts
|
|
706
|
-
|
|
338
|
+
import {
|
|
339
|
+
db,
|
|
340
|
+
} from "bcp/database";
|
|
707
341
|
```
|
|
708
342
|
|
|
709
|
-
|
|
343
|
+
The database layer provides a lazy MySQL pool, prepared execution, queries and transactions. Database migrations are managed through `bcp db` commands.
|
|
344
|
+
|
|
345
|
+
## Authentication
|
|
710
346
|
|
|
711
347
|
```ts
|
|
712
348
|
import {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
} from "bcp/cache";
|
|
349
|
+
auth,
|
|
350
|
+
requireAuth,
|
|
351
|
+
requireRole,
|
|
352
|
+
} from "bcp/auth";
|
|
718
353
|
```
|
|
719
354
|
|
|
720
|
-
|
|
355
|
+
BCP also exposes lower-level JWT cookie session helpers through `bcp/server`.
|
|
721
356
|
|
|
722
357
|
## Middleware
|
|
723
358
|
|
|
724
|
-
|
|
725
|
-
import {
|
|
726
|
-
next,
|
|
727
|
-
redirect,
|
|
728
|
-
type MiddlewareRequest,
|
|
729
|
-
} from "bcp/middleware";
|
|
359
|
+
Middleware v2 uses true onion execution:
|
|
730
360
|
|
|
731
|
-
|
|
732
|
-
|
|
361
|
+
```ts
|
|
362
|
+
export async function middleware(
|
|
363
|
+
request,
|
|
364
|
+
context,
|
|
365
|
+
next
|
|
733
366
|
) {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
367
|
+
const response =
|
|
368
|
+
await next();
|
|
369
|
+
|
|
370
|
+
response.headers.set(
|
|
371
|
+
"x-app",
|
|
372
|
+
"example"
|
|
373
|
+
);
|
|
740
374
|
|
|
741
|
-
return
|
|
375
|
+
return response;
|
|
742
376
|
}
|
|
743
377
|
```
|
|
744
378
|
|
|
745
|
-
|
|
379
|
+
Existing v1 middleware remains supported.
|
|
746
380
|
|
|
747
|
-
##
|
|
748
|
-
|
|
749
|
-
```ts
|
|
750
|
-
import {
|
|
751
|
-
defineConfig,
|
|
752
|
-
} from "bcp/config";
|
|
381
|
+
## Hydration parity
|
|
753
382
|
|
|
754
|
-
|
|
755
|
-
server: {
|
|
756
|
-
port: 3000,
|
|
757
|
-
hostname: "localhost",
|
|
758
|
-
bodyLimit: 1024 * 1024,
|
|
759
|
-
},
|
|
760
|
-
compression: true,
|
|
761
|
-
build: {
|
|
762
|
-
minify: true,
|
|
763
|
-
sourceMaps: false,
|
|
764
|
-
},
|
|
765
|
-
cache: {
|
|
766
|
-
response: true,
|
|
767
|
-
},
|
|
768
|
-
security: {
|
|
769
|
-
poweredByHeader: false,
|
|
770
|
-
contentSecurityPolicy: false,
|
|
771
|
-
frameOptions: "SAMEORIGIN",
|
|
772
|
-
referrerPolicy:
|
|
773
|
-
"strict-origin-when-cross-origin",
|
|
774
|
-
permissionsPolicy:
|
|
775
|
-
"camera=(), microphone=(), geolocation=()",
|
|
776
|
-
},
|
|
777
|
-
});
|
|
778
|
-
```
|
|
383
|
+
BCP 0.1.20 normalized Windows CRLF/CR source line endings in development instrumentation. BCP 0.1.21 completed SSR/client JSX semantic parity by preserving JSX through the Babel React Refresh pass and allowing esbuild to perform the development JSX transform.
|
|
779
384
|
|
|
780
|
-
|
|
385
|
+
Multiline quoted JSX attributes are supported without rewriting classes onto a single line or applying `suppressHydrationWarning` as a framework workaround.
|
|
781
386
|
|
|
782
387
|
## Production
|
|
783
388
|
|
|
@@ -786,78 +391,74 @@ npm run build
|
|
|
786
391
|
npm start
|
|
787
392
|
```
|
|
788
393
|
|
|
789
|
-
|
|
394
|
+
Standalone output is generated under:
|
|
790
395
|
|
|
791
396
|
```text
|
|
792
397
|
.bcp-framework/build/
|
|
793
398
|
├─ client/
|
|
794
399
|
├─ public/
|
|
795
400
|
└─ server/
|
|
796
|
-
|
|
797
|
-
├─ middleware.mjs
|
|
798
|
-
├─ guards.mjs
|
|
799
|
-
├─ actions.mjs
|
|
800
|
-
├─ cache-manifest.json
|
|
801
|
-
└─ config.json
|
|
401
|
+
└─ server.mjs
|
|
802
402
|
```
|
|
803
403
|
|
|
804
|
-
|
|
404
|
+
## Updating
|
|
805
405
|
|
|
806
|
-
|
|
406
|
+
```bash
|
|
407
|
+
bcp update
|
|
408
|
+
bcp update --check
|
|
409
|
+
bcp update --dry-run
|
|
410
|
+
bcp update 0.1.24
|
|
411
|
+
bcp update next
|
|
412
|
+
```
|
|
807
413
|
|
|
808
|
-
|
|
414
|
+
Projects published before the updater can bootstrap it once with:
|
|
809
415
|
|
|
810
416
|
```bash
|
|
811
|
-
|
|
812
|
-
npm run package:check
|
|
813
|
-
npm run release:check
|
|
417
|
+
npx @chidchanun/bcp@latest update
|
|
814
418
|
```
|
|
815
419
|
|
|
816
|
-
|
|
420
|
+
## Release validation
|
|
817
421
|
|
|
818
422
|
```bash
|
|
819
|
-
npm
|
|
423
|
+
npm run typecheck
|
|
424
|
+
npm run test:unit
|
|
820
425
|
npm run rc:check
|
|
821
426
|
```
|
|
822
427
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
After a real publish, `npm run release:visibility-check` verifies that the exact framework and generator versions are readable from the npm registry before the release is treated as ready for installation.
|
|
826
|
-
|
|
827
|
-
`package:prepare` stages the framework at `.package/bcp`. The default package name is `bcp`; set `BCP_PACKAGE_NAME` when preparing a scoped or alternate package name.
|
|
828
|
-
|
|
829
|
-
No real npm publish command is run automatically by the repository.
|
|
428
|
+
After RC passes, releases are tagged and published manually with the guarded release scripts.
|
|
830
429
|
|
|
831
430
|
## Documentation
|
|
832
431
|
|
|
833
432
|
- [Getting Started](docs/getting-started.md)
|
|
834
433
|
- [Application Modules](docs/application-modules.md)
|
|
434
|
+
- [Routing](docs/routing.md)
|
|
835
435
|
- [Server Request APIs](docs/server-request-apis.md)
|
|
836
436
|
- [Server Data Loaders](docs/server-data-loaders.md)
|
|
837
437
|
- [Protected Route Guards](docs/route-guards.md)
|
|
838
|
-
- [Form Actions
|
|
438
|
+
- [Form Actions](docs/form-actions.md)
|
|
839
439
|
- [Validation](docs/validation.md)
|
|
840
440
|
- [Error Handling](docs/error-handling.md)
|
|
441
|
+
- [File Upload](docs/file-upload.md)
|
|
841
442
|
- [Authentication](docs/authentication.md)
|
|
842
443
|
- [Auth Route Guards](docs/auth-route-guards.md)
|
|
843
|
-
- [JWT
|
|
444
|
+
- [JWT Sessions](docs/session-auth.md)
|
|
844
445
|
- [Database](docs/database.md)
|
|
845
446
|
- [Database Migrations](docs/database-migrations.md)
|
|
846
447
|
- [Middleware](docs/middleware.md)
|
|
847
|
-
- [Hydration
|
|
448
|
+
- [Hydration](docs/hydration.md)
|
|
848
449
|
- [Developer Tools](docs/developer-tools.md)
|
|
849
|
-
- [
|
|
850
|
-
- [Routing](docs/routing.md)
|
|
851
|
-
- [Configuration](docs/configuration.md)
|
|
450
|
+
- [Development Logging](docs/development-logging.md)
|
|
852
451
|
- [Caching](docs/caching.md)
|
|
853
452
|
- [Security](docs/security.md)
|
|
854
453
|
- [Deployment](docs/deployment.md)
|
|
454
|
+
- [Updating](docs/updating.md)
|
|
855
455
|
- [Releasing](docs/releasing.md)
|
|
856
456
|
|
|
857
457
|
## Requirements
|
|
858
458
|
|
|
859
|
-
|
|
459
|
+
- Node.js 24.11 or newer
|
|
460
|
+
- React 19
|
|
860
461
|
|
|
861
462
|
## License
|
|
862
463
|
|
|
863
|
-
BCP Framework and `create-bcp-app` are released under the MIT License.
|
|
464
|
+
BCP Framework and `create-bcp-app` are released under the MIT License.
|