@chidchanun/bcp 0.1.22 → 0.1.24
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 +241 -640
- package/docs/README.md +114 -337
- package/docs/auth-route-guards.md +14 -0
- package/docs/developer-tools.md +83 -12
- package/docs/development-logging.md +234 -6
- package/docs/file-upload.md +280 -0
- package/docs/releases/0.1.22.md +66 -1
- package/docs/releases/0.1.23.md +131 -0
- package/docs/releases/0.1.24.md +166 -0
- package/package.json +3 -2
- package/packages/bundler/src/server-production-actions.ts +15 -0
- package/packages/bundler/src/server-production-guards.ts +15 -0
- package/packages/cli/bin/bcp.mjs +111 -0
- package/packages/client/src/form.tsx +20 -13
- package/packages/client/src/server.ts +31 -0
- package/packages/server/src/dev-hmr.ts +131 -9
- package/packages/server/src/dev-route-graph.ts +91 -0
- package/packages/server/src/logger.ts +474 -0
- package/packages/server/src/middleware-dev-server.ts +217 -9
- package/packages/server/src/upload.ts +556 -0
package/docs/developer-tools.md
CHANGED
|
@@ -2,12 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
BCP Framework 0.1.22 adds project diagnostics and runtime inspection commands for debugging a BCP application without starting the development server.
|
|
4
4
|
|
|
5
|
+
## CLI executable names
|
|
6
|
+
|
|
7
|
+
The published BCP package exposes two executable names that point to the same CLI:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
bcp
|
|
11
|
+
bcp-framework
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`bcp` remains the primary and backward-compatible executable name inside npm scripts.
|
|
15
|
+
|
|
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
|
+
|
|
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
|
+
|
|
20
|
+
```powershell
|
|
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
|
+
```
|
|
27
|
+
|
|
28
|
+
Or call the generated Windows shim explicitly:
|
|
29
|
+
|
|
30
|
+
```powershell
|
|
31
|
+
.\node_modules\.bin\bcp-framework.cmd doctor
|
|
32
|
+
```
|
|
33
|
+
|
|
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
|
+
|
|
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
|
+
|
|
5
57
|
## `bcp doctor`
|
|
6
58
|
|
|
7
|
-
Run a health check from
|
|
59
|
+
Run a health check from an installed application root. On Windows PowerShell, prefer:
|
|
8
60
|
|
|
9
|
-
```
|
|
10
|
-
bcp doctor
|
|
61
|
+
```powershell
|
|
62
|
+
npm exec -- bcp-framework doctor
|
|
11
63
|
```
|
|
12
64
|
|
|
13
65
|
The command checks:
|
|
@@ -54,12 +106,29 @@ Invalid hook call. Hooks can only be called inside of the body of a function com
|
|
|
54
106
|
|
|
55
107
|
For local release testing, install the packed `.tgz` artifact instead of linking `.package/bcp` directly into another project. A packed package lets the BCP peer dependencies resolve from the application's normal `node_modules` tree.
|
|
56
108
|
|
|
109
|
+
### Duplicate BCP package protection
|
|
110
|
+
|
|
111
|
+
A BCP application must not load `node_modules/bcp` and `node_modules/@chidchanun/bcp` as two separate framework copies.
|
|
112
|
+
|
|
113
|
+
The CLI performs a startup preflight. If both package roots exist, runtime commands such as `dev`, `build`, and `start` stop before SSR starts. Two framework copies create separate React contexts, which can make APIs such as `useLoaderData()` incorrectly report missing loader data even when the route loader executed successfully.
|
|
114
|
+
|
|
115
|
+
For local release verification, install the tarball under the existing `bcp` dependency key instead of adding the scoped package beside it:
|
|
116
|
+
|
|
117
|
+
```powershell
|
|
118
|
+
npm install `
|
|
119
|
+
--no-save `
|
|
120
|
+
--package-lock=false `
|
|
121
|
+
"bcp@file:D:\bcp-framework\.package\artifacts\chidchanun-bcp-0.1.22.tgz"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The installed folder remains `node_modules/bcp` even when the package inside the tarball is published as `@chidchanun/bcp`.
|
|
125
|
+
|
|
57
126
|
## `bcp inspect`
|
|
58
127
|
|
|
59
128
|
Use `inspect` when you want a deterministic snapshot of the project inputs BCP sees:
|
|
60
129
|
|
|
61
|
-
```
|
|
62
|
-
bcp inspect
|
|
130
|
+
```powershell
|
|
131
|
+
npm exec -- bcp-framework inspect
|
|
63
132
|
```
|
|
64
133
|
|
|
65
134
|
It prints:
|
|
@@ -78,16 +147,16 @@ It prints:
|
|
|
78
147
|
|
|
79
148
|
Both commands support machine-readable output:
|
|
80
149
|
|
|
81
|
-
```
|
|
82
|
-
bcp doctor --json
|
|
83
|
-
bcp inspect --json
|
|
150
|
+
```powershell
|
|
151
|
+
npm exec -- bcp-framework doctor --json
|
|
152
|
+
npm exec -- bcp-framework inspect --json
|
|
84
153
|
```
|
|
85
154
|
|
|
86
|
-
You can also target another
|
|
155
|
+
You can also target another installed BCP application directory:
|
|
87
156
|
|
|
88
|
-
```
|
|
89
|
-
bcp doctor --root
|
|
90
|
-
bcp inspect --root
|
|
157
|
+
```powershell
|
|
158
|
+
npm exec -- bcp-framework doctor --root ..\my-app --json
|
|
159
|
+
npm exec -- bcp-framework inspect --root ..\my-app --json
|
|
91
160
|
```
|
|
92
161
|
|
|
93
162
|
`--json` is intentionally limited to `doctor` and `inspect` so other CLI commands keep their existing human-oriented output contracts.
|
|
@@ -115,4 +184,6 @@ A simple project-health gate can use:
|
|
|
115
184
|
bcp doctor --json > bcp-doctor.json
|
|
116
185
|
```
|
|
117
186
|
|
|
187
|
+
Inside npm scripts or CI package-script execution, npm already places `node_modules/.bin` on `PATH`.
|
|
188
|
+
|
|
118
189
|
The command exits non-zero when blocking checks fail, while the JSON report remains available for CI logs or artifacts.
|
|
@@ -1,13 +1,241 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Logging and observability
|
|
2
2
|
|
|
3
|
-
BCP
|
|
3
|
+
BCP Framework 0.1.23 adds a structured server logger and upgrades development request timing output while keeping framework-internal browser/bootstrap traffic quiet by default.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Server logger
|
|
6
|
+
|
|
7
|
+
Import logging APIs from the server-only `bcp/server` entry:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
logger,
|
|
12
|
+
} from "bcp/server";
|
|
13
|
+
|
|
14
|
+
logger.info(
|
|
15
|
+
"Application started",
|
|
16
|
+
{
|
|
17
|
+
service: "catalog",
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Available levels are:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
debug
|
|
26
|
+
info
|
|
27
|
+
warn
|
|
28
|
+
error
|
|
29
|
+
silent
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The default level is `info`.
|
|
33
|
+
|
|
34
|
+
Structured fields can contain strings, numbers, booleans, objects, arrays, `Error` values and `bigint` values. Circular references are replaced with `[Circular]` rather than crashing the logger.
|
|
35
|
+
|
|
36
|
+
## Request-scoped logging
|
|
37
|
+
|
|
38
|
+
Use `requestLogger()` while handling an API route, loader, guard or form action:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import {
|
|
42
|
+
requestLogger,
|
|
43
|
+
} from "bcp/server";
|
|
44
|
+
|
|
45
|
+
export async function loader() {
|
|
46
|
+
const log =
|
|
47
|
+
await requestLogger({
|
|
48
|
+
feature: "categories",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
log.info(
|
|
52
|
+
"Loading categories"
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
items: [],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The returned child logger automatically binds the current request identity:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
requestId
|
|
65
|
+
method
|
|
66
|
+
path
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`requestId` uses the same request-scoped value exposed by `requestId()`: a valid incoming `X-Request-Id` is reused, otherwise BCP creates a UUID for the request.
|
|
70
|
+
|
|
71
|
+
## Response request ID
|
|
72
|
+
|
|
73
|
+
When an application wants to return the request identity to a caller, wrap a response with `attachRequestId()`:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import {
|
|
77
|
+
attachRequestId,
|
|
78
|
+
json,
|
|
79
|
+
} from "bcp/server";
|
|
80
|
+
|
|
81
|
+
export async function GET() {
|
|
82
|
+
return attachRequestId(
|
|
83
|
+
json({
|
|
84
|
+
ok: true,
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The returned response includes:
|
|
6
91
|
|
|
7
92
|
```text
|
|
8
|
-
|
|
93
|
+
X-Request-Id: <current request id>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This helper preserves the original response status, status text, body and headers.
|
|
97
|
+
|
|
98
|
+
## Child loggers
|
|
99
|
+
|
|
100
|
+
Bind shared fields once with `child()`:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import {
|
|
104
|
+
logger,
|
|
105
|
+
} from "bcp/server";
|
|
106
|
+
|
|
107
|
+
const dbLog =
|
|
108
|
+
logger.child({
|
|
109
|
+
component: "database",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
dbLog.debug(
|
|
113
|
+
"Executing query",
|
|
114
|
+
{
|
|
115
|
+
operation: "users.list",
|
|
116
|
+
}
|
|
117
|
+
);
|
|
9
118
|
```
|
|
10
119
|
|
|
11
|
-
|
|
120
|
+
Child bindings are inherited by further child loggers.
|
|
121
|
+
|
|
122
|
+
## Log level
|
|
123
|
+
|
|
124
|
+
Set the minimum log level with:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
BCP_LOG_LEVEL=debug
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Supported values:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
debug
|
|
134
|
+
info
|
|
135
|
+
warn
|
|
136
|
+
error
|
|
137
|
+
silent
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Invalid or missing values fall back to `info`.
|
|
141
|
+
|
|
142
|
+
Use `debug` during framework/application diagnostics to see development SSR and module-import timing events.
|
|
143
|
+
|
|
144
|
+
## Log format
|
|
12
145
|
|
|
13
|
-
|
|
146
|
+
Human-readable output is the default:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
BCP_LOG_FORMAT=pretty
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
|
|
154
|
+
```text
|
|
155
|
+
[2026-08-27T10:00:00.000Z] [INFO] [bcp] User loaded userId=42
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
For containers, log collectors and CI systems use JSON lines:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
BCP_LOG_FORMAT=json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{"timestamp":"2026-08-27T10:00:00.000Z","level":"info","logger":"bcp","message":"User loaded","userId":42}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Each log call emits exactly one line.
|
|
171
|
+
|
|
172
|
+
## Development HTTP logging
|
|
173
|
+
|
|
174
|
+
Development request output remains focused on application API traffic. BCP suppresses request-style logs for framework and browser bootstrap traffic such as:
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
/_bcp/*
|
|
178
|
+
/api/bcp-styles
|
|
179
|
+
page/document requests
|
|
180
|
+
public static assets
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
An application API request is converted into the structured logger format. With pretty output it resembles:
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
[2026-08-27T10:00:00.000Z] [INFO] [bcp-dev] HTTP request event=http.request method=GET path=/api/users status=200 durationMs=1.24
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
With `BCP_LOG_FORMAT=json` the same event is emitted as one JSON object containing:
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
event=http.request
|
|
193
|
+
method
|
|
194
|
+
path
|
|
195
|
+
status
|
|
196
|
+
durationMs
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Existing development `Import: ...ms` and `SSR: ...ms` measurements are converted into `debug` events:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
module.import
|
|
203
|
+
ssr.render
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
They are therefore visible when `BCP_LOG_LEVEL=debug` and stay out of normal `info` output.
|
|
207
|
+
|
|
208
|
+
## Errors
|
|
209
|
+
|
|
210
|
+
Pass an `Error` as a structured field:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
try {
|
|
214
|
+
// ...
|
|
215
|
+
} catch (error) {
|
|
216
|
+
logger.error(
|
|
217
|
+
"Operation failed",
|
|
218
|
+
{
|
|
219
|
+
error,
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`Error` values are serialized with their name, message, stack and cause when available.
|
|
226
|
+
|
|
227
|
+
## Environment example
|
|
228
|
+
|
|
229
|
+
A development `.env.local` can use:
|
|
230
|
+
|
|
231
|
+
```text
|
|
232
|
+
BCP_LOG_LEVEL=debug
|
|
233
|
+
BCP_LOG_FORMAT=pretty
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
A production/container environment can use:
|
|
237
|
+
|
|
238
|
+
```text
|
|
239
|
+
BCP_LOG_LEVEL=info
|
|
240
|
+
BCP_LOG_FORMAT=json
|
|
241
|
+
```
|
|
@@ -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.
|