@chidchanun/bcp 0.1.23 → 0.1.25

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,863 +1,943 @@
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 focused on file-based routing, server rendering, server-side data loading, guarded application flows, API routes, authentication, database access, validation, logging, file uploads, storage adapters and standalone production deployment.
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
+ > **Development target:** `0.1.25`
6
+ >
7
+ > BCP is still pre-1.0. Features documented for the current development target should not be presented as published npm behavior until the release candidate has passed and the matching version has been published.
6
8
 
7
- ## Quick start
8
-
9
- After the packages are published:
10
-
11
- ```bash
12
- npx create-bcp-app my-app
13
- cd my-app
14
- npm run dev
15
- ```
9
+ ## Overview
16
10
 
17
- Open `http://localhost:3000`.
11
+ BCP provides a single application model for React pages and server code:
18
12
 
19
- For local package testing from this repository:
20
-
21
- ```bash
22
- npm run package:check
13
+ ```text
14
+ Browser
15
+
16
+ BCP middleware / security
17
+
18
+ Route guard
19
+
20
+ Loader / action / API route
21
+
22
+ React SSR
23
+
24
+ Hydration / SPA navigation
23
25
  ```
24
26
 
25
- This creates package tarballs under `.package/artifacts` and verifies that `create-bcp-app` can generate a project from the packed BCP Framework artifact.
26
-
27
- ## Project structure
27
+ The framework is designed so application code can stay close to the route that owns it:
28
28
 
29
29
  ```text
30
30
  app/
31
31
  ├─ layout.tsx
32
32
  ├─ page.tsx
33
- ├─ loading.tsx
34
- ├─ error.tsx
35
- ├─ not-found.tsx
36
33
  ├─ dashboard/
37
34
  │ ├─ guard.ts
38
- │ ├─ page.tsx
39
35
  │ └─ users/
40
36
  │ └─ [id]/
41
37
  │ ├─ loader.ts
42
38
  │ ├─ actions.ts
43
39
  │ └─ page.tsx
44
40
  └─ api/
45
- └─ hello/
41
+ └─ upload/
46
42
  └─ route.ts
47
-
48
- lib/
49
- public/
50
- bcp.config.ts
51
- package.json
52
- tsconfig.json
53
43
  ```
54
44
 
55
- ## Application imports and boundaries
45
+ ## Current capabilities
46
+
47
+ | Area | Capability |
48
+ | --- | --- |
49
+ | Application | React SSR, hydration, layouts, metadata, SPA navigation |
50
+ | Routing | Static, dynamic, catch-all, optional catch-all and route groups |
51
+ | Server data | `loader.ts`, request-scoped server APIs |
52
+ | Mutations | Route-owned `actions.ts` and `<Form>` |
53
+ | Authorization | `guard.ts`, `requireAuth()`, `requireRole()` |
54
+ | Authentication | JWT cookie sessions and auth helpers |
55
+ | Middleware | Middleware System v2 with onion execution |
56
+ | Validation | Typed validation helpers and structured validation errors |
57
+ | Error handling | HTTP error helpers and consistent error responses |
58
+ | Database | MySQL pool/query helpers, transactions and migrations |
59
+ | Logging | Structured logger, request logger and request IDs |
60
+ | Uploads | Multipart parsing, file validation and safe local persistence |
61
+ | Storage | `StorageAdapter`, local storage adapter and file delivery |
62
+ | Caching | Response cache and revalidation primitives |
63
+ | Developer tools | `doctor`, `inspect`, updater and route inspection |
64
+ | Production | Standalone server build with production middleware pipeline |
56
65
 
57
- Generated applications include the project-root `@/` alias:
66
+ ## Requirements
58
67
 
59
- ```ts
60
- import {
61
- db,
62
- } from "@/lib/database";
63
- ```
68
+ - Node.js `24.11` or newer
69
+ - React `19`
70
+ - npm
64
71
 
65
- Modules that use React client hooks must declare `"use client"`:
72
+ Database features currently target MySQL.
66
73
 
67
- ```tsx
68
- "use client";
74
+ ## Quick start
69
75
 
70
- import {
71
- useState,
72
- } from "react";
76
+ Create a new application:
77
+
78
+ ```bash
79
+ npx create-bcp-app@latest my-app
80
+ cd my-app
81
+ npm run dev
73
82
  ```
74
83
 
75
- Server-only modules can declare:
84
+ Default development URL:
76
85
 
77
- ```ts
78
- import "bcp/server-only";
86
+ ```text
87
+ http://localhost:3000
79
88
  ```
80
89
 
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.
90
+ A generated project normally exposes scripts such as:
84
91
 
85
- ## Tailwind and critical CSS
92
+ ```json
93
+ {
94
+ "scripts": {
95
+ "dev": "bcp dev",
96
+ "build": "bcp build",
97
+ "start": "bcp start",
98
+ "routes": "bcp routes",
99
+ "update": "bcp update"
100
+ }
101
+ }
102
+ ```
86
103
 
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.
104
+ ## Packages
88
105
 
89
- ## Development hydration parity
106
+ The public framework package is published as:
90
107
 
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.
108
+ ```text
109
+ @chidchanun/bcp
110
+ ```
92
111
 
93
- For example, this is supported application code:
112
+ Applications normally consume it through the dependency key:
94
113
 
95
- ```tsx
96
- <div
97
- className="
98
- min-h-screen
99
- bg-white
100
- text-slate-950
101
- "
102
- />
114
+ ```text
115
+ bcp
103
116
  ```
