diff --git a/packages/xo-collection/.jshintrc b/packages/xo-collection/.jshintrc new file mode 100644 index 000000000..9e30c8a86 --- /dev/null +++ b/packages/xo-collection/.jshintrc @@ -0,0 +1,93 @@ +{ + // Julien Fontanet JSHint configuration + // https://gist.github.com/julien-f/8095615 + // + // Changes from defaults: + // - all enforcing options (except `++` & `--`) enabled + // - single quotes + // - indentation set to 2 instead of 4 + // - almost all relaxing options disabled + // - environments are set to Node.js + // + // See http://jshint.com/docs/ for more details + + "maxerr" : 50, // {int} Maximum error before stopping + + // Enforcing + "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase" : true, // true: Identifiers must be in camelCase + "curly" : true, // true: Require {} for every new block or scope + "eqeqeq" : true, // true: Require triple equals (===) for comparison + "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() + "freeze" : true, // true: Prohibit overwriting prototypes of native objects (Array, Date, ...) + "immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` + "indent" : 2, // {int} Number of spaces to use for indentation + "latedef" : true, // true: Require variables/functions to be defined before being used + "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty" : true, // true: Prohibit use of empty blocks + "nonbsp" : true, // true: Prohibit use of non breakable spaces + "nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus" : false, // true: Prohibit use of `++` & `--` + "quotmark" : "single", // Quotation mark consistency: + // false : do nothing (default) + // true : ensure whatever is used is consistent + // "single" : require single quotes + // "double" : require double quotes + "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) + "unused" : true, // true: Require all defined variables be used + "strict" : false, // true: Requires all functions run in ES5 Strict Mode + "maxcomplexity" : 7, // {int} Max cyclomatic complexity per function + "maxdepth" : 3, // {int} Max depth of nested blocks (within functions) + "maxlen" : 80, // {int} Max number of characters per line + "maxparams" : 4, // {int} Max number of formal params allowed per function + "maxstatements" : 20, // {int} Max number statements per function + + // Relaxing + "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss" : false, // true: Tolerate assignments where comparisons would be expected + "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull" : false, // true: Tolerate use of `== null` + "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) + "evil" : false, // true: Tolerate use of `eval` and `new Function()` + "expr" : false, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : false, // true: Tolerate defining variables inside control statements + "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') + "iterator" : false, // true: Tolerate using the `__iterator__` property + "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak" : false, // true: Tolerate possibly unsafe line breakings + "laxcomma" : false, // true: Tolerate comma-first style coding + "loopfunc" : false, // true: Tolerate functions being defined in loops + "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "multistr" : false, // true: Tolerate multi-line strings + "notypeof" : false, // true: Tolerate typeof comparison with unknown values. + "proto" : false, // true: Tolerate using the `__proto__` property + "scripturl" : false, // true: Tolerate script-targeted URLs + "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis" : false, // true: Tolerate using this in a non-constructor function + "noyield" : false, // true: Tolerate generators without yields + + // Environments + "browser" : false, // Web Browser (window, document, etc) + "browserify" : false, // Browserify (node.js code in the browser) + "couch" : false, // CouchDB + "devel" : false, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jquery" : false, // jQuery + "mocha" : false, // mocha + "mootools" : false, // MooTools + "node" : true, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "phantom" : false, // PhantomJS + "prototypejs" : false, // Prototype and Scriptaculous + "rhino" : false, // Rhino + "worker" : false, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface + + // Custom Globals + "globals" : {} // additional predefined global variables +} diff --git a/packages/xo-collection/collection.js b/packages/xo-collection/collection.js new file mode 100644 index 000000000..c236b3c71 --- /dev/null +++ b/packages/xo-collection/collection.js @@ -0,0 +1,238 @@ +import makeError from 'make-error'; + +var IllegalKey = makeError('IllegalKey'); +var OverrideViolation = makeError('OverrideViolation'); +var NotFound = makeError('NotFound'); +var TransactionAlreadyOpened = makeError('TransactionAlreadyOpened'); +var NoTransactionOpened = makeError('NoTransactionOpened'); + +class Collection { + + constructor(strictMode = true) { + + this._collection = {}; + this._strictMode = strictMode; + this._transaction = null; + + } + + begin(callback = undefined) { + + try { + + if (this._strictMode && null !== this._transaction) { + throw new TransactionAlreadyOpened('A transaction is already opened'); + } + + this._transaction = this._transaction || []; + + if ('function' === typeof callback) { + callback(null, this); + } else { + return this; + } + + + } catch (err) { + + if ('function' === typeof callback) { + callback(err); + } else { + throw err; + } + + } + + } + + commit(callback = undefined) { + + try { + + if (this._strictMode && null === this._transaction) { + throw new NoTransactionOpened('No opened transaction to commit.'); + } + + let transactionLog = this._transaction || []; + this._transaction = null; + + if ('function' === typeof callback) { + callback(null, transactionLog); + } else { + return transactionLog; + } + + + } catch (err) { + + if ('function' === typeof callback) { + callback(err); + } else { + throw err; + } + + } + + } + + insert(id, item, callback = undefined) { + + try { + + id = this.checkId(id); + + if (this._strictMode && this._has(id)) { + throw new OverrideViolation( + 'An insertion must not override the pre-existing key ' + id + '. ' + + 'Consider using update instead depending on your use case.' + ); + } + + this._collection[id] = item; + + if ('function' === typeof callback) { + callback(null, this); + } else { + return this; + } + + } catch (err) { + + if ('function' === typeof callback) { + callback(err); + } else { + throw err; + } + + } + + } + + update(id, item, callback = undefined) { + + try { + + id = this.checkId(id); + + if (this._strictMode && !this._has(id)) { + throw new NotFound( + 'No item to update at this key ' + id + '. ' + + 'Consider using insert instead depending on your usecase.' + ); + } + + this._collection[id] = item; + + if ('function' === typeof callback) { + callback(null, this); + } else { + return this; + } + + } catch (err) { + + if ('function' === typeof callback) { + callback(err); + } else { + throw err; + } + + } + + } + + delete(id, callback = undefined) { + + try { + + id = this.checkId(id); + + if (this._strictMode && !this._has(id)) { + throw new NotFound( + 'No item to remove at key' + id + '.' + ); + } + + delete this._collection[id]; + + if ('function' === typeof callback) { + callback(null, this); + } else { + return this; + } + + } catch (err) { + + if ('function' === typeof callback) { + callback(err); + } else { + throw err; + } + + } + + } + + get(id, callback = undefined) { + + try { + + id = this.checkId(id); + + if (this._strictMode && !this._has(id)) { + throw new NotFound( + 'No item found at key ' + id + ); + } + + if ('function' === typeof callback) { + callback(null, this._collection[id]); + } else { + return this._collection[id]; + } + + } catch (err) { + + if ('function' === typeof callback) { + callback(err); + } else { + throw err; + } + + } + + } + + checkId (id) { + + if (undefined === id || null === id) { + throw new IllegalKey('Illegal key : ' + id); + } + + id = this._strictMode && id || String(id); + if ('string' === typeof id && id.length > 0) { + return id; + } else { + throw new IllegalKey('Key must be a non-empty string'); + } + + } + + has(id) { + + return this._has(this.checkId(id)); + + } + + _has(id) { + + return this._collection.hasOwnProperty(id); + + } + +} + +export var Collection = Collection; +export var IllegalKey = IllegalKey; +export var OverrideViolation = OverrideViolation; +export var NotFound = NotFound; \ No newline at end of file