@chidchanun/bcp 0.1.10 → 0.1.12

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/CHANGELOG.md CHANGED
@@ -2,6 +2,47 @@
2
2
 
3
3
  All notable framework changes are tracked here before release.
4
4
 
5
+ ## 0.1.11 - Form actions and server mutations
6
+
7
+ ### Route mutations
8
+
9
+ - Added route-level `actions.ts` / `actions.tsx` files next to `page.tsx` for server-only create, update and delete operations without requiring an API route for every page-owned mutation.
10
+ - Actions are named exports and receive browser `FormData` plus route `params`, target `searchParams`, merged `guardData` and the semantic mutation `method`.
11
+ - Supported action methods are `POST`, `PUT`, `PATCH` and `DELETE`; GET remains a loader/API read concern.
12
+ - Added `PageAction`, `PageActionContext` and `PageActionMethod` types through the server-only `bcp/server` entrypoint.
13
+ - Action modules may use normal server-only application/database imports plus request/session APIs such as `cookies()`, `getSession()`, `requestUrl()` and `requestMethod()`.
14
+ - `requestMethod()` reflects the semantic action method even though progressive HTML forms use POST as the browser transport.
15
+ - Actions may return JSON-safe data, `undefined` (normalized to `null`) or a Web `Response` such as `redirect()`.
16
+ - Action data uses JSON-safe validation and rejects unsupported/circular values before browser transport.
17
+
18
+ ### Form client API and progressive enhancement
19
+
20
+ - Added public `<Form action="name">` with `post`, `put`, `patch` and `delete` semantic methods.
21
+ - Added `useFormStatus()` for pending/action/method/error state, `useActionData<T>()` for the latest serializable mutation result and `useActionError()` for transport/runtime failures.
22
+ - Enhanced submissions send the original `FormData` through the internal `/_bcp/action` transport without a document reload.
23
+ - Named actions use strings instead of importing server functions into the client graph, preserving server-only boundaries until the later compiler-backed Server Actions milestone.
24
+ - Same-origin redirects from enhanced actions continue through BCP client navigation while response cookies remain intact.
25
+ - Forms without JavaScript use normal HTML POST and POST/Redirect/GET `303` behavior for data-returning actions, preventing duplicate resubmission on browser refresh.
26
+ - Added optional `<Form refresh>` support to rerun the current BCP navigation/loader after a successful data-returning mutation.
27
+
28
+ ### Authorization, caching and production
29
+
30
+ - Route guards execute before matching actions and can stop a mutation with a redirect/Response; merged authorization data is passed to the action through `context.guardData`.
31
+ - Project middleware and framework security remain outside the action gateway, so mutation requests keep the existing request interception and body-limit policy.
32
+ - Cookies created by actions or guards are preserved for data and redirect responses in both enhanced and progressive paths.
33
+ - `revalidatePath()` and `revalidateTag()` can be called directly inside actions using the shared process-local BCP cache runtime.
34
+ - Pages with sibling form actions are excluded from the automatic production response-cache manifest to avoid stale POST/Redirect/GET pages after mutations.
35
+ - Standalone builds generate `server/actions.mjs` only when action routes exist and execute bundled action modules entirely on the server.
36
+ - Added development and standalone action gateways without exposing action/database modules to browser bundles.
37
+
38
+ ### Reliability and documentation
39
+
40
+ - Added `/action-demo/[id]` plus guarded mutation fixtures for FormData, semantic methods, cookies, redirects, cache revalidation hooks, progressive enhancement and guard-data handoff.
41
+ - Added unit coverage for action naming, method normalization, route context, Response returns and serialization rejection.
42
+ - Added development integration and standalone E2E coverage for enhanced mutations, no-JavaScript PRG behavior, response cookies and guards-before-actions ordering.
43
+ - Added publish-artifact smoke checks for the public Form API, server mutation runtime, dev gateway, standalone action gateway and production action bundle.
44
+ - Added dedicated Form Actions and Server Mutations documentation.
45
+
5
46
  ## 0.1.10 - Framework updater and protected route guards