104
117
 
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.
118
+ This keeps imports concise:
106
119
 
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.
120
+ ```ts
121
+ import {
122
+ Form,
123
+ Link,
124
+ useLoaderData,
125
+ } from "bcp";
126
+ ```
108
127
 
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.
128
+ Server-only APIs use dedicated entrypoints such as:
110
129
 
111
- See [Hydration and deterministic rendering](docs/hydration.md) for the transform pipeline and troubleshooting guidance.
130
+ ```ts
131
+ import {
132
+ cookies,
133
+ logger,
134
+ requestId,
135
+ } from "bcp/server";
136
+ ```
112
137
 
113
- ## Developer diagnostics
138
+ ## Project structure
114
139
 
115
- BCP 0.1.22 adds dedicated developer tooling for diagnosing application setup without starting the development server.
140
+ A typical application can grow into this layout:
116
141
 
117
- Run a project health check:
142
+ ```text
143
+ app/
144
+ ├─ layout.tsx
145
+ ├─ page.tsx
146
+ ├─ login/
147
+ │ └─ page.tsx
148
+ ├─ dashboard/
149
+ │ ├─ guard.ts
150
+ │ ├─ page.tsx
151
+ │ └─ users/
152
+ │ └─ [id]/
153
+ │ ├─ loader.ts
154
+ │ ├─ actions.ts
155
+ │ └─ page.tsx
156
+ └─ api/
157
+ ├─ auth/
158
+ │ └─ login/
159
+ │ └─ route.ts
160
+ └─ upload/
161
+ └─ route.ts
118
162
 
119
- ```bash
120
- bcp doctor
163
+ lib/
164
+ public/
165
+ migrations/
166
+ bcp.config.ts
167
+ package.json
168
+ tsconfig.json
121
169
  ```
122
170
 
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.
171
+ BCP keeps page rendering, route authorization, server data and route mutations close together without requiring one large application router configuration file.
172
+
173
+ ## Routing
124
174
 
125
- Inspect the resolved project inputs BCP sees:
175
+ Page routes are discovered from `app/**/page.tsx`.
126
176
 
127
- ```bash
128
- bcp inspect
177
+ ```text
178
+ app/page.tsx /
179
+ app/about/page.tsx /about
180
+ app/users/[id]/page.tsx /users/:id
181
+ app/docs/[...slug]/page.tsx /docs/*
182
+ app/catalog/[[...slug]]/page.tsx /catalog and /catalog/*
183
+ app/(admin)/settings/page.tsx /settings
129
184
  ```
130
185
 
131
- This prints development env filenames, public environment variable names, resolved BCP configuration, dependency versions and discovered page/API routes.
186
+ Static routes have priority over dynamic and catch-all routes.
132
187
 
133
- Both commands support JSON output:
188
+ API routes use `route.ts`:
134
189
 
135
- ```bash
136
- bcp doctor --json
137
- bcp inspect --json
190
+ ```text
191
+ app/api/users/route.ts /api/users
192
+ app/api/users/[id]/route.ts /api/users/:id
138
193
  ```
139
194
 
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.
195
+ Read more: [Routing](docs/routing.md)
141
196
 
142
- See [Developer Tools](docs/developer-tools.md) for the complete command reference.
197
+ ## Layouts and metadata
143
198
 
144
- ## Commands
199
+ Routes can inherit layouts from parent directories. The framework resolves the layout chain while rendering both development and standalone production requests.
145
200
 
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
- ```
201
+ Document metadata is route-aware and can be generated alongside the page tree.
158
202
 
159
- CLI server overrides are available with `--port` and `--hostname`.
203
+ Read more: [Routing](docs/routing.md)
160
204
 
161
- ## Updating an existing project
205
+ ## Server data loaders
162
206
 
163
- Once a project is on a BCP version that contains the updater, update to the current npm `latest` release with:
207
+ Place `loader.ts` next to a page when the route needs server-side data.
164
208
 
165
- ```bash
166
- bcp update
209
+ ```ts
210
+ // app/users/[id]/loader.ts
211
+ export async function loader({
212
+ params,
213
+ }) {
214
+ return {
215
+ id:
216
+ params.id,
217
+ };
218
+ }
167
219
  ```
168
220
 
169
- Preview an update without changing files:
221
+ Consume the serializable result in a client page:
170
222
 
171
- ```bash
172
- bcp update --check
173
- bcp update --dry-run
174
- ```
223
+ ```tsx
224
+ "use client";
225
+
226
+ import {
227
+ useLoaderData,
228
+ } from "bcp";
175
229
 
