@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.
@@ -211,6 +211,20 @@ export function guard() {
211
211
  }
212
212
  ```
213
213
 
214
+ ## Standalone production behavior
215
+
216
+ BCP 0.1.24 keeps `bcp/auth` inside the same production guard runtime graph as `runWithRequestContext()`. This guarantees that `requireAuth()`, `requireRole()`, `auth()`, `getSession()` and `cookies()` observe the active request after `bcp build`, rather than resolving through a second framework request-context instance.
217
+
218
+ This fixes the production-only failure:
219
+
220
+ ```text
221
+ BCP Framework: server request APIs can only be used while handling a request.
222
+ ```
223
+
224
+ No application guard changes are required. Continue importing authentication helpers from `bcp/auth` normally.
225
+
226
+ The production form-action bundle applies the same runtime unification because route guards may execute before an action.
227
+
214
228
  ## Security boundary
215
229
 
216
230
  `bcp/auth` is server-only. BCP blocks it from page/client graphs and maps its browser export to the server-only runtime guard. Authentication and authorization checks should stay in guards, loaders, actions, API routes, or other server modules.
@@ -11,34 +11,55 @@ bcp
11
11
  bcp-framework
12
12
  ```
13
13
 
14
- `bcp` remains the primary and backward-compatible command.
14
+ `bcp` remains the primary and backward-compatible executable name inside npm scripts.
15
15
 
16
- On Windows, Microsoft SQL Server also installs a native utility named `bcp.exe`. When that executable appears earlier on `PATH`, typing `bcp` in PowerShell can launch the SQL Server utility instead of BCP Framework.
16
+ On Windows, Microsoft SQL Server also installs a native utility named `bcp.exe`. When that executable appears earlier on `PATH`, typing `bcp` directly in PowerShell can launch the SQL Server utility instead of BCP Framework.
17
17
 
18
- Use the Windows-safe alias in that situation:
18
+ PowerShell also does not add an application's `node_modules/.bin` directory to its normal interactive `PATH`. Therefore the recommended interactive Windows invocation is the project-local executable through npm:
19
19
 
20
20
  ```powershell
21
- bcp-framework doctor
22
- bcp-framework inspect
23
- bcp-framework dev
24
- bcp-framework build
25
- bcp-framework update
21
+ npm exec -- bcp-framework doctor
22
+ npm exec -- bcp-framework inspect
23
+ npm exec -- bcp-framework dev
24
+ npm exec -- bcp-framework build
25
+ npm exec -- bcp-framework update
26
26
  ```
27
27
 
28
- You can also explicitly run the project-local executable with npm:
28
+ Or call the generated Windows shim explicitly:
29
29
 
30
30
  ```powershell
31
- npm exec -- bcp doctor
31
+ .\node_modules\.bin\bcp-framework.cmd doctor
32
32
  ```
33
33
 
34
34
  BCP application npm scripts can continue using `bcp dev`, `bcp build`, and other existing commands because npm places the project's `node_modules/.bin` directory on the script `PATH` before system executables.
35
35
 
36
+ ### Framework source checkout
37
+
38
+ The framework source repository itself is a private development workspace and is not an installed copy of the published package. Merely opening PowerShell in `D:\bcp-framework` does not make `bcp-framework` available on `PATH`.
39
+
40
+ `bcp doctor` and `bcp inspect` are application diagnostics: they expect an application root with `app/` plus an installed BCP dependency. Do not use them as the release-health command for the framework source checkout.
41
+
42
+ For framework development and release validation, use:
43
+
44
+ ```powershell
45
+ npm run typecheck
46
+ npm run test:unit
47
+ npm run rc:check
48
+ ```
49
+
50
+ After a packed or published package is installed into an application such as `bcp-docs`, run:
51
+
52
+ ```powershell
53
+ npm exec -- bcp-framework doctor
54
+ npm exec -- bcp-framework inspect
55
+ ```
56
+
36
57
  ## `bcp doctor`
37
58
 
38
- Run a health check from the application root:
59
+ Run a health check from an installed application root. On Windows PowerShell, prefer:
39
60
 
