diff --git a/index.js b/index.js index d5b67eba..155be1c7 100644 --- a/index.js +++ b/index.js @@ -4,10 +4,43 @@ var diskStorage = require('./storage/disk') var memoryStorage = require('./storage/memory') var MulterError = require('./lib/multer-error') +/** + * Default file filter function that allows all files to be processed. + * + * @param {Object} req - The HTTP request object. + * @param {Object} file - The file object containing file details such as `fieldname`, `originalname`, `encoding`, and `mimetype`. + * @param {Function} cb - A callback function that takes two arguments: + * - `err` (Error, if any, or `null` to allow the file) + * - `acceptFile` (Boolean, `true` to accept the file, `false` to reject it) + */ function allowAll (req, file, cb) { cb(null, true) } +/** + * Multer constructor function that configures the file handling middleware. + * + * @class + * @param {Object} options - Configuration options for Multer. + * @param {Object} [options.storage] - Storage engine to use for saving files. + * @param {string} [options.dest] - Directory path for storing files, if no custom storage engine is provided. + * @param {Object} [options.limits] - Limits for file size, field size, etc. + * @param {boolean} [options.preservePath=false] - Whether to preserve the file's original path. + * @param {Function} [options.fileFilter=allowAll] - Function to filter which files to accept or reject. Receives `req`, `file`, and a callback function as arguments. + * + * @throws {TypeError} Throws an error if `options` is neither undefined nor an object. + * + * @property {Object} storage - Storage engine used for saving files. + * @property {Object} limits - File size and count limits. + * @property {boolean} preservePath - Flag for preserving original file paths. + * @property {Function} fileFilter - Filter function to determine if a file should be saved. + * + * @example + * const upload = new Multer({ dest: 'uploads/' }); + * + * @description + * Multer is a file handling middleware for Node.js. It provides configuration for file storage location, size limits, and file filtering, enabling file uploads for multipart form-data. + */ function Multer (options) { if (options.storage) { this.storage = options.storage @@ -22,6 +55,29 @@ function Multer (options) { this.fileFilter = options.fileFilter || allowAll } +/** + * Creates a middleware function for handling file uploads with custom settings. + * + * @private + * @param {Array<{name: string, maxCount: number}>} fields - Array of field configurations. Each field object specifies a `name` and a `maxCount` for the field, defining which fields to handle and how many files are allowed per field. + * @param {string} fileStrategy - Strategy for handling files, which can be 'VALUE', 'ARRAY', 'OBJECT', or 'NONE'. + * + * @returns {Function} Middleware function that can handle multipart file uploads. + * + * @description + * `_makeMiddleware` generates middleware for processing file uploads, controlling how fields and files are managed based on specified fields and file strategy. + * + * - **File Strategy**: Determines how uploaded files are organized: + * - `VALUE`: Allows a single file per field. + * - `ARRAY`: Allows multiple files to be collected as an array. + * - `OBJECT`: Stores files in an object keyed by field name. + * - `NONE`: No files are allowed. + * + * @example + * const upload = new Multer({ dest: 'uploads/' }); + * const singleUploadMiddleware = upload.single('avatar'); + * const arrayUploadMiddleware = upload.array('photos', 5); + */ Multer.prototype._makeMiddleware = function (fields, fileStrategy) { function setup () { var fileFilter = this.fileFilter @@ -35,6 +91,33 @@ Multer.prototype._makeMiddleware = function (fields, fileStrategy) { } }) + /** + * Wraps the user-defined file filter to enforce field-specific file count limits. + * + * @param {Object} req - The Express request object. + * @param {Object} file - The file object representing the current file being processed. + * @param {Function} cb - Callback function to indicate whether the file should be accepted or rejected. Calls with `cb(null, true)` to accept or `cb(new Error)` to reject. + * + * @throws {MulterError} Throws a `LIMIT_UNEXPECTED_FILE` error if the file exceeds the allowed count for its field. + * + * @description + * `wrappedFileFilter` is an internal function used to wrap the user-defined `fileFilter`. It ensures that each field does not exceed its specified file count (`maxCount`), calling the provided `fileFilter` function only when this condition is met. + * + * - **Exceeding Limit**: If the number of files for a field exceeds its `maxCount`, the function calls `cb` with a `MulterError`, preventing additional files for that field from being processed. + * - **Calling User File Filter**: After verifying the limit, it passes the file to the user-defined `fileFilter` for custom processing or further validation. + * + * @example + * const upload = multer({ limits: { fileSize: 1000000 } }); + * const fileFilter = (req, file, cb) => { + * if (file.mimetype === 'image/jpeg') cb(null, true); + * else cb(new Error('Only JPEGs are allowed')); + * }; + * + * const middleware = upload.fields([ + * { name: 'avatar', maxCount: 1 }, + * { name: 'gallery', maxCount: 5 } + * ]); + */ function wrappedFileFilter (req, file, cb) { if ((filesLeft[file.fieldname] || 0) <= 0) { return cb(new MulterError('LIMIT_UNEXPECTED_FILE', file.fieldname)) @@ -56,22 +139,148 @@ Multer.prototype._makeMiddleware = function (fields, fileStrategy) { return makeMiddleware(setup.bind(this)) } +/** + * Creates middleware for handling a single file upload for a specified field. + * + * @param {string} name - The name of the form field to accept a single file upload. + * @returns {Function} Middleware function to handle single file uploads. + * + * @description + * `Multer.prototype.single` is a method that generates middleware specifically for handling single file uploads for a specified field. This middleware will only accept one file for the given `name` field, rejecting additional files if provided in the same field. + * + * - **File Handling Strategy**: Sets the `fileStrategy` to `'VALUE'`, meaning only a single file is stored as `req.file` rather than in an array or object. + * - **Field-Specific Limit**: Allows only one file in the specified field name and rejects additional files in that field. + * + * @example + * // Usage example: + * const multer = require('multer'); + * const upload = multer({ dest: 'uploads/' }); + * + * app.post('/profile', upload.single('avatar'), (req, res) => { + * // Access the uploaded file with req.file + * res.send('Single file upload successful!'); + * }); + * + * @throws {MulterError} Throws `LIMIT_UNEXPECTED_FILE` if additional files are uploaded in the specified field. + */ Multer.prototype.single = function (name) { return this._makeMiddleware([{ name: name, maxCount: 1 }], 'VALUE') } +/** + * Creates middleware for handling multiple file uploads for a specified field. + * + * @param {string} name - The name of the form field to accept multiple file uploads. + * @param {number} [maxCount] - Optional maximum number of files allowed for the specified field. If not specified, there is no limit. + * @returns {Function} Middleware function to handle multiple file uploads. + * + * @description + * `Multer.prototype.array` is a method that generates middleware for handling multiple file uploads on a specified field name. This middleware allows multiple files to be uploaded under the same field, storing them in `req.files` as an array. + * + * - **File Handling Strategy**: Sets the `fileStrategy` to `'ARRAY'`, meaning files are stored as an array under `req.files`, making each file accessible as individual elements in the array. + * - **Field-Specific Limit**: Accepts multiple files but can limit the number of files in the specified field if `maxCount` is provided. + * + * @example + * // Usage example: + * const multer = require('multer'); + * const upload = multer({ dest: 'uploads/' }); + * + * app.post('/photos', upload.array('photos', 10), (req, res) => { + * // Access uploaded files with req.files + * res.send(`Uploaded ${req.files.length} photos successfully!`); + * }); + * + * @throws {MulterError} Throws `LIMIT_UNEXPECTED_FILE` if the number of uploaded files exceeds `maxCount`. + */ Multer.prototype.array = function (name, maxCount) { return this._makeMiddleware([{ name: name, maxCount: maxCount }], 'ARRAY') } +/** + * Creates middleware for handling multiple file uploads for multiple fields. + * + * @param {Array} fields - An array of objects specifying fields to accept files for. Each object in the array should have: + * - `name` (string): The name of the field to accept file uploads. + * - `maxCount` (number, optional): Maximum number of files allowed for this field. Defaults to `Infinity` if not provided. + * @returns {Function} Middleware function to handle file uploads across multiple fields. + * + * @description + * `Multer.prototype.fields` generates middleware that allows handling file uploads across multiple form fields, with each field accepting a defined number of files. Uploaded files are organized in `req.files` as an object where each key is a field name, and each value is an array of files for that field. + * + * - **File Handling Strategy**: Sets the `fileStrategy` to `'OBJECT'`, organizing files in `req.files` by field name. + * - **Field-Specific Limits**: Each field can specify a `maxCount` to restrict the number of files for that particular field. + * + * @example + * // Usage example: + * const multer = require('multer'); + * const upload = multer({ dest: 'uploads/' }); + * + * app.post('/profile', upload.fields([ + * { name: 'avatar', maxCount: 1 }, + * { name: 'gallery', maxCount: 5 } + * ]), (req, res) => { + * // Access uploaded files with req.files + * res.send(`Uploaded ${req.files['gallery'].length} gallery images successfully!`); + * }); + * + * @throws {MulterError} Throws `LIMIT_UNEXPECTED_FILE` if the number of uploaded files exceeds the specified `maxCount` for any field. + */ Multer.prototype.fields = function (fields) { return this._makeMiddleware(fields, 'OBJECT') } +/** + * Creates middleware for handling form data without any file uploads. + * + * @returns {Function} Middleware function that processes only text fields and rejects any file uploads. + * + * @description + * `Multer.prototype.none` generates middleware that processes only form fields without allowing any file uploads. + * If any file is uploaded, it will result in an error, as this middleware is strictly for text-based form submissions. + * + * - **File Handling Strategy**: Sets the `fileStrategy` to `'NONE'`, ensuring `req.files` will be empty. + * - **Text-Only Forms**: Suitable for forms that only contain text fields. + * + * @example + * // Usage example: + * const multer = require('multer'); + * const upload = multer(); + * + * app.post('/submit', upload.none(), (req, res) => { + * // Access form fields through req.body + * res.send(`Received submission with name: ${req.body.name}`); + * }); + * + * @throws {MulterError} Throws `LIMIT_UNEXPECTED_FILE` if any file upload is attempted. + */ Multer.prototype.none = function () { return this._makeMiddleware([], 'NONE') } +/** + * Creates middleware for handling any number of file uploads on any field name. + * + * @returns {Function} Middleware function that accepts files uploaded on any field name, storing them in `req.files` as an array. + * + * @description + * `Multer.prototype.any` generates middleware that allows unlimited file uploads on any field, storing all files in `req.files` as an array. + * This middleware is flexible for forms where the field names for file uploads are dynamic or undefined in advance. + * + * - **File Handling Strategy**: Sets the `fileStrategy` to `'ARRAY'`, ensuring all uploaded files are appended to `req.files`. + * - **Flexible Field Handling**: Accepts files on any field name, unlike `single`, `array`, or `fields`, which target specific fields. + * + * @example + * // Usage example: + * const multer = require('multer'); + * const upload = multer(); + * + * app.post('/upload', upload.any(), (req, res) => { + * // Access all uploaded files through req.files array + * res.send(`Uploaded ${req.files.length} files`); + * }); + * + * @throws {MulterError} Throws an error for any violations of size, count, or field limits as configured. + */ Multer.prototype.any = function () { function setup () { return { @@ -86,6 +295,48 @@ Multer.prototype.any = function () { return makeMiddleware(setup.bind(this)) } +/** + * Creates a new Multer instance for handling file uploads. + * + * @param {Object} [options] - Optional configuration options for the Multer instance. + * @param {Object} [options.storage] - A custom storage engine, such as `diskStorage` or `memoryStorage`. + * @param {String} [options.dest] - The destination directory for file uploads (if using disk storage). + * @param {Object} [options.limits] - Limits for file upload size, field counts, etc. + * @param {Boolean} [options.preservePath] - Whether to preserve the original file path. + * @param {Function} [options.fileFilter] - A function to filter file uploads based on their attributes. + * + * @returns {Multer} A new Multer instance configured with the provided options. + * + * @throws {TypeError} Throws an error if the options argument is not an object. + * + * @example + * // Example of using multer to handle file uploads: + * const multer = require('multer'); + * + * // Using disk storage and custom file filter + * const upload = multer({ + * storage: multer.diskStorage({ + * destination: function (req, file, cb) { + * cb(null, 'uploads/') + * }, + * filename: function (req, file, cb) { + * cb(null, Date.now() + path.extname(file.originalname)) + * } + * }), + * limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit + * fileFilter: function (req, file, cb) { + * if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') { + * cb(null, true); // Accept file + * } else { + * cb(new Error('Only JPEG and PNG files are allowed'), false); // Reject file + * } + * } + * }); + * + * app.post('/upload', upload.single('image'), (req, res) => { + * res.send('File uploaded!'); + * }); + */ function multer (options) { if (options === undefined) { return new Multer({}) diff --git a/lib/counter.js b/lib/counter.js index 29c410c7..dcb55139 100644 --- a/lib/counter.js +++ b/lib/counter.js @@ -1,24 +1,46 @@ var EventEmitter = require('events').EventEmitter +/** + * A counter class that can be incremented or decremented. + * Emits a 'zero' event when the counter reaches zero. + * @extends EventEmitter + */ function Counter () { EventEmitter.call(this) + /** @type {number} The current value of the counter. */ this.value = 0 } Counter.prototype = Object.create(EventEmitter.prototype) +/** + * Increments the counter by 1. + */ Counter.prototype.increment = function increment () { this.value++ } +/** + * Decrements the counter by 1. + * If the counter reaches zero, emits a 'zero' event. + */ Counter.prototype.decrement = function decrement () { if (--this.value === 0) this.emit('zero') } +/** + * Checks if the counter value is zero. + * @returns {boolean} True if the counter is zero, false otherwise. + */ Counter.prototype.isZero = function isZero () { return (this.value === 0) } +/** + * Calls a function when the counter reaches zero. + * If the counter is already zero, calls the function immediately. + * @param {Function} fn - The function to call when the counter reaches zero. + */ Counter.prototype.onceZero = function onceZero (fn) { if (this.isZero()) return fn() diff --git a/lib/file-appender.js b/lib/file-appender.js index 1a2c5e76..b7e23f6b 100644 --- a/lib/file-appender.js +++ b/lib/file-appender.js @@ -1,10 +1,23 @@ var objectAssign = require('object-assign') +/** + * Removes a specific item from an array. + * @param {Array} arr - The array to remove the item from. + * @param {*} item - The item to remove. + */ function arrayRemove (arr, item) { var idx = arr.indexOf(item) if (~idx) arr.splice(idx, 1) } +/** + * Handles file storage strategies within a request, providing flexible + * storage options for file uploads by managing different data structures + * (none, single value, array, or object). + * @param {string} strategy - The file storage strategy: 'NONE', 'VALUE', 'ARRAY', or 'OBJECT'. + * @param {Object} req - The request object that will store the files. + * @throws Will throw an error for an unknown file strategy. + */ function FileAppender (strategy, req) { this.strategy = strategy this.req = req @@ -18,6 +31,11 @@ function FileAppender (strategy, req) { } } +/** + * Inserts a placeholder for a file based on the current strategy. + * @param {Object} file - The file object with at least a fieldname property. + * @returns {Object} The placeholder object for the file. + */ FileAppender.prototype.insertPlaceholder = function (file) { var placeholder = { fieldname: file.fieldname @@ -39,6 +57,10 @@ FileAppender.prototype.insertPlaceholder = function (file) { return placeholder } +/** + * Removes a placeholder for a file from the request. + * @param {Object} placeholder - The placeholder object to remove. + */ FileAppender.prototype.removePlaceholder = function (placeholder) { switch (this.strategy) { case 'NONE': break @@ -54,6 +76,12 @@ FileAppender.prototype.removePlaceholder = function (placeholder) { } } +/** + * Replaces a placeholder object with a complete file object. + * For 'VALUE' strategy, assigns the file directly to req.file. + * @param {Object} placeholder - The placeholder object to be replaced. + * @param {Object} file - The complete file object with all properties. + */ FileAppender.prototype.replacePlaceholder = function (placeholder, file) { if (this.strategy === 'VALUE') { this.req.file = file diff --git a/lib/make-middleware.js b/lib/make-middleware.js index b033cbd9..8fbf1700 100644 --- a/lib/make-middleware.js +++ b/lib/make-middleware.js @@ -9,10 +9,19 @@ var MulterError = require('./multer-error') var FileAppender = require('./file-appender') var removeUploadedFiles = require('./remove-uploaded-files') +/** + * Consumes all data from the stream to avoid hanging. + * @param {Stream} stream - The stream to consume. + */ function drainStream (stream) { stream.on('readable', stream.read.bind(stream)) } +/** + * Middleware factory for handling file uploads with Busboy. + * @param {Function} setup - A function that returns the options for the middleware. + * @returns {Function} The multer middleware function. + */ function makeMiddleware (setup) { return function multerMiddleware (req, res, next) { if (!is(req, ['multipart'])) return next() @@ -42,6 +51,10 @@ function makeMiddleware (setup) { var pendingWrites = new Counter() var uploadedFiles = [] + /** + * Completes the middleware execution and calls the next middleware. + * @param {Error} [err] - An optional error to pass to the next middleware. + */ function done (err) { if (isDone) return isDone = true @@ -53,10 +66,17 @@ function makeMiddleware (setup) { onFinished(req, function () { next(err) }) } + /** + * Checks if all reads are complete and writes are done, then calls done(). + */ function indicateDone () { if (readFinished && pendingWrites.isZero() && !errorOccured) done() } + /** + * Aborts the upload with a provided error and handles cleanup. + * @param {MulterError} uploadError - The error that caused the abort. + */ function abortWithError (uploadError) { if (errorOccured) return errorOccured = true @@ -75,6 +95,11 @@ function makeMiddleware (setup) { }) } + /** + * Aborts the upload with a specific error code and optional field. + * @param {string} code - The error code that indicates the failure reason. + * @param {string} [optionalField] - An optional field name for the error. + */ function abortWithCode (code, optionalField) { abortWithError(new MulterError(code, optionalField)) } diff --git a/lib/multer-error.js b/lib/multer-error.js index d56b00e8..fac8bb55 100644 --- a/lib/multer-error.js +++ b/lib/multer-error.js @@ -11,14 +11,30 @@ var errorMessages = { MISSING_FIELD_NAME: 'Field name missing' } +/** + * Custom error class for handling Multer errors. + * @class + * @param {string} code - The error code corresponding to the error message. + * @param {string} [field] - The field associated with the error (optional). + */ function MulterError (code, field) { + // Capture the stack trace for the custom error Error.captureStackTrace(this, this.constructor) + + // Set the error name to the constructor name this.name = this.constructor.name + + // Set the error message based on the provided error code this.message = errorMessages[code] + + // Set the error code this.code = code + + // If a field is provided, associate it with the error if (field) this.field = field } +// Inherit from the native Error class util.inherits(MulterError, Error) module.exports = MulterError diff --git a/lib/remove-uploaded-files.js b/lib/remove-uploaded-files.js index f0b16ea5..bd7b50c6 100644 --- a/lib/remove-uploaded-files.js +++ b/lib/remove-uploaded-files.js @@ -1,19 +1,38 @@ +/** + * Removes uploaded files from the server. + * This function processes each uploaded file and removes them one by one, + * collecting any errors encountered during the removal process. + * + * @param {Array} uploadedFiles - An array of uploaded files that need to be removed. + * @param {Function} remove - A function that removes a file. It takes a file and a callback function as arguments. + * @param {Function} cb - A callback function to be called after all files have been processed. + * It receives two arguments: + * - `err` (null if no errors) + * - `errors` (an array of errors that occurred during the removal process) + */ function removeUploadedFiles (uploadedFiles, remove, cb) { var length = uploadedFiles.length var errors = [] + // If no files to remove, immediately invoke the callback with an empty error array if (length === 0) return cb(null, errors) + /** + * Recursively handles the removal of each file in the uploadedFiles array. + * @param {number} idx - The index of the file to remove. + */ function handleFile (idx) { var file = uploadedFiles[idx] remove(file, function (err) { if (err) { + // Attach file-specific information to the error object err.file = file err.field = file.fieldname errors.push(err) } + // Proceed to the next file if there are more, otherwise call the callback if (idx < length - 1) { handleFile(idx + 1) } else { @@ -22,6 +41,7 @@ function removeUploadedFiles (uploadedFiles, remove, cb) { }) } + // Start processing the files from the first index handleFile(0) } diff --git a/storage/disk.js b/storage/disk.js index 2f77c9f1..fd05bf3b 100644 --- a/storage/disk.js +++ b/storage/disk.js @@ -4,16 +4,37 @@ var path = require('path') var crypto = require('crypto') var mkdirp = require('mkdirp') +/** + * Generates a unique filename for the uploaded file using random bytes. + * + * @param {Object} req - The request object. + * @param {Object} file - The file object. + * @param {Function} cb - A callback function to return the generated filename. + */ function getFilename (req, file, cb) { crypto.randomBytes(16, function (err, raw) { cb(err, err ? undefined : raw.toString('hex')) }) } +/** + * Determines the destination directory for the uploaded file. + * Defaults to the system's temporary directory. + * + * @param {Object} req - The request object. + * @param {Object} file - The file object. + * @param {Function} cb - A callback function to return the destination directory. + */ function getDestination (req, file, cb) { cb(null, os.tmpdir()) } +/** + * DiskStorage class for handling file uploads to the disk. + * @param {Object} opts - Configuration options for the DiskStorage. + * @param {Function} [opts.filename] - A custom function for generating the filename. + * @param {string|Function} [opts.destination] - A custom destination path or function to determine the destination. + */ function DiskStorage (opts) { this.getFilename = (opts.filename || getFilename) @@ -25,21 +46,35 @@ function DiskStorage (opts) { } } +/** + * Handles the file processing (saving the file to disk). + * + * @param {Object} req - The request object. + * @param {Object} file - The file object. + * @param {Function} cb - A callback function that will be invoked once the file is saved. + * It receives two arguments: + * - `err` (null if no error occurred) + * - `info` (an object containing the file's `destination`, `filename`, `path`, and `size`) + */ DiskStorage.prototype._handleFile = function _handleFile (req, file, cb) { var that = this + // Determine the destination directory that.getDestination(req, file, function (err, destination) { if (err) return cb(err) + // Generate a unique filename that.getFilename(req, file, function (err, filename) { if (err) return cb(err) var finalPath = path.join(destination, filename) var outStream = fs.createWriteStream(finalPath) + // Pipe the file stream to the file output stream file.stream.pipe(outStream) outStream.on('error', cb) outStream.on('finish', function () { + // File successfully saved, return the file info cb(null, { destination: destination, filename: filename, @@ -51,16 +86,31 @@ DiskStorage.prototype._handleFile = function _handleFile (req, file, cb) { }) } +/** + * Removes a file from the disk. + * + * @param {Object} req - The request object. + * @param {Object} file - The file object to be removed. + * @param {Function} cb - A callback function to be invoked after the file is removed. + */ DiskStorage.prototype._removeFile = function _removeFile (req, file, cb) { var path = file.path + // Remove file-related properties delete file.destination delete file.filename delete file.path + // Delete the file from disk fs.unlink(path, cb) } +/** + * Factory function to create a new DiskStorage instance with the given options. + * + * @param {Object} opts - Configuration options for the DiskStorage. + * @returns {DiskStorage} A new instance of the DiskStorage class. + */ module.exports = function (opts) { return new DiskStorage(opts) } diff --git a/storage/memory.js b/storage/memory.js index f953ded1..e12eb0ab 100644 --- a/storage/memory.js +++ b/storage/memory.js @@ -1,9 +1,26 @@ var concat = require('concat-stream') +/** + * MemoryStorage class for handling file uploads directly into memory (buffer). + * + * @param {Object} opts - Configuration options for the MemoryStorage. + */ function MemoryStorage (opts) {} +/** + * Handles the file processing (storing the file in memory as a buffer). + * + * @param {Object} req - The request object. + * @param {Object} file - The file object. + * @param {Function} cb - A callback function that will be invoked once the file is processed. + * It receives two arguments: + * - `err` (null if no error occurred) + * - `info` (an object containing the `buffer` and `size` of the file) + */ MemoryStorage.prototype._handleFile = function _handleFile (req, file, cb) { + // Concatenate the file stream data into a buffer file.stream.pipe(concat({ encoding: 'buffer' }, function (data) { + // Return the file buffer and its size cb(null, { buffer: data, size: data.length @@ -11,11 +28,25 @@ MemoryStorage.prototype._handleFile = function _handleFile (req, file, cb) { })) } +/** + * Removes a file from memory. + * + * @param {Object} req - The request object. + * @param {Object} file - The file object to be removed. + * @param {Function} cb - A callback function to be invoked after the file is removed. + */ MemoryStorage.prototype._removeFile = function _removeFile (req, file, cb) { + // Remove the file's buffer from memory delete file.buffer cb(null) } +/** + * Factory function to create a new MemoryStorage instance with the given options. + * + * @param {Object} opts - Configuration options for the MemoryStorage. + * @returns {MemoryStorage} A new instance of the MemoryStorage class. + */ module.exports = function (opts) { return new MemoryStorage(opts) }