@eggjs/multipart 4.0.0

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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +444 -0
  3. package/dist/commonjs/app/extend/context.d.ts +110 -0
  4. package/dist/commonjs/app/extend/context.js +279 -0
  5. package/dist/commonjs/app/middleware/multipart.d.ts +4 -0
  6. package/dist/commonjs/app/middleware/multipart.js +20 -0
  7. package/dist/commonjs/app/schedule/clean_tmpdir.d.ts +3 -0
  8. package/dist/commonjs/app/schedule/clean_tmpdir.js +58 -0
  9. package/dist/commonjs/app.d.ts +6 -0
  10. package/dist/commonjs/app.js +21 -0
  11. package/dist/commonjs/config/config.default.d.ts +98 -0
  12. package/dist/commonjs/config/config.default.js +31 -0
  13. package/dist/commonjs/index.d.ts +2 -0
  14. package/dist/commonjs/index.js +5 -0
  15. package/dist/commonjs/lib/LimitError.d.ts +5 -0
  16. package/dist/commonjs/lib/LimitError.js +16 -0
  17. package/dist/commonjs/lib/MultipartFileTooLargeError.d.ts +6 -0
  18. package/dist/commonjs/lib/MultipartFileTooLargeError.js +18 -0
  19. package/dist/commonjs/lib/utils.d.ts +4 -0
  20. package/dist/commonjs/lib/utils.js +93 -0
  21. package/dist/commonjs/package.json +3 -0
  22. package/dist/esm/app/extend/context.d.ts +110 -0
  23. package/dist/esm/app/extend/context.js +273 -0
  24. package/dist/esm/app/middleware/multipart.d.ts +4 -0
  25. package/dist/esm/app/middleware/multipart.js +18 -0
  26. package/dist/esm/app/schedule/clean_tmpdir.d.ts +3 -0
  27. package/dist/esm/app/schedule/clean_tmpdir.js +53 -0
  28. package/dist/esm/app.d.ts +6 -0
  29. package/dist/esm/app.js +18 -0
  30. package/dist/esm/config/config.default.d.ts +98 -0
  31. package/dist/esm/config/config.default.js +26 -0
  32. package/dist/esm/index.d.ts +2 -0
  33. package/dist/esm/index.js +3 -0
  34. package/dist/esm/lib/LimitError.d.ts +5 -0
  35. package/dist/esm/lib/LimitError.js +12 -0
  36. package/dist/esm/lib/MultipartFileTooLargeError.d.ts +6 -0
  37. package/dist/esm/lib/MultipartFileTooLargeError.js +14 -0
  38. package/dist/esm/lib/utils.d.ts +4 -0
  39. package/dist/esm/lib/utils.js +85 -0
  40. package/dist/esm/package.json +3 -0
  41. package/dist/package.json +4 -0
  42. package/package.json +103 -0
  43. package/src/app/extend/context.ts +368 -0
  44. package/src/app/middleware/multipart.ts +20 -0
  45. package/src/app/schedule/clean_tmpdir.ts +55 -0
  46. package/src/app.ts +20 -0
  47. package/src/config/config.default.ts +130 -0
  48. package/src/index.ts +2 -0
  49. package/src/lib/LimitError.ts +12 -0
  50. package/src/lib/MultipartFileTooLargeError.ts +14 -0
  51. package/src/lib/utils.ts +92 -0
  52. package/src/typings/index.d.ts +4 -0
