@chidchanun/bcp 0.1.22 → 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 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, client navigation, API routes, middleware, metadata, client islands, cache/revalidation, validation, structured errors, authentication, database primitives, security defaults and standalone production builds.
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.22`. BCP is still pre-1.0 and validates each release candidate before the manual npm publish step.
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
- Open `http://localhost:3000`.
15
+ Default development URL:
18
16
 
19
- For local package testing from this repository:
17
+ ```text
18
+ http://localhost:3000
19
+ ```
20
+
21
+ ## CLI
20
22
 
21
23
  ```bash
22
- npm run package:check
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
- This creates package tarballs under `.package/artifacts` and verifies that `create-bcp-app` can generate a project from the packed BCP Framework artifact.
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
- └─ hello/
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 routes, which have priority over catch-all routes.
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 request APIs
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
- ## Server data loaders
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
- Pages consume the serializable result with `useLoaderData<T>()`:
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
- Loader data is validated as JSON-safe before SSR and is serialized into framework data so hydration reads the same value without importing `loader.ts` into the browser graph. A loader may also return a Web `Response`; response cookies are preserved on redirects.
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
- Loader-backed pages now use the same SPA navigation model as normal BCP routes. `<Link>`, `navigate()`, `router.push()`, `router.replace()`, history navigation and `router.refresh()` request `/_bcp/navigation`; the server executes the target loader inside a fresh target-page request context and returns current loader data with the route payload. Same-origin loader redirects continue through the router while preserving `Set-Cookie` headers. Superseded navigations are aborted and sequence-checked so stale responses cannot update the active route. Loader pages remain excluded from the production response-cache manifest by default because their output may depend on session, cookie, request identity or user-specific database state.
128
+ ## Server request APIs
367
129
 
368
- See [Server Data Loaders](docs/server-data-loaders.md) for serialization rules, authentication patterns, redirect behavior, SPA navigation, cache safety and request-context semantics.
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
- ## Protected routes and auth guards
142
+ `requestId()` accepts a valid incoming `X-Request-Id` or generates a stable UUID for the request.
371
143
 
372
- BCP 0.1.10 adds scoped `guard.ts` / `guard.tsx` files for page authorization. A guard protects the pages in its directory and descendant route directories, runs before the page loader, and can use the same request/session APIs as a loader.
144
+ ## Logging & Observability
373
145
 
374
- ```text
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
- getSession,
388
- redirect,
150
+ logger,
151
+ requestLogger,
389
152
  } from "bcp/server";
390
153
 
391
- export async function guard() {
392
- const session =
393
- await getSession<{
394
- userId: number;
395
- email: string;
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
- return {
407
- session,
408
- role:
409
- session.role,
410
- };
411
- }
412
- ```
162
+ export async function loader() {
163
+ const log =
164
+ await requestLogger({
165
+ feature:
166
+ "categories",
167
+ });
413
168
 
414
- Descendant loaders receive the merged result as `guardData`, so they do not need to repeat `getSession()`:
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
- id:
423
- params.id,
424
- user:
425
- guardData.session,
426
- role:
427
- guardData.role,
174
+ items: [],
428
175
  };
429
176
  }
430
177
  ```
431
178
 
432
- A page can consume the same authorization data with `useGuardData<T>()`:
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
- return (
451
- <main>
452
- {guard.session.email}
453
- </main>
454
- );
455
- }
181
+ ```env
182
+ BCP_LOG_LEVEL=debug
183
+ BCP_LOG_FORMAT=json
456
184
  ```
457
185
 
458
- Nested guards execute from root to child and receive merged ancestor output as `guardData`. Guard redirects and response cookies work for both direct document requests and SPA navigation. Guarded pages are excluded from the automatic production response cache, and standalone production strips user-supplied internal guard transport headers before evaluating authorization.
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
- BCP 0.1.11 adds route-owned server mutations with `actions.ts` / `actions.tsx`. Actions stay on the server and let page forms perform create, update and delete operations without creating a dedicated API route for every mutation.
188
+ ## File Upload
465
189
 
