millas 0.2.32 → 0.2.33

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "millas",
3
- "version": "0.2.32",
3
+ "version": "0.2.33",
4
4
  "description": "A modern batteries-included backend framework for Node.js — built on Express, inspired by Laravel, Django, and FastAPI",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -197,13 +197,50 @@ class MillasResponse {
197
197
  });
198
198
  }
199
199
 
200
- /** File download / serve response */
201
- static file(filePath, { download = false, name = null, headers = {} } = {}) {
200
+ /** File download / serve response.
201
+ *
202
+ * filePath can be:
203
+ * - string — path on disk
204
+ * - Buffer — in-memory buffer (requires mimetype)
205
+ * - Readable — Node stream (requires mimetype)
206
+ *
207
+ * Options:
208
+ * download {boolean} — force Content-Disposition: attachment
209
+ * name {string} — filename for Content-Disposition
210
+ * mimetype {string} — override Content-Type
211
+ * maxAge {number} — Cache-Control max-age in seconds
212
+ * lastModified {Date|string} — override Last-Modified header
213
+ */
214
+ static file(filePath, { download = false, name = null, mimetype = null, maxAge = null, lastModified = null, headers = {} } = {}) {
215
+ const isBuffer = Buffer.isBuffer(filePath);
216
+ const isStream = filePath && typeof filePath.pipe === 'function';
217
+ const isInMemory = isBuffer || isStream;
218
+
219
+ const extraHeaders = {};
220
+ if (mimetype) extraHeaders['Content-Type'] = mimetype;
221
+ if (maxAge !== null) extraHeaders['Cache-Control'] = `public, max-age=${maxAge}`;
222
+ if (lastModified) extraHeaders['Last-Modified'] = new Date(lastModified).toUTCString();
223
+ if (download || name) {
224
+ extraHeaders['Content-Disposition'] =
225
+ `attachment; filename="${name || (typeof filePath === 'string' ? require('path').basename(filePath) : 'download')}"` ;
226
+ }
227
+
228
+ if (isInMemory) {
229
+ const { Readable } = require('stream');
230
+ const readable = isBuffer ? Readable.from(filePath) : filePath;
231
+ return new MillasResponse({
232
+ type: 'stream',
233
+ body: readable,
234
+ status: 200,
235
+ headers: { ...extraHeaders, ...headers },
236
+ });
237
+ }
238
+
202
239
  return new MillasResponse({
203
240
  type: 'file',
204
- body: { path: filePath, download, name },
241
+ body: { path: filePath, download, name, mimetype, maxAge, lastModified },
205
242
  status: 200,
206
- headers,
243
+ headers: { ...extraHeaders, ...headers },
207
244
  });
208
245
  }
209
246
 
@@ -211,14 +211,17 @@ class ExpressAdapter extends HttpAdapter {
211
211
  return expressRes.end();
212
212
 
213
213
  case 'file': {
214
- const {path: filePath, download, name: fileName} = body;
214
+ const { path: filePath, download, name: fileName, mimetype } = body;
215
+ const sendOpts = {};
216
+ if (mimetype) expressRes.setHeader('Content-Type', mimetype);
215
217
  if (download) {
216
218
  return expressRes.download(
217
219
  filePath,
218
- fileName || require('path').basename(filePath)
220
+ fileName || require('path').basename(filePath),
221
+ sendOpts
219
222
  );
220
223
  }
221
- return expressRes.sendFile(require('path').resolve(filePath));
224
+ return expressRes.sendFile(require('path').resolve(filePath), sendOpts);
222
225
  }
223
226
 
224
227
  case 'view': {
@@ -46,16 +46,21 @@ function text(content, options = {}) {
46
46
 
47
47
  /**
48
48
  * Serve a file.
49
- * The path is automatically validated against app.storageRoot (config/app.js).
49
+ *
50
+ * filePath can be a string path, Buffer, or Readable stream.
51
+ * String paths are validated against app.storageRoot (config/app.js).
50
52
  * Any path that escapes the storage root throws a 403.
53
+ *
54
+ * Options: download, name, mimetype, maxAge, lastModified
51
55
  */
52
56
  function file(filePath, options = {}) {
53
- const { resolveStoragePath, SafeFilePath } = require('./SafeFilePath');
54
- const root = SafeFilePath.getStorageRoot();
55
- if (root) {
56
- // Throws PathTraversalError (403) if path escapes root
57
- const safe = resolveStoragePath(filePath, root);
58
- return MillasResponse.file(safe, options);
57
+ if (typeof filePath === 'string') {
58
+ const { resolveStoragePath, SafeFilePath } = require('./SafeFilePath');
59
+ const root = SafeFilePath.getStorageRoot();
60
+ if (root) {
61
+ const safe = resolveStoragePath(filePath, root);
62
+ return MillasResponse.file(safe, options);
63
+ }
59
64
  }
60
65
  return MillasResponse.file(filePath, options);
61
66
  }