6
47
 
7
48
  ### Framework updater
@@ -20,7 +61,7 @@ All notable framework changes are tracked here before release.
20
61
  ### Protected routes and auth guards
21
62
 
22
63
  - Added scoped `guard.ts` / `guard.tsx` files that protect pages in their route directory and descendant page directories.
23
- - Guards execute from app root toward the matched page and receive `params`, target `searchParams` and merged ancestor `parentData`.
64
+ - Guards execute from app root toward the matched page and receive `params`, target `searchParams` and merged ancestor `guardData`.
24
65
  - A guard may return JSON-safe authorization data or a Web `Response`; redirects stop the pipeline before loader/page execution.
25
66
  - Guards run inside the normal BCP request context, so sessions, cookies, headers, request URLs, request IDs and other `bcp/server` helpers are available directly.
26
67
  - Loader context now receives merged `guardData`, avoiding repeated session/role lookups after a guard has already validated the request.
@@ -65,7 +106,7 @@ All notable framework changes are tracked here before release.
65
106
  - Navigation loaders receive a request context for the target page URL rather than the internal navigation transport URL, preserving target query parameters, cookies, sessions and request helpers.
66
107
  - Loader redirects are converted to a navigation redirect payload while preserving all response cookies; same-origin redirects continue through the SPA router and cross-origin redirects use normal browser navigation.
67
108
  - Non-navigation Web `Response` values continue to fall back to a document request so their original HTTP semantics remain authoritative.
68
- - Client navigation explicitly sends same-origin credentials, aborts superseded requests and uses a monotonically increasing navigation sequence so stale responses cannot update router state after a newer navigation has started.
109
+ - Client navigation explicitly sends same-origin credentials, aborts superseded requests and uses a monotonically increasing navigation sequence so stale responses cannot update the active route after a newer navigation has started.
69
110
  - Navigation redirect chains are capped at 10 redirects to prevent loops.
70
111
  - `useNavigation()` becomes active before the server request starts and route loading markup can be displayed while the target client runtime/assets finish after the payload arrives.
71
112
  - Standalone loader navigation is placed inside the existing middleware/security chain rather than bypassing application middleware or framework security gateways.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  BCP Framework is a React full-stack framework with file-based routing, SSR, client navigation, API routes, middleware, metadata, client islands, cache/revalidation, security defaults and standalone production builds.
4
4
 
5
- > Current release target: `0.1.10`. BCP is still pre-1.0 and validates each release candidate before the manual npm publish step.
5
+ > Current release target: `0.1.11`. BCP is still pre-1.0 and validates each release candidate before the manual npm publish step.
6
6
 
7
7
  ## Quick start
8
8
 
@@ -39,6 +39,7 @@ app/
39
39
  │ └─ users/
40
40
  │ └─ [id]/
41
41
  │ ├─ loader.ts
42
+ │ ├─ actions.ts
42
43
  │ └─ page.tsx
43
44
  └─ api/
44
45
  └─ hello/
@@ -77,7 +78,7 @@ Server-only modules can declare:
77
78
  import "bcp/server-only";
78
79
  ```
79
80
 
80
- 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` or `guard.ts`.
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`.
81
82
 
82
83
  See [Application Modules](docs/application-modules.md) for the complete boundary model and examples.
83
84
 
@@ -116,7 +117,7 @@ bcp update --dry-run
116
117
  Or select a published version/dist-tag explicitly:
117
118
 
118
119
  ```bash
119
- bcp update 0.1.10
120
+ bcp update 0.1.11
120
121
  bcp update next
121
122
  ```
122
123
 
@@ -158,7 +159,7 @@ Supported methods include GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS. HEAD
158
159
 
159
160
  ## Server request APIs
160
161
 
161
- 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 and route guards.
162
+ 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.
162
163
 
163
164
  ```ts