466
- ```text
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
- redirect,
480
- type PageActionContext,
194
+ parseMultipartFormData,
195
+ requireUploadedFile,
196
+ saveUploadedFile,
481
197
  } from "bcp/server";
482
198
 
483
- import {
484
- revalidatePath,
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 name =
492
- String(
493
- formData.get("name") ?? ""
494
- ).trim();
495
-
496
- // await db.execute(...)
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
- import {
531
- Form,
532
- useActionData,
533
- useActionError,
534
- useFormStatus,
535
- } from "bcp";
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
- function SubmitButton() {
538
- const status =
539
- useFormStatus();
540
-
541
- return (
542
- <button
543
- type="submit"
544
- disabled={status.pending}
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
- export default function UserForm() {
554
- const result =
555
- useActionData<{
556
- ok: boolean;
557
- message?: string;
558
- }>();
559
- const error =
560
- useActionError();
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
- Supported semantic methods are POST, PUT, PATCH and DELETE. Guards execute before actions and pass merged authorization state through `context.guardData`. Actions can use cookies, JWT sessions, `requestMethod()`, redirects and cache revalidation. Enhanced forms submit through the BCP SPA transport; forms without JavaScript fall back to standard HTML POST with POST/Redirect/GET semantics. Action-backed pages are excluded from automatic production response caching.
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
- BCP 0.1.8 adds HS256 JWT cookie sessions directly to `bcp/server`. Configure a server-only secret of at least 32 bytes:
584
-
585
- ```env
586
- BCP_SESSION_SECRET=replace-this-with-a-long-random-secret-at-least-32-bytes
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
- Login-style APIs can create an HttpOnly session cookie without manually signing or serializing the JWT:
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
- createSession,
594
- json,
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
- return json({
611
- success: true,
612
- });
613
- }
271
+ export default defineConfig({
272
+ server: {
273
+ bodyLimit:
274
+ 10 * 1024 * 1024,
275
+ },
276
+ });
614
277
  ```
615
278
 
616
- Protected API routes can verify and read the session:
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
- ```ts
619
- import {
620
- getSession,
621
- json,
622
- } from "bcp/server";
281
+ See [File Upload](docs/file-upload.md).
623
282
 
