multer-gridfs-storage
Advanced tools
Comparing version 1.1.1 to 1.2.0
@@ -0,1 +1,7 @@ | ||
1.2.0 | ||
===== | ||
* Added generator function support | ||
* Allow to use promises in configuration options instead of callbacks | ||
1.1.1 | ||
@@ -6,2 +12,3 @@ ===== | ||
1.1.0 | ||
@@ -13,2 +20,3 @@ ================== | ||
* Allow the api to be called with the `new` operator | ||
* Added Typescript support | ||
@@ -15,0 +23,0 @@ 1.0.3 |
15
index.js
@@ -0,1 +1,8 @@ | ||
/** | ||
* | ||
* Module entry point | ||
* @module multer-gridfs-storage | ||
* | ||
*/ | ||
'use strict'; | ||
@@ -5,2 +12,10 @@ | ||
/** | ||
* Storage constructor function | ||
* @name GridFSStorage | ||
* @type class | ||
* @static | ||
* @see [GridFSStorage]{@link module:multer-gridfs-storage/gridfs~GridFSStorage} | ||
* **/ | ||
module.exports = GridFsStorage; |
@@ -0,1 +1,7 @@ | ||
/** | ||
* | ||
* Plugin definition | ||
* @module multer-gridfs-storage/gridfs | ||
* | ||
*/ | ||
'use strict'; | ||
@@ -8,95 +14,67 @@ | ||
var util = require('util'); | ||
var storageUtils = require('./utils'); | ||
var __ = require('./utils'); | ||
var noop = storageUtils.noop; | ||
var getFilename = storageUtils.getFilename; | ||
var logMessage = storageUtils.logMessage; | ||
var isFunction = storageUtils.isFunction; | ||
var generateValue = storageUtils.generateValue; | ||
var validateOptions = storageUtils.validateOptions; | ||
/** | ||
* @class GridFSStorage | ||
* @classdesc Multer GridFS Storage Engine class definition. | ||
* @extends EventEmitter | ||
* @param {object} opts | ||
* @param {string} opts.url - The url pointing to a MongoDb database | ||
* @param {Grid | Promise} opts.gfs - The Grid instance to use or | ||
* @param {Function} [opts.filename] - A function to control the file naming in the database | ||
* @param {Function} [opts.identifier] - A function to control the unique identifier of the file | ||
* @param {Function} [opts.metadata] - A function to control the metadata object associated to the file | ||
* @param {Function} [opts.chunkSize] - The preferred size of file chunks in bytes | ||
* @param {string | Function} [opts.root] - The root collection to store the files | ||
* @param {boolean | Function} [opts.log=false] - Enable or disable logging | ||
* @param {string} [opts.logLevel='file'] - The events to be logged out | ||
* @fires GridFSStorage#connection | ||
* @fires GridFSStorage#file | ||
* @version 0.0.3 | ||
*/ | ||
function GridFSStorage(opts) { | ||
var self = this; | ||
if (!(self instanceof GridFSStorage)) { | ||
if (!(this instanceof GridFSStorage)) { | ||
return new GridFSStorage(opts); | ||
} | ||
EventEmitter.call(self); | ||
EventEmitter.call(this); | ||
validateOptions(opts); | ||
self.gfs = null; | ||
self._log = opts.log || false; | ||
self._logLevel = opts.logLevel || 'file'; | ||
self._getIdentifier = opts.identifier || noop; | ||
self._getFilename = opts.filename || getFilename; | ||
self._getMetadata = opts.metadata || noop; | ||
if (opts.chunkSize && isFunction(opts.chunkSize)) { | ||
self._getChunkSize = opts.chunkSize; | ||
} else { | ||
self._getChunkSize = generateValue(opts.chunkSize ? opts.chunkSize : 261120); | ||
} | ||
__.validateOptions(opts); | ||
if (opts.root) { | ||
if (isFunction(opts.root)) { | ||
self._getRoot = opts.root; | ||
} else { | ||
self._getRoot = generateValue(opts.root); | ||
} | ||
} else { | ||
self._getRoot = noop; | ||
} | ||
if (!opts.gfs) { | ||
mongo.MongoClient.connect(opts.url, function (err, db) { | ||
var gfs; | ||
if (err) { | ||
throw err; | ||
} | ||
function errEvent(err) { | ||
logMessage(self, err, true); | ||
} | ||
gfs = new Grid(db, mongo); | ||
if (self._logLevel === 'all') { | ||
logMessage(self, { message: 'MongoDb connected in url ' + opts.url }); | ||
db.on('close', function () { | ||
logMessage(self, 'Disconnected from MongoDb database', true); | ||
}); | ||
db.on('error', errEvent).on('parseError', errEvent).on('timeout', errEvent); | ||
} | ||
self.gfs = gfs; | ||
self.emit('connection', self.gfs, self.gfs.db); | ||
}); | ||
} else { | ||
if ('then' in opts.gfs) { | ||
var promise = opts.gfs; | ||
promise | ||
.then(function (gfs) { | ||
self.gfs = gfs; | ||
self.emit('connection', self.gfs, self.gfs.db); | ||
}) | ||
.then(null, function (err) { | ||
logMessage(self, err, true); | ||
// There is no need to rethrow because the promise is not returned. | ||
// We are just adding a new handler to the received promise | ||
// so any handlers in user code will execute as well | ||
}); | ||
} else { | ||
self.gfs = opts.gfs; | ||
self.emit('connection', self.gfs, self.gfs.db); | ||
} | ||
} | ||
this.gfs = null; | ||
this._configure(opts); | ||
this._connect(opts); | ||
} | ||
/** | ||
* Event emmited when the Storage is instantiated with the `url` option | ||
* @event module:multer-gridfs-storage/gridfs~GridFSStorage#connection | ||
* @param {Grid} gfs - The created gfs instance | ||
* @param {MongoDb} db - The MongoDb database used to create the grid instance | ||
* | ||
*/ | ||
/** | ||
* Event emmited when a new file is uploaded | ||
* @event module:multer-gridfs-storage/gridfs~GridFSStorage#file | ||
* @param {File} file - The uploaded file | ||
* | ||
*/ | ||
util.inherits(GridFSStorage, EventEmitter); | ||
/** | ||
* Storage interface method to handle incoming files | ||
* @function _handleFile | ||
* @instance | ||
* @memberOf module:multer-gridfs-storage/gridfs~GridFSStorage | ||
* @param {Express.Request} req - The request that trigger the upload | ||
* @param {Multer.File} file - The uploaded file stream | ||
* @param {function} cb - A standard node callback to signal the end of the upload or an error | ||
* | ||
* */ | ||
GridFSStorage.prototype._handleFile = function _handleFile(req, file, cb) { | ||
var self = this; | ||
var streamOpts = { content_type: file.mimetype }; | ||
self._getChunkSize(req, file, function (err, chunkSize) { | ||
self._generate('_getChunkSize', req, file, function (err, chunkSize) { | ||
if (err) { | ||
@@ -106,3 +84,3 @@ return cb(err); | ||
streamOpts.chunkSize = chunkSize; | ||
self._getRoot(req, file, function (err, root) { | ||
self._generate('_getRoot', req, file, function (err, root) { | ||
if (err) { | ||
@@ -112,3 +90,3 @@ return cb(err); | ||
streamOpts.root = root; | ||
self._getIdentifier(req, file, function (err, id) { | ||
self._generate('_getIdentifier', req, file, function (err, id) { | ||
if (err) { | ||
@@ -121,3 +99,3 @@ return cb(err); | ||
self._getFilename(req, file, function (err, filename) { | ||
self._generate('_getFilename', req, file, function (err, filename) { | ||
if (err) { | ||
@@ -127,3 +105,5 @@ return cb(err); | ||
streamOpts.filename = filename; | ||
self._getMetadata(req, file, function (err, metadata) { | ||
self._generate('_getMetadata', req, file, function (err, metadata) { | ||
var writeStream; | ||
if (err) { | ||
@@ -134,10 +114,10 @@ return cb(err); | ||
var writestream = self.gfs.createWriteStream(streamOpts); | ||
writeStream = self.gfs.createWriteStream(streamOpts); | ||
file.stream.pipe(writestream); | ||
writestream.on('error', cb); | ||
file.stream.pipe(writeStream); | ||
writeStream.on('error', cb); | ||
writestream.on('close', function (f) { | ||
writeStream.on('close', function (f) { | ||
self.emit('file', f); | ||
logMessage(self, { message: 'saved file', extra: f }); | ||
self._logMessage({ message: 'Saved file', extra: f }); | ||
cb(null, { | ||
@@ -158,18 +138,227 @@ filename: filename, | ||
/** | ||
* Storage interface method to delete files in case an error turns the request invalid | ||
* @function _removeFile | ||
* @instance | ||
* @memberOf module:multer-gridfs-storage/gridfs~GridFSStorage | ||
* @param {Express.Request} req - The request that trigger the upload | ||
* @param {Multer.File} file - The uploaded file stream | ||
* @param {function} cb - A standard node callback to signal the end of the upload or an error | ||
* | ||
* */ | ||
GridFSStorage.prototype._removeFile = function _removeFile(req, file, cb) { | ||
function onRemove(err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
this._logMessage({ message: 'Deleted file ', extra: file }); | ||
return cb(null); | ||
} | ||
if (file.grid) { | ||
this.gfs.remove({ _id: file.id }, onRemove.bind(this)); | ||
} else { | ||
return cb(null); | ||
} | ||
}; | ||
/** | ||
* Tests for generator functions or plain functions and delegates to the apropiate method | ||
* @function _generate | ||
* @instance | ||
* @memberOf module:multer-gridfs-storage/gridfs~GridFSStorage | ||
* @param {string} method - The internal function name that should be executed | ||
* @param {Express.Request} req - The request that trigger the upload as received in _handleFile | ||
* @param {Multer.File} file - The uploaded file stream as received in _handleFile | ||
* @param {function} cb - A standard node callback to signal the end of the upload or an error as received in _handleFile | ||
* | ||
* */ | ||
GridFSStorage.prototype._generate = function _generate(method, req, file, cb) { | ||
var result, generator; | ||
try { | ||
if (__.isGeneratorFunction(this[method])) { | ||
generator = this[method](req, file); | ||
// Should we store a reference? | ||
//this[method + 'Ref'] = this[method]; | ||
this[method] = generator; | ||
result = generator.next(); | ||
this._handleResult(result, cb, true); | ||
} else if (__.isGenerator(this[method])) { | ||
generator = this[method]; | ||
result = generator.next([req, file]); | ||
this._handleResult(result, cb, true); | ||
} else { | ||
result = this[method](req, file, cb); | ||
this._handleResult(result, cb, false); | ||
} | ||
} catch (e) { | ||
return cb(e, null); | ||
} | ||
}; | ||
/** | ||
* Handles generator function and promise results | ||
* @function _handleResult | ||
* @instance | ||
* @memberOf module:multer-gridfs-storage/gridfs~GridFSStorage | ||
* @param {object} result - Can be a promise or a generator yielded value | ||
* @param {function} cb - A standard node callback to signal the end of the upload or an error as received in _handleFile | ||
* @param {boolean} isGen - True if is a yielded value | ||
* | ||
* */ | ||
GridFSStorage.prototype._handleResult = function (result, cb, isGen) { | ||
var value = result; | ||
function onFullfill(data) { | ||
cb(null, data); | ||
} | ||
function onReject(err) { | ||
cb(err, null); | ||
} | ||
if (isGen) { | ||
if (result.done) { | ||
throw new Error('Generator ended unexpectedly'); | ||
} | ||
value = result.value; | ||
} | ||
if (__.isPromise(value)) { | ||
value.then(onFullfill, onReject); | ||
} else if (isGen) { | ||
return cb(null, value); | ||
} | ||
}; | ||
/** | ||
* Handles optional configuration properties | ||
* @function _configure | ||
* @instance | ||
* @memberOf module:multer-gridfs-storage/gridfs~GridFSStorage | ||
* @param {object} opts - Configuration object passed in the constructor | ||
* | ||
* */ | ||
GridFSStorage.prototype._configure = function (opts) { | ||
this._log = opts.log || false; | ||
this._logLevel = opts.logLevel || 'file'; | ||
this._getIdentifier = opts.identifier || __.noop; | ||
this._getFilename = opts.filename || __.getFilename; | ||
this._getMetadata = opts.metadata || __.noop; | ||
if (opts.chunkSize && __.isFuncOrGeneratorFunc(opts.chunkSize)) { | ||
this._getChunkSize = opts.chunkSize; | ||
} else { | ||
this._getChunkSize = __.generateValue(opts.chunkSize ? opts.chunkSize : 261120); | ||
} | ||
if (opts.root) { | ||
if (__.isFuncOrGeneratorFunc(opts.root)) { | ||
this._getRoot = opts.root; | ||
} else { | ||
this._getRoot = __.generateValue(opts.root); | ||
} | ||
} else { | ||
this._getRoot = __.noop; | ||
} | ||
}; | ||
/** | ||
* Handles connection settings and emits connection event | ||
* @function _connect | ||
* @instance | ||
* @memberOf module:multer-gridfs-storage/gridfs~GridFSStorage | ||
* @param {object} opts - Configuration object passed in the constructor | ||
* | ||
* */ | ||
GridFSStorage.prototype._connect = function (opts) { | ||
var self = this; | ||
if (file.grid) { | ||
self.gfs.remove({ _id: file.id }, function (err) { | ||
var promise; | ||
function onFullfill(gfs) { | ||
self.gfs = gfs; | ||
self.emit('connection', self.gfs, self.gfs.db); | ||
} | ||
function onReject(err) { | ||
self._logError(err); | ||
} | ||
if (!opts.gfs) { | ||
mongo.MongoClient.connect(opts.url, function (err, db) { | ||
var gfs; | ||
if (err) { | ||
cb(err); | ||
throw err; | ||
} | ||
logMessage(self, { message: 'Deleted file ', extra: file }); | ||
cb(null); | ||
function errEvent(err) { | ||
// Needs verification. Sometimes the event fires without an error object | ||
// although the docs specify each of the events has a MongoError argument | ||
self._logError(err || new Error()); | ||
} | ||
gfs = new Grid(db, mongo); | ||
if (self._logLevel === 'all') { | ||
self._logMessage({ message: 'MongoDb connected in url ' + opts.url, extra: db }); | ||
// This are all the events that emit errors | ||
db.on('error', errEvent) | ||
.on('parseError', errEvent) | ||
.on('timeout', errEvent) | ||
.on('close', errEvent); | ||
} | ||
self.gfs = gfs; | ||
self.emit('connection', self.gfs, self.gfs.db); | ||
}); | ||
} else { | ||
cb(null); | ||
if ('then' in opts.gfs) { | ||
promise = opts.gfs; | ||
promise.then(onFullfill, onReject); | ||
} else { | ||
self.gfs = opts.gfs; | ||
self.emit('connection', self.gfs, self.gfs.db); | ||
} | ||
} | ||
}; | ||
/** | ||
* Logs messages or errors | ||
* Use the console if enabled or the passed function in the constructor `log` option | ||
* @function _logMessage | ||
* @instance | ||
* @memberOf module:multer-gridfs-storage/gridfs~GridFSStorage | ||
* @param {object | string | Error} log - The object or error to log | ||
* @param {boolean} error - If true logs the message as an error | ||
* | ||
* */ | ||
GridFSStorage.prototype._logMessage = function _logMessage(log, error) { | ||
var method = error ? 'error' : 'log'; | ||
function logConsole() { | ||
/*eslint-disable no-console */ | ||
console[method](log.message, log.extra); | ||
/*eslint-enable no-console */ | ||
} | ||
var logFn = this._log === true ? logConsole : this._log; | ||
if (logFn) { | ||
if (error) { | ||
return logFn(log, null); | ||
} | ||
logFn(null, log); | ||
} | ||
}; | ||
/** | ||
* Logs errors only. Uses `_logMessage` under the hood. | ||
* @function _logError | ||
* @instance | ||
* @memberOf module:multer-gridfs-storage/gridfs~GridFSStorage | ||
* @param {string | Error} err - The error to log | ||
* | ||
* */ | ||
GridFSStorage.prototype._logError = function (err) { | ||
this._logMessage(err, true); | ||
}; | ||
module.exports = GridFSStorage; |
431
lib/utils.js
@@ -0,1 +1,7 @@ | ||
/** | ||
* | ||
* Utility functions | ||
* @module multer-gridfs-storage/utils | ||
* | ||
* */ | ||
'use strict'; | ||
@@ -6,9 +12,68 @@ | ||
/** | ||
* Returns the ES6 built-in tag of the tested value (The internal [[Class]] slot in ES5) | ||
* @function builtinTag | ||
* @inner | ||
* @param {object} target - The object to be tested | ||
* @since 1.1.0 | ||
* @returns {string} The built in tag of the target | ||
* | ||
* */ | ||
function builtinTag(target) { | ||
return Object.prototype.toString.call(target); | ||
} | ||
/** | ||
* Test a value to see if is a Promise | ||
* @function isPromise | ||
* @static | ||
* @param {object} target - The object to be tested | ||
* @since 1.1.0 | ||
* @returns {boolean} Returns true if the target parameter is a thenable (Promsie) | ||
* | ||
* */ | ||
function isPromise(target) { | ||
// Promise A+ spec - 1.1 | ||
// "Promise is an object or function with a then method" | ||
// The spec also specifies that it must conform to a certain behavior but this is impossible to check by just inspecting the target | ||
// So we can only check if is a valid thenable | ||
return target !== null && (typeof target === 'object' || isFunction(target)) && isFunction(target.then); | ||
} | ||
/** | ||
* Test a value to see if is a Promise or a Grid instance | ||
* @function isGfsOrPromise | ||
* @static | ||
* @param {object} target - The object to be tested | ||
* @since 1.1.0 | ||
* @returns {boolean} Returns true if the target parameter is a promise or an instance of gridfs-stream | ||
* */ | ||
function isGfsOrPromise(target) { | ||
return target instanceof Grid || ((typeof target === 'object' || isFunction(target)) && 'then' in target); | ||
return target instanceof Grid || isPromise(target); | ||
} | ||
/** | ||
* Test a value to see if is a function or a generator function | ||
* @function isFuncOrGeneratorFunc | ||
* @static | ||
* @param {object} target - The object to be tested | ||
* @since 1.1.0 | ||
* @returns {boolean} Returns true if the target parameter is a function or a generator function | ||
* */ | ||
function isFuncOrGeneratorFunc(target) { | ||
return isFunction(target) || isGeneratorFunction(target); | ||
} | ||
/** | ||
* Generates a random string to use as the filename | ||
* @function getFileName | ||
* @static | ||
* @param {Request} req - A reference to the request object | ||
* @param {File} file - A reference to the file being uploaded | ||
* @param {Function} cb - A callback function to return the name used | ||
* @since 1.1.0 | ||
* */ | ||
function getFileName(req, file, cb) { | ||
var randomBytes = crypto.randomBytes || crypto.pseudoRandomBytes; | ||
randomBytes(16, function (err, buffer) { | ||
@@ -19,2 +84,9 @@ cb(err, err ? null : buffer.toString('hex')); | ||
/** | ||
* Generate any fixed value using a callback | ||
* @function generateValue | ||
* @static | ||
* @param {any} value - A function to generate callbacks that invokes with a given value | ||
* @since 1.1.0 | ||
* */ | ||
function generateValue(value) { | ||
@@ -26,2 +98,11 @@ return function getValue(req, file, cb) { | ||
/** | ||
* Generates a null value using a callback | ||
* @function noop | ||
* @static | ||
* @param {Request} req - A reference to the request object | ||
* @param {File} file - A reference to the file being uploaded | ||
* @param {Function} cb - A callback function to return the name used | ||
* @since 1.1.0 | ||
* */ | ||
function noop(req, file, cb) { | ||
@@ -31,63 +112,308 @@ cb(null, null); | ||
/** | ||
* Test a value to see if is a plain function | ||
* Return false for proxies, generator functions and async functions | ||
* @function isFunction | ||
* @static | ||
* @param {any} target - The value to test | ||
* @returns {boolean} Returns true if the target parameter is a function | ||
* @since 1.1.0 | ||
* */ | ||
function isFunction(target) { | ||
// taken from: lodash - isFunction method | ||
// https://github.com/lodash/lodash/blob/master/isFunction.js | ||
// lodash also returns true for Proxy, async and generator functions which is not the desired behavior in this case | ||
var type = typeof target; | ||
var mayBeFunc = (type === 'object' || type === 'function'); | ||
var objClass = Object.prototype.toString.call(target); | ||
return target !== null && mayBeFunc && objClass === '[object Function]'; | ||
return target !== null && mayBeFunc && builtinTag(target) === '[object Function]'; | ||
} | ||
function logMessage(instance, log, error) { | ||
var method = error ? 'error' : 'log'; | ||
function logConsole() { | ||
/*eslint-disable no-console */ | ||
console[method](log.message, log.extra); | ||
/*eslint-enable no-console */ | ||
/** | ||
* Test a value to see if is a plain object | ||
* Return false for any "object" like value that is not created with an Object or null prototype | ||
* @function isObject | ||
* @static | ||
* @param {any} target - The value to test | ||
* @returns {boolean} Returns true if the target parameter is an object | ||
* @since 1.2.0 | ||
* */ | ||
function isObject(target) { | ||
// taken from: lodash - isPlainObject method | ||
// https://github.com/lodash/lodash/blob/master/isPlainObject.js | ||
// avoid having an extra dependency | ||
var notAnObject, proto, toStr, Ctor; | ||
notAnObject = target === null || typeof target !== 'object' || builtinTag(target) !== '[object Object]'; | ||
if (notAnObject) { | ||
return false; | ||
} | ||
var logFn = instance._log === true ? logConsole : instance._log; | ||
if (logFn) { | ||
if (error) { | ||
return logFn(log, null); | ||
proto = Object.getPrototypeOf(target); | ||
// true for Object.create(null); | ||
if (proto === null) { | ||
return true; | ||
} | ||
toStr = Function.prototype.toString; | ||
Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor') && proto.constructor; | ||
return proto.hasOwnProperty('constructor') && typeof Ctor === 'function' && | ||
Ctor instanceof Ctor && toStr.call(Object) === toStr.call(proto.constructor); | ||
} | ||
/** | ||
* Test an object to see if it has all of the given properties | ||
* @function hasProps | ||
* @static | ||
* @param {any} target - The value to test | ||
* @param {array} props - An array of property names that should be present in the target | ||
* @returns {boolean} Return false if any of the properties is missing | ||
* @since 1.2.0 | ||
* */ | ||
function hasProps(target, props) { | ||
var prop, i; | ||
var valid = false; | ||
if (typeof props === 'string') { | ||
valid = props in target; | ||
} else { | ||
for (i = 0; i < props.length; i++) { | ||
prop = props[i]; | ||
if (prop in target) { | ||
valid = true; | ||
break; | ||
} | ||
} | ||
logFn(null, log); | ||
} | ||
return valid; | ||
} | ||
// TODO: Use joi or similar module to validate input | ||
function validateOptions(opts) { | ||
var i, prop, fnOpts, type; | ||
if (!opts || (!('url' in opts) && !('gfs' in opts))) { | ||
throw new Error('Missing required configuration'); | ||
/** | ||
* Checks if a value is a generator function | ||
* @function isGeneratorFunction | ||
* @static | ||
* @param {any} target - The value to test | ||
* @returns {boolean} Return true if the target is a generator function | ||
* @since 1.2.0 | ||
* @todo Support polyfills which are plain functions that returns a generator like object | ||
* */ | ||
function isGeneratorFunction(target) { | ||
return builtinTag(target) === '[object GeneratorFunction]'; | ||
} | ||
/** | ||
* Checks if a value is a generator | ||
* @function isGenerator | ||
* @static | ||
* @param {any} target - The value to test | ||
* @returns {boolean} Return true only if the target is a generator created as the result of invoking a generator function | ||
* @since 1.2.0 | ||
* @todo Support polyfills | ||
* */ | ||
function isGenerator(target) { | ||
if (target === null || target === undefined) { | ||
return false; | ||
} | ||
if ('gfs' in opts) { | ||
if (!isGfsOrPromise(opts.gfs)) { | ||
throw new Error('Expected gfs configuration to be a Grid instance or a promise'); | ||
return builtinTag(target) === '[object Generator]'; | ||
/* | ||
// Since this module only accepts generator functions as input (and not generator objects) we can safely disable the other checks | ||
// These might be required for polyfills | ||
var isGen = builtinTag(target) === '[object Generator]'; | ||
if (isGen) { | ||
return true; | ||
} | ||
var isSymbolSupported = !!global.Symbol; | ||
if (isSymbolSupported) { | ||
var iteratorProp = target[Symbol.iterator]; | ||
var isIterable = isFunction(iteratorProp) && iteratorProp.length === 0; | ||
if (!isIterable) { | ||
return false; | ||
} | ||
// In some implementations if called as iteratorProp() throws an error because `this` is undefined | ||
var iterator = target[Symbol.iterator](); | ||
// Make sure is a well-formed iterable | ||
// The Iterator spec says `next` is "suposed" to be called without arguments but they might receive some | ||
// Doesn't specify how many | ||
// The generator spec specifies at most one | ||
// Eg: | ||
// generator.next(value); | ||
// ...Inside the generator | ||
// var parameter = yield nextValue; // parameter === value | ||
return iterator !== null && iterator !== undefined && isFunction(iterator.next) && isFunction(iterator.return) && isFunction(iterator.throw); | ||
} | ||
if ('logLevel' in opts && !(opts.logLevel === 'file' || opts.logLevel === 'all')) { | ||
throw new Error('Invalid log level configuration. Must be either "file" or "all"'); | ||
return false; | ||
*/ | ||
} | ||
/** | ||
* Checks a value to see if has a given type | ||
* @function matchType | ||
* @static | ||
* @param {any} value - The value to test | ||
* @param {Function} type - The constructor function that defines the type | ||
* @returns {boolean} Return true if the target is an instance of the given type or if is a primitive value from a given type | ||
* @since 1.2.0 | ||
* | ||
* */ | ||
function matchType(value, type) { | ||
return value instanceof type || type(value) === value; | ||
} | ||
/** | ||
* Checks a value to see if it belong to a given set of values | ||
* @function hasValue | ||
* @static | ||
* @param {any} target - The value to compare | ||
* @param {array} set - An array consisting of values to compare | ||
* @returns {boolean} Return true if the target is one of the items of the set | ||
* @since 1.2.0 | ||
* | ||
* */ | ||
function hasValue(target, set) { | ||
return set.indexOf(target) !== -1; | ||
} | ||
/** | ||
* A function to check if value applied to a validation rule is valid or not | ||
* @function checkRule | ||
* @static | ||
* @param {object} target - The object to test for validity | ||
* @param {object} rule - The rule to check | ||
* @param {string|null} rule.prop - If null the rule is applied to the target object otherwise is applied to the specified property | ||
* @param {array} rule.validations - An array of validations to execute | ||
* @param {string} rule.error - The error message to throw in case validation fails | ||
* @param {string} rule.condition - The logical operation to use when checking. It defaults to `and` | ||
* @returns {boolean} Return true if target passes the validation rule, false otherwise | ||
* @since 1.2.0 | ||
* | ||
* */ | ||
function checkRule(target, rule) { | ||
var i, validation, args; | ||
var source = rule.prop ? target[rule.prop] : target; | ||
var result; | ||
var isAnd = !rule.condition || rule.condition !== 'or'; | ||
var isValid = true; | ||
if (source !== undefined) { | ||
isValid = isAnd === true; | ||
for (i = 0; i < rule.validations.length; i++) { | ||
validation = rule.validations[i]; | ||
args = [source]; | ||
if (validation.args) { | ||
args.push(validation.args); | ||
} | ||
result = validation.check.apply(null, args); | ||
if (isAnd) { | ||
if (!result) { | ||
isValid = false; | ||
break; | ||
} | ||
} else { | ||
if (result) { | ||
isValid = true; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
fnOpts = ['identifier', 'filename', 'metadata']; | ||
for (i = 0; i < fnOpts.length; i++) { | ||
prop = fnOpts[i]; | ||
if (prop in opts && !isFunction(opts[prop])) { | ||
throw new Error('Expected ' + prop + ' configuration to be a function'); | ||
return isValid; | ||
} | ||
/** | ||
* Input validation function | ||
* Checks the properties of the configuration object to see if they match their intended type and throw useful error messages | ||
* to help debugging | ||
* @function validateOptions | ||
* @static | ||
* @param {object} opts - The options passed to the constructor | ||
* @since 1.1.0 | ||
* | ||
* */ | ||
function validateOptions(opts) { | ||
var i, failed, error; | ||
var errorRegexp = /%prop%/; | ||
var rules = [ | ||
{ | ||
prop: null, | ||
validations: [ | ||
{check: isObject}, | ||
{check: hasProps, args: ['url', 'gfs']} | ||
], | ||
error: 'Missing required configuration' | ||
}, | ||
{ | ||
prop: 'gfs', | ||
validations: [ | ||
{check: isGfsOrPromise} | ||
], | ||
error: 'Expected gfs configuration to be a Grid instance or a promise' | ||
}, | ||
{ | ||
prop: 'logLevel', | ||
validations: [ | ||
{check: hasValue, args: ['file', 'all']} | ||
], | ||
error: 'Invalid log level configuration. Must be either "file" or "all"' | ||
}, | ||
{ | ||
prop: 'identifier', | ||
validations: [ | ||
{check: isFuncOrGeneratorFunc} | ||
], | ||
error: 'Expected %prop% configuration to be a function or a generator function' | ||
}, | ||
{ | ||
prop: 'filename', | ||
validations: [ | ||
{check: isFuncOrGeneratorFunc} | ||
], error: 'Expected %prop% configuration to be a function or a generator function' | ||
}, | ||
{ | ||
prop: 'metadata', | ||
validations: [ | ||
{check: isFuncOrGeneratorFunc} | ||
], | ||
error: 'Expected %prop% configuration to be a function or a generator function' | ||
}, | ||
{ | ||
prop: 'chunkSize', | ||
validations: [ | ||
{check: isFuncOrGeneratorFunc}, | ||
{check: matchType, args: Number} | ||
], | ||
condition: 'or', | ||
error: 'Expected %prop% configuration to be a function, a generator function or a Number' | ||
}, | ||
{ | ||
prop: 'root', | ||
validations: [ | ||
{check: isFuncOrGeneratorFunc}, | ||
{check: matchType, args: String} | ||
], | ||
condition: 'or', | ||
error: 'Expected %prop% configuration to be a function, a generator function or a String' | ||
}, | ||
{ | ||
prop: 'log', | ||
validations: [ | ||
{check: isFunction}, | ||
{check: matchType, args: Boolean} | ||
], | ||
condition: 'or', | ||
error: 'Expected %prop% configuration to be a function or a Boolean' | ||
} | ||
]; | ||
for (i = 0; i < rules.length; i++) { | ||
if (!checkRule(opts, rules[i])) { | ||
failed = rules[i]; | ||
break; | ||
} | ||
} | ||
var valueOrFnOpts = [{prop: 'chunkSize', type: Number}, {prop: 'root', type: String}, {prop: 'log', type: Boolean}]; | ||
for (i = 0; i < valueOrFnOpts.length; i++) { | ||
prop = valueOrFnOpts[i].prop; | ||
type = valueOrFnOpts[i].type; | ||
if (prop in opts && !isFunction(opts[prop]) && type(opts[prop]) !== opts[prop] && !(opts[prop] instanceof type)) { | ||
throw new Error('Expected ' + prop + ' configuration to be a function or a ' + type.name); | ||
if (failed) { | ||
error = failed.error; | ||
if (errorRegexp.test(failed.error)) { | ||
error = error.replace(errorRegexp, failed.prop); | ||
} | ||
throw new Error(error); | ||
} | ||
@@ -101,5 +427,14 @@ } | ||
isFunction: isFunction, | ||
logMessage: logMessage, | ||
validateOptions: validateOptions | ||
isPromise: isPromise, | ||
isGeneratorFunction: isGeneratorFunction, | ||
isFuncOrGeneratorFunc: isFuncOrGeneratorFunc, | ||
isGenerator: isGenerator, | ||
isGfsOrPromise: isGfsOrPromise, | ||
isObject: isObject, | ||
validateOptions: validateOptions, | ||
hasValue: hasValue, | ||
hasProps: hasProps, | ||
checkRule: checkRule, | ||
matchType: matchType | ||
}; | ||
{ | ||
"name": "multer-gridfs-storage", | ||
"version": "1.1.1", | ||
"version": "1.2.0", | ||
"description": "Multer storage engine for GridFS", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "mocha", | ||
"test": "mocha --require babel-register --require babel-polyfill", | ||
"lint": "eslint .", | ||
"cover": "istanbul cover ./node_modules/mocha/bin/_mocha", | ||
"docs": "./node_modules/.bin/jsdoc -c ./conf.json --readme ./README.md", | ||
"coveralls": "npm run cover -- --report lcovonly && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage" | ||
@@ -32,15 +33,20 @@ }, | ||
"devDependencies": { | ||
"babel-polyfill": "^6.23.0", | ||
"babel-preset-env": "^1.4.0", | ||
"babel-register": "^6.24.1", | ||
"bluebird": "^3.5.0", | ||
"chai": "^3.5.0", | ||
"chai-interface": "^2.0.3", | ||
"chai-spies": "^0.7.1", | ||
"coveralls": "^2.11.9", | ||
"eslint": "^2.10.2", | ||
"express": "^4.13.4", | ||
"express": "^4.15.2", | ||
"istanbul": "^0.4.3", | ||
"jsdoc": "^3.4.3", | ||
"md5-file": "^2.0.4", | ||
"mocha": "^2.4.5", | ||
"mocha-lcov-reporter": "^1.2.0", | ||
"multer": "^1.1.0", | ||
"multer": "^1.3.0", | ||
"mute": "^2.0.6", | ||
"sinon": "^2.2.0", | ||
"sinon-chai": "^2.10.0", | ||
"supertest": "^1.2.0" | ||
@@ -47,0 +53,0 @@ }, |
# Multer's GridFS storage engine | ||
[![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] | ||
[![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] ![Npm version][version-image] | ||
@@ -52,2 +52,21 @@ [GridFS](https://docs.mongodb.com/manual/core/gridfs) storage engine for [Multer](https://github.com/expressjs/multer) to store uploaded files directly to MongoDb | ||
Starting from version 1.1.0 the module function can be called with | ||
or without the javascript `new` operator like this | ||
```javascript | ||
var storage = require('multer-gridfs-storage')(options); | ||
var upload = multer({ storage: storage }); | ||
//or | ||
var GridFSStorage = require('multer-gridfs-storage'); | ||
var storage = new GridFSStorage(options) | ||
var upload = multer({ storage: storage }); | ||
``` | ||
The 1.2 version brings full support for promises and ES6 generators. | ||
You can check the [wiki][wiki] for more information. | ||
### Options | ||
The options parameter is an object with the following properties. | ||
@@ -57,3 +76,3 @@ | ||
Type: **Object** or **Promise** | ||
Type: `object` or `Promise` | ||
@@ -71,3 +90,3 @@ Required if [`url`][url-option] option is not present | ||
var mongo = require('mongodb'); | ||
var GridFsStorage = require('multer-gridfs-storage'); | ||
var GridFSStorage = require('multer-gridfs-storage'); | ||
@@ -80,3 +99,3 @@ var db = new mongo.Db('database', new mongo.Server("127.0.0.1", 27017)); | ||
var storage = GridFsStorage({ | ||
var storage = GridFSStorage({ | ||
gfs: gfs | ||
@@ -90,3 +109,3 @@ }); | ||
Type: **String** | ||
Type: `string` | ||
@@ -139,3 +158,3 @@ Required if [`gfs`][gfs-option] option is not present | ||
Type: **Function** | ||
Type: `function` or `function*` | ||
@@ -148,3 +167,3 @@ Not required | ||
By default this module behaves exactly like the default Multer disk storage does. | ||
By default, this module behaves exactly like the default Multer disk storage does. | ||
It generates a 16 bytes long name in hexadecimal format with no extension for the file | ||
@@ -190,3 +209,3 @@ to guarantee that there are very low probabilities of naming collisions. You can override this | ||
Type: **Function** | ||
Type: `function` or `function*` | ||
@@ -221,3 +240,3 @@ Not required | ||
***Important note*** | ||
***Note:*** | ||
@@ -229,3 +248,3 @@ > Normally you shouldn't use this function | ||
Type: **Function** | ||
Type: `function` or `function*` | ||
@@ -239,3 +258,3 @@ Not required | ||
By default the stored metadata value for uploaded files is `null`. | ||
By default, the stored metadata value for uploaded files is `null`. | ||
@@ -260,9 +279,12 @@ Example: | ||
Type: **Number** or **Function** | ||
Type: `number`, `function` or `function*` | ||
Not required | ||
The preferred size of file chunks. Default value is 261120. You can use a | ||
fixed number as the value or a function to use different values per file. | ||
The preferred size of file chunks in bytes. | ||
Default value is 261120 (255kb). | ||
You can use a fixed number as the value or a function to use different values per upload. | ||
Example using fixed value: | ||
@@ -296,7 +318,7 @@ | ||
Type: **String** or **Function** | ||
Type: `string`, `function` or `function*` | ||
Not required | ||
The root collection to store the files. By default this value is `null`. | ||
The root collection to store the files. By default, this value is `null`. | ||
When the value of this property is `null` MongoDb will use the default collection name `'fs'` | ||
@@ -394,3 +416,3 @@ to store files. This value can be changed with this option and you can use a different fixed value | ||
This event is ememitted every time a new file is stored in the db. This is useful when you have | ||
This event is emitted every time a new file is stored in the db. This is useful when you have | ||
a custom logging mechanism and want to record every uploaded file. | ||
@@ -416,3 +438,3 @@ | ||
Type: **Boolean** or **Function** | ||
Type: `boolean` or `function` | ||
@@ -425,3 +447,3 @@ Default: `false` | ||
By default the module will not output anything. Set this option to `true` to log when the connection is opened, | ||
By default, the module will not output anything. Set this option to `true` to log when the connection is opened, | ||
files are stored or an error occurs. This is useful when you want to see logging about incoming files. | ||
@@ -457,3 +479,3 @@ | ||
Type: **string** | ||
Type: `string` | ||
@@ -509,5 +531,6 @@ Default: `'file'` | ||
[travis-url]: https://travis-ci.org/devconcept/multer-gridfs-storage | ||
[travis-image]: https://travis-ci.org/devconcept/multer-gridfs-storage.svg?branch=master | ||
[travis-image]: https://travis-ci.org/devconcept/multer-gridfs-storage.svg?branch=master "Build status" | ||
[coveralls-url]: https://coveralls.io/github/devconcept/multer-gridfs-storage?branch=master | ||
[coveralls-image]: https://coveralls.io/repos/github/devconcept/multer-gridfs-storage/badge.svg?branch=master | ||
[coveralls-image]: https://coveralls.io/repos/github/devconcept/multer-gridfs-storage/badge.svg?branch=master "Coverage report" | ||
[version-image]:https://img.shields.io/npm/v/multer-gridfs-storage.svg "Npm version" | ||
@@ -523,1 +546,2 @@ [url-option]: #url | ||
[logLevel-option]: #loglevel | ||
[wiki]: https://github.com/devconcept/multer-gridfs-storage/wiki |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
44054
743
529
19
6712
1