176
- Or select a published version/dist-tag explicitly:
230
+ export default function UserPage() {
231
+ const data =
232
+ useLoaderData<{
233
+ id: string;
234
+ }>();
177
235
 
178
- ```bash
179
- bcp update 0.1.22
180
- bcp update next
236
+ return (
237
+ <main>
238
+ User {data.id}
239
+ </main>
240
+ );
241
+ }
181
242
  ```
182
243
 
183
- Versions published before the updater do not recognize `bcp update`. Bootstrap the newest CLI once from those projects:
244
+ Read more: [Server Data Loaders](docs/server-data-loaders.md)
184
245
 
185
- ```bash
186
- npx @chidchanun/bcp@latest update
187
- ```
246
+ ## Route guards
188
247
 
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.
248
+ A route tree can define `guard.ts` to authorize access before the protected route is rendered.
190
249
 
191
- ## Routing
250
+ Authentication-aware guards are available through `bcp/auth`:
192
251
 
193
- BCP supports:
252
+ ```ts
253
+ import {
254
+ requireRole,
255
+ } from "bcp/auth";
194
256
 
195
- ```text
196
- app/page.tsx /
197
- app/about/page.tsx /about
198
- app/users/[id]/page.tsx /users/:id
199
- app/docs/[...slug]/page.tsx /docs/*
200
- app/catalog/[[...slug]]/page.tsx /catalog and /catalog/*
201
- app/(admin)/settings/page.tsx /settings
257
+ export const guard =
258
+ requireRole("admin");
202
259
  ```
203
260
 
204
- Static routes have priority over dynamic routes, which have priority over catch-all routes.
261
+ The standalone production pipeline preserves the same active request context used by authentication and server request APIs.
205
262
 
206
- ## API routes
263
+ Read more:
207
264
 
208
- ```ts
209
- // app/api/hello/route.ts
210
- export function GET() {
211
- return Response.json({
212
- message: "Hello",
213
- });
214
- }
215
- ```
265
+ - [Route Guards](docs/route-guards.md)
266
+ - [Auth Route Guards](docs/auth-route-guards.md)
267
+
268
+ ## Form actions
269
+
270
+ Route-owned mutations live in `actions.ts` and can be invoked through the public `<Form>` API.
271
+
272
+ This supports both progressive form submission and SPA action transport while keeping mutation logic server-only.
216
273
 
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.
274
+ Read more: [Form Actions](docs/form-actions.md)
218
275
 
219
276
  ## Server request APIs
220
277
 
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.
278
+ Request-scoped APIs are exposed through `bcp/server`:
222
279
 
223
280
  ```ts
224
281
  import {
225
282
  bearerToken,
226
283
  clientIp,
227
284
  cookies,
228
- json,
285
+ headers,
229
286
  requestId,
230
287
  requestMethod,
231
288
  requestUrl,
232
289
  } 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
290
  ```
258
291
 
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.
292
+ `requestId()` uses a valid incoming `X-Request-Id` when available or generates a stable UUID for the active request.
260
293
 
261
- Response cookies and redirects can be composed for login-style flows:
294
+ Read more: [Server Request APIs](docs/server-request-apis.md)
295
+
296
+ ## Authentication and sessions
297
+
298
+ High-level authentication helpers are available through:
262
299
 
263
300
  ```ts
264
301
  import {
265
- cookies,
266
- redirect,
302
+ auth,
303
+ requireAuth,
304
+ requireRole,
305
+ } from "bcp/auth";
306
+ ```
307
+
308
+ Lower-level JWT cookie session primitives are available through `bcp/server`:
309
+
310
+ ```ts
311
+ import {
312
+ createSession,
313
+ createSessionToken,
314
+ destroySession,
315
+ getSession,
316
+ verifySessionToken,
267
317
  } from "bcp/server";
318
+ ```
268
319
 
269
- export async function POST() {
270
- const cookieStore =
271
- await cookies();
320
+ Authentication is intentionally separated from application-specific credential lookup so projects can connect their own user table or identity provider.
272
321
 
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
- );
322
+ Read more:
323
+
324
+ - [Authentication](docs/authentication.md)
325
+ - [JWT Cookie Sessions](docs/session-auth.md)
284
326
 
285
- return redirect(
286
- "/dashboard",
287
- 303
327
+ ## Middleware
328
+
329
+ Middleware System v2 uses onion-style execution:
330
+
331
+ ```ts
332
+ export async function middleware(
333
+ request,
334
+ context,
335
+ next
336
+ ) {
337
+ const response =
338
+ await next();
339
+
340
+ response.headers.set(
341
+ "x-app",
342
+ "example"
288
343
  );
344
+
345
+ return response;
289
346
  }
290
347
  ```
291
348
 
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.
349
+ This allows middleware to run logic both before and after downstream route execution.
293
350
 
294
- ## Server data loaders
351
+ Existing middleware v1 behavior remains supported for compatibility.
295
352
 
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.
353
+ Read more: [Middleware](docs/middleware.md)
297
354
 
298
- ```text
299
- app/users/[id]/
300
- ├─ loader.ts
301
- └─ page.tsx
302
- ```
355
+ ## Validation
356
+
357
+ BCP includes typed validation primitives:
303
358
 
304
359
  ```ts
305
- // app/users/[id]/loader.ts
306
360
  import {
307
- getSession,
308
- redirect,
309
- } from "bcp/server";
361
+ v,
362
+ validateFormData,
363
+ } from "bcp/validation";
364
+ ```
310
365
 
311
- export async function loader({
312
- params,
313
- searchParams,
314
- }: {
315
- params: {
316
- id: string;
317
- };
318
- searchParams: URLSearchParams;
319
- }) {
320
- const session =
321
- await getSession();
366
+ Validation can be shared by API routes and form actions without coupling application schemas to the rendering layer.
322
367
 
323
- if (!session) {
324
- return redirect(
325
- "/login",
326
- 303
327
- );
328
- }
368
+ Read more: [Validation](docs/validation.md)
329
369
 
330
- return {
331
- id:
332
- params.id,
333
- query:
334
- searchParams.get("q"),
335
- session,
336
- };
370
+ ## Error handling
371
+
372
+ Structured HTTP error helpers are exposed through `bcp/error`:
373
+
374
+ ```ts
375
+ import {
376
+ badRequest,
377
+ forbidden,
378
+ notFoundResponse,
379
+ toErrorResponse,
380
+ unauthorized,
381
+ } from "bcp/error";
382
+ ```
383
+
384
+ The common error envelope is:
385
+
386
+ ```json
387
+ {
388
+ "error": {
389
+ "status": 400,
390
+ "code": "BAD_REQUEST",
391
+ "message": "Invalid request"
392
+ }
337
393
  }
338
394
  ```
339
395
 
340
- Pages consume the serializable result with `useLoaderData<T>()`:
396
+ Read more: [Error Handling](docs/error-handling.md)
341
397
 
342
- ```tsx
343
- "use client";
398
+ ## Database
399
+
400
+ Database helpers are exposed through:
344
401
 
402
+ ```ts
345
403
  import {
346
- useLoaderData,
347
- } from "bcp";
404
+ db,
405
+ } from "bcp/database";
406
+ ```
348
407
 
349
- export default function UserPage() {
350
- const data =
351
- useLoaderData<{
352
- id: string;
353
- query: string | null;
354
- }>();
408
+ The database layer provides:
355
409
 
356
- return (
357
- <main>
358
- User {data.id}
359
- </main>
360
- );
361
- }
362
- ```
410
+ - lazy MySQL pool creation,
411
+ - prepared execution,
412
+ - query helpers,
413
+ - transactions,
414
+ - migration status and rollback support.
363
415
 
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.
416
+ Migration commands:
365
417
 
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.
418
+ ```bash
419
+ bcp db create create_users
420
+ bcp db migrate
421
+ bcp db status
422
+ bcp db rollback
423
+ ```
367
424
 
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.
425
+ Read more:
369
426
 
370
- ## Protected routes and auth guards
427
+ - [Database](docs/database.md)
428
+ - [Database Migrations](docs/database-migrations.md)
371
429
 
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.
430
+ ## Logging and observability
373
431
 
374
- ```text
375
- app/dashboard/
376
- ├─ guard.ts
377
- ├─ page.tsx
378
- └─ users/
379
- └─ [id]/
380
- ├─ loader.ts
381
- └─ page.tsx
382
- ```
432
+ Structured server logging is available through `bcp/server`:
383
433
 
384
434
  ```ts
385
- // app/dashboard/guard.ts
386
435
  import {
387
- getSession,
388
- redirect,
436
+ logger,
437
+ requestLogger,
389
438
  } from "bcp/server";
390
439
 
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
- );
440
+ logger.info(
441
+ "Application event",
442
+ {
443
+ feature:
444
+ "catalog",
404
445
  }
405
-
406
- return {
407
- session,
408
- role:
409
- session.role,
410
- };
411
- }
446
+ );
412
447
  ```
413
448
 
414
- Descendant loaders receive the merged result as `guardData`, so they do not need to repeat `getSession()`:
449
+ Request-scoped logging can automatically include request identity:
415
450
 
416
451
  ```ts
417
- export async function loader({
418
- params,
419
- guardData,
420
- }) {
452
+ export async function loader() {
453
+ const log =
454
+ await requestLogger({
455
+ feature:
456
+ "categories",
457
+ });
458
+
459
+ log.info(
460
+ "Loading categories"
461
+ );
462
+
421
463
  return {
422
- id:
423
- params.id,
424
- user:
425
- guardData.session,
426
- role:
427
- guardData.role,
464
+ items: [],
428
465
  };
429
466
  }
430
467
  ```
431
468
 
432
- A page can consume the same authorization data with `useGuardData<T>()`:
433
-
434
- ```tsx
435
- "use client";
469
+ Environment controls:
436
470
 
437
- import {
438
- useGuardData,
439
- } from "bcp";
471
+ ```env
472
+ BCP_LOG_LEVEL=debug
473
+ BCP_LOG_FORMAT=json
474
+ ```
440
475
 
441
- export default function DashboardPage() {
442
- const guard =
443
- useGuardData<{
444
- session: {
445
- email: string;
446
- };
447
- role: string;
448
- }>();
476
+ Supported levels:
449
477
 
450
- return (
451
- <main>
452
- {guard.session.email}
453
- </main>
454
- );
455
- }
478
+ ```text
479
+ debug
480
+ info
481
+ warn
482
+ error
483
+ silent
456
484
  ```
457
485
 
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.
486
+ Supported formats:
459
487
 
460
- See [Protected Route Guards](docs/route-guards.md) for nested role policies, serialization rules, middleware ordering, cookie behavior and production hardening.
488
+ ```text
489
+ pretty
490
+ json
491
+ ```
461
492
 
462
- ## Form actions and server mutations
493
+ Read more: [Logging and Observability](docs/development-logging.md)
463
494
 
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.
495
+ ## File upload
465
496
 
466
- ```text
467
- app/users/[id]/
468
- ├─ guard.ts
469
- ├─ loader.ts
470
- ├─ actions.ts
471
- └─ page.tsx
472
- ```
497
+ BCP `0.1.24` introduced multipart parsing and file validation:
473
498
 
474
499
  ```ts
475
- // app/users/[id]/actions.ts
476
- import "bcp/server-only";
477
-
478
500
  import {
479
- redirect,
480
- type PageActionContext,
501
+ parseMultipartFormData,
502
+ requireUploadedFile,
503
+ saveUploadedFile,
481
504
  } from "bcp/server";
482
505
 
483
- import {
484
- revalidatePath,
485
- } from "bcp/cache";
486
-
487
- export async function saveUser(
488
- formData: FormData,
489
- context: PageActionContext
506
+ export async function POST(
507
+ request: Request
490
508
  ) {
491
- const name =
492
- String(
493
- formData.get("name") ?? ""
494
- ).trim();
509
+ const formData =
510
+ await parseMultipartFormData(
511
+ request,
512
+ {
513
+ maxBytes:
514
+ 8 * 1024 * 1024,
515
+ }
516
+ );
495
517
 
496
- // await db.execute(...)
518
+ const file =
519
+ requireUploadedFile(
520
+ formData,
521
+ "file",
522
+ {
523
+ maxBytes:
524
+ 5 * 1024 * 1024,
525
+ allowedTypes: [
526
+ "image/png",
527
+ "image/jpeg",
528
+ "image/webp",
529
+ ],
530
+ allowedExtensions: [
531
+ ".png",
532
+ ".jpg",
533
+ ".jpeg",
534
+ ".webp",
535
+ ],
536
+ }
537
+ );
497
538
 
498
- revalidatePath("/users");
539
+ return Response.json(
540
+ await saveUploadedFile(
541
+ file,
542
+ {
543
+ directory:
544
+ "./uploads",
545
+ }
546
+ )
547
+ );
548
+ }
549
+ ```
499
550
 
500
- if (!name) {
501
- return {
502
- ok: false,
503
- message: "Name is required",
504
- };
505
- }
551
+ Upload helpers provide:
506
552
 
507
- if (
508
- context.searchParams.get("done") === "1"
509
- ) {
510
- return redirect(
511
- "/users",
512
- 303
513
- );
514
- }
553
+ - multipart validation,
554
+ - total request and per-file size limits,
555
+ - MIME and extension allowlists,
556
+ - required/optional file fields,
557
+ - safe UUID-based storage names,
558
+ - filename sanitization,
559
+ - path traversal protection,
560
+ - no-overwrite-by-default persistence,
561
+ - SHA-256 checksum metadata.
515
562
 
516
- return {
517
- ok: true,
518
- id: context.params.id,
519
- name,
520
- method: context.method,
521
- };
522
- }
563
+ The security gateway applies `server.bodyLimit` / `BCP_BODY_LIMIT` before application upload parsing. Applications accepting larger files must raise that outer limit explicitly.
564
+
565
+ ```ts
566
+ import {
567
+ defineConfig,
568
+ } from "bcp/config";
569
+
570
+ export default defineConfig({
571
+ server: {
572
+ bodyLimit:
573
+ 10 * 1024 * 1024,
574
+ },
575
+ });
523
576
  ```
524
577
 
525
- Client forms reference the named server action without importing it into the browser graph:
578
+ MIME type and extension validation are metadata checks, not content-signature verification. Security-sensitive applications should additionally verify content and use malware scanning where appropriate.
526
579
 
527
- ```tsx
528
- "use client";
580
+ Read more: [File Upload](docs/file-upload.md)
581
+
582
+ ## Storage adapters
529
583
 
584
+ BCP `0.1.25` adds the first application-facing storage abstraction.
585
+
586
+ ```ts
530
587
  import {
531
- Form,
532
- useActionData,
533
- useActionError,
534
- useFormStatus,
535
- } from "bcp";
588
+ createLocalStorage,
589
+ storeUploadedFile,
590
+ } from "bcp/server";
536
591
 
537
- function SubmitButton() {
538
- const status =
539
- useFormStatus();
592
+ const storage =
593
+ createLocalStorage({
594
+ directory:
595
+ "./uploads",
596
+ });
540
597
 
541
- return (
542
- <button
543
- type="submit"
544
- disabled={status.pending}
545
- >
546
- {status.pending
547
- ? "Saving..."
548
- : "Save"}
549
- </button>
598
+ const stored =
599
+ await storeUploadedFile(
600
+ file,
601
+ {
602
+ storage,
603
+ key:
604
+ "avatars/user-101.webp",
605
+ }
550
606
  );
551
- }
607
+ ```
552
608
 
553
- export default function UserForm() {
554
- const result =
555
- useActionData<{
556
- ok: boolean;
557
- message?: string;
558
- }>();
559
- const error =
560
- useActionError();
609
+ The `StorageAdapter` contract contains:
561
610
 
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
- );
574
- }
611
+ ```text
612
+ put
613
+ stat
614
+ read
615
+ exists
616
+ delete
575
617
  ```
576
618
 
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.
619
+ Application code can depend on this contract instead of depending directly on filesystem paths.
578
620
 
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.
621
+ The built-in adapter in `0.1.25` is local filesystem storage. Cloud/object-storage adapters are planned for a later milestone.
580
622
 
581
- ## JWT cookie sessions
623
+ Storage keys are logical relative paths. Absolute paths and traversal segments are rejected.
582
624
 
583
- BCP 0.1.8 adds HS256 JWT cookie sessions directly to `bcp/server`. Configure a server-only secret of at least 32 bytes:
625
+ Read more: [Storage and File Delivery](docs/storage.md)
584
626
 
585
- ```env
586
- BCP_SESSION_SECRET=replace-this-with-a-long-random-secret-at-least-32-bytes
587
- ```
627
+ ## Production file delivery
588
628
 
589
- Login-style APIs can create an HttpOnly session cookie without manually signing or serializing the JWT:
629
+ Storage objects can be returned through a hardened HTTP response helper:
590
630
 
591
631
  ```ts
592
632
  import {
593
- createSession,
594
- json,
633
+ createStorageResponse,
595
634
  } from "bcp/server";
596
635
 
597
- export async function POST() {
598
- await createSession(
599
- {
600
- userId: 42,
601
- email: "user@example.com",
602
- role: "admin",
603
- },
636
+ export async function GET(
637
+ request: Request
638
+ ) {
639
+ return createStorageResponse(
640
+ request,
641
+ storage,
642
+ "documents/report.pdf",
604
643
  {
605
- issuer: "my-app",
606
- audience: "my-app-users",
644
+ disposition:
645
+ "attachment",
646
+ downloadName:
647
+ "report.pdf",
607
648
  }
608
649
  );
609
-
610
- return json({
611
- success: true,
612
- });
613
650
  }
614
651
  ```
615
652
 
616
- Protected API routes can verify and read the session:
617
-
618
- ```ts
619
- import {
620
- getSession,
621
- json,
622
- } from "bcp/server";
653
+ `createStorageResponse()` supports:
623
654
 
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
- });
655
+ - `GET`,
656
+ - `HEAD`,
657
+ - `ETag`,
658
+ - `Last-Modified`,
659
+ - `If-None-Match`,
660
+ - `If-Modified-Since`,
661
+ - `If-Range`,
662
+ - single byte ranges with `206 Partial Content`,
663
+ - `304 Not Modified`,
664
+ - `416 Range Not Satisfiable`,
665
+ - safe `Content-Disposition` filenames.
634
666
 
635
- if (!session) {
636
- return json(
637
- {
638
- error: "Unauthorized",
639
- },
640
- {
641
- status: 401,
642
- }
643
- );
644
- }
667
+ The default cache policy is intentionally conservative:
645
668
 
646
- return json({
647
- userId:
648
- session.userId,
649
- email:
650
- session.email,
651
- role:
652
- session.role,
653
- });
654
- }
669
+ ```text
670
+ private, max-age=0, must-revalidate
655
671
  ```
656
672
 
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.
673
+ Public immutable caching must be opted into explicitly.
658
674
 
659
- ## create-bcp-app auth preset
675
+ Multiple byte ranges are intentionally not supported in `0.1.25`.
660
676
 
661
- BCP 0.1.8 can scaffold the JWT cookie foundation automatically:
677
+ Read more: [Storage and File Delivery](docs/storage.md)
662
678
 
663
- ```bash
664
- npx create-bcp-app my-app --database mysql --auth jwt-cookie
679
+ ## Caching
680
+
681
+ BCP includes server response caching and revalidation primitives used by development and standalone production runtimes.
682
+
683
+ Read more: [Caching](docs/caching.md)
684
+
685
+ ## Security
686
+
687
+ The framework security layer includes request body limits and production request handling defaults. Application authorization is still the responsibility of route guards and application logic.
688
+
689
+ Storage keys, filenames and MIME metadata must not be treated as authorization decisions.
690
+
691
+ Read more: [Security](docs/security.md)
692
+
693
+ ## Environment and configuration
694
+
695
+ Application configuration lives in:
696
+
697
+ ```text
698
+ bcp.config.ts
665
699
  ```
666
700
 
667
- Interactive setup offers `None` and `JWT Cookie`. Selecting JWT Cookie creates:
701
+ Public environment variables use the prefix:
668
702
 
669
703
  ```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
704
+ BCP_PUBLIC_
674
705
  ```
675
706
 
676
- and adds:
707
+ Server-only environment values remain server-side and are not emitted into browser bundles.
677
708
 
678
- ```env
679
- BCP_SESSION_SECRET=
709
+ Read more: [Configuration](docs/configuration.md)
710
+
711
+ ## Developer tools
712
+
713
+ BCP includes project diagnostics:
714
+
715
+ ```bash
716
+ bcp doctor
717
+ bcp inspect
680
718
  ```
681
719
 
682
- to `.env.example`.
720
+ `bcp doctor` checks areas such as:
683
721
 
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.
722
+ - project structure,
723
+ - BCP installation,
724
+ - React / ReactDOM compatibility,
725
+ - duplicate framework copies,
726
+ - environment/config loading,
727
+ - route conflicts,
728
+ - client/server boundaries.
685
729
 
686
- The generated MySQL preset uses a reusable `db` pool and separate `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD` and `DB_NAME` settings.
730
+ `bcp inspect` reports resolved configuration, environment sources, dependencies and discovered routes.
687
731
 
688
- ## Metadata
732
+ Read more: [Developer Tools](docs/developer-tools.md)
689
733
 
690
- ```ts
691
- import type {
692
- Metadata,
693
- } from "bcp";
734
+ ## Windows CLI
735
+
736
+ Microsoft SQL Server also installs an executable named `bcp.exe` on Windows.
694
737
 
695
- export const metadata: Metadata = {
696
- title: "Dashboard",
697
- description: "Dashboard page",
698
- };
738
+ BCP therefore publishes the collision-free alias:
739
+
740
+ ```text
741
+ bcp-framework
699
742
  ```
700
743
 
701
- Dynamic routes can export `generateMetadata()` and receive route params.
744
+ Inside project npm scripts, `bcp` remains safe because npm puts `node_modules/.bin` at the front of `PATH`.
702
745
 
703
- ## Cache and revalidation
746
+ For direct PowerShell usage, prefer:
704
747
 
705
- ```ts
706
- export const revalidate = 60;
748
+ ```powershell
749
+ npm exec -- bcp-framework doctor
750
+ npm exec -- bcp-framework inspect
751
+ npm exec -- bcp-framework dev
752
+ npm exec -- bcp-framework build
707
753
  ```
708
754
 
709
- Server data can use:
755
+ ## CLI reference
710
756
 
711
- ```ts
712
- import {
713
- cache,
714
- dedupe,
715
- revalidatePath,
716
- revalidateTag,
717
- } from "bcp/cache";
757
+ ```bash
758
+ bcp dev
759
+ bcp routes
760
+ bcp build
761
+ bcp start
762
+ bcp doctor
763
+ bcp doctor --json
764
+ bcp inspect
765
+ bcp inspect --json
766
+ bcp update
767
+ bcp version
768
+
769
+ bcp db create create_users
770
+ bcp db migrate
771
+ bcp db status
772
+ bcp db rollback
718
773
  ```
719
774
 
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.
775
+ ## Development behavior
721
776
 
722
- ## Middleware
777
+ BCP includes Fast Refresh and deterministic development hydration behavior.
723
778
 
724
- ```ts
725
- import {
726
- next,
727
- redirect,
728
- type MiddlewareRequest,
729
- } from "bcp/middleware";
779
+ Recent stabilization work also covers:
730
780
 
731
- export function middleware(
732
- request: MiddlewareRequest
733
- ) {
734
- if (
735
- request.nextUrl.pathname === "/private" &&
736
- !request.cookies.has("session")
737
- ) {
738
- return redirect("/login");
739
- }
781
+ - Windows line-ending parity,
782
+ - multiline JSX hydration parity,
783
+ - duplicate BCP installation detection,
784
+ - automatic page-route/client-bundle graph resynchronization,
785
+ - standalone authentication guard request-context parity.
740
786
 
741
- return next();
742
- }
743
- ```
787
+ A development topology change should no longer require manually deleting `.bcp-framework` to recover a missing client route bundle.
744
788
 
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.
789
+ Read more: [Hydration](docs/hydration.md)
746
790
 
747
- ## Configuration
791
+ ## Production build
748
792
 
749
- ```ts
750
- import {
751
- defineConfig,
752
- } from "bcp/config";
793
+ Build an application:
753
794
 
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
- });
795
+ ```bash
796
+ npm run build
778
797
  ```
779
798
 
780
- Development/build precedence is CLI > `BCP_*` environment > `bcp.config.*` > defaults. Standalone start uses CLI > runtime environment > the config frozen into the production build.
781
-
782
- ## Production
799
+ Start the generated standalone runtime:
783
800
 
784
801
  ```bash
785
- npm run build
786
- npm start
802
+ npm run start
787
803
  ```
788
804
 
789
- The standalone output is generated under:
805
+ Production output is written under:
790
806
 
791
807
  ```text
792
808
  .bcp-framework/build/
793
809
  ├─ client/
794
810
  ├─ public/
795
811
  └─ server/
796
- ├─ server.mjs
797
- ├─ middleware.mjs
798
- ├─ guards.mjs
799
- ├─ actions.mjs
800
- ├─ cache-manifest.json
801
- └─ config.json
812
+ └─ server.mjs
802
813
  ```
803
814
 
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.
815
+ The standalone runtime composes production middleware, security, cache, actions, guards, loaders and page rendering into the final HTTP pipeline.
816
+
817
+ Runtime hostname/port overrides can be supplied to `bcp start` without rebuilding the application.
818
+
819
+ Read more: [Deployment](docs/deployment.md)
805
820
 
806
- ## Package preparation
821
+ ## Updating BCP
822
+
823
+ ```bash
824
+ bcp update
825
+ bcp update --check
826
+ bcp update --dry-run
827
+ bcp update 0.1.25
828
+ bcp update next
829
+ ```
807
830
 
808
- The development monorepo stays private. Publishable artifacts are produced separately:
831
+ Projects created before the updater was introduced can bootstrap it once using the public package:
809
832
 
810
833
  ```bash
811
- npm run package:prepare
812
- npm run package:check
813
- npm run release:check
834
+ npx @chidchanun/bcp@latest update
814
835
  ```
815
836
 
816
- For the final Release Candidate gate:
837
+ Read more: [Updating](docs/updating.md)
838
+
839
+ ## Framework development
840
+
841
+ When working inside the BCP Framework repository itself:
842
+
843
+ ```bash
844
+ npm install
845
+ npm run typecheck
846
+ npm run test:unit
847
+ npm run test:e2e
848
+ npm run test:package
849
+ ```
850
+
851
+ Full release-candidate validation:
817
852
 
818
853
  ```bash
819
- npm login
820
854
  npm run rc:check
821
855
  ```
822
856
 
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.
857
+ A version must not be tagged or published until its release candidate and packed-package verification pass.
824
858
 
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.
859
+ Read more: [Releasing](docs/releasing.md)
826
860
 
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.
861
+ ## Documentation source
828
862
 
829
- No real npm publish command is run automatically by the repository.
863
+ The `docs/` directory is the source content intended to feed the future **`bcp-docs-web`** documentation website.
830
864
 
831
- ## Documentation
865
+ Start with:
832
866
 
867
+ - [Documentation Source Map](docs/README.md)
833
868
  - [Getting Started](docs/getting-started.md)
869
+ - [Configuration](docs/configuration.md)
834
870
  - [Application Modules](docs/application-modules.md)
835
- - [Server Request APIs](docs/server-request-apis.md)
871
+ - [Routing](docs/routing.md)
836
872
  - [Server Data Loaders](docs/server-data-loaders.md)
837
- - [Protected Route Guards](docs/route-guards.md)
838
- - [Form Actions and Server Mutations](docs/form-actions.md)
873
+ - [Route Guards](docs/route-guards.md)
874
+ - [Form Actions](docs/form-actions.md)
875
+ - [Server Request APIs](docs/server-request-apis.md)
839
876
  - [Validation](docs/validation.md)
840
877
  - [Error Handling](docs/error-handling.md)
878
+ - [File Upload](docs/file-upload.md)
879
+ - [Storage and File Delivery](docs/storage.md)
841
880
  - [Authentication](docs/authentication.md)
842
881
  - [Auth Route Guards](docs/auth-route-guards.md)
843
- - [JWT Cookie Sessions](docs/session-auth.md)
882
+ - [JWT Sessions](docs/session-auth.md)
844
883
  - [Database](docs/database.md)
845
884
  - [Database Migrations](docs/database-migrations.md)
846
885
  - [Middleware](docs/middleware.md)
847
- - [Hydration and deterministic rendering](docs/hydration.md)
886
+ - [Hydration](docs/hydration.md)
848
887
  - [Developer Tools](docs/developer-tools.md)
849
- - [Updating BCP Framework](docs/updating.md)
850
- - [Routing](docs/routing.md)
851
- - [Configuration](docs/configuration.md)
888
+ - [Logging and Observability](docs/development-logging.md)
852
889
  - [Caching](docs/caching.md)
853
890
  - [Security](docs/security.md)
854
891
  - [Deployment](docs/deployment.md)
892
+ - [Updating](docs/updating.md)
855
893
  - [Releasing](docs/releasing.md)
856
894
 
857
- ## Requirements
895
+ ## Documentation website model
896
+
897
+ When `bcp-docs-web` is built, the recommended top-level information architecture is:
898
+
899
+ ```text
900
+ Getting Started
901
+ Routing & Data
902
+ Authentication
903
+ Database
904
+ Runtime & Infrastructure
905
+ API Reference
906
+ Releases
907
+ ```
908
+
909
+ `docs/README.md` contains the proposed route-to-source mapping for that website.
910
+
911
+ ## Release history
912
+
913
+ Release notes are stored under:
914
+
915
+ ```text
916
+ docs/releases/
917
+ ```
918
+
919
+ Recent milestones:
920
+
921
+ | Version | Milestone |
922
+ | --- | --- |
923
+ | `0.1.20` | Hydration line-ending stabilization |
924
+ | `0.1.21` | Hydration semantic parity |
925
+ | `0.1.22` | Developer tools and diagnostics |
926
+ | `0.1.23` | Logging and observability |
927
+ | `0.1.24` | File Upload Foundation |
928
+ | `0.1.25` | Storage Adapters and File Delivery |
929
+
930
+ ## Roadmap
931
+
932
+ Current planned direction after `0.1.25`:
933
+
934
+ 1. S3-compatible / cloud storage adapter integration.
935
+ 2. Production upload streaming.
936
+ 3. Broader storage adapter ecosystem.
937
+ 4. Additional production hardening as new workloads expose edge cases.
858
938
 
859
- BCP Framework currently targets Node.js 24.11 or newer and React 19.
939
+ Roadmap items are plans, not published API guarantees.
860
940
 
861
941
  ## License
862
942
 
863
- BCP Framework and `create-bcp-app` are released under the MIT License. See [LICENSE](LICENSE).
943
+ BCP Framework and `create-bcp-app` are released under the MIT License.