From 5d7510a4d9e20080300b290ee2cb23ec656a6b5c Mon Sep 17 00:00:00 2001 From: Phillip Barta Date: Fri, 15 Aug 2025 14:16:05 +0200 Subject: [PATCH] refactor: remove concat-stream dependency and modernize MemoryStorage Replace external concat-stream with a simplified custom implementation without additional deps and convert to ES6 classes --- package.json | 2 +- storage/memory.js | 96 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index d97bc286..b5e117b1 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,10 @@ "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", - "concat-stream": "^2.0.0", "type-is": "^1.6.18" }, "devDependencies": { + "concat-stream": "^2.0.0", "deep-equal": "^2.0.3", "express": "^4.21.2", "form-data": "^4.0.2", diff --git a/storage/memory.js b/storage/memory.js index f953ded1..d55c936a 100644 --- a/storage/memory.js +++ b/storage/memory.js @@ -1,21 +1,91 @@ -var concat = require('concat-stream') +const Writable = require('stream').Writable +const Buffer = require('buffer').Buffer +const isUint8Array = require('util').types.isUint8Array -function MemoryStorage (opts) {} +class MemoryStorage { + _handleFile (req, file, cb) { + file.stream.pipe( + new ConcatStream(function (data) { + cb(null, { + buffer: data, + size: data.length + }) + }) + ) + } -MemoryStorage.prototype._handleFile = function _handleFile (req, file, cb) { - file.stream.pipe(concat({ encoding: 'buffer' }, function (data) { - cb(null, { - buffer: data, - size: data.length + _removeFile (req, file, cb) { + delete file.buffer + cb(null) + } +} + +module.exports = function () { + return new MemoryStorage() +} + +/** + * Writable stream that concatenates all written chunks into a single Buffer. + * + * Implementation inspired by the concat-stream npm package (https://www.npmjs.com/package/concat-stream), + * modified for our specific use case. + */ +class ConcatStream extends Writable { + /** + * Creates a new ConcatStream instance. + * @param {function(Buffer): void} cb - Callback invoked with the concatenated Buffer when the stream finishes. + */ + constructor (cb) { + super() + this.body = [] + this.on('finish', function () { + cb(this.getBody()) }) - })) + } + + _write (chunk, enc, next) { + this.body.push(chunk) + next() + } + + /** + * Concatenates all collected chunks into a single Buffer. + * @returns {Buffer} The concatenated buffer containing all written data. + */ + getBody () { + const bufs = [] + for (const p of this.body) { + // Buffer.concat can handle Buffer and Uint8Array (and subclasses) + if (Buffer.isBuffer(p) || isUint8Array(p)) { + bufs.push(p) + } else if (isBufferish(p)) { + bufs.push(Buffer.from(p)) + } else { + bufs.push(Buffer.from(String(p))) + } + } + return Buffer.concat(bufs) + } } -MemoryStorage.prototype._removeFile = function _removeFile (req, file, cb) { - delete file.buffer - cb(null) +/** + * Checks if the given value is array-like. + * @param {*} arr + * @returns {boolean} + */ +function isArrayish (arr) { + return /Array\]$/.test(Object.prototype.toString.call(arr)) } -module.exports = function (opts) { - return new MemoryStorage(opts) +/** + * Checks if the given value is Buffer-like. + * @param {*} p + * @returns {boolean} + */ +function isBufferish (p) { + return ( + typeof p === 'string' || + isArrayish(p) || + (p && typeof p.subarray === 'function') + ) }