624
- export async function GET() {
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
- if (!session) {
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
- return json({
647
- userId:
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
- Use `destroySession()` to expire the cookie. Low-level `createSessionToken()` and `verifySessionToken()` helpers are also available when an application needs to manage token storage itself. JWT payloads are signed, not encrypted, so sensitive secrets must not be stored inside them. See [JWT Cookie Sessions](docs/session-auth.md) for options and the security model.
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
- ## create-bcp-app auth preset
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
- BCP 0.1.8 can scaffold the JWT cookie foundation automatically:
295
+ ## Developer diagnostics
662
296
 
663
297
  ```bash
664
- npx create-bcp-app my-app --database mysql --auth jwt-cookie
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
- to `.env.example`.
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
- The generated `authenticateCredentials(email, password)` returns `null` by default. Applications must connect it to their own database lookup and password-hash verification before login can succeed. This is intentional so a newly generated project does not trust user identity supplied directly by the browser.
304
+ `bcp inspect` prints resolved configuration, development env filenames, public variable names, dependency versions and discovered routes.
685
305
 
686
- The generated MySQL preset uses a reusable `db` pool and separate `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD` and `DB_NAME` settings.
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
- ## Metadata
308
+ ## Validation and errors
689
309
 
690
310
  ```ts
691
- import type {
692
- Metadata,
693
- } from "bcp";
311
+ import {
312
+ v,
313
+ validateFormData,
314
+ } from "bcp/validation";
694
315
 
695
- export const metadata: Metadata = {
696
- title: "Dashboard",
697
- description: "Dashboard page",
698
- };
316
+ import {
317
+ badRequest,
318
+ unauthorized,
319
+ toErrorResponse,
320
+ } from "bcp/error";
699
321
  ```
700
322
 
701
- Dynamic routes can export `generateMetadata()` and receive route params.
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
- ## Cache and revalidation
335
+ ## Database
704
336
 
705
337
  ```ts
706
- export const revalidate = 60;
338
+ import {
339
+ db,
340
+ } from "bcp/database";
707
341
  ```
708
342
 
709
- Server data can use:
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
- cache,
714
- dedupe,
715
- revalidatePath,
716
- revalidateTag,
717
- } from "bcp/cache";
349
+ auth,
350
+ requireAuth,
351
+ requireRole,
352
+ } from "bcp/auth";
718
353
  ```
719
354
 
720
- The current cache implementation is process-local and intentionally does not provide distributed invalidation across multiple Node.js instances. Loader-backed, route-guarded and action-backed pages are excluded from the automatic production response-cache manifest when their output or mutation flow may depend on request/session state. Cache server data explicitly only when the application has a safe user-aware cache key and invalidation strategy.
355
+ BCP also exposes lower-level JWT cookie session helpers through `bcp/server`.
721
356
 
722
357
  ## Middleware
723
358
 
724
- ```ts
725
- import {
726
- next,
727
- redirect,
728
- type MiddlewareRequest,
729
- } from "bcp/middleware";
359
+ Middleware v2 uses true onion execution:
730
360
 
731
- export function middleware(
732
- request: MiddlewareRequest
361
+ ```ts
362
+ export async function middleware(
363
+ request,
364
+ context,
365
+ next
733
366
  ) {
734
- if (
735
- request.nextUrl.pathname === "/private" &&
736
- !request.cookies.has("session")
737
- ) {
738
- return redirect("/login");
739
- }
367
+ const response =
368
+ await next();
369
+
370
+ response.headers.set(
371
+ "x-app",
372
+ "example"
373
+ );
740
374
 
741
- return next();
375
+ return response;
742
376
  }
743
377
  ```
744
378
 
745
- Use middleware for request-wide interception and `guard.ts` for page-subtree authorization that needs to feed identity/role data into loaders, actions and pages. In standalone production, project middleware runs before route guards and form actions.
379
+ Existing v1 middleware remains supported.
746
380
 
747
- ## Configuration
748
-
749
- ```ts
750
- import {
751
- defineConfig,
752
- } from "bcp/config";
381
+ ## Hydration parity
753
382
 
754
- export default defineConfig({
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
- Development/build precedence is CLI > `BCP_*` environment > `bcp.config.*` > defaults. Standalone start uses CLI > runtime environment > the config frozen into the production build.
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
- The standalone output is generated under:
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
- ├─ server.mjs
797
- ├─ middleware.mjs
798
- ├─ guards.mjs
799
- ├─ actions.mjs
800
- ├─ cache-manifest.json
801
- └─ config.json
401
+ └─ server.mjs
802
402
  ```
803
403
 
804
- `guards.mjs` is generated only when the application contains protected route guards. `actions.mjs` is generated only when the application contains route form actions.
404
+ ## Updating
805
405
 
806
- ## Package preparation
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
- The development monorepo stays private. Publishable artifacts are produced separately:
414
+ Projects published before the updater can bootstrap it once with:
809
415
 
810
416
  ```bash
811
- npm run package:prepare
812
- npm run package:check
813
- npm run release:check
417
+ npx @chidchanun/bcp@latest update
814
418
  ```
815
419
 
816
- For the final Release Candidate gate:
420
+ ## Release validation
817
421
 
818
422
  ```bash
819
- npm login
423
+ npm run typecheck
424
+ npm run test:unit
820
425
  npm run rc:check
821
426
  ```
822
427
 
823
- `rc:check` validates tests/release metadata, checks npm package-name availability or ownership, performs `npm publish --dry-run`, and clean-installs both generated tarballs into temporary projects.
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 and Server Mutations](docs/form-actions.md)
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 Cookie Sessions](docs/session-auth.md)
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 and deterministic rendering](docs/hydration.md)
448
+ - [Hydration](docs/hydration.md)
848
449
  - [Developer Tools](docs/developer-tools.md)
849
- - [Updating BCP Framework](docs/updating.md)
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
- BCP Framework currently targets Node.js 24.11 or newer and React 19.
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. See [LICENSE](LICENSE).
464
+ BCP Framework and `create-bcp-app` are released under the MIT License.