@chidchanun/bcp 0.1.24 → 0.1.26
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 +587 -202
- package/docs/README.md +278 -86
- package/docs/file-upload.md +194 -32
- package/docs/releases/0.1.25.md +132 -0
- package/docs/releases/0.1.26.md +281 -0
- package/docs/s3-storage.md +276 -0
- package/docs/storage.md +401 -0
- package/package.json +5 -2
- package/packages/client/src/database.mjs +236 -0
- package/packages/client/src/server.ts +46 -0
- package/packages/server/src/file-delivery.ts +613 -0
- package/packages/server/src/storage-s3.ts +1159 -0
- package/packages/server/src/storage.ts +1515 -0
- package/packages/server/src/upload-stream.ts +846 -0
package/README.md
CHANGED
|
@@ -1,8 +1,75 @@
|
|
|
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, SSR, server data loading, guarded application flows, API routes, authentication, database access, validation, logging, uploads, storage adapters and standalone production deployment.
|
|
4
4
|
|
|
5
|
-
>
|
|
5
|
+
> **Release candidate target:** `0.1.26`
|
|
6
|
+
>
|
|
7
|
+
> BCP is pre-1.0. The `0.1.26` source is complete for RC validation, but it must not be presented as a published npm release until the full release checks pass and the matching packages are published.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
BCP keeps React pages and their server behavior close to the route that owns them:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
Browser
|
|
15
|
+
↓
|
|
16
|
+
BCP security / middleware / cache
|
|
17
|
+
↓
|
|
18
|
+
Route guard
|
|
19
|
+
↓
|
|
20
|
+
Loader / action / API route
|
|
21
|
+
↓
|
|
22
|
+
React SSR
|
|
23
|
+
↓
|
|
24
|
+
Hydration / SPA navigation
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
A typical route can colocate its page, loader, guard and actions:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
app/
|
|
31
|
+
├─ layout.tsx
|
|
32
|
+
├─ page.tsx
|
|
33
|
+
├─ dashboard/
|
|
34
|
+
│ ├─ guard.ts
|
|
35
|
+
│ └─ users/
|
|
36
|
+
│ └─ [id]/
|
|
37
|
+
│ ├─ loader.ts
|
|
38
|
+
│ ├─ actions.ts
|
|
39
|
+
│ └─ page.tsx
|
|
40
|
+
└─ api/
|
|
41
|
+
└─ upload/
|
|
42
|
+
└─ route.ts
|
|
43
|
+
```
|
|
44
|
+
|
|
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 | Buffered multipart helpers and production multipart streaming |
|
|
61
|
+
| Storage | Local + S3-compatible adapters, streaming I/O and ranged 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 |
|
|
65
|
+
|
|
66
|
+
## Requirements
|
|
67
|
+
|
|
68
|
+
- Node.js `24.11` or newer
|
|
69
|
+
- React `19`
|
|
70
|
+
- npm
|
|
71
|
+
|
|
72
|
+
Database features currently target MySQL.
|
|
6
73
|
|
|
7
74
|
## Quick start
|
|
8
75
|
|
|
@@ -18,36 +85,48 @@ Default development URL:
|
|
|
18
85
|
http://localhost:3000
|
|
19
86
|
```
|
|
20
87
|
|
|
21
|
-
|
|
88
|
+
Generated projects normally include:
|
|
22
89
|
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
bcp
|
|
27
|
-
bcp
|
|
28
|
-
bcp
|
|
29
|
-
bcp
|
|
30
|
-
bcp
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"scripts": {
|
|
93
|
+
"dev": "bcp dev",
|
|
94
|
+
"build": "bcp build",
|
|
95
|
+
"start": "bcp start",
|
|
96
|
+
"routes": "bcp routes",
|
|
97
|
+
"update": "bcp update",
|
|
98
|
+
"typecheck": "tsc --noEmit"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
34
102
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
103
|
+
## Packages
|
|
104
|
+
|
|
105
|
+
The public framework package is published as:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
@chidchanun/bcp
|
|
39
109
|
```
|
|
40
110
|
|
|
41
|
-
|
|
111
|
+
Applications normally consume it through the dependency key `bcp`, keeping imports concise:
|
|
42
112
|
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
113
|
+
```ts
|
|
114
|
+
import {
|
|
115
|
+
Form,
|
|
116
|
+
Link,
|
|
117
|
+
useLoaderData,
|
|
118
|
+
} from "bcp";
|
|
48
119
|
```
|
|
49
120
|
|
|
50
|
-
|
|
121
|
+
Server-only APIs use dedicated entrypoints:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import {
|
|
125
|
+
cookies,
|
|
126
|
+
logger,
|
|
127
|
+
requestId,
|
|
128
|
+
} from "bcp/server";
|
|
129
|
+
```
|
|
51
130
|
|
|
52
131
|
## Project structure
|
|
53
132
|
|
|
@@ -55,21 +134,26 @@ Normal project npm scripts can continue using `bcp` because npm places `node_mod
|
|
|
55
134
|
app/
|
|
56
135
|
├─ layout.tsx
|
|
57
136
|
├─ page.tsx
|
|
137
|
+
├─ login/
|
|
138
|
+
│ └─ page.tsx
|
|
58
139
|
├─ dashboard/
|
|
59
140
|
│ ├─ guard.ts
|
|
141
|
+
│ ├─ page.tsx
|
|
60
142
|
│ └─ users/
|
|
61
143
|
│ └─ [id]/
|
|
62
144
|
│ ├─ loader.ts
|
|
63
145
|
│ ├─ actions.ts
|
|
64
146
|
│ └─ page.tsx
|
|
65
147
|
└─ api/
|
|
66
|
-
├─
|
|
67
|
-
│ └─
|
|
148
|
+
├─ auth/
|
|
149
|
+
│ └─ login/
|
|
150
|
+
│ └─ route.ts
|
|
68
151
|
└─ upload/
|
|
69
152
|
└─ route.ts
|
|
70
153
|
|
|
71
154
|
lib/
|
|
72
155
|
public/
|
|
156
|
+
migrations/
|
|
73
157
|
bcp.config.ts
|
|
74
158
|
package.json
|
|
75
159
|
tsconfig.json
|
|
@@ -77,6 +161,8 @@ tsconfig.json
|
|
|
77
161
|
|
|
78
162
|
## Routing
|
|
79
163
|
|
|
164
|
+
Page routes are discovered from `app/**/page.tsx`:
|
|
165
|
+
|
|
80
166
|
```text
|
|
81
167
|
app/page.tsx /
|
|
82
168
|
app/about/page.tsx /about
|
|
@@ -88,9 +174,24 @@ app/(admin)/settings/page.tsx /settings
|
|
|
88
174
|
|
|
89
175
|
Static routes have priority over dynamic and catch-all routes.
|
|
90
176
|
|
|
91
|
-
|
|
177
|
+
API routes use `route.ts`:
|
|
92
178
|
|
|
93
|
-
|
|
179
|
+
```text
|
|
180
|
+
app/api/users/route.ts /api/users
|
|
181
|
+
app/api/users/[id]/route.ts /api/users/:id
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Read more: [Routing](docs/routing.md)
|
|
185
|
+
|
|
186
|
+
## Layouts and metadata
|
|
187
|
+
|
|
188
|
+
Routes inherit layouts from parent directories. BCP resolves the layout chain in development and standalone production. Document metadata is route-aware and can be generated alongside the page tree.
|
|
189
|
+
|
|
190
|
+
Read more: [Routing](docs/routing.md)
|
|
191
|
+
|
|
192
|
+
## Server data loaders
|
|
193
|
+
|
|
194
|
+
Place `loader.ts` next to a page when the route needs server-side data:
|
|
94
195
|
|
|
95
196
|
```ts
|
|
96
197
|
// app/users/[id]/loader.ts
|
|
@@ -104,7 +205,7 @@ export async function loader({
|
|
|
104
205
|
}
|
|
105
206
|
```
|
|
106
207
|
|
|
107
|
-
|
|
208
|
+
Consume serializable loader data from a client page:
|
|
108
209
|
|
|
109
210
|
```tsx
|
|
110
211
|
"use client";
|
|
@@ -119,11 +220,37 @@ export default function UserPage() {
|
|
|
119
220
|
id: string;
|
|
120
221
|
}>();
|
|
121
222
|
|
|
122
|
-
return <main>{data.id}</main>;
|
|
223
|
+
return <main>User {data.id}</main>;
|
|
123
224
|
}
|
|
124
225
|
```
|
|
125
226
|
|
|
126
|
-
|
|
227
|
+
Read more: [Server Data Loaders](docs/server-data-loaders.md)
|
|
228
|
+
|
|
229
|
+
## Route guards
|
|
230
|
+
|
|
231
|
+
A route tree can define `guard.ts` to authorize access before rendering:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import {
|
|
235
|
+
requireRole,
|
|
236
|
+
} from "bcp/auth";
|
|
237
|
+
|
|
238
|
+
export const guard =
|
|
239
|
+
requireRole("admin");
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The standalone production pipeline preserves the same active request context used by authentication and server request APIs.
|
|
243
|
+
|
|
244
|
+
Read more:
|
|
245
|
+
|
|
246
|
+
- [Route Guards](docs/route-guards.md)
|
|
247
|
+
- [Auth Route Guards](docs/auth-route-guards.md)
|
|
248
|
+
|
|
249
|
+
## Form actions
|
|
250
|
+
|
|
251
|
+
Route-owned mutations live in `actions.ts` and can be invoked through the public `<Form>` API. BCP supports progressive form submission and SPA action transport while keeping mutation code server-only.
|
|
252
|
+
|
|
253
|
+
Read more: [Form Actions](docs/form-actions.md)
|
|
127
254
|
|
|
128
255
|
## Server request APIs
|
|
129
256
|
|
|
@@ -139,11 +266,127 @@ import {
|
|
|
139
266
|
} from "bcp/server";
|
|
140
267
|
```
|
|
141
268
|
|
|
142
|
-
`requestId()` accepts a valid incoming `X-Request-Id` or generates a stable UUID for the request.
|
|
269
|
+
`requestId()` accepts a valid incoming `X-Request-Id` or generates a stable UUID for the active request.
|
|
270
|
+
|
|
271
|
+
Read more: [Server Request APIs](docs/server-request-apis.md)
|
|
272
|
+
|
|
273
|
+
## Authentication and sessions
|
|
274
|
+
|
|
275
|
+
High-level authentication helpers:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import {
|
|
279
|
+
auth,
|
|
280
|
+
requireAuth,
|
|
281
|
+
requireRole,
|
|
282
|
+
} from "bcp/auth";
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Lower-level JWT cookie session primitives:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import {
|
|
289
|
+
createSession,
|
|
290
|
+
createSessionToken,
|
|
291
|
+
destroySession,
|
|
292
|
+
getSession,
|
|
293
|
+
verifySessionToken,
|
|
294
|
+
} from "bcp/server";
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Authentication is separated from application-specific credential lookup so projects can connect their own user table or identity provider.
|
|
298
|
+
|
|
299
|
+
Read more:
|
|
300
|
+
|
|
301
|
+
- [Authentication](docs/authentication.md)
|
|
302
|
+
- [JWT Cookie Sessions](docs/session-auth.md)
|
|
303
|
+
|
|
304
|
+
## Middleware
|
|
305
|
+
|
|
306
|
+
Middleware System v2 uses onion-style execution:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
export async function middleware(
|
|
310
|
+
request,
|
|
311
|
+
context,
|
|
312
|
+
next
|
|
313
|
+
) {
|
|
314
|
+
const response =
|
|
315
|
+
await next();
|
|
316
|
+
|
|
317
|
+
response.headers.set(
|
|
318
|
+
"x-app",
|
|
319
|
+
"example"
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
return response;
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Existing middleware v1 behavior remains supported for compatibility.
|
|
143
327
|
|
|
144
|
-
|
|
328
|
+
Read more: [Middleware](docs/middleware.md)
|
|
145
329
|
|
|
146
|
-
|
|
330
|
+
## Validation
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
import {
|
|
334
|
+
v,
|
|
335
|
+
validateFormData,
|
|
336
|
+
} from "bcp/validation";
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Read more: [Validation](docs/validation.md)
|
|
340
|
+
|
|
341
|
+
## Error handling
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
import {
|
|
345
|
+
badRequest,
|
|
346
|
+
forbidden,
|
|
347
|
+
notFoundResponse,
|
|
348
|
+
toErrorResponse,
|
|
349
|
+
unauthorized,
|
|
350
|
+
} from "bcp/error";
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
The common error envelope is:
|
|
354
|
+
|
|
355
|
+
```json
|
|
356
|
+
{
|
|
357
|
+
"error": {
|
|
358
|
+
"status": 400,
|
|
359
|
+
"code": "BAD_REQUEST",
|
|
360
|
+
"message": "Invalid request"
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Read more: [Error Handling](docs/error-handling.md)
|
|
366
|
+
|
|
367
|
+
## Database
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
import {
|
|
371
|
+
db,
|
|
372
|
+
} from "bcp/database";
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
The database layer provides a lazy MySQL pool, prepared execution, query helpers, transactions and migrations.
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
bcp db create create_users
|
|
379
|
+
bcp db migrate
|
|
380
|
+
bcp db status
|
|
381
|
+
bcp db rollback
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Read more:
|
|
385
|
+
|
|
386
|
+
- [Database](docs/database.md)
|
|
387
|
+
- [Database Migrations](docs/database-migrations.md)
|
|
388
|
+
|
|
389
|
+
## Logging and observability
|
|
147
390
|
|
|
148
391
|
```ts
|
|
149
392
|
import {
|
|
@@ -158,25 +401,9 @@ logger.info(
|
|
|
158
401
|
"catalog",
|
|
159
402
|
}
|
|
160
403
|
);
|
|
161
|
-
|
|
162
|
-
export async function loader() {
|
|
163
|
-
const log =
|
|
164
|
-
await requestLogger({
|
|
165
|
-
feature:
|
|
166
|
-
"categories",
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
log.info(
|
|
170
|
-
"Loading categories"
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
items: [],
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
404
|
```
|
|
178
405
|
|
|
179
|
-
|
|
406
|
+
Environment controls:
|
|
180
407
|
|
|
181
408
|
```env
|
|
182
409
|
BCP_LOG_LEVEL=debug
|
|
@@ -185,9 +412,11 @@ BCP_LOG_FORMAT=json
|
|
|
185
412
|
|
|
186
413
|
Supported levels are `debug`, `info`, `warn`, `error` and `silent`. Formats are `pretty` and `json`.
|
|
187
414
|
|
|
188
|
-
|
|
415
|
+
Read more: [Logging and Observability](docs/development-logging.md)
|
|
416
|
+
|
|
417
|
+
## File uploads
|
|
189
418
|
|
|
190
|
-
BCP
|
|
419
|
+
BCP keeps the buffered multipart API for small forms:
|
|
191
420
|
|
|
192
421
|
```ts
|
|
193
422
|
import {
|
|
@@ -196,202 +425,296 @@ import {
|
|
|
196
425
|
saveUploadedFile,
|
|
197
426
|
} from "bcp/server";
|
|
198
427
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
428
|
+
const formData =
|
|
429
|
+
await parseMultipartFormData(
|
|
430
|
+
request,
|
|
431
|
+
{
|
|
432
|
+
maxBytes:
|
|
433
|
+
8 * 1024 * 1024,
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const file =
|
|
438
|
+
requireUploadedFile(
|
|
439
|
+
formData,
|
|
440
|
+
"file",
|
|
441
|
+
{
|
|
442
|
+
maxBytes:
|
|
443
|
+
5 * 1024 * 1024,
|
|
444
|
+
allowedTypes: [
|
|
445
|
+
"image/png",
|
|
446
|
+
"image/jpeg",
|
|
447
|
+
"image/webp",
|
|
448
|
+
],
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
BCP `0.1.26` adds direct multipart-to-storage streaming for larger production uploads:
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
import {
|
|
457
|
+
storeMultipartFile,
|
|
458
|
+
} from "bcp/server";
|
|
459
|
+
|
|
460
|
+
const stored =
|
|
461
|
+
await storeMultipartFile(
|
|
462
|
+
request,
|
|
463
|
+
{
|
|
464
|
+
storage,
|
|
465
|
+
fieldName:
|
|
466
|
+
"file",
|
|
467
|
+
key:
|
|
468
|
+
"documents/report.pdf",
|
|
469
|
+
maxBytes:
|
|
470
|
+
100 * 1024 * 1024,
|
|
471
|
+
constraints: {
|
|
216
472
|
maxBytes:
|
|
217
|
-
|
|
473
|
+
80 * 1024 * 1024,
|
|
218
474
|
allowedTypes: [
|
|
219
|
-
"
|
|
220
|
-
"image/jpeg",
|
|
221
|
-
"image/webp",
|
|
475
|
+
"application/pdf",
|
|
222
476
|
],
|
|
223
477
|
allowedExtensions: [
|
|
224
|
-
".
|
|
225
|
-
".jpg",
|
|
226
|
-
".jpeg",
|
|
227
|
-
".webp",
|
|
478
|
+
".pdf",
|
|
228
479
|
],
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const saved =
|
|
233
|
-
await saveUploadedFile(
|
|
234
|
-
file,
|
|
235
|
-
{
|
|
236
|
-
directory:
|
|
237
|
-
"./uploads",
|
|
238
|
-
}
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
return Response.json({
|
|
242
|
-
fileName:
|
|
243
|
-
saved.fileName,
|
|
244
|
-
size:
|
|
245
|
-
saved.size,
|
|
246
|
-
checksumSha256:
|
|
247
|
-
saved.checksumSha256,
|
|
248
|
-
});
|
|
249
|
-
}
|
|
480
|
+
},
|
|
481
|
+
}
|
|
482
|
+
);
|
|
250
483
|
```
|
|
251
484
|
|
|
252
|
-
|
|
485
|
+
`storeMultipartFile()` parses `Request.body` incrementally and streams the selected file directly into the configured `StorageAdapter`. It does not call `request.formData()` for the target file.
|
|
486
|
+
|
|
487
|
+
The security gateway still applies `server.bodyLimit` / `BCP_BODY_LIMIT` as the outer request limit. Streaming controls memory use; it does not bypass proxy/platform/body limits.
|
|
253
488
|
|
|
254
|
-
|
|
255
|
-
- total and per-file size limits,
|
|
256
|
-
- MIME/extension allowlists,
|
|
257
|
-
- optional or required file fields,
|
|
258
|
-
- safe UUID-based storage names,
|
|
259
|
-
- filename sanitization,
|
|
260
|
-
- path traversal protection,
|
|
261
|
-
- no-overwrite-by-default storage,
|
|
262
|
-
- SHA-256 checksum metadata.
|
|
489
|
+
MIME type and extension validation are metadata checks, not content-signature verification.
|
|
263
490
|
|
|
264
|
-
|
|
491
|
+
Read more: [File Upload](docs/file-upload.md)
|
|
492
|
+
|
|
493
|
+
## Storage adapters
|
|
494
|
+
|
|
495
|
+
### Local filesystem
|
|
265
496
|
|
|
266
497
|
```ts
|
|
267
498
|
import {
|
|
268
|
-
|
|
269
|
-
} from "bcp/
|
|
499
|
+
createLocalStorage,
|
|
500
|
+
} from "bcp/server";
|
|
270
501
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
});
|
|
502
|
+
const storage =
|
|
503
|
+
createLocalStorage({
|
|
504
|
+
directory:
|
|
505
|
+
"./uploads",
|
|
506
|
+
});
|
|
277
507
|
```
|
|
278
508
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
See [File Upload](docs/file-upload.md).
|
|
509
|
+
### S3-compatible storage
|
|
282
510
|
|
|
283
|
-
|
|
511
|
+
BCP `0.1.26` adds an S3-compatible backend:
|
|
284
512
|
|
|
285
|
-
|
|
513
|
+
```ts
|
|
514
|
+
import {
|
|
515
|
+
createS3Storage,
|
|
516
|
+
} from "bcp/server";
|
|
286
517
|
|
|
287
|
-
|
|
288
|
-
|
|
518
|
+
const storage =
|
|
519
|
+
createS3Storage({
|
|
520
|
+
bucket:
|
|
521
|
+
process.env.S3_BUCKET!,
|
|
522
|
+
region:
|
|
523
|
+
process.env.S3_REGION!,
|
|
524
|
+
endpoint:
|
|
525
|
+
process.env.S3_ENDPOINT,
|
|
526
|
+
accessKeyId:
|
|
527
|
+
process.env.S3_ACCESS_KEY_ID,
|
|
528
|
+
secretAccessKey:
|
|
529
|
+
process.env.S3_SECRET_ACCESS_KEY,
|
|
530
|
+
});
|
|
289
531
|
```
|
|
290
532
|
|
|
291
|
-
|
|
533
|
+
Custom endpoints and `forcePathStyle` allow use with S3-compatible services such as MinIO. Explicit credentials are optional; when omitted, the AWS SDK can use its normal server-side credential provider chain.
|
|
292
534
|
|
|
293
|
-
|
|
535
|
+
Keep cloud credentials server-only. Never expose them through `BCP_PUBLIC_*` variables.
|
|
294
536
|
|
|
295
|
-
|
|
537
|
+
Read more: [S3-Compatible Storage](docs/s3-storage.md)
|
|
296
538
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
539
|
+
### Streaming storage
|
|
540
|
+
|
|
541
|
+
The `StorageAdapter` contract keeps its original buffered methods and adds optional streaming capabilities:
|
|
542
|
+
|
|
543
|
+
```text
|
|
544
|
+
put
|
|
545
|
+
putStream? ← 0.1.26
|
|
546
|
+
stat
|
|
547
|
+
read
|
|
548
|
+
readStream? ← 0.1.26
|
|
549
|
+
exists
|
|
550
|
+
delete
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Use generic helpers so legacy and native-streaming adapters can share application code:
|
|
554
|
+
|
|
555
|
+
```ts
|
|
556
|
+
import {
|
|
557
|
+
getStorageCapabilities,
|
|
558
|
+
putStorageStream,
|
|
559
|
+
readStorageStream,
|
|
560
|
+
} from "bcp/server";
|
|
300
561
|
```
|
|
301
562
|
|
|
302
|
-
|
|
563
|
+
Both built-in `0.1.26` adapters support native streaming reads, streaming writes and byte ranges.
|
|
303
564
|
|
|
304
|
-
`
|
|
565
|
+
Streaming writes support `maxBytes` and `AbortSignal`. Local incomplete objects are removed on failure; S3 multipart uploads are configured to clean up parts on failed completion.
|
|
305
566
|
|
|
306
|
-
|
|
567
|
+
Read more: [Storage and File Delivery](docs/storage.md)
|
|
307
568
|
|
|
308
|
-
##
|
|
569
|
+
## Production file delivery
|
|
309
570
|
|
|
310
571
|
```ts
|
|
311
572
|
import {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
} from "bcp/validation";
|
|
573
|
+
createStorageResponse,
|
|
574
|
+
} from "bcp/server";
|
|
315
575
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
576
|
+
export function GET(
|
|
577
|
+
request: Request
|
|
578
|
+
) {
|
|
579
|
+
return createStorageResponse(
|
|
580
|
+
request,
|
|
581
|
+
storage,
|
|
582
|
+
"documents/report.pdf",
|
|
583
|
+
{
|
|
584
|
+
disposition:
|
|
585
|
+
"attachment",
|
|
586
|
+
downloadName:
|
|
587
|
+
"report.pdf",
|
|
588
|
+
}
|
|
589
|
+
);
|
|
590
|
+
}
|
|
321
591
|
```
|
|
322
592
|
|
|
323
|
-
|
|
593
|
+
`createStorageResponse()` uses storage streaming and supports:
|
|
324
594
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
595
|
+
- `GET` / `HEAD`,
|
|
596
|
+
- ETag / Last-Modified validators,
|
|
597
|
+
- `If-Range`,
|
|
598
|
+
- single byte ranges with `206 Partial Content`,
|
|
599
|
+
- `304 Not Modified`,
|
|
600
|
+
- `416 Range Not Satisfiable`,
|
|
601
|
+
- safe `Content-Disposition` filenames,
|
|
602
|
+
- configurable cache control.
|
|
603
|
+
|
|
604
|
+
The default cache policy remains:
|
|
605
|
+
|
|
606
|
+
```text
|
|
607
|
+
private, max-age=0, must-revalidate
|
|
333
608
|
```
|
|
334
609
|
|
|
335
|
-
|
|
610
|
+
Multiple byte ranges remain intentionally unsupported in `0.1.26`.
|
|
336
611
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
612
|
+
Read more: [Storage and File Delivery](docs/storage.md)
|
|
613
|
+
|
|
614
|
+
## Caching
|
|
615
|
+
|
|
616
|
+
BCP includes response caching and revalidation primitives used by development and standalone production runtimes.
|
|
617
|
+
|
|
618
|
+
Read more: [Caching](docs/caching.md)
|
|
619
|
+
|
|
620
|
+
## Security
|
|
621
|
+
|
|
622
|
+
The framework security layer includes request body limits and production request handling defaults. Application authorization remains the responsibility of route guards and application logic.
|
|
623
|
+
|
|
624
|
+
Storage keys, filenames and MIME metadata must not be treated as authorization decisions.
|
|
625
|
+
|
|
626
|
+
Read more: [Security](docs/security.md)
|
|
627
|
+
|
|
628
|
+
## Environment and configuration
|
|
629
|
+
|
|
630
|
+
Application configuration lives in:
|
|
631
|
+
|
|
632
|
+
```text
|
|
633
|
+
bcp.config.ts
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Public environment variables use:
|
|
637
|
+
|
|
638
|
+
```text
|
|
639
|
+
BCP_PUBLIC_
|
|
341
640
|
```
|
|
342
641
|
|
|
343
|
-
|
|
642
|
+
Server-only values remain server-side and are not emitted into browser bundles.
|
|
344
643
|
|
|
345
|
-
|
|
644
|
+
Read more: [Configuration](docs/configuration.md)
|
|
346
645
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
} from "bcp/auth";
|
|
646
|
+
## Developer tools
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
bcp doctor
|
|
650
|
+
bcp inspect
|
|
353
651
|
```
|
|
354
652
|
|
|
355
|
-
BCP
|
|
653
|
+
`bcp doctor` checks project structure, BCP/React installation parity, duplicate framework copies, environment/config loading, route conflicts and client/server boundaries.
|
|
356
654
|
|
|
357
|
-
|
|
655
|
+
`bcp inspect` reports resolved configuration, environment sources, dependency versions and discovered routes.
|
|
358
656
|
|
|
359
|
-
|
|
657
|
+
Read more: [Developer Tools](docs/developer-tools.md)
|
|
360
658
|
|
|
361
|
-
|
|
362
|
-
export async function middleware(
|
|
363
|
-
request,
|
|
364
|
-
context,
|
|
365
|
-
next
|
|
366
|
-
) {
|
|
367
|
-
const response =
|
|
368
|
-
await next();
|
|
659
|
+
## Windows CLI
|
|
369
660
|
|
|
370
|
-
|
|
371
|
-
"x-app",
|
|
372
|
-
"example"
|
|
373
|
-
);
|
|
661
|
+
Microsoft SQL Server can install another executable named `bcp.exe`. BCP therefore publishes the collision-free alias `bcp-framework`.
|
|
374
662
|
|
|
375
|
-
|
|
376
|
-
|
|
663
|
+
Inside npm scripts, `bcp` is safe because npm prepends the project's `node_modules/.bin` to `PATH`.
|
|
664
|
+
|
|
665
|
+
For direct PowerShell usage, use the project-local CLI:
|
|
666
|
+
|
|
667
|
+
```powershell
|
|
668
|
+
npm exec -- bcp-framework --version
|
|
669
|
+
npm exec -- bcp-framework doctor
|
|
670
|
+
npm exec -- bcp-framework inspect
|
|
671
|
+
npm exec -- bcp-framework routes
|
|
672
|
+
npm exec -- bcp-framework dev
|
|
673
|
+
npm exec -- bcp-framework build
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
This keeps the CLI version aligned with the framework installed by the application.
|
|
677
|
+
|
|
678
|
+
## CLI reference
|
|
679
|
+
|
|
680
|
+
```bash
|
|
681
|
+
bcp dev
|
|
682
|
+
bcp routes
|
|
683
|
+
bcp build
|
|
684
|
+
bcp start
|
|
685
|
+
bcp doctor
|
|
686
|
+
bcp doctor --json
|
|
687
|
+
bcp inspect
|
|
688
|
+
bcp inspect --json
|
|
689
|
+
bcp update
|
|
690
|
+
bcp version
|
|
691
|
+
|
|
692
|
+
bcp db create create_users
|
|
693
|
+
bcp db migrate
|
|
694
|
+
bcp db status
|
|
695
|
+
bcp db rollback
|
|
377
696
|
```
|
|
378
697
|
|
|
379
|
-
|
|
698
|
+
## Development behavior
|
|
380
699
|
|
|
381
|
-
|
|
700
|
+
BCP includes Fast Refresh and deterministic development hydration behavior. Recent stabilization work covers:
|
|
382
701
|
|
|
383
|
-
|
|
702
|
+
- Windows line-ending parity,
|
|
703
|
+
- multiline JSX hydration parity,
|
|
704
|
+
- duplicate BCP installation detection,
|
|
705
|
+
- automatic page-route/client-bundle graph resynchronization,
|
|
706
|
+
- standalone authentication guard request-context parity.
|
|
384
707
|
|
|
385
|
-
|
|
708
|
+
Read more: [Hydration](docs/hydration.md)
|
|
386
709
|
|
|
387
|
-
## Production
|
|
710
|
+
## Production build
|
|
388
711
|
|
|
389
712
|
```bash
|
|
390
713
|
npm run build
|
|
391
|
-
npm start
|
|
714
|
+
npm run start
|
|
392
715
|
```
|
|
393
716
|
|
|
394
|
-
|
|
717
|
+
Production output:
|
|
395
718
|
|
|
396
719
|
```text
|
|
397
720
|
.bcp-framework/build/
|
|
@@ -401,44 +724,73 @@ Standalone output is generated under:
|
|
|
401
724
|
└─ server.mjs
|
|
402
725
|
```
|
|
403
726
|
|
|
404
|
-
|
|
727
|
+
The standalone runtime composes production middleware, security, cache, actions, guards, loaders and page rendering into the final HTTP pipeline.
|
|
728
|
+
|
|
729
|
+
Runtime hostname/port overrides can be supplied to `bcp start` without rebuilding.
|
|
730
|
+
|
|
731
|
+
Read more: [Deployment](docs/deployment.md)
|
|
732
|
+
|
|
733
|
+
## Updating BCP
|
|
405
734
|
|
|
406
735
|
```bash
|
|
407
736
|
bcp update
|
|
408
737
|
bcp update --check
|
|
409
738
|
bcp update --dry-run
|
|
410
|
-
bcp update 0.1.
|
|
739
|
+
bcp update 0.1.26
|
|
411
740
|
bcp update next
|
|
412
741
|
```
|
|
413
742
|
|
|
414
|
-
Projects
|
|
743
|
+
Projects created before the updater was introduced can bootstrap it once with:
|
|
415
744
|
|
|
416
745
|
```bash
|
|
417
746
|
npx @chidchanun/bcp@latest update
|
|
418
747
|
```
|
|
419
748
|
|
|
420
|
-
|
|
749
|
+
Read more: [Updating](docs/updating.md)
|
|
750
|
+
|
|
751
|
+
## Framework development and release validation
|
|
752
|
+
|
|
753
|
+
Inside the BCP Framework repository:
|
|
421
754
|
|
|
422
755
|
```bash
|
|
756
|
+
npm install
|
|
423
757
|
npm run typecheck
|
|
424
758
|
npm run test:unit
|
|
759
|
+
npm run test:integration
|
|
760
|
+
npm run test:e2e
|
|
761
|
+
npm run test:package
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
Full release-candidate validation:
|
|
765
|
+
|
|
766
|
+
```bash
|
|
425
767
|
npm run rc:check
|
|
426
768
|
```
|
|
427
769
|
|
|
428
|
-
|
|
770
|
+
A version must not be tagged or published until RC and packed-package verification pass.
|
|
429
771
|
|
|
430
|
-
|
|
772
|
+
Read more: [Releasing](docs/releasing.md)
|
|
431
773
|
|
|
774
|
+
## Documentation source
|
|
775
|
+
|
|
776
|
+
The `docs/` directory is the source content for the future **`bcp-docs-web`** documentation website.
|
|
777
|
+
|
|
778
|
+
Start with:
|
|
779
|
+
|
|
780
|
+
- [Documentation Source Map](docs/README.md)
|
|
432
781
|
- [Getting Started](docs/getting-started.md)
|
|
782
|
+
- [Configuration](docs/configuration.md)
|
|
433
783
|
- [Application Modules](docs/application-modules.md)
|
|
434
784
|
- [Routing](docs/routing.md)
|
|
435
|
-
- [Server Request APIs](docs/server-request-apis.md)
|
|
436
785
|
- [Server Data Loaders](docs/server-data-loaders.md)
|
|
437
|
-
- [
|
|
786
|
+
- [Route Guards](docs/route-guards.md)
|
|
438
787
|
- [Form Actions](docs/form-actions.md)
|
|
788
|
+
- [Server Request APIs](docs/server-request-apis.md)
|
|
439
789
|
- [Validation](docs/validation.md)
|
|
440
790
|
- [Error Handling](docs/error-handling.md)
|
|
441
791
|
- [File Upload](docs/file-upload.md)
|
|
792
|
+
- [Storage and File Delivery](docs/storage.md)
|
|
793
|
+
- [S3-Compatible Storage](docs/s3-storage.md)
|
|
442
794
|
- [Authentication](docs/authentication.md)
|
|
443
795
|
- [Auth Route Guards](docs/auth-route-guards.md)
|
|
444
796
|
- [JWT Sessions](docs/session-auth.md)
|
|
@@ -447,17 +799,50 @@ After RC passes, releases are tagged and published manually with the guarded rel
|
|
|
447
799
|
- [Middleware](docs/middleware.md)
|
|
448
800
|
- [Hydration](docs/hydration.md)
|
|
449
801
|
- [Developer Tools](docs/developer-tools.md)
|
|
450
|
-
- [
|
|
802
|
+
- [Logging and Observability](docs/development-logging.md)
|
|
451
803
|
- [Caching](docs/caching.md)
|
|
452
804
|
- [Security](docs/security.md)
|
|
453
805
|
- [Deployment](docs/deployment.md)
|
|
454
806
|
- [Updating](docs/updating.md)
|
|
455
807
|
- [Releasing](docs/releasing.md)
|
|
456
808
|
|
|
457
|
-
|
|
809
|
+
Recommended `bcp-docs-web` top-level navigation:
|
|
810
|
+
|
|
811
|
+
```text
|
|
812
|
+
Getting Started
|
|
813
|
+
Routing & Data
|
|
814
|
+
Authentication
|
|
815
|
+
Database
|
|
816
|
+
Runtime & Infrastructure
|
|
817
|
+
Storage & Uploads
|
|
818
|
+
API Reference
|
|
819
|
+
Releases
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
## Release history
|
|
823
|
+
|
|
824
|
+
Release notes live under `docs/releases/`.
|
|
825
|
+
|
|
826
|
+
| Version | Milestone |
|
|
827
|
+
| --- | --- |
|
|
828
|
+
| `0.1.20` | Hydration line-ending stabilization |
|
|
829
|
+
| `0.1.21` | Hydration semantic parity |
|
|
830
|
+
| `0.1.22` | Developer tools and diagnostics |
|
|
831
|
+
| `0.1.23` | Logging and observability |
|
|
832
|
+
| `0.1.24` | File Upload Foundation |
|
|
833
|
+
| `0.1.25` | Storage Adapters and File Delivery |
|
|
834
|
+
| `0.1.26` | S3-Compatible Storage and Production Streaming |
|
|
835
|
+
|
|
836
|
+
## Next direction
|
|
837
|
+
|
|
838
|
+
After `0.1.26`, the recommended direction is:
|
|
839
|
+
|
|
840
|
+
1. signed storage URLs where needed,
|
|
841
|
+
2. object listing/copy/move capabilities,
|
|
842
|
+
3. broader production hardening and graceful shutdown,
|
|
843
|
+
4. stronger storage/provider diagnostics.
|
|
458
844
|
|
|
459
|
-
|
|
460
|
-
- React 19
|
|
845
|
+
These are roadmap items, not `0.1.26` API guarantees.
|
|
461
846
|
|
|
462
847
|
## License
|
|
463
848
|
|