millas 0.2.16 → 0.2.17
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/package.json
CHANGED
|
@@ -11,20 +11,27 @@
|
|
|
11
11
|
* installed if you actually handle uploads — multer is listed as an optional
|
|
12
12
|
* peer dependency).
|
|
13
13
|
*
|
|
14
|
-
* ──
|
|
14
|
+
* ── Usage patterns ───────────────────────────────────────────────────────────
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
* "in" schema, the Router injects UploadMiddleware automatically.
|
|
18
|
-
* You never touch it.
|
|
16
|
+
* 1. No shape, no config — just add 'upload' to middleware:
|
|
19
17
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
18
|
+
* Route.post('/media/upload', ['auth', 'upload'], MediaController, 'upload');
|
|
19
|
+
*
|
|
20
|
+
* async upload({ file, files, user }) {
|
|
21
|
+
* // file — first uploaded file (any field name), UploadedFile instance
|
|
22
|
+
* // files — all files keyed by field name
|
|
23
|
+
* const path = await file.store('media');
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* 2. Parameterized alias — configure field/count inline:
|
|
27
|
+
*
|
|
28
|
+
* 'upload' → any field, 50 MB limit (default)
|
|
29
|
+
* 'upload:avatar' → single field named 'avatar'
|
|
30
|
+
* 'upload:photos,5' → field 'photos', up to 5 files
|
|
31
|
+
*
|
|
32
|
+
* Route.post('/avatar', ['auth', 'upload:avatar'], UserController, 'updateAvatar');
|
|
33
|
+
*
|
|
34
|
+
* 3. With a shape — Router auto-injects UploadMiddleware, zero manual config:
|
|
28
35
|
*
|
|
29
36
|
* async upload({ body, file, user }) {
|
|
30
37
|
* // file is a multer file object: { buffer, mimetype, size, originalname }
|
|
@@ -87,13 +94,14 @@ class UploadMiddleware {
|
|
|
87
94
|
*/
|
|
88
95
|
constructor(options = {}) {
|
|
89
96
|
this._options = {
|
|
90
|
-
field:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
field: options.field || 'file',
|
|
98
|
+
_explicitField: !!options.field, // true only when caller set field explicitly
|
|
99
|
+
fields: options.fields || null,
|
|
100
|
+
any: options.any || false,
|
|
101
|
+
maxSize: options.maxSize !== undefined ? _parseSize(options.maxSize) : 50 * 1024 * 1024,
|
|
102
|
+
storage: options.storage || 'memory',
|
|
103
|
+
dest: options.dest || null,
|
|
104
|
+
filter: options.filter || null,
|
|
97
105
|
};
|
|
98
106
|
this._multerInstance = null;
|
|
99
107
|
}
|
|
@@ -106,15 +114,28 @@ class UploadMiddleware {
|
|
|
106
114
|
*
|
|
107
115
|
* Because multer is Express middleware internally, we reach into ctx.req.raw
|
|
108
116
|
* to get the underlying Express req/res and call multer directly.
|
|
117
|
+
*
|
|
118
|
+
* Skips gracefully when the request is not multipart/form-data so that
|
|
119
|
+
* non-upload requests to the same route (or misconfigured clients) get a
|
|
120
|
+
* clear 400 rather than a multer internal error.
|
|
109
121
|
*/
|
|
110
122
|
async handle(ctx, next) {
|
|
123
|
+
const expressReq = ctx.req.raw;
|
|
124
|
+
const contentType = expressReq.headers?.['content-type'] || '';
|
|
125
|
+
|
|
126
|
+
// Skip multer entirely for non-multipart requests.
|
|
127
|
+
// ctx.file / ctx.files stay null/{} — the handler or shape validation
|
|
128
|
+
// will surface a missing-required-field error if needed.
|
|
129
|
+
if (!contentType.includes('multipart/form-data')) {
|
|
130
|
+
return next();
|
|
131
|
+
}
|
|
132
|
+
|
|
111
133
|
const multerMw = this._getMulterMiddleware();
|
|
112
|
-
const expressReq = ctx.req.raw;
|
|
113
134
|
|
|
114
135
|
await new Promise((resolve, reject) => {
|
|
115
136
|
// multer needs a real Express res object; we synthesize a minimal one
|
|
116
|
-
// that satisfies multer's internal usage (it only calls res.end /
|
|
117
|
-
// on hard errors, which we convert to thrown exceptions
|
|
137
|
+
// that satisfies multer's internal usage (it only calls res.end /
|
|
138
|
+
// statusCode on hard errors, which we convert to thrown exceptions).
|
|
118
139
|
const fakeRes = _buildFakeRes(reject);
|
|
119
140
|
multerMw(expressReq, fakeRes, (err) => {
|
|
120
141
|
if (err) return reject(err);
|
|
@@ -136,14 +157,45 @@ class UploadMiddleware {
|
|
|
136
157
|
expressReq._millaFile = file;
|
|
137
158
|
expressReq._millaFiles = files;
|
|
138
159
|
|
|
139
|
-
//
|
|
140
|
-
//
|
|
160
|
+
// Also update ctx.body with any non-file multipart fields multer parsed.
|
|
161
|
+
// req.body is mutated in-place by multer, but ctx.body was built at
|
|
162
|
+
// RequestContext construction time. Reassign so the handler sees the fields.
|
|
163
|
+
if (expressReq.body && typeof expressReq.body === 'object') {
|
|
164
|
+
// Merge multer-parsed text fields into the existing ctx.body object
|
|
165
|
+
// without breaking the .validate() / .only() / .except() helpers
|
|
166
|
+
// that _buildBody attached as non-enumerable properties.
|
|
167
|
+
Object.assign(ctx.body, expressReq.body);
|
|
168
|
+
}
|
|
141
169
|
|
|
142
170
|
return next();
|
|
143
171
|
}
|
|
144
172
|
|
|
145
173
|
// ── Static factory helpers ──────────────────────────────────────────────────
|
|
146
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Build an UploadMiddleware from parameterized alias string parts.
|
|
177
|
+
* Called by MiddlewareRegistry when the alias includes parameters.
|
|
178
|
+
*
|
|
179
|
+
* 'upload' → any field, 50 MB limit (default)
|
|
180
|
+
* 'upload:avatar' → single field named 'avatar'
|
|
181
|
+
* 'upload:photos,5' → field 'photos', up to 5 files
|
|
182
|
+
* 'upload:*' → any field (explicit)
|
|
183
|
+
*
|
|
184
|
+
* @param {string[]} params Parts after the colon, split by comma
|
|
185
|
+
* @returns {UploadMiddleware}
|
|
186
|
+
*/
|
|
187
|
+
static fromParams(params) {
|
|
188
|
+
if (!params || !params.length || params[0] === '*') {
|
|
189
|
+
return new UploadMiddleware({ any: true });
|
|
190
|
+
}
|
|
191
|
+
const field = params[0];
|
|
192
|
+
const maxCount = params[1] ? parseInt(params[1], 10) : 1;
|
|
193
|
+
if (maxCount > 1) {
|
|
194
|
+
return new UploadMiddleware({ fields: [{ name: field, maxCount }] });
|
|
195
|
+
}
|
|
196
|
+
return new UploadMiddleware({ field });
|
|
197
|
+
}
|
|
198
|
+
|
|
147
199
|
/**
|
|
148
200
|
* Build an UploadMiddleware from a shape definition.
|
|
149
201
|
* Called internally by the Router when auto-injecting.
|
|
@@ -208,8 +260,10 @@ class UploadMiddleware {
|
|
|
208
260
|
|
|
209
261
|
const upload = multer(multerConfig);
|
|
210
262
|
|
|
211
|
-
// Pick the right multer handler based on options
|
|
212
|
-
|
|
263
|
+
// Pick the right multer handler based on options.
|
|
264
|
+
// When used without a shape (e.g. just 'upload' alias), default to .any()
|
|
265
|
+
// so the handler receives whatever field the client sends, regardless of name.
|
|
266
|
+
if (any || (!fields && this._options.field === 'file' && !this._options._explicitField)) {
|
|
213
267
|
this._multerInstance = upload.any();
|
|
214
268
|
} else if (fields) {
|
|
215
269
|
this._multerInstance = upload.fields(fields);
|