40
- ```bash
41
- bcp doctor
61
+ ```powershell
62
+ npm exec -- bcp-framework doctor
42
63
  ```
43
64
 
44
65
  The command checks:
@@ -106,8 +127,8 @@ The installed folder remains `node_modules/bcp` even when the package inside the
106
127
 
107
128
  Use `inspect` when you want a deterministic snapshot of the project inputs BCP sees:
108
129
 
109
- ```bash
110
- bcp inspect
130
+ ```powershell
131
+ npm exec -- bcp-framework inspect
111
132
  ```
112
133
 
113
134
  It prints:
@@ -126,16 +147,16 @@ It prints:
126
147
 
127
148
  Both commands support machine-readable output:
128
149
 
129
- ```bash
130
- bcp doctor --json
131
- bcp inspect --json
150
+ ```powershell
151
+ npm exec -- bcp-framework doctor --json
152
+ npm exec -- bcp-framework inspect --json
132
153
  ```
133
154
 
134
- You can also target another project directory:
155
+ You can also target another installed BCP application directory:
135
156
 
136
- ```bash
137
- bcp doctor --root ../my-app --json
138
- bcp inspect --root ../my-app --json
157
+ ```powershell
158
+ npm exec -- bcp-framework doctor --root ..\my-app --json
159
+ npm exec -- bcp-framework inspect --root ..\my-app --json
139
160
  ```
140
161
 
141
162
  `--json` is intentionally limited to `doctor` and `inspect` so other CLI commands keep their existing human-oriented output contracts.
@@ -163,4 +184,6 @@ A simple project-health gate can use:
163
184
  bcp doctor --json > bcp-doctor.json