package/LICENSE ADDED
@@ -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.
package/README.md ADDED
@@ -0,0 +1,444 @@
1
+ # @eggjs/multipart
2
+
3
+ [![NPM version][npm-image]][npm-url]
4
+ [![Node.js CI](https://github.com/eggjs/multipart/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/multipart/actions/workflows/nodejs.yml)
5
+ [![Test coverage][codecov-image]][codecov-url]
6
+ [![Known Vulnerabilities][snyk-image]][snyk-url]
7
+ [![npm download][download-image]][download-url]
8
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)
9
+ ![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/multipart)
10
+
11
+ [npm-image]: https://img.shields.io/npm/v/@eggjs/multipart.svg?style=flat-square
12
+ [npm-url]: https://npmjs.org/package/@eggjs/multipart
13
+ [codecov-image]: https://codecov.io/github/eggjs/multipart/coverage.svg?branch=master
14
+ [codecov-url]: https://codecov.io/github/eggjs/multipart?branch=master
15
+ [snyk-image]: https://snyk.io/test/npm/@eggjs/multipart/badge.svg?style=flat-square
16
+ [snyk-url]: https://snyk.io/test/npm/@eggjs/multipart
17
+ [download-image]: https://img.shields.io/npm/dm/@eggjs/multipart.svg?style=flat-square
18
+ [download-url]: https://npmjs.org/package/@eggjs/multipart
19
+
20
+ Use [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and
21
+ process it without save to disk(using the `stream` mode).
22
+
23
+ Just use `ctx.multipart()` to got file stream, then pass to image processing module such as `gm` or upload to cloud storage such as `oss`.
24
+
25
+ ## Whitelist of file extensions
26
+
27
+ For security, if uploading file extension is not in white list, will response as `400 Bad request`.
28
+
29
+ Default Whitelist:
30
+
31
+ ```js
32
+ const whitelist = [
33
+ // images
34
+ '.jpg', '.jpeg', // image/jpeg
35
+ '.png', // image/png, image/x-png
36
+ '.gif', // image/gif
37
+ '.bmp', // image/bmp
38
+ '.wbmp', // image/vnd.wap.wbmp
39
+ '.webp',
40
+ '.tif',
41
+ '.psd',
42
+ // text
43
+ '.svg',
44
+ '.js', '.jsx',
45
+ '.json',
46
+ '.css', '.less',
47
+ '.html', '.htm',
48
+ '.xml',
49
+ // tar
50
+ '.zip',
51
+ '.gz', '.tgz', '.gzip',
52
+ // video
53
+ '.mp3',
54
+ '.mp4',
55
+ '.avi',
56
+ ];
57
+ ```
58
+
59
+ ### fileSize
60
+
61
+ The default fileSize that multipart can accept is `10mb`. if you upload a large file, you should specify this config.
62
+
63
+ ```js
64
+ // config/config.default.js
65
+ exports.multipart = {
66
+ fileSize: '50mb',
67
+ };
68
+ ```
69
+
70
+ ### Custom Config
71
+
72
+ Developer can custom additional file extensions:
73
+
74
+ ```js
75
+ // config/config.default.js
76
+ exports.multipart = {
77
+ // will append to whilelist
78
+ fileExtensions: [
79
+ '.foo',
80
+ '.apk',
81
+ ],
82
+ };
83
+ ```
84
+
85
+ Can also **override** built-in whitelist, such as only allow png:
86
+
87
+ ```js
88
+ // config/config.default.js
89
+ exports.multipart = {
90
+ whitelist: [
91
+ '.png',
92
+ ],
93
+ };
94
+ ```
95
+
96
+ Or by function:
97
+
98
+ ```js
99
+ exports.multipart = {
100
+ whitelist: (filename) => [ '.png' ].includes(path.extname(filename) || '')
101
+ };
102
+ ```
103
+
104
+ **Note: if define `whitelist`, then `fileExtensions` will be ignored.**
105
+
106
+ ## Examples
107
+
108
+ More examples please follow:
109
+
110
+ - [Handle multipart request in `stream` mode](https://github.com/eggjs/examples/tree/master/multipart)
111
+ - [Handle multipart request in `file` mode](https://github.com/eggjs/examples/tree/master/multipart-file-mode)
112
+
113
+ ## `file` mode: the easy way
114
+
115
+ If you don't know the [Node.js Stream](https://nodejs.org/dist/latest-v18.x/docs/api/stream.html) work,
116
+ maybe you should use the `file` mode to get started.
117
+
118
+ The usage very similar to [bodyParser](https://eggjs.org/en/basics/controller.html#body).
119
+
120
+ - `ctx.request.body`: Get all the multipart fields and values, except `file`.
121
+ - `ctx.request.files`: Contains all `file` from the multipart request, it's an Array object.
122
+
123
+ **WARNING: you should remove the temporary upload files after you use it**,
124
+ the `async ctx.cleanupRequestFiles()` method will be very helpful.
125
+
126
+ ### Enable `file` mode on config
127
+
128
+ You need to set `config.multipart.mode = 'file'` to enable `file` mode:
129
+
130
+ ```js
131
+ // config/config.default.js
132
+ exports.multipart = {
133
+ mode: 'file',
134
+ };
135
+ ```
136
+
137
+ After `file` mode enable, egg will remove the old temporary files(don't include today's files) on `04:30 AM` every day by default.
138
+
139
+ ```js
140
+ config.multipart = {
141
+ mode: 'file',
142
+ tmpdir: path.join(os.tmpdir(), 'egg-multipart-tmp', appInfo.name),
143
+ cleanSchedule: {
144
+ // run tmpdir clean job on every day 04:30 am
145
+ // cron style see https://github.com/eggjs/egg-schedule#cron-style-scheduling
146
+ cron: '0 30 4 * * *',
147
+ disable: false,
148
+ },
149
+ };
150
+ ```
151
+
152
+ Default will use the last field which has same name, if need the all fields value, please set `allowArrayField` in config.
153
+
154
+ ```js
155
+ // config/config.default.js
156
+ exports.multipart = {
157
+ mode: 'file',
158
+ allowArrayField: true,
159
+ };
160
+ ```
161
+
162
+ ### Upload One File
163
+
164
+ ```html
165
+ <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
166
+ title: <input name="title" />
167
+ file: <input name="file" type="file" />
168
+ <button type="submit">Upload</button>
169
+ </form>
170
+ ```
171
+
172
+ Controller which hanlder `POST /upload`:
173
+
174
+ ```js
175
+ // app/controller/upload.js
176
+ const Controller = require('egg').Controller;
177
+ const fs = require('mz/fs');
178
+
179
+ module.exports = class extends Controller {
180
+ async upload() {
181
+ const { ctx } = this;
182
+ const file = ctx.request.files[0];
183
+ const name = 'egg-multipart-test/' + path.basename(file.filename);
184
+ let result;
185
+ try {
186
+ // process file or upload to cloud storage
187
+ result = await ctx.oss.put(name, file.filepath);
188
+ } finally {
189
+ // remove tmp files and don't block the request's response
190
+ // cleanupRequestFiles won't throw error even remove file io error happen
191
+ ctx.cleanupRequestFiles();
192
+ // remove tmp files before send response
193
+ // await ctx.cleanupRequestFiles();
194
+ }
195
+
196
+ ctx.body = {
197
+ url: result.url,
198
+ // get all field values
199
+ requestBody: ctx.request.body,
200
+ };
201
+ }
202
+ };
203
+ ```
204
+
205
+ ### Upload Multiple Files
206
+
207
+ ```html
208
+ <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
209
+ title: <input name="title" />
210
+ file1: <input name="file1" type="file" />
211
+ file2: <input name="file2" type="file" />
212
+ <button type="submit">Upload</button>
213
+ </form>
214
+ ```
215
+
216
+ Controller which hanlder `POST /upload`:
217
+
218
+ ```js
219
+ // app/controller/upload.js
220
+ const Controller = require('egg').Controller;
221
+ const fs = require('mz/fs');
222
+
223
+ module.exports = class extends Controller {
224
+ async upload() {
225
+ const { ctx } = this;
226
+ console.log(ctx.request.body);
227
+ console.log('got %d files', ctx.request.files.length);
228
+ for (const file of ctx.request.files) {
229
+ console.log('field: ' + file.fieldname);
230
+ console.log('filename: ' + file.filename);
231
+ console.log('encoding: ' + file.encoding);
232
+ console.log('mime: ' + file.mime);
233
+ console.log('tmp filepath: ' + file.filepath);
234
+ let result;
235
+ try {
236
+ // process file or upload to cloud storage
237
+ result = await ctx.oss.put('egg-multipart-test/' + file.filename, file.filepath);
238
+ } finally {
239
+ // remove tmp files and don't block the request's response
240
+ // cleanupRequestFiles won't throw error even remove file io error happen
241
+ ctx.cleanupRequestFiles([ file ]);
242
+ }
243
+ console.log(result);
244
+ }
245
+ }
246
+ };
247
+ ```
248
+
249
+ ## `stream` mode: the hard way
250
+
251
+ If you're well-known about know the Node.js Stream work, you should use the `stream` mode.
252
+
253
+ ### Use with `for await...of`
254
+
255
+ ```html
256
+ <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
257
+ title: <input name="title" />
258
+ file1: <input name="file1" type="file" />
259
+ file2: <input name="file2" type="file" />
260
+ <button type="submit">Upload</button>
261
+ </form>
262
+ ```
263
+
264
+ Controller which hanlder `POST /upload`:
265
+
266
+ ```js
267
+ // app/controller/upload.js
268
+ const { Controller } = require('egg');
269
+ const fs = require('fs');
270
+ const stream = require('stream');
271
+ const util = require('util');
272
+ const { randomUUID } = require('crypto');
273
+ const pipeline = util.promisify(stream.pipeline);
274
+
275
+ module.exports = class UploadController extends Controller {
276
+ async upload() {
277
+ const parts = this.ctx.multipart();
278
+ const fields = {};
279
+ const files = {};
280
+
281
+ for await (const part of parts) {
282
+ if (Array.isArray(part)) {
283
+ // fields
284
+ console.log('field: ' + part[0]);
285
+ console.log('value: ' + part[1]);
286
+ } else {
287
+ // otherwise, it's a stream
288
+ const { filename, fieldname, encoding, mime } = part;
289
+
290
+ console.log('field: ' + fieldname);
291
+ console.log('filename: ' + filename);
292
+ console.log('encoding: ' + encoding);
293
+ console.log('mime: ' + mime);
294
+
295
+ // how to handler?
296
+ // 1. save to tmpdir with pipeline
297
+ // 2. or send to oss
298
+ // 3. or just consume it with another for await
299
+
300
+ // WARNING: You should almost never use the origin filename as it could contain malicious input.
301
+ const targetPath = path.join(os.tmpdir(), randomUUID() + path.extname(filename));
302
+ await pipeline(part, createWriteStream(targetPath)); // use `pipeline` not `pipe`
303
+ }
304
+ }
305
+
306
+ this.ctx.body = 'ok';
307
+ }
308
+ };
309
+ ```
310
+
311
+ ### Upload One File (DEPRECATED)
312
+
313
+ You can got upload stream by `ctx.getFileStream*()`.
314
+
315
+ ```html
316
+ <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
317
+ title: <input name="title" />
318
+ file: <input name="file" type="file" />
319
+ <button type="submit">Upload</button>
320
+ </form>
321
+ ```
322
+
323
+ Controller which handler `POST /upload`:
324
+
325
+ ```js
326
+ // app/controller/upload.js
327
+ const path = require('node:path');
328
+ const { sendToWormhole } = require('stream-wormhole');
329
+ const { Controller } = require('egg');
330
+
331
+ module.exports = class extends Controller {
332
+ async upload() {
333
+ const { ctx } = this;
334
+ // file not exists will response 400 error
335
+ const stream = await ctx.getFileStream();
336
+ const name = 'egg-multipart-test/' + path.basename(stream.filename);
337
+ // process file or upload to cloud storage
338
+ const result = await ctx.oss.put(name, stream);
339
+
340
+ ctx.body = {
341
+ url: result.url,
342
+ // process form fields by `stream.fields`
343
+ fields: stream.fields,
344
+ };
345
+ }
346
+
347
+ async uploadNotRequiredFile() {
348
+ const { ctx } = this;
349
+ // file not required
350
+ const stream = await ctx.getFileStream({ requireFile: false });
351
+ let result;
352
+ if (stream.filename) {
353
+ const name = 'egg-multipart-test/' + path.basename(stream.filename);
354
+ // process file or upload to cloud storage
355
+ const result = await ctx.oss.put(name, stream);
356
+ } else {
357
+ // must consume the empty stream
358
+ await sendToWormhole(stream);
359
+ }
360
+
361
+ ctx.body = {
362
+ url: result && result.url,
363
+ // process form fields by `stream.fields`
364
+ fields: stream.fields,
365
+ };
366
+ }
367
+ };
368
+ ```
369
+
370
+ ### Upload Multiple Files (DEPRECATED)
371
+
372
+ ```html
373
+ <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
374
+ title: <input name="title" />
375
+ file1: <input name="file1" type="file" />
376
+ file2: <input name="file2" type="file" />
377
+ <button type="submit">Upload</button>
378
+ </form>
379
+ ```
380
+
381
+ Controller which hanlder `POST /upload`:
382
+
383
+ ```js
384
+ // app/controller/upload.js
385
+ const Controller = require('egg').Controller;
386
+
387
+ module.exports = class extends Controller {
388
+ async upload() {
389
+ const { ctx } = this;
390
+ const parts = ctx.multipart();
391
+ let part;
392
+ while ((part = await parts()) != null) {
393
+ if (part.length) {
394
+ // arrays are busboy fields
395
+ console.log('field: ' + part[0]);
396
+ console.log('value: ' + part[1]);
397
+ console.log('valueTruncated: ' + part[2]);
398
+ console.log('fieldnameTruncated: ' + part[3]);
399
+ } else {
400
+ if (!part.filename) {
401
+ // user click `upload` before choose a file,
402
+ // `part` will be file stream, but `part.filename` is empty
403
+ // must handler this, such as log error.
404
+ continue;
405
+ }
406
+ // otherwise, it's a stream
407
+ console.log('field: ' + part.fieldname);
408
+ console.log('filename: ' + part.filename);
409
+ console.log('encoding: ' + part.encoding);
410
+ console.log('mime: ' + part.mime);
411
+ const result = await ctx.oss.put('egg-multipart-test/' + part.filename, part);
412
+ console.log(result);
413
+ }
414
+ }
415
+ console.log('and we are done parsing the form!');
416
+ }
417
+ };
418
+ ```
419
+
420
+ ### Support `file` and `stream` mode in the same time
421
+
422
+ If the default `mode` is `stream`, use the `fileModeMatch` options to match the request urls switch to `file` mode.
423
+
424
+ ```js
425
+ config.multipart = {
426
+ mode: 'stream',
427
+ // let POST /upload_file request use the file mode, other requests use the stream mode.
428
+ fileModeMatch: /^\/upload_file$/,
429
+ // or glob
430
+ // fileModeMatch: '/upload_file',
431
+ };
432
+ ```
433
+
434
+ NOTICE: `fileModeMatch` options only work on `stream` mode.
435
+
436
+ ## License
437
+
438
+ [MIT](LICENSE)
439
+
440
+ ## Contributors
441
+
442
+ [![Contributors](https://contrib.rocks/image?repo=eggjs/multipart)](https://github.com/eggjs/multipart/graphs/contributors)
443
+
444
+ Made with [contributors-img](https://contrib.rocks).
@@ -0,0 +1,110 @@
1
+ import { Readable } from 'node:stream';
2
+ import { Context } from '@eggjs/core';
3
+ export interface EggFile {
4
+ field: string;
5
+ filename: string;
6
+ encoding: string;
7
+ mime: string;
8
+ filepath: string;
9
+ }
10
+ export interface MultipartFileStream extends Readable {
11
+ fields: Record<string, any>;
12
+ filename: string;
13
+ fieldname: string;
14
+ mime: string;
15
+ mimeType: string;
16
+ transferEncoding: string;
17
+ encoding: string;
18
+ truncated: boolean;
19
+ }
20
+ export interface MultipartOptions {
21
+ autoFields?: boolean;
22
+ /**
23
+ * required file submit, default is true
24
+ */
25
+ requireFile?: boolean;
26
+ /**
27
+ * default charset encoding
28
+ */
29
+ defaultCharset?: string;
30
+ /**
31
+ * compatible with defaultCharset
32
+ * @deprecated use `defaultCharset` instead
33
+ */
34
+ defCharset?: string;
35
+ defaultParamCharset?: string;
36
+ /**
37
+ * compatible with defaultParamCharset
38
+ * @deprecated use `defaultParamCharset` instead
39
+ */
40
+ defParamCharset?: string;
41
+ limits?: {
42
+ fieldNameSize?: number;
43
+ fieldSize?: number;
44
+ fields?: number;
45
+ fileSize?: number;
46
+ files?: number;
47
+ parts?: number;
48
+ headerPairs?: number;
49
+ };
50
+ checkFile?(fieldname: string, file: any, filename: string, encoding: string, mimetype: string): void | Error;
51
+ }
52
+ export default class MultipartContext extends Context {
53
+ /**
54
+ * create multipart.parts instance, to get separated files.
55
+ * @function Context#multipart
56
+ * @param {Object} [options] - override default multipart configurations
57
+ * - {Boolean} options.autoFields
58
+ * - {String} options.defaultCharset
59
+ * - {String} options.defaultParamCharset
60
+ * - {Object} options.limits
61
+ * - {Function} options.checkFile
62
+ * @return {Yieldable | AsyncIterable<Yieldable>} parts
63
+ */
64
+ multipart(options?: MultipartOptions): AsyncIterable<MultipartFileStream>;
65
+ /**
66
+ * save request multipart data and files to `ctx.request`
67
+ * @function Context#saveRequestFiles
68
+ * @param {Object} options - { limits, checkFile, ... }
69
+ */
70
+ saveRequestFiles(options?: MultipartOptions): Promise<void>;
71
+ /**
72
+ * get upload file stream
73
+ * @example
74
+ * ```js
75
+ * const stream = await ctx.getFileStream();
76
+ * // get other fields
77
+ * console.log(stream.fields);
78
+ * ```
79
+ * @function Context#getFileStream
80
+ * @param {Object} options
81
+ * - {Boolean} options.requireFile - required file submit, default is true
82
+ * - {String} options.defaultCharset
83
+ * - {String} options.defaultParamCharset
84
+ * - {Object} options.limits
85
+ * - {Function} options.checkFile
86
+ * @return {ReadStream} stream
87
+ * @since 1.0.0
88
+ * @deprecated Not safe enough, use `ctx.multipart()` instead
89
+ */
90
+ getFileStream(options?: MultipartOptions): Promise<MultipartFileStream>;
91
+ /**
92
+ * clean up request tmp files helper
93
+ * @function Context#cleanupRequestFiles
94
+ * @param {Array<String>} [files] - file paths need to cleanup, default is `ctx.request.files`.
95
+ */
96
+ cleanupRequestFiles(files?: EggFile[]): Promise<void>;
97
+ }
98
+ declare module '@eggjs/core' {
99
+ interface Request {
100
+ /**
101
+ * Files Object Array
102
+ */
103
+ files?: EggFile[];
104
+ }
105
+ interface Context {
106
+ saveRequestFiles(options?: MultipartOptions): Promise<void>;
107
+ getFileStream(options?: MultipartOptions): Promise<MultipartFileStream>;
108
+ cleanupRequestFiles(files?: EggFile[]): Promise<void>;
109
+ }
110
+ }