164
165
  import {
@@ -309,7 +310,7 @@ See [Server Data Loaders](docs/server-data-loaders.md) for serialization rules,
309
310
 
310
311
  ## Protected routes and auth guards
311
312
 
312
- The `0.1.10` release target 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.
313
+ 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.
313
314
 
314
315
  ```text
315
316
  app/dashboard/
@@ -395,10 +396,129 @@ export default function DashboardPage() {
395
396
  }
396
397
  ```
397
398
 
398
- Nested guards execute from root to child and receive ancestor output as `parentData`. 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.
399
+ 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.
399
400
 
400
401
  See [Protected Route Guards](docs/route-guards.md) for nested role policies, serialization rules, middleware ordering, cookie behavior and production hardening.
401
402
 
403
+ ## Form actions and server mutations
404
+
405
+ 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.
406
+
407
+ ```text
408
+ app/users/[id]/
409
+ ├─ guard.ts
410
+ ├─ loader.ts
411
+ ├─ actions.ts
412
+ └─ page.tsx
413
+ ```
414
+
415
+ ```ts
416
+ // app/users/[id]/actions.ts
417
+ import "bcp/server-only";
418
+
419
+ import {
420
+ redirect,
421
+ type PageActionContext,
422
+ } from "bcp/server";
423
+
424
+ import {
425
+ revalidatePath,
426
+ } from "bcp/cache";
427
+
428
+ export async function saveUser(
429
+ formData: FormData,
430
+ context: PageActionContext
431
+ ) {
432
+ const name =
433
+ String(
434
+ formData.get("name") ?? ""
435
+ ).trim();
436
+
437
+ // await db.execute(...)
438
+
439
+ revalidatePath("/users");
440
+
441
+ if (!name) {
442
+ return {
443
+ ok: false,
444
+ message: "Name is required",
445
+ };
446
+ }
447
+
448
+ if (
449
+ context.searchParams.get("done") === "1"
450
+ ) {
451
+ return redirect(
452
+ "/users",
453
+ 303
454
+ );
455
+ }
456
+
457
+ return {
458
+ ok: true,
459
+ id: context.params.id,
460
+ name,
461
+ method: context.method,
462
+ };
463
+ }
464
+ ```
465
+
466
+ Client forms reference the named server action without importing it into the browser graph:
467
+
468
+ ```tsx
469
+ "use client";
470
+
471
+ import {
472
+ Form,
473
+ useActionData,
474
+ useActionError,
475
+ useFormStatus,
476
+ } from "bcp";
477
+
478
+ function SubmitButton() {
479
+ const status =
480
+ useFormStatus();
481
+
482
+ return (
483
+ <button
484
+ type="submit"
485
+ disabled={status.pending}
486
+ >
487
+ {status.pending
488
+ ? "Saving..."
489
+ : "Save"}
490
+ </button>
491
+ );
492
+ }
493
+
494
+ export default function UserForm() {
495
+ const result =
496
+ useActionData<{
497
+ ok: boolean;
498
+ message?: string;
499
+ }>();
500
+ const error =
501
+ useActionError();
502
+
503
+ return (
504
+ <Form
505
+ action="saveUser"
506
+ method="patch"
507
+ refresh
508
+ >
509
+ <input name="name" />
510
+ <SubmitButton />
511
+ {result?.message}
512
+ {error?.message}
513
+ </Form>
514
+ );
515
+ }
516
+ ```
517
+
518
+ 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.
519
+
520
+ 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.
521
+
402
522
  ## JWT cookie sessions
403
523
 
404
524
  BCP 0.1.8 adds HS256 JWT cookie sessions directly to `bcp/server`. Configure a server-only secret of at least 32 bytes:
@@ -538,7 +658,7 @@ import {
538
658
  } from "bcp/cache";
539
659
  ```
540
660
 
541
- The current cache implementation is process-local and intentionally does not provide distributed invalidation across multiple Node.js instances. Loader-backed and route-guarded pages are excluded from the automatic production response-cache manifest because their output may depend on request/session identity. Cache server data explicitly only when the application has a safe user-aware cache key and invalidation strategy.
661
+ 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.
542
662
 
543
663
  ## Middleware
544
664
 
@@ -563,7 +683,7 @@ export function middleware(
563
683
  }
564
684
  ```
565
685
 
566
- Use middleware for request-wide interception and `guard.ts` for page-subtree authorization that needs to feed identity/role data into loaders and pages. In standalone production, project middleware runs before route guards.
686
+ 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.
567
687
 
568
688
  ## Configuration
569
689
 
@@ -617,11 +737,12 @@ The standalone output is generated under:
617
737
  ├─ server.mjs
618
738
  ├─ middleware.mjs
619
739
  ├─ guards.mjs
740
+ ├─ actions.mjs
620
741
  ├─ cache-manifest.json
621
742
  └─ config.json
622
743
  ```
623
744
 
624
- `guards.mjs` is generated only when the application contains protected route guards.
745
+ `guards.mjs` is generated only when the application contains protected route guards. `actions.mjs` is generated only when the application contains route form actions.
625
746
 
626
747
  ## Package preparation
627
748
 
@@ -655,6 +776,7 @@ No real npm publish command is run automatically by the repository.
655
776
  - [Server Request APIs](docs/server-request-apis.md)
656
777
  - [Server Data Loaders](docs/server-data-loaders.md)
657
778
  - [Protected Route Guards](docs/route-guards.md)
779
+ - [Form Actions and Server Mutations](docs/form-actions.md)
658
780
  - [JWT Cookie Sessions](docs/session-auth.md)
659
781
  - [Updating BCP Framework](docs/updating.md)
660
782
  - [Routing](docs/routing.md)
@@ -0,0 +1,314 @@
1
+ # Form Actions and Server Mutations
2
+
3
+ BCP form actions provide a route-scoped server mutation primitive for application forms without requiring an API route for every create, update or delete operation.
4
+
5
+ This feature is intentionally separate from future BCP Server Actions. In this release, client pages do **not** import a server function directly. A page submits a named action such as `"saveUser"`, and BCP resolves that name from the sibling `actions.ts` / `actions.tsx` file on the server.
6
+
7
+ ## Route convention
8
+
9
+ ```text
10
+ app/users/[id]/
11
+ ├─ guard.ts # optional
12
+ ├─ loader.ts # optional read path
13
+ ├─ actions.ts # server-only mutation path
14
+ └─ page.tsx
15
+ ```
16
+
17
+ Only one of `actions.ts` or `actions.tsx` may exist next to a page.
18
+
19
+ ## Defining an action
20
+
21
+ ```ts
22
+ // app/users/[id]/actions.ts
23
+ import "bcp/server-only";
24
+
25
+ import {
26
+ cookies,
27
+ redirect,
28
+ type PageActionContext,
29
+ } from "bcp/server";
30
+
31
+ import {
32
+ revalidatePath,
33
+ revalidateTag,
34
+ } from "bcp/cache";
35
+
36
+ export async function saveUser(
37
+ formData: FormData,
38
+ context: PageActionContext
39
+ ) {
40
+ const name =
41
+ String(
42
+ formData.get("name") ?? ""
43
+ ).trim();
44
+
45
+ // Perform the database mutation here.
46
+
47
+ const cookieStore =
48
+ await cookies();
49
+
50
+ cookieStore.set(
51
+ "last-user",
52
+ context.params.id,
53
+ {
54
+ httpOnly: true,
55
+ sameSite: "lax",
56
+ path: "/",
57
+ }
58
+ );
59
+
60
+ revalidatePath("/users");
61
+ revalidateTag("users");
62
+
63
+ return {
64
+ ok: true,
65
+ id: context.params.id,
66
+ name,
67
+ };
68
+ }
69
+
70
+ export async function removeUser(
71
+ _formData: FormData,
72
+ context: PageActionContext
73
+ ) {
74
+ // Delete from the database.
75
+
76
+ revalidatePath("/users");
77
+
78
+ return redirect(
79
+ "/users",
80
+ 303
81
+ );
82
+ }
83
+ ```
84
+
85
+ An action receives:
86
+
87
+ - `formData` — the submitted browser `FormData`.
88
+ - `context.params` — matched dynamic route parameters.
89
+ - `context.searchParams` — a fresh `URLSearchParams` for the target page URL.
90
+ - `context.guardData` — merged data from route guards that ran before the action.
91
+ - `context.method` — the semantic mutation method: `POST`, `PUT`, `PATCH`, or `DELETE`.
92
+
93
+ The normal `bcp/server` request context is active while the action runs, so `cookies()`, `headers()`, `requestUrl()`, `requestMethod()`, `requestId()`, `getSession()` and other server helpers work directly. `requestMethod()` reflects the semantic action method rather than the internal POST transport.
94
+
95
+ ## Rendering a form
96
+
97
+ ```tsx
98
+ "use client";
99
+
100
+ import {
101
+ Form,
102
+ useActionData,
103
+ useActionError,
104
+ useFormStatus,
105
+ } from "bcp";
106
+
107
+ interface SaveResult {
108
+ ok: boolean;
109
+ id: string;
110
+ name: string;
111
+ }
112
+
113
+ export default function UserEditor() {
114
+ return (
115
+ <Form
116
+ action="saveUser"
117
+ method="patch"
118
+ >
119
+ <input
120
+ name="name"
121
+ required
122
+ />
123
+
124
+ <SubmitButton />
125
+ <Result />
126
+ </Form>
127
+ );
128
+ }
129
+
130
+ function SubmitButton() {
131
+ const status =
132
+ useFormStatus();
133
+
134
+ return (
135
+ <button
136
+ type="submit"
137
+ disabled={status.pending}
138
+ >
139
+ {status.pending
140
+ ? "Saving..."
141
+ : "Save"}
142
+ </button>
143
+ );
144
+ }
145
+
146
+ function Result() {
147
+ const data =
148
+ useActionData<SaveResult>();
149
+ const error =
150
+ useActionError();
151
+
152
+ if (error) {
153
+ return (
154
+ <p role="alert">
155
+ {error.message}
156
+ </p>
157
+ );
158
+ }
159
+
160
+ if (!data) {
161
+ return null;
162
+ }
163
+
164
+ return (
165
+ <p>
166
+ Saved {data.name}
167
+ </p>
168
+ );
169
+ }
170
+ ```
171
+
172
+ ## Supported mutation methods
173
+
174
+ `<Form>` supports:
175
+
176
+ ```tsx
177
+ <Form action="createUser" method="post" />
178
+ <Form action="replaceUser" method="put" />
179
+ <Form action="updateUser" method="patch" />
180
+ <Form action="deleteUser" method="delete" />
181
+ ```
182
+
183
+ Browsers only support GET and POST as native HTML form methods. BCP therefore uses POST as the wire transport for progressive enhancement and preserves the intended method separately. The action context and `requestMethod()` still report `PUT`, `PATCH`, or `DELETE` as requested.
184
+
185
+ GET is deliberately not an action method. Reads belong in page loaders or API GET handlers.
186
+
187
+ ## Action return values
188
+
189
+ A mutation may return either:
190
+
191
+ 1. JSON-safe action data.
192
+ 2. `undefined`, which BCP normalizes to `null`.
193
+ 3. A Web `Response`, commonly `redirect()`.
194
+
195
+ Serializable action data follows the same safety model as loader data: primitives, arrays and plain objects are allowed. Functions, symbols, BigInt, non-finite numbers, Date/Map/Set/class instances and circular references are rejected before transport.
196
+
197
+ For validation-style UI in this release, return a serializable object such as:
198
+
199
+ ```ts
200
+ return {
201
+ ok: false,
202
+ fieldErrors: {
203
+ email: "Email is required",
204
+ },
205
+ };
206
+ ```
207
+
208
+ Typed validation helpers and CSRF-specific APIs are planned as a separate security/validation milestone.
209
+
210
+ ## Pending, data, and transport errors
211
+
212
+ Inside a `<Form>` subtree:
213
+
214
+ - `useFormStatus()` exposes `pending`, `action`, `method`, and the current transport/server error.
215
+ - `useActionData<T>()` exposes the most recent serializable action result.
216
+ - `useActionError()` exposes the most recent thrown transport/runtime error.
217
+
218
+ Submitting the form again clears the previous action data before the new request begins.
219
+
220
+ ## Redirects
221
+
222
+ Actions can return a normal BCP redirect:
223
+
224
+ ```ts
225
+ return redirect(
226
+ "/users",
227
+ 303
228
+ );
229
+ ```
230
+
231
+ With JavaScript enabled, BCP converts the redirect into an action transport payload and continues with client navigation. Without JavaScript, the browser receives the normal HTTP redirect.
232
+
233
+ Cookies set before the redirect are preserved in both paths.
234
+
235
+ ## Revalidation and refreshing loaders
236
+
237
+ `revalidatePath()` and `revalidateTag()` can be called directly inside an action to invalidate BCP data cache entries:
238
+
239
+ ```ts
240
+ revalidatePath("/users");
241
+ revalidateTag("users");
242
+ ```
243
+
244
+ If the current page should immediately rerun its loader after a successful data-returning action, enable the form's `refresh` option:
245
+
246
+ ```tsx
247
+ <Form
248
+ action="saveUser"
249
+ method="patch"
250
+ refresh
251
+ >
252
+ ...
253
+ </Form>
254
+ ```
255
+
256
+ `refresh` uses the existing BCP router refresh path after the action result is received. Redirecting actions do not need `refresh`.
257
+
258
+ ## Progressive enhancement
259
+
260
+ The rendered HTML form posts to BCP's internal action endpoint using a normal browser POST. The target page is recovered from the same-origin referrer when JavaScript is unavailable.
261
+
262
+ When an action returns serializable data and no explicit `Response`, the no-JavaScript path uses POST/Redirect/GET with HTTP `303` back to the original target page. This prevents duplicate browser resubmission on refresh.
263
+
264
+ With JavaScript enabled, `<Form>` intercepts the submit and sends the same `FormData` through the action transport without reloading the document.
265
+
266
+ ## Middleware, guards, and request order
267
+
268
+ For a mutation, the effective server order is:
269
+
270
+ ```text
271
+ Security gateway
272
+
273
+ Project middleware
274
+
275
+ Resolve target page
276
+
277
+ Route guards (root → child)
278
+
279
+ Named action
280
+
281
+ Response cookies / redirect / data
282
+ ```
283
+
284
+ A guard redirect prevents the action from running. Guard data is passed into `context.guardData`, so an authorization lookup does not need to be repeated in the action.
285
+
286
+ ## Why action names are strings
287
+
288
+ This release intentionally uses:
289
+
290
+ ```tsx
291
+ <Form action="saveUser">
292
+ ```
293
+
294
+ and not:
295
+
296
+ ```tsx
297
+ <Form action={saveUser}>
298
+ ```
299
+
300
+ Passing a server function through a client bundle requires a compiler-generated server reference, serialization protocol and action identifier system. That belongs to the later BCP Server Actions milestone. Keeping named route actions separate now prevents server/database modules from being pulled into hydrated client graphs.
301
+
302
+ ## When to use an API route instead
303
+
304
+ Use a form action when the mutation belongs to a BCP page UI.
305
+
306
+ Keep an API route when the caller is an external client such as:
307
+
308
+ - a mobile application,
309
+ - another backend service,
310
+ - a webhook provider,
311
+ - Postman/API consumers,
312
+ - a public or versioned HTTP API.
313
+
314
+ A project can use both patterns: loaders for page reads, guards for authorization, form actions for page-owned mutations, and API routes for external HTTP contracts.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chidchanun/bcp",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "BCP Framework - a React full-stack framework with file-based routing, SSR, APIs, middleware, islands, caching and standalone production builds.",
5
5
  "type": "module",
6
6
  "license": "MIT",