@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 +658 -578
- package/docs/README.md +317 -318
- package/docs/auth-route-guards.md +14 -0
- package/docs/developer-tools.md +45 -22
- package/docs/file-upload.md +280 -0
- package/docs/releases/0.1.24.md +166 -0
- package/docs/releases/0.1.25.md +132 -0
- package/docs/storage.md +285 -0
- package/package.json +1 -1
- package/packages/bundler/src/server-production-actions.ts +15 -0
- package/packages/bundler/src/server-production-guards.ts +15 -0
- package/packages/client/src/server.ts +40 -0
- package/packages/server/src/dev-route-graph.ts +91 -0
- package/packages/server/src/file-delivery.ts +629 -0
- package/packages/server/src/middleware-dev-server.ts +217 -9
- package/packages/server/src/storage.ts +829 -0
- package/packages/server/src/upload.ts +556 -0
package/README.md
CHANGED
|
@@ -1,863 +1,943 @@
|
|
|
1
1
|
# BCP Framework
|
|
2
2
|
|
|
3
|
-
BCP Framework is a React full-stack framework
|
|
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
|
-
>
|
|
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
|
-
##
|
|
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
|
-
|
|
11
|
+
BCP provides a single application model for React pages and server code:
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
└─
|
|
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
|
-
##
|
|
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
|
-
|
|
66
|
+
## Requirements
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
} from "@/lib/database";
|
|
63
|
-
```
|
|
68
|
+
- Node.js `24.11` or newer
|
|
69
|
+
- React `19`
|
|
70
|
+
- npm
|
|
64
71
|
|
|
65
|
-
|
|
72
|
+
Database features currently target MySQL.
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
"use client";
|
|
74
|
+
## Quick start
|
|
69
75
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
84
|
+
Default development URL:
|
|
76
85
|
|
|
77
|
-
```
|
|
78
|
-
|
|
86
|
+
```text
|
|
87
|
+
http://localhost:3000
|
|
79
88
|
```
|
|
80
89
|
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
+
## Packages
|
|
88
105
|
|
|
89
|
-
|
|
106
|
+
The public framework package is published as:
|
|
90
107
|
|
|
91
|
-
|
|
108
|
+
```text
|
|
109
|
+
@chidchanun/bcp
|
|
110
|
+
```
|
|
92
111
|
|
|
93
|
-
|
|
112
|
+
Applications normally consume it through the dependency key:
|
|
94
113
|
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
className="
|
|
98
|
-
min-h-screen
|
|
99
|
-
bg-white
|
|
100
|
-
text-slate-950
|
|
101
|
-
"
|
|
102
|
-
/>
|
|
114
|
+
```text
|
|
115
|
+
bcp
|
|
103
116
|
```
|
|
104
117
|
|
|
105
|
-
|
|
118
|
+
This keeps imports concise:
|
|
106
119
|
|
|
107
|
-
|
|
120
|
+
```ts
|
|
121
|
+
import {
|
|
122
|
+
Form,
|
|
123
|
+
Link,
|
|
124
|
+
useLoaderData,
|
|
125
|
+
} from "bcp";
|
|
126
|
+
```
|
|
108
127
|
|
|
109
|
-
|
|
128
|
+
Server-only APIs use dedicated entrypoints such as:
|
|
110
129
|
|
|
111
|
-
|
|
130
|
+
```ts
|
|
131
|
+
import {
|
|
132
|
+
cookies,
|
|
133
|
+
logger,
|
|
134
|
+
requestId,
|
|
135
|
+
} from "bcp/server";
|
|
136
|
+
```
|
|
112
137
|
|
|
113
|
-
##
|
|
138
|
+
## Project structure
|
|
114
139
|
|
|
115
|
-
|
|
140
|
+
A typical application can grow into this layout:
|
|
116
141
|
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
|
|
163
|
+
lib/
|
|
164
|
+
public/
|
|
165
|
+
migrations/
|
|
166
|
+
bcp.config.ts
|
|
167
|
+
package.json
|
|
168
|
+
tsconfig.json
|
|
121
169
|
```
|
|
122
170
|
|
|
123
|
-
|
|
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
|
-
|
|
175
|
+
Page routes are discovered from `app/**/page.tsx`.
|
|
126
176
|
|
|
127
|
-
```
|
|
128
|
-
|
|
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
|
-
|
|
186
|
+
Static routes have priority over dynamic and catch-all routes.
|
|
132
187
|
|
|
133
|
-
|
|
188
|
+
API routes use `route.ts`:
|
|
134
189
|
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
195
|
+
Read more: [Routing](docs/routing.md)
|
|
141
196
|
|
|
142
|
-
|
|
197
|
+
## Layouts and metadata
|
|
143
198
|
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
+
Read more: [Routing](docs/routing.md)
|
|
160
204
|
|
|
161
|
-
##
|
|
205
|
+
## Server data loaders
|
|
162
206
|
|
|
163
|
-
|
|
207
|
+
Place `loader.ts` next to a page when the route needs server-side data.
|
|
164
208
|
|
|
165
|
-
```
|
|
166
|
-
|
|
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
|
-
|
|
221
|
+
Consume the serializable result in a client page:
|
|
170
222
|
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
223
|
+
```tsx
|
|
224
|
+
"use client";
|
|
225
|
+
|
|
226
|
+
import {
|
|
227
|
+
useLoaderData,
|
|
228
|
+
} from "bcp";
|
|
175
229
|
|
|
176
|
-
|
|
230
|
+
export default function UserPage() {
|
|
231
|
+
const data =
|
|
232
|
+
useLoaderData<{
|
|
233
|
+
id: string;
|
|
234
|
+
}>();
|
|
177
235
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
236
|
+
return (
|
|
237
|
+
<main>
|
|
238
|
+
User {data.id}
|
|
239
|
+
</main>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
181
242
|
```
|
|
182
243
|
|
|
183
|
-
|
|
244
|
+
Read more: [Server Data Loaders](docs/server-data-loaders.md)
|
|
184
245
|
|
|
185
|
-
|
|
186
|
-
npx @chidchanun/bcp@latest update
|
|
187
|
-
```
|
|
246
|
+
## Route guards
|
|
188
247
|
|
|
189
|
-
|
|
248
|
+
A route tree can define `guard.ts` to authorize access before the protected route is rendered.
|
|
190
249
|
|
|
191
|
-
|
|
250
|
+
Authentication-aware guards are available through `bcp/auth`:
|
|
192
251
|
|
|
193
|
-
|
|
252
|
+
```ts
|
|
253
|
+
import {
|
|
254
|
+
requireRole,
|
|
255
|
+
} from "bcp/auth";
|
|
194
256
|
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
261
|
+
The standalone production pipeline preserves the same active request context used by authentication and server request APIs.
|
|
205
262
|
|
|
206
|
-
|
|
263
|
+
Read more:
|
|
207
264
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
274
|
+
Read more: [Form Actions](docs/form-actions.md)
|
|
218
275
|
|
|
219
276
|
## Server request APIs
|
|
220
277
|
|
|
221
|
-
|
|
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
|
-
|
|
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()`
|
|
292
|
+
`requestId()` uses a valid incoming `X-Request-Id` when available or generates a stable UUID for the active request.
|
|
260
293
|
|
|
261
|
-
|
|
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
|
-
|
|
266
|
-
|
|
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
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
349
|
+
This allows middleware to run logic both before and after downstream route execution.
|
|
293
350
|
|
|
294
|
-
|
|
351
|
+
Existing middleware v1 behavior remains supported for compatibility.
|
|
295
352
|
|
|
296
|
-
|
|
353
|
+
Read more: [Middleware](docs/middleware.md)
|
|
297
354
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
} from "bcp/
|
|
361
|
+
v,
|
|
362
|
+
validateFormData,
|
|
363
|
+
} from "bcp/validation";
|
|
364
|
+
```
|
|
310
365
|
|
|
311
|
-
|
|
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
|
-
|
|
324
|
-
return redirect(
|
|
325
|
-
"/login",
|
|
326
|
-
303
|
|
327
|
-
);
|
|
328
|
-
}
|
|
368
|
+
Read more: [Validation](docs/validation.md)
|
|
329
369
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
396
|
+
Read more: [Error Handling](docs/error-handling.md)
|
|
341
397
|
|
|
342
|
-
|
|
343
|
-
|
|
398
|
+
## Database
|
|
399
|
+
|
|
400
|
+
Database helpers are exposed through:
|
|
344
401
|
|
|
402
|
+
```ts
|
|
345
403
|
import {
|
|
346
|
-
|
|
347
|
-
} from "bcp";
|
|
404
|
+
db,
|
|
405
|
+
} from "bcp/database";
|
|
406
|
+
```
|
|
348
407
|
|
|
349
|
-
|
|
350
|
-
const data =
|
|
351
|
-
useLoaderData<{
|
|
352
|
-
id: string;
|
|
353
|
-
query: string | null;
|
|
354
|
-
}>();
|
|
408
|
+
The database layer provides:
|
|
355
409
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
416
|
+
Migration commands:
|
|
365
417
|
|
|
366
|
-
|
|
418
|
+
```bash
|
|
419
|
+
bcp db create create_users
|
|
420
|
+
bcp db migrate
|
|
421
|
+
bcp db status
|
|
422
|
+
bcp db rollback
|
|
423
|
+
```
|
|
367
424
|
|
|
368
|
-
|
|
425
|
+
Read more:
|
|
369
426
|
|
|
370
|
-
|
|
427
|
+
- [Database](docs/database.md)
|
|
428
|
+
- [Database Migrations](docs/database-migrations.md)
|
|
371
429
|
|
|
372
|
-
|
|
430
|
+
## Logging and observability
|
|
373
431
|
|
|
374
|
-
|
|
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
|
-
|
|
388
|
-
|
|
436
|
+
logger,
|
|
437
|
+
requestLogger,
|
|
389
438
|
} from "bcp/server";
|
|
390
439
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
449
|
+
Request-scoped logging can automatically include request identity:
|
|
415
450
|
|
|
416
451
|
```ts
|
|
417
|
-
export async function loader({
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
423
|
-
params.id,
|
|
424
|
-
user:
|
|
425
|
-
guardData.session,
|
|
426
|
-
role:
|
|
427
|
-
guardData.role,
|
|
464
|
+
items: [],
|
|
428
465
|
};
|
|
429
466
|
}
|
|
430
467
|
```
|
|
431
468
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
```tsx
|
|
435
|
-
"use client";
|
|
469
|
+
Environment controls:
|
|
436
470
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
471
|
+
```env
|
|
472
|
+
BCP_LOG_LEVEL=debug
|
|
473
|
+
BCP_LOG_FORMAT=json
|
|
474
|
+
```
|
|
440
475
|
|
|
441
|
-
|
|
442
|
-
const guard =
|
|
443
|
-
useGuardData<{
|
|
444
|
-
session: {
|
|
445
|
-
email: string;
|
|
446
|
-
};
|
|
447
|
-
role: string;
|
|
448
|
-
}>();
|
|
476
|
+
Supported levels:
|
|
449
477
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
478
|
+
```text
|
|
479
|
+
debug
|
|
480
|
+
info
|
|
481
|
+
warn
|
|
482
|
+
error
|
|
483
|
+
silent
|
|
456
484
|
```
|
|
457
485
|
|
|
458
|
-
|
|
486
|
+
Supported formats:
|
|
459
487
|
|
|
460
|
-
|
|
488
|
+
```text
|
|
489
|
+
pretty
|
|
490
|
+
json
|
|
491
|
+
```
|
|
461
492
|
|
|
462
|
-
|
|
493
|
+
Read more: [Logging and Observability](docs/development-logging.md)
|
|
463
494
|
|
|
464
|
-
|
|
495
|
+
## File upload
|
|
465
496
|
|
|
466
|
-
|
|
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
|
-
|
|
480
|
-
|
|
501
|
+
parseMultipartFormData,
|
|
502
|
+
requireUploadedFile,
|
|
503
|
+
saveUploadedFile,
|
|
481
504
|
} from "bcp/server";
|
|
482
505
|
|
|
483
|
-
|
|
484
|
-
|
|
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
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
509
|
+
const formData =
|
|
510
|
+
await parseMultipartFormData(
|
|
511
|
+
request,
|
|
512
|
+
{
|
|
513
|
+
maxBytes:
|
|
514
|
+
8 * 1024 * 1024,
|
|
515
|
+
}
|
|
516
|
+
);
|
|
495
517
|
|
|
496
|
-
|
|
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
|
-
|
|
539
|
+
return Response.json(
|
|
540
|
+
await saveUploadedFile(
|
|
541
|
+
file,
|
|
542
|
+
{
|
|
543
|
+
directory:
|
|
544
|
+
"./uploads",
|
|
545
|
+
}
|
|
546
|
+
)
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
```
|
|
499
550
|
|
|
500
|
-
|
|
501
|
-
return {
|
|
502
|
-
ok: false,
|
|
503
|
-
message: "Name is required",
|
|
504
|
-
};
|
|
505
|
-
}
|
|
551
|
+
Upload helpers provide:
|
|
506
552
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
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
|
-
|
|
528
|
-
|
|
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
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
useFormStatus,
|
|
535
|
-
} from "bcp";
|
|
588
|
+
createLocalStorage,
|
|
589
|
+
storeUploadedFile,
|
|
590
|
+
} from "bcp/server";
|
|
536
591
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
592
|
+
const storage =
|
|
593
|
+
createLocalStorage({
|
|
594
|
+
directory:
|
|
595
|
+
"./uploads",
|
|
596
|
+
});
|
|
540
597
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
619
|
+
Application code can depend on this contract instead of depending directly on filesystem paths.
|
|
578
620
|
|
|
579
|
-
|
|
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
|
-
|
|
623
|
+
Storage keys are logical relative paths. Absolute paths and traversal segments are rejected.
|
|
582
624
|
|
|
583
|
-
|
|
625
|
+
Read more: [Storage and File Delivery](docs/storage.md)
|
|
584
626
|
|
|
585
|
-
|
|
586
|
-
BCP_SESSION_SECRET=replace-this-with-a-long-random-secret-at-least-32-bytes
|
|
587
|
-
```
|
|
627
|
+
## Production file delivery
|
|
588
628
|
|
|
589
|
-
|
|
629
|
+
Storage objects can be returned through a hardened HTTP response helper:
|
|
590
630
|
|
|
591
631
|
```ts
|
|
592
632
|
import {
|
|
593
|
-
|
|
594
|
-
json,
|
|
633
|
+
createStorageResponse,
|
|
595
634
|
} from "bcp/server";
|
|
596
635
|
|
|
597
|
-
export async function
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
636
|
+
export async function GET(
|
|
637
|
+
request: Request
|
|
638
|
+
) {
|
|
639
|
+
return createStorageResponse(
|
|
640
|
+
request,
|
|
641
|
+
storage,
|
|
642
|
+
"documents/report.pdf",
|
|
604
643
|
{
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
|
|
617
|
-
|
|
618
|
-
```ts
|
|
619
|
-
import {
|
|
620
|
-
getSession,
|
|
621
|
-
json,
|
|
622
|
-
} from "bcp/server";
|
|
653
|
+
`createStorageResponse()` supports:
|
|
623
654
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
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
|
-
|
|
647
|
-
|
|
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
|
-
|
|
673
|
+
Public immutable caching must be opted into explicitly.
|
|
658
674
|
|
|
659
|
-
|
|
675
|
+
Multiple byte ranges are intentionally not supported in `0.1.25`.
|
|
660
676
|
|
|
661
|
-
|
|
677
|
+
Read more: [Storage and File Delivery](docs/storage.md)
|
|
662
678
|
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
701
|
+
Public environment variables use the prefix:
|
|
668
702
|
|
|
669
703
|
```text
|
|
670
|
-
|
|
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
|
|
707
|
+
Server-only environment values remain server-side and are not emitted into browser bundles.
|
|
677
708
|
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
720
|
+
`bcp doctor` checks areas such as:
|
|
683
721
|
|
|
684
|
-
|
|
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
|
-
|
|
730
|
+
`bcp inspect` reports resolved configuration, environment sources, dependencies and discovered routes.
|
|
687
731
|
|
|
688
|
-
|
|
732
|
+
Read more: [Developer Tools](docs/developer-tools.md)
|
|
689
733
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
} from "bcp";
|
|
734
|
+
## Windows CLI
|
|
735
|
+
|
|
736
|
+
Microsoft SQL Server also installs an executable named `bcp.exe` on Windows.
|
|
694
737
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
738
|
+
BCP therefore publishes the collision-free alias:
|
|
739
|
+
|
|
740
|
+
```text
|
|
741
|
+
bcp-framework
|
|
699
742
|
```
|
|
700
743
|
|
|
701
|
-
|
|
744
|
+
Inside project npm scripts, `bcp` remains safe because npm puts `node_modules/.bin` at the front of `PATH`.
|
|
702
745
|
|
|
703
|
-
|
|
746
|
+
For direct PowerShell usage, prefer:
|
|
704
747
|
|
|
705
|
-
```
|
|
706
|
-
|
|
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
|
-
|
|
755
|
+
## CLI reference
|
|
710
756
|
|
|
711
|
-
```
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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
|
-
|
|
775
|
+
## Development behavior
|
|
721
776
|
|
|
722
|
-
|
|
777
|
+
BCP includes Fast Refresh and deterministic development hydration behavior.
|
|
723
778
|
|
|
724
|
-
|
|
725
|
-
import {
|
|
726
|
-
next,
|
|
727
|
-
redirect,
|
|
728
|
-
type MiddlewareRequest,
|
|
729
|
-
} from "bcp/middleware";
|
|
779
|
+
Recent stabilization work also covers:
|
|
730
780
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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
|
-
|
|
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
|
-
|
|
789
|
+
Read more: [Hydration](docs/hydration.md)
|
|
746
790
|
|
|
747
|
-
##
|
|
791
|
+
## Production build
|
|
748
792
|
|
|
749
|
-
|
|
750
|
-
import {
|
|
751
|
-
defineConfig,
|
|
752
|
-
} from "bcp/config";
|
|
793
|
+
Build an application:
|
|
753
794
|
|
|
754
|
-
|
|
755
|
-
|
|
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
|
-
|
|
781
|
-
|
|
782
|
-
## Production
|
|
799
|
+
Start the generated standalone runtime:
|
|
783
800
|
|
|
784
801
|
```bash
|
|
785
|
-
npm run
|
|
786
|
-
npm start
|
|
802
|
+
npm run start
|
|
787
803
|
```
|
|
788
804
|
|
|
789
|
-
|
|
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
|
-
|
|
797
|
-
├─ middleware.mjs
|
|
798
|
-
├─ guards.mjs
|
|
799
|
-
├─ actions.mjs
|
|
800
|
-
├─ cache-manifest.json
|
|
801
|
-
└─ config.json
|
|
812
|
+
└─ server.mjs
|
|
802
813
|
```
|
|
803
814
|
|
|
804
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
831
|
+
Projects created before the updater was introduced can bootstrap it once using the public package:
|
|
809
832
|
|
|
810
833
|
```bash
|
|
811
|
-
|
|
812
|
-
npm run package:check
|
|
813
|
-
npm run release:check
|
|
834
|
+
npx @chidchanun/bcp@latest update
|
|
814
835
|
```
|
|
815
836
|
|
|
816
|
-
|
|
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
|
-
|
|
857
|
+
A version must not be tagged or published until its release candidate and packed-package verification pass.
|
|
824
858
|
|
|
825
|
-
|
|
859
|
+
Read more: [Releasing](docs/releasing.md)
|
|
826
860
|
|
|
827
|
-
|
|
861
|
+
## Documentation source
|
|
828
862
|
|
|
829
|
-
|
|
863
|
+
The `docs/` directory is the source content intended to feed the future **`bcp-docs-web`** documentation website.
|
|
830
864
|
|
|
831
|
-
|
|
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
|
-
- [
|
|
871
|
+
- [Routing](docs/routing.md)
|
|
836
872
|
- [Server Data Loaders](docs/server-data-loaders.md)
|
|
837
|
-
- [
|
|
838
|
-
- [Form Actions
|
|
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
|
|
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
|
|
886
|
+
- [Hydration](docs/hydration.md)
|
|
848
887
|
- [Developer Tools](docs/developer-tools.md)
|
|
849
|
-
- [
|
|
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
|
-
##
|
|
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
|
-
|
|
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.
|
|
943
|
+
BCP Framework and `create-bcp-app` are released under the MIT License.
|