Skip to content

Commit 379106b

Browse files
fengmk2claude
andcommitted
feat(co-busboy): port co-busboy from JavaScript to TypeScript
Port the co-busboy library as @eggjs/co-busboy with full TypeScript support: - Replace `chan` library with native Promise-based queue implementation - Add comprehensive type definitions for Parts, FileStream, FieldTuple - Support autoFields, checkField, and checkFile options - Handle gzip/deflate compression via inflation - Enforce limits with proper 413 error codes - Port all tests from Mocha to Vitest (22 tests passing) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 19db506 commit 379106b

12 files changed

Lines changed: 1393 additions & 0 deletions

File tree

packages/co-busboy/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
.tshy*
3+
coverage
4+
dist

packages/co-busboy/CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Changelog
2+
3+
## 5.0.0-beta.36
4+
5+
**Initial TypeScript Release**
6+
7+
This is a TypeScript port of [co-busboy](https://github.com/cojs/busboy) with the following improvements:
8+
9+
- Full TypeScript support with comprehensive type definitions
10+
- Modern async/await API (no generator dependencies)
11+
- Replaced `chan` library with native Promise-based queue implementation
12+
- ESM module format
13+
- Compatible with Node.js >= 22.18.0
14+
15+
### Features
16+
17+
- Parse multipart/form-data with async/await
18+
- Support for both Node.js native requests and Koa context objects
19+
- Auto-decompression of gzip/deflate compressed requests
20+
- Field auto-collection with `autoFields` option
21+
- Validation hooks: `checkField` and `checkFile`
22+
- Limit enforcement (413 errors for parts/files/fields limits)
23+
24+
### Breaking Changes from co-busboy 2.x
25+
26+
- Requires Node.js >= 22.18.0
27+
- ESM only (no CommonJS support)
28+
- Generator/yield syntax is no longer supported (use async/await)

packages/co-busboy/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2017-present Alibaba Group Holding Limited and other contributors.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/co-busboy/README.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# @eggjs/co-busboy
2+
3+
Multipart form data handling with async/await support for Egg.js and Koa.
4+
5+
A TypeScript port of [co-busboy](https://github.com/cojs/busboy), providing a promise-based wrapper around [busboy](https://github.com/mscdex/busboy) for parsing multipart/form-data.
6+
7+
## Installation
8+
9+
```bash
10+
npm install @eggjs/co-busboy
11+
```
12+
13+
## Usage
14+
15+
```typescript
16+
import { parse } from '@eggjs/co-busboy';
17+
18+
// In a Koa middleware
19+
app.use(async (ctx) => {
20+
const parts = parse(ctx, { autoFields: true });
21+
let part;
22+
23+
while ((part = await parts())) {
24+
if (Array.isArray(part)) {
25+
// It's a field: [name, value, nameTruncated, valueTruncated]
26+
console.log('Field:', part[0], '=', part[1]);
27+
} else {
28+
// It's a file stream with additional properties
29+
console.log('File:', part.filename, part.mimeType);
30+
// Consume the stream
31+
part.pipe(fs.createWriteStream(`./uploads/${part.filename}`));
32+
}
33+
}
34+
35+
// Access auto-collected fields (when autoFields: true)
36+
console.log(parts.field); // { fieldName: value }
37+
console.log(parts.fields); // [[name, value, nameTrunc, valTrunc], ...]
38+
});
39+
```
40+
41+
## API
42+
43+
### `parse(request, options?)`
44+
45+
Parse multipart form data from a request.
46+
47+
#### Parameters
48+
49+
- `request` - Node.js IncomingMessage or Koa context
50+
- `options` - Optional configuration object
51+
52+
#### Options
53+
54+
All standard [busboy options](https://github.com/mscdex/busboy#api) are supported, plus:
55+
56+
- `autoFields` (boolean, default: `false`) - When true, automatically collects all form fields. Fields will be available via `parts.field` (object lookup) and `parts.fields` (array lookup). Only file streams will be returned in the iteration.
57+
58+
- `checkField` (function) - Hook to validate form fields. Return an Error to reject the field.
59+
60+
```typescript
61+
checkField: (name, value, fieldnameTruncated, valueTruncated) => {
62+
if (name === '_csrf' && !isValidToken(value)) {
63+
return new Error('Invalid CSRF token');
64+
}
65+
}
66+
```
67+
68+
- `checkFile` (function) - Hook to validate file uploads. Return an Error to reject the file.
69+
```typescript
70+
checkFile: (fieldname, stream, filename, encoding, mimetype) => {
71+
if (!filename.endsWith('.jpg')) {
72+
const err = new Error('Only JPG files allowed');
73+
err.status = 400;
74+
return err;
75+
}
76+
}
77+
```
78+
79+
#### Return Value
80+
81+
Returns a `Parts` function that yields parts when called:
82+
83+
```typescript
84+
interface Parts {
85+
(): Promise<Part | null>;
86+
field: Record<string, string | string[]>;
87+
fields: FieldTuple[];
88+
}
89+
```
90+
91+
- Call `parts()` repeatedly to get each part
92+
- Returns `null` when parsing is complete
93+
- When `autoFields` is true, fields are collected in `parts.field` and `parts.fields`
94+
95+
#### Part Types
96+
97+
**Field** - An array with 4 elements:
98+
99+
```typescript
100+
type FieldTuple = [
101+
string, // name
102+
string, // value
103+
boolean, // fieldnameTruncated
104+
boolean // valueTruncated
105+
];
106+
```
107+
108+
**File** - A Readable stream with additional properties:
109+
110+
```typescript
111+
interface FileStream extends Readable {
112+
fieldname: string;
113+
filename: string;
114+
encoding: string;
115+
transferEncoding: string;
116+
mime: string;
117+
mimeType: string;
118+
}
119+
```
120+
121+
## Error Handling
122+
123+
Limit errors include status and code properties:
124+
125+
```typescript
126+
try {
127+
while ((part = await parts())) {
128+
// process part
129+
}
130+
} catch (err) {
131+
console.log(err.status); // 413
132+
console.log(err.code); // 'Request_files_limit', 'Request_fields_limit', or 'Request_parts_limit'
133+
}
134+
```
135+
136+
## Compression Support
137+
138+
Gzip and deflate compressed requests are automatically decompressed via the `inflation` library.
139+
140+
## License
141+
142+
[MIT](LICENSE)

packages/co-busboy/package.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"name": "@eggjs/co-busboy",
3+
"version": "5.0.0-beta.36",
4+
"description": "co-busboy for egg - multipart form data handling with async/await support",
5+
"keywords": [
6+
"busboy",
7+
"egg",
8+
"form-data",
9+
"koa",
10+
"multipart",
11+
"upload"
12+
],
13+
"homepage": "https://github.com/eggjs/egg/tree/next/packages/co-busboy",
14+
"license": "MIT",
15+
"author": "eggjs",
16+
"repository": {
17+
"type": "git",
18+
"url": "git+https://github.com/eggjs/egg.git",
19+
"directory": "packages/co-busboy"
20+
},
21+
"files": [
22+
"dist"
23+
],
24+
"type": "module",
25+
"main": "./dist/index.js",
26+
"module": "./dist/index.js",
27+
"types": "./dist/index.d.ts",
28+
"exports": {
29+
".": "./src/index.ts",
30+
"./package.json": "./package.json"
31+
},
32+
"publishConfig": {
33+
"access": "public",
34+
"exports": {
35+
".": "./dist/index.js",
36+
"./package.json": "./package.json"
37+
}
38+
},
39+
"scripts": {
40+
"typecheck": "tsgo --noEmit"
41+
},
42+
"dependencies": {
43+
"black-hole-stream": "catalog:",
44+
"busboy": "catalog:",
45+
"inflation": "catalog:"
46+
},
47+
"devDependencies": {
48+
"@eggjs/tsconfig": "workspace:*",
49+
"@types/busboy": "catalog:",
50+
"formstream": "catalog:",
51+
"typescript": "catalog:"
52+
},
53+
"engines": {
54+
"node": ">=22.18.0"
55+
}
56+
}

0 commit comments

Comments
 (0)