164
185
  ```
165
186
 
187
+ Inside npm scripts or CI package-script execution, npm already places `node_modules/.bin` on `PATH`.
188
+
166
189
  The command exits non-zero when blocking checks fail, while the JSON report remains available for CI logs or artifacts.
@@ -0,0 +1,280 @@
1
+ # File Upload
2
+
3
+ BCP Framework 0.1.24 adds server-only multipart and file-storage helpers through `bcp/server`.
4
+
5
+ ## Basic API route
6
+
7
+ ```ts
8
+ import {
9
+ parseMultipartFormData,
10
+ requireUploadedFile,
11
+ saveUploadedFile,
12
+ } from "bcp/server";
13
+
14
+ export async function POST(
15
+ request: Request
16
+ ) {
17
+ const formData =
18
+ await parseMultipartFormData(
19
+ request,
20
+ {
21
+ maxBytes:
22
+ 8 * 1024 * 1024,
23
+ }
24
+ );
25
+
26
+ const file =
27
+ requireUploadedFile(
28
+ formData,
29
+ "file",
30
+ {
31
+ maxBytes:
32
+ 5 * 1024 * 1024,
33
+ allowedTypes: [
34
+ "image/png",
35
+ "image/jpeg",
36
+ "image/webp",
37
+ ],
38
+ allowedExtensions: [
39
+ ".png",
40
+ ".jpg",
41
+ ".jpeg",
42
+ ".webp",
43
+ ],
44
+ }
45
+ );
46
+
47
+ const saved =
48
+ await saveUploadedFile(
49
+ file,
50
+ {
51
+ directory:
52
+ "./uploads",
53
+ }
54
+ );
55
+
56
+ return Response.json({
57
+ fileName:
58
+ saved.fileName,
59
+ size:
60
+ saved.size,
61
+ checksumSha256:
62
+ saved.checksumSha256,
63
+ });
64
+ }
65
+ ```
66
+
67
+ ## Request body limit
68
+
69
+ BCP's security gateway enforces the framework-wide request body limit before the request reaches the application server.
70
+
71
+ The default is 1 MiB. Applications that accept larger uploads must explicitly raise it:
72
+
73
+ ```ts
74
+ // bcp.config.ts
75
+ import {
76
+ defineConfig,
77
+ } from "bcp/config";
78
+
79
+ export default defineConfig({
80
+ server: {
81
+ bodyLimit:
82
+ 10 * 1024 * 1024,
83
+ },
84
+ });
85
+ ```
86
+
87
+ The same setting can be supplied with `BCP_BODY_LIMIT`.
88
+
89
+ Keep `server.bodyLimit` greater than or equal to the multipart limit used by `parseMultipartFormData()`.
90
+
91
+ ## Multipart parsing
92
+
93
+ ```ts
94
+ const formData =
95
+ await parseMultipartFormData(
96
+ request,
97
+ {
98
+ maxBytes:
99
+ 10 * 1024 * 1024,
100
+ }
101
+ );
102
+ ```
103
+
104
+ The helper:
105
+
106
+ - requires `multipart/form-data`,
107
+ - rejects a declared `Content-Length` above `maxBytes`,
108
+ - validates the parsed form payload size,
109
+ - throws `UploadError` with HTTP-oriented status information on validation failures.
110
+
111
+ `maxBytes` is optional. The framework-wide `server.bodyLimit` remains the outer request limit.
112
+
113
+ ## Reading files
114
+
115
+ Use `getUploadedFile()` for optional files:
116
+
117
+ ```ts
118
+ const avatar =
119
+ getUploadedFile(
120
+ formData,
121
+ "avatar"
122
+ );
123
+
124
+ if (!avatar) {
125
+ // no file supplied
126
+ }
127
+ ```
128
+
129
+ Use `requireUploadedFile()` when a file is mandatory:
130
+
131
+ ```ts
132
+ const avatar =
133
+ requireUploadedFile(
134
+ formData,
135
+ "avatar"
136
+ );
137
+ ```
138
+
139
+ A missing required file throws `UploadError` with code `FILE_REQUIRED` and status `400`.
140
+
141
+ ## File constraints
142
+
143
+ `getUploadedFile()`, `requireUploadedFile()` and `saveUploadedFile()` can validate:
144
+
145
+ ```ts
146
+ {
147
+ maxBytes: 5 * 1024 * 1024,
148
+ allowedTypes: [
149
+ "application/pdf",
150
+ ],
151
+ allowedExtensions: [
152
+ ".pdf",
153
+ ],
154
+ }
155
+ ```
156
+
157
+ Supported checks are:
158
+
159
+ - maximum file size,
160
+ - MIME type allowlist,
161
+ - extension allowlist.
162
+
163
+ MIME type and file extension are metadata supplied with the upload. They are useful validation signals but are not content-signature verification. Applications accepting security-sensitive formats should inspect the file content or magic bytes before trusting the file type.
164
+
165
+ ## Saving files
166
+
167
+ ```ts
168
+ const saved =
169
+ await saveUploadedFile(
170
+ file,
171
+ {
172
+ directory:
173
+ "./uploads/documents",
174
+ }
175
+ );
176
+ ```
177
+
178
+ When `fileName` is omitted, BCP generates a UUID-based storage name while preserving a short sanitized extension.
179
+
180
+ The result contains:
181
+
182
+ ```ts
183
+ {
184
+ originalName: string;
185
+ fileName: string;
186
+ path: string;
187
+ type: string;
188
+ size: number;
189
+ checksumSha256: string;
190
+ }
191
+ ```
192
+
193
+ Files are created with exclusive-write behavior by default. An existing destination produces `FILE_EXISTS` instead of silently overwriting data.
194
+
195
+ Use `overwrite: true` only when replacement is intentional:
196
+
197
+ ```ts
198
+ await saveUploadedFile(
199
+ file,
200
+ {
201
+ directory:
202
+ "./uploads",
203
+ fileName:
204
+ "avatar.webp",
205
+ overwrite:
206
+ true,
207
+ }
208
+ );
209
+ ```
210
+
211
+ ## File-name safety
212
+
213
+ `saveUploadedFile()` sanitizes the final file name and verifies that the resolved destination remains inside the configured directory.
214
+
215
+ You can use the same sanitizer independently:
216
+
217
+ ```ts
218
+ import {
219
+ sanitizeUploadFileName,
220
+ } from "bcp/server";
221
+
222
+ const safeName =
223
+ sanitizeUploadFileName(
224
+ userFileName
225
+ );
226
+ ```
227
+
228
+ Path separators, control characters and operating-system-invalid filename characters are removed or replaced.
229
+
230
+ ## Upload errors
231
+
232
+ ```ts
233
+ import {
234
+ UploadError,
235
+ } from "bcp/server";
236
+
237
+ try {
238
+ // upload work
239
+ } catch (error) {
240
+ if (
241
+ error instanceof UploadError
242
+ ) {
243
+ return Response.json(
244
+ {
245
+ error: {
246
+ code:
247
+ error.code,
248
+ message:
249
+ error.message,
250
+ },
251
+ },
252
+ {
253
+ status:
254
+ error.status,
255
+ }
256
+ );
257
+ }
258
+
259
+ throw error;
260
+ }
261
+ ```
262
+
263
+ Current error codes include:
264
+
265
+ ```text
266
+ INVALID_MULTIPART
267
+ UPLOAD_TOO_LARGE
268
+ FILE_REQUIRED
269
+ FILE_TOO_LARGE
270
+ FILE_TYPE_NOT_ALLOWED
271
+ FILE_EXTENSION_NOT_ALLOWED
272
+ INVALID_FILE_NAME
273
+ FILE_EXISTS
274
+ ```
275
+
276
+ ## Storage model
277
+
278
+ The 0.1.24 foundation writes to the local filesystem. It does not provide S3/object-storage adapters, multipart streaming directly to object storage, virus scanning or image transcoding yet.
279
+
280
+ For horizontally scaled applications, use shared/object storage rather than relying on instance-local disk.
@@ -0,0 +1,166 @@
1
+ # BCP Framework 0.1.24
2
+
3
+ BCP 0.1.24 introduces the File Upload foundation and includes production/development stabilization fixes found while validating the framework against `bcp-docs`.
4
+
5
+ ## Highlights
6
+
7
+ - Added multipart upload parsing through `bcp/server`.
8
+ - Added optional and required uploaded-file helpers.
9
+ - Added maximum multipart and per-file size validation.
10
+ - Added MIME type and extension allowlists.
11
+ - Added safe local file persistence with UUID storage names by default.
12
+ - Added filename sanitization and destination traversal protection.
13
+ - Existing files are not overwritten unless `overwrite: true` is explicitly requested.
14
+ - Saved upload metadata includes SHA-256 checksum, original name, stored name, MIME type and byte size.
15
+ - Added `UploadError` with HTTP-oriented status codes and stable upload error codes.
16
+ - Added unit and package-export regression coverage.
17
+ - Fixed development route graph synchronization so page/client bundles rebuild when the actual route topology changes even if the filesystem watcher reports the change as `change` rather than `add`/`unlink`.
18
+ - Fixed standalone auth guards using `requireAuth()` / `requireRole()` after `bcp build` by keeping `bcp/auth` inside the same bundled request-context runtime as the guard evaluator.
19
+ - Applied the same auth-runtime unification to production form-action bundles so guarded actions do not split request context.
20
+ - Added standalone E2E coverage that builds the application and executes a real `requireRole("admin")` guard with a signed session cookie.
21
+
22
+ ## Development client-bundle graph fix
23
+
24
+ A development server could previously reach this inconsistent state:
25
+
26
+ ```text
27
+ routes contains /docs/[...slug]
28
+ clientBundles does not contain /docs/[...slug]
29
+ ```
30
+
31
+ The resulting request failed with:
32
+
33
+ ```text
34
+ BCP Framework Error:
35
+ Error: Client bundle was not found for route "/docs/[...slug]".
36
+ ```
37
+
38
+ Restarting after deleting `.bcp-framework` rebuilt the graph and hid the problem.
39
+
40
+ BCP 0.1.24 tracks the actual page route graph separately at the dev gateway. When pathname, page file or layout topology changes, the internal development server/client bundler is refreshed automatically on the same upstream port. The behavior no longer depends on whether Windows/editor filesystem events are classified as `add`, `unlink` or `change`.
41
+
42
+ ## Production auth-guard request context fix
43
+
44
+ A standalone build could previously fail when an application guard imported authentication helpers from `bcp/auth`:
45
+
46
+ ```ts
47
+ import {
48
+ requireRole,
49
+ } from "bcp/auth";
50
+
51
+ export async function guard() {
52
+ return requireRole(
53
+ "admin"
54
+ );
55
+ }
56
+ ```
57
+
58
+ The production guard evaluator bundled its own `runWithRequestContext()` runtime, but `bcp/auth` could remain an external package import. This created two request-context module instances inside one request path:
59
+
60
+ ```text
61
+ guards.mjs request context
62
+
63
+ application guard
64
+
65
+ external bcp/auth
66
+
67
+ second request context
68
+ ```
69
+
70
+ The authentication helper then failed while reading cookies with:
71
+
72
+ ```text
73
+ BCP Framework: server request APIs can only be used while handling a request.
74
+ ```
75
+
76
+ BCP 0.1.24 resolves `bcp/auth` into the guard build graph so `requireAuth()`, `requireRole()`, sessions and cookies share the same active request context as the production guard evaluator. The production action bundle uses the same rule because route guards may execute before server mutations.
77
+
78
+ ## Upload example
79
+
80
+ ```ts
81
+ import {
82
+ parseMultipartFormData,
83
+ requireUploadedFile,
84
+ saveUploadedFile,
85
+ } from "bcp/server";
86
+
87
+ export async function POST(
88
+ request: Request
89
+ ) {
90
+ const formData =
91
+ await parseMultipartFormData(
92
+ request,
93
+ {
94
+ maxBytes:
95
+ 8 * 1024 * 1024,
96
+ }
97
+ );
98
+
99
+ const file =
100
+ requireUploadedFile(
101
+ formData,
102
+ "file",
103
+ {
104
+ maxBytes:
105
+ 5 * 1024 * 1024,
106
+ allowedTypes: [
107
+ "image/png",
108
+ "image/jpeg",
109
+ "image/webp",
110
+ ],
111
+ allowedExtensions: [
112
+ ".png",
113
+ ".jpg",
114
+ ".jpeg",
115
+ ".webp",
116
+ ],
117
+ }
118
+ );
119
+
120
+ const saved =
121
+ await saveUploadedFile(
122
+ file,
123
+ {
124
+ directory:
125
+ "./uploads",
126
+ }
127
+ );
128
+
129
+ return Response.json(
130
+ saved
131
+ );
132
+ }
133
+ ```
134
+
135
+ ## Body-limit relationship
136
+
137
+ The existing BCP security gateway still enforces `server.bodyLimit` / `BCP_BODY_LIMIT` before request bodies reach application handlers.
138
+
139
+ The default framework body limit is 1 MiB. Applications accepting larger files must raise the framework body limit as well as choosing appropriate multipart/file limits.
140
+
141
+ ```ts
142
+ export default defineConfig({
143
+ server: {
144
+ bodyLimit:
145
+ 10 * 1024 * 1024,
146
+ },
147
+ });
148
+ ```
149
+
150
+ ## Security model
151
+
152
+ The upload helpers protect storage paths and enforce declared size/type/extension policies, but MIME type and extension do not prove file content. Security-sensitive applications should additionally inspect file signatures/content and run malware scanning when appropriate.
153
+
154
+ The 0.1.24 storage helper targets local filesystem storage. Object-storage adapters and direct streaming are outside this foundation milestone.
155
+
156
+ ## Compatibility
157
+
158
+ The upload APIs are additive and server-only. Existing routing, loaders, guards, actions, middleware, authentication, database, validation and logging APIs remain compatible.
159
+
160
+ The dev route-graph fix changes only rebuild behavior when the discovered page topology has actually changed.
161
+
162
+ The production auth-guard change affects bundling only: existing `bcp/auth` application imports and guard APIs remain unchanged.
163
+
164
+ ## Next milestone
165
+
166
+ The next planned milestone is BCP 0.1.25 — upload/storage adapters and production file-delivery hardening, unless roadmap priorities are regrouped.
@@ -0,0 +1,132 @@
1
+ # BCP Framework 0.1.25
2
+
3
+ BCP 0.1.25 adds the first storage-adapter contract and hardened file-delivery responses on top of the multipart/file upload foundation introduced in 0.1.24.
4
+
5
+ ## Highlights
6
+
7
+ - Added `StorageAdapter` for application-level storage abstraction.
8
+ - Added built-in `createLocalStorage()` filesystem adapter.
9
+ - Added traversal-safe relative storage keys.
10
+ - Added overwrite protection by default.
11
+ - Added persistent local metadata for content type and SHA-256 identity.
12
+ - Added `stat()`, ranged `read()`, `exists()` and `delete()` operations.
13
+ - Added `storeUploadedFile()` to write validated multipart `File` values into any storage adapter.
14
+ - Added `createStorageResponse()` for production GET/HEAD delivery.
15
+ - Added strong ETag validators for objects written through the local adapter.
16
+ - Added `If-None-Match`, `If-Modified-Since` and `If-Range` support.
17
+ - Added single byte-range delivery with `206 Partial Content`.
18
+ - Added `416 Range Not Satisfiable` handling.
19
+ - Added safe `Content-Disposition` generation for downloads.
20
+ - Added conservative private cache defaults.
21
+ - Added storage/file-delivery unit and public-export regression coverage.
22
+
23
+ ## Storage abstraction
24
+
25
+ ```ts
26
+ import {
27
+ createLocalStorage,
28
+ } from "bcp/server";
29
+
30
+ const storage =
31
+ createLocalStorage({
32
+ directory:
33
+ "./uploads",
34
+ });
35
+
36
+ await storage.put(
37
+ "documents/report.txt",
38
+ "Hello",
39
+ {
40
+ contentType:
41
+ "text/plain",
42
+ }
43
+ );
44
+ ```
45
+
46
+ The public adapter contract includes:
47
+
48
+ ```text
49
+ put
50
+ stat
51
+ read
52
+ exists
53
+ delete
54
+ ```
55
+
56
+ Application code can depend on `StorageAdapter` instead of the local filesystem implementation.
57
+
58
+ ## Upload integration
59
+
60
+ ```ts
61
+ const stored =
62
+ await storeUploadedFile(
63
+ file,
64
+ {
65
+ storage,
66
+ key:
67
+ "avatars/user-101.webp",
68
+ }
69
+ );
70
+ ```
71
+
72
+ The upload validation APIs from 0.1.24 remain compatible. `storeUploadedFile()` is additive and reuses the same `UploadConstraints` validation model.
73
+
74
+ ## File delivery
75
+
76
+ ```ts
77
+ return createStorageResponse(
78
+ request,
79
+ storage,
80
+ "documents/report.pdf",
81
+ {
82
+ disposition:
83
+ "attachment",
84
+ downloadName:
85
+ "report.pdf",
86
+ }
87
+ );
88
+ ```
89
+
90
+ The helper supports:
91
+
92
+ ```text
93
+ 200 full response
94
+ 206 single byte range
95
+ 304 validator hit
96
+ 404 missing object
97
+ 405 unsupported request method
98
+ 416 invalid/unsupported range
99
+ ```
100
+
101
+ `HEAD` returns the same entity metadata headers without reading object bytes into the response body.
102
+
103
+ Multiple ranges are intentionally unsupported in 0.1.25 and return `416`.
104
+
105
+ ## Cache policy
106
+
107
+ The default is:
108
+
109
+ ```text
110
+ private, max-age=0, must-revalidate
111
+ ```
112
+
113
+ Applications can explicitly opt public immutable files into stronger caching.
114
+
115
+ ## Security model
116
+
117
+ - Storage keys reject absolute paths and traversal segments.
118
+ - BCP's local metadata directory cannot be addressed as an application storage key.
119
+ - Existing objects are not overwritten unless requested explicitly.
120
+ - Download filenames are stripped of path/control characters before being placed in response headers.
121
+ - Storage keys do not provide authorization; route/API guards must still enforce ownership and access permissions.
122
+ - Content type remains metadata, not content-signature verification.
123
+
124
+ ## Compatibility
125
+
126
+ `saveUploadedFile()` from 0.1.24 remains supported. Applications can migrate incrementally to `StorageAdapter` / `storeUploadedFile()` when they need backend-independent storage behavior.
127
+
128
+ The route-graph synchronization and standalone auth-guard fixes completed during the 0.1.24 stabilization cycle remain part of the 0.1.25 baseline.
129
+
130
+ ## Next milestone
131
+
132
+ The next planned milestone is BCP 0.1.26 — storage adapter ecosystem / S3-compatible integration and production upload streaming, unless roadmap priorities are regrouped.