First main methods. No events, no buffer yet
This commit is contained in:
parent
48d9fde3b6
commit
265d77d776
@ -1,499 +1,195 @@
|
|||||||
import makeError from 'make-error';
|
import makeError from 'make-error';
|
||||||
|
|
||||||
const IllegalId = makeError('IllegalId');
|
const AlreadyBuffering = makeError('AlreadyBuffering');
|
||||||
const OverrideViolation = makeError('OverrideViolation');
|
const NotBuffering = makeError('NotBuffering');
|
||||||
const NotFound = makeError('NotFound');
|
const IllegalAdd = makeError('IllegalAdd');
|
||||||
const TransactionAlreadyOpened = makeError('TransactionAlreadyOpened');
|
const DuplicateEntry = makeError('DuplicateEntry');
|
||||||
const NoTransactionOpened = makeError('NoTransactionOpened');
|
const NoSuchEntry = makeError('NoSuchEntry');
|
||||||
const FailedRollback = makeError('FailedRollback');
|
|
||||||
const FailedReplay = makeError('FailedReplay');
|
|
||||||
const UnrecognizedTransactionItem = makeError('UnrecognizedTransactionItem');
|
|
||||||
const UnexpectedLogFormat = makeError('UnexpectedLogFormat');
|
|
||||||
const UnexpectedLogItemData = makeError('UnexpectedLogItemData');
|
|
||||||
const AlreadyPaused = makeError('AlreadyPaused');
|
|
||||||
const NothingToFlush = makeError('NothingToFlush');
|
|
||||||
|
|
||||||
class Collection {
|
class Collection {
|
||||||
|
|
||||||
constructor() {
|
constructor () {
|
||||||
|
|
||||||
this._collection = {};
|
this._map = {};
|
||||||
this._transaction = null;
|
this._buffering = false;
|
||||||
this._track = [];
|
this._size = 0;
|
||||||
this._pause = false;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pause() {
|
_initBuffer () {
|
||||||
|
|
||||||
if (this._pause) {
|
this._buffer = {
|
||||||
throw new AlreadyPaused('Already paused');
|
remove: [],
|
||||||
|
add: [],
|
||||||
|
update: []
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferChanges (state = true) {
|
||||||
|
|
||||||
|
if (state && this._buffer) {
|
||||||
|
throw new AlreadyBuffering('Already buffering'); // FIXME Really ?...
|
||||||
}
|
}
|
||||||
this._pause = true;
|
if (!state && !this._buffer) {
|
||||||
|
throw new NotBuffering('Not buffering'); // FIXME Really ?...
|
||||||
}
|
|
||||||
|
|
||||||
flush(callback = undefined) {
|
|
||||||
|
|
||||||
if (!this._pause) {
|
|
||||||
throw new NothingToFlush('NothingToFlush');
|
|
||||||
}
|
}
|
||||||
|
this._buffer = state;
|
||||||
|
|
||||||
this._pause = false;
|
if (!this._buffer) {
|
||||||
let track = this._track;
|
this._initBuffer();
|
||||||
this._track = [];
|
|
||||||
|
|
||||||
return this.replay(track, callback);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
begin(callback = undefined) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
if (null !== this._transaction) {
|
|
||||||
throw new TransactionAlreadyOpened('A transaction is already opened');
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
flush () {
|
||||||
|
|
||||||
try {
|
if (!this._buffer) {
|
||||||
|
throw new NotBuffering('NothingToFlush');
|
||||||
|
}
|
||||||
|
|
||||||
if (null === this._transaction) {
|
this._buffering = false; // FIXME Really ?
|
||||||
throw new NoTransactionOpened('No opened transaction to commit.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const transactionLog = this._transaction;
|
// TODO Throws buffered events
|
||||||
this._transaction = null;
|
|
||||||
|
|
||||||
if ('function' === typeof callback) {
|
this._initBuffer();
|
||||||
callback(null, transactionLog);
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_touch (key, action) {
|
||||||
|
|
||||||
|
// TODO Buffers changes or throws an event
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_set (key, value) {
|
||||||
|
|
||||||
|
this._map[key] = value;
|
||||||
|
this._touch(key, 'update');
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_unset (key) {
|
||||||
|
|
||||||
|
delete this._map[key];
|
||||||
|
this._touch(key, 'remove');
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getId (item) {
|
||||||
|
return item.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
has (key) {
|
||||||
|
|
||||||
|
return this._map.hasOwnProperty(key);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveEntry (keyOrObjectWithId, valueIfKey = null) {
|
||||||
|
|
||||||
|
let value;
|
||||||
|
let key = (undefined !== keyOrObjectWithId) ?
|
||||||
|
this.getId(keyOrObjectWithId) :
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
if (undefined === key) {
|
||||||
|
if (arguments.length < 2) {
|
||||||
|
throw new IllegalAdd();
|
||||||
} else {
|
} else {
|
||||||
return transactionLog;
|
key = keyOrObjectWithId;
|
||||||
|
value = valueIfKey;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
value = keyOrObjectWithId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [key, value];
|
||||||
|
}
|
||||||
|
|
||||||
} catch (err) {
|
_assertHas(key) {
|
||||||
|
|
||||||
if ('function' === typeof callback) {
|
|
||||||
callback(err);
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!this.has(key)) {
|
||||||
|
throw new NoSuchEntry();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rollback(callback = undefined) {
|
_assertNotHas(key) {
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
if (null === this._transaction) {
|
|
||||||
throw new NoTransactionOpened('No opened transaction to rollback.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const log = this._transaction;
|
|
||||||
this._transaction = null;
|
|
||||||
|
|
||||||
return this._rollback(log, callback);
|
|
||||||
|
|
||||||
} catch(err) {
|
|
||||||
|
|
||||||
if ('function' === typeof callback) {
|
|
||||||
callback(err);
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (this.has(key)) {
|
||||||
|
throw new DuplicateEntry();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_rollback(log, callback = undefined) {
|
add (keyOrObjectWithId, valueIfKey = null) {
|
||||||
|
|
||||||
try {
|
const [key, value] = this.resolveEntry.apply(this, arguments);
|
||||||
|
|
||||||
if (!Array.isArray(log)) {
|
this._assertNotHas(key);
|
||||||
throw new UnexpectedLogFormat('A transaction log must be an Array.');
|
this._size++;
|
||||||
}
|
|
||||||
|
|
||||||
let item;
|
return this._set(key, value);
|
||||||
const done = [];
|
|
||||||
|
|
||||||
while(item = log.pop()) {
|
}
|
||||||
try {
|
|
||||||
|
|
||||||
this.checkLogItem(item);
|
set (keyOrObjectWithId, valueIfKey = null) {
|
||||||
|
|
||||||
switch (item.action) {
|
const [key, value] = this.resolveEntry.apply(this, arguments);
|
||||||
case 'insert':
|
|
||||||
this.delete(item.id);
|
|
||||||
break;
|
|
||||||
case 'update':
|
|
||||||
this.update(item.id, item.former);
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
this.insert(item.id, item.former);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new UnrecognizedTransactionItem(
|
|
||||||
'Unrecognized item action : "' + item.action +
|
|
||||||
'" at index ' + log.lenght + '.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
done.unshift(item);
|
|
||||||
} catch(err) {
|
|
||||||
const exc = new FailedRollback(
|
|
||||||
'Rollback failed on index ' + log.lenght + '.'
|
|
||||||
);
|
|
||||||
exc.undone = log;
|
|
||||||
exc.done = done;
|
|
||||||
exc.internal = err;
|
|
||||||
exc.failedAction = item;
|
|
||||||
exc.index = log.lenght;
|
|
||||||
throw exc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('function' === typeof callback) {
|
|
||||||
callback(null, this);
|
|
||||||
} else {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch(err) {
|
|
||||||
|
|
||||||
if ('function' === typeof callback) {
|
|
||||||
callback(err);
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!this.has(key)) {
|
||||||
|
this._size++;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
return this._set(key, value);
|
||||||
|
|
||||||
replay(log, callback = undefined) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
if (!Array.isArray(log)) {
|
|
||||||
throw new UnexpectedLogFormat('A transaction log must be an Array.');
|
|
||||||
}
|
|
||||||
|
|
||||||
let item;
|
|
||||||
const done = [];
|
|
||||||
|
|
||||||
while(item = log.shift()) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
this.checkLogItem(item);
|
|
||||||
|
|
||||||
switch (item.action) {
|
|
||||||
case 'insert':
|
|
||||||
this.insert(item.id, item.item);
|
|
||||||
break;
|
|
||||||
case 'update':
|
|
||||||
this.update(item.id, item.item);
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
this.delete(item.id);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new UnrecognizedTransactionItem(
|
|
||||||
'Unrecognized item action : "' + item.action +
|
|
||||||
'" at index ' + done.lenght + '.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
done.push(item);
|
|
||||||
} catch(err) {
|
|
||||||
const exc = new FailedReplay(
|
|
||||||
'Replay failed on index ' + done.lenght + '.'
|
|
||||||
);
|
|
||||||
exc.undone = log;
|
|
||||||
exc.done = done;
|
|
||||||
exc.internal = err;
|
|
||||||
exc.failedAction = item;
|
|
||||||
exc.index = done.lenght;
|
|
||||||
throw exc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('function' === typeof callback) {
|
|
||||||
callback(null, this);
|
|
||||||
} else {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch(err) {
|
|
||||||
|
|
||||||
if ('function' === typeof callback) {
|
|
||||||
callback(err);
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkLogItem (item) {
|
get (key) {
|
||||||
|
|
||||||
if (!item.hasOwnProperty('id')) {
|
this._assertHas(key);
|
||||||
throw new UnexpectedLogItemData(
|
|
||||||
'Missing id for ' + item.action + ' object.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkFormer = () => {
|
return this._map[key];
|
||||||
if (!item.hasOwnProperty('former')) {
|
}
|
||||||
throw new UnexpectedLogItemData(
|
|
||||||
'Missing former item in ' + item.action + ' object.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkItem = () => {
|
update (keyOrObjectWithId, valueIfKey = null) {
|
||||||
if (!item.hasOwnProperty('item')) {
|
|
||||||
throw new UnexpectedLogItemData(
|
|
||||||
'Missing item in ' + item.action + ' object.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (item.action) {
|
const [key, value] = this.resolveEntry.apply(this, arguments);
|
||||||
|
|
||||||
case 'update':
|
this._assertHas(key);
|
||||||
checkItem();
|
|
||||||
case 'delete':
|
|
||||||
checkFormer();
|
|
||||||
break;
|
|
||||||
case 'insert':
|
|
||||||
checkItem();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
return this._set(key, value);
|
||||||
throw new UnrecognizedTransactionItem(
|
|
||||||
'Unrecognizes item action : "' + item.action + '.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
insert(id, item, callback = undefined) {
|
remove (keyOrObjectWithId) {
|
||||||
|
|
||||||
try {
|
const [key] = this.resolveEntry(keyOrObjectWithId, null);
|
||||||
|
|
||||||
this.checkId(id);
|
this._assertHas(key);
|
||||||
|
this._size--;
|
||||||
|
|
||||||
if (this._has(id)) {
|
return this._unset(key);
|
||||||
throw new OverrideViolation(
|
|
||||||
'An insertion must not override the pre-existing id ' + id + '. ' +
|
|
||||||
'Consider using update instead depending on your use case.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._pause) {
|
|
||||||
this._collection[id] = item;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._track.push({
|
|
||||||
action: 'insert',
|
|
||||||
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) {
|
get size () {
|
||||||
|
return this._size;
|
||||||
try {
|
|
||||||
|
|
||||||
this.checkId(id);
|
|
||||||
|
|
||||||
if (!this._has(id)) {
|
|
||||||
throw new NotFound(
|
|
||||||
'No item to update at id ' + id + '. ' +
|
|
||||||
'Consider using insert instead depending on your usecase.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const former = this._collection[id];
|
|
||||||
|
|
||||||
if (!this._pause) {
|
|
||||||
this._collection[id] = item;
|
|
||||||
} else {
|
|
||||||
this._track.push({
|
|
||||||
action: 'update',
|
|
||||||
id,
|
|
||||||
former,
|
|
||||||
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) {
|
get all () {
|
||||||
|
return this._map;
|
||||||
try {
|
|
||||||
|
|
||||||
this.checkId(id);
|
|
||||||
|
|
||||||
if (!this._has(id)) {
|
|
||||||
throw new NotFound(
|
|
||||||
'No item to remove at id' + id + '.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const former = this._collection[id];
|
|
||||||
|
|
||||||
if (!this._pause) {
|
|
||||||
delete this._collection[id];
|
|
||||||
} else {
|
|
||||||
this._track.push({
|
|
||||||
action:'delete',
|
|
||||||
id,
|
|
||||||
former
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
|
|
||||||
this.checkId(id);
|
|
||||||
|
|
||||||
if (!this._has(id)) {
|
|
||||||
throw new NotFound(
|
|
||||||
'No item found at id ' + 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 ('string' !== typeof id || id.length < 1) {
|
|
||||||
throw new IllegalId('id must be a non-empty string');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
has(id) {
|
|
||||||
|
|
||||||
return this._has(this.checkId(id));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_has(id) {
|
|
||||||
|
|
||||||
return this._collection.hasOwnProperty(id);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Collection,
|
Collection,
|
||||||
IllegalId,
|
AlreadyBuffering,
|
||||||
OverrideViolation,
|
NotBuffering,
|
||||||
NotFound,
|
IllegalAdd,
|
||||||
TransactionAlreadyOpened,
|
DuplicateEntry,
|
||||||
NoTransactionOpened,
|
NoSuchEntry
|
||||||
FailedRollback,
|
|
||||||
FailedReplay,
|
|
||||||
UnrecognizedTransactionItem,
|
|
||||||
UnexpectedLogFormat,
|
|
||||||
UnexpectedLogItemData
|
|
||||||
};
|
};
|
241
packages/xo-collection/collection.spec.js
Normal file
241
packages/xo-collection/collection.spec.js
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
var chai = require('chai');
|
||||||
|
var expect = chai.expect;
|
||||||
|
var dirtyChai = require('dirty-chai');
|
||||||
|
chai.use(dirtyChai);
|
||||||
|
var leche = require('leche');
|
||||||
|
|
||||||
|
console.log(expect);
|
||||||
|
|
||||||
|
var Collection = require('./collection');
|
||||||
|
|
||||||
|
var col = new Collection.Collection();
|
||||||
|
|
||||||
|
describe('collection', function () {
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
var fixtureValues1 = {
|
||||||
|
'primitive value': ['foo1', 1],
|
||||||
|
'array value': ['bar1', [1,2]],
|
||||||
|
'object value': ['baz1', {a:1, b:2}]
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('add', function () {
|
||||||
|
|
||||||
|
leche.withData(fixtureValues1, function (key, value) {
|
||||||
|
|
||||||
|
it('Adds a new entry to the collection', function () {
|
||||||
|
expect(col.add(key, value)).to.eql(col);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
leche.withData(fixtureValues1, function (key, value) {
|
||||||
|
|
||||||
|
it('We cannot add on a pre-existing key', function () {
|
||||||
|
expect(function () {
|
||||||
|
col.add(key, value);
|
||||||
|
}).to.throw(Collection.DuplicateEntry);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
var fixtureValues2 = {
|
||||||
|
'primitive value': ['foo2', 1],
|
||||||
|
'array value': ['bar2', [1,2]],
|
||||||
|
'object value': ['baz2', {a:1, b:2}]
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('set', function () {
|
||||||
|
|
||||||
|
leche.withData(fixtureValues2, function (key, value) {
|
||||||
|
|
||||||
|
it('Sets an entry of the collection...', function () {
|
||||||
|
expect(col.set(key, value)).to.eql(col);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
leche.withData(fixtureValues2, function (key, value) {
|
||||||
|
|
||||||
|
it('...would it already exists or not', function () {
|
||||||
|
expect(col.set(key, value)).to.eql(col);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
var fixtureUnexisting = {
|
||||||
|
'Unexisting key/entry': ['wat', 'any']
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('get', function () {
|
||||||
|
|
||||||
|
leche.withData(fixtureValues1, function (key, value) {
|
||||||
|
|
||||||
|
it('Returns the value of an entry of the collection...', function () {
|
||||||
|
expect(col.get(key)).to.eql(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
leche.withData(fixtureValues2, function (key, value) {
|
||||||
|
|
||||||
|
it('Returns the value of an entry of the collection...', function () {
|
||||||
|
expect(col.get(key)).to.eql(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
leche.withData(fixtureUnexisting, function (key) {
|
||||||
|
|
||||||
|
it('...or throws if it does not exist', function () {
|
||||||
|
expect(function () {
|
||||||
|
col.get(key);
|
||||||
|
}).to.throw(Collection.NoSuchEntry);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
var fixtureUpdates = {
|
||||||
|
'primitive value': ['foo2', 3],
|
||||||
|
'array value': ['bar2', [3,4]],
|
||||||
|
'object value': ['baz2', {c:3, d:4}]
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('update', function () {
|
||||||
|
|
||||||
|
leche.withData(fixtureUpdates, function (key, value) {
|
||||||
|
|
||||||
|
it('updates the given entries...', function () {
|
||||||
|
expect(col.update(key, value)).to.eql(col);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
leche.withData(fixtureUpdates, function (key, value) {
|
||||||
|
|
||||||
|
it('...so we can see the values we get have changed accordingly', function () {
|
||||||
|
expect(col.get(key)).to.eql(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
leche.withData(fixtureUnexisting, function (key, value) {
|
||||||
|
|
||||||
|
it('If the entry does not exist, updating throws', function () {
|
||||||
|
expect(function () {
|
||||||
|
col.update(key, value);
|
||||||
|
}).to.throw(Collection.NoSuchEntry);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
describe('remove', function () {
|
||||||
|
|
||||||
|
leche.withData(fixtureValues2, function (key) {
|
||||||
|
|
||||||
|
it('removes the given entries...', function () {
|
||||||
|
expect(col.remove(key)).to.eql(col);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
leche.withData(fixtureValues2, function (key) {
|
||||||
|
|
||||||
|
it('...so trying to get them again throws...', function () {
|
||||||
|
expect(function () {
|
||||||
|
col.get(key);
|
||||||
|
}).to.throw(Collection.NoSuchEntry);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
leche.withData(fixtureValues2, function (key) {
|
||||||
|
|
||||||
|
it('...and trying to remove them again also throws', function () {
|
||||||
|
expect(function () {
|
||||||
|
col.remove(key);
|
||||||
|
}).to.throw(Collection.NoSuchEntry);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
describe('has', function () {
|
||||||
|
|
||||||
|
leche.withData(fixtureValues1, function (key) {
|
||||||
|
|
||||||
|
it('Tells us if an entry exists...', function () {
|
||||||
|
expect(col.has(key)).to.be.true();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
leche.withData(fixtureValues2, function (key) {
|
||||||
|
|
||||||
|
it('...or not', function () {
|
||||||
|
expect(col.has(key)).to.be.false();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
describe('size', function () {
|
||||||
|
|
||||||
|
it('Reveals the number of existing entries', function () {
|
||||||
|
expect(col.size).to.eq(Object.keys(fixtureValues1).length);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
describe('all', function () {
|
||||||
|
|
||||||
|
leche.withData(fixtureValues1, function (key, value) {
|
||||||
|
|
||||||
|
it('Gives access to the internal collection...', function () {
|
||||||
|
expect(col.all).to.have.ownProperty(key);
|
||||||
|
expect(col.all[key]).to.eq(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
leche.withData(fixtureValues2, function (key) {
|
||||||
|
|
||||||
|
it('Gives access to the internal collection...', function () {
|
||||||
|
expect(col.all).to.not.have.ownProperty(key);
|
||||||
|
expect(col.all[key]).to.eq(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Gives access to the internal collection...', function () {
|
||||||
|
expect(col.size).to.eq(Object.keys(col.all).length);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -6,9 +6,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"make-error": "^0.3.0"
|
"make-error": "^0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {},
|
"devDependencies": {
|
||||||
|
"babel": "^4.7.16",
|
||||||
|
"chai": "^2.2.0",
|
||||||
|
"dirty-chai": "^1.2.0",
|
||||||
|
"leche": "^2.1.1",
|
||||||
|
"mocha": "^2.2.1"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "mocha --require babel/register *.spec.js"
|
||||||
},
|
},
|
||||||
"author": "Fabrice Marsaud <fabrice.marsaud@vates.fr>",
|
"author": "Fabrice Marsaud <fabrice.marsaud@vates.fr>",
|
||||||
"license": "aGPLv3"
|
"license": "aGPLv3"
|
||||||
|
@ -1,65 +1,81 @@
|
|||||||
import Collection from './collection';
|
import Collection from './collection';
|
||||||
|
|
||||||
var co = new Collection.Collection();
|
let col = new Collection.Collection();
|
||||||
|
|
||||||
co.begin();
|
col.add('foo', 1);
|
||||||
|
|
||||||
|
// An object with id property
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// Jouer sur le passage par référence, et la convention d'objets avec une prop ID
|
||||||
|
|
||||||
|
let obj = {id: 'bar', content: 2};
|
||||||
|
col.add(obj);
|
||||||
|
console.log(obj.get('bar'));
|
||||||
|
// > {id: 'bar', content: 2}
|
||||||
|
|
||||||
|
col.bufferChanges(true);
|
||||||
|
col.update('bar').content = 4;
|
||||||
|
// update accesses obj at bar key and marks bar as updated. No event emitted.
|
||||||
|
|
||||||
|
col.get('bar').content = 5;
|
||||||
|
obj.content = 6;
|
||||||
|
// bar is already marked as updated, so ...
|
||||||
|
|
||||||
|
col.flush();
|
||||||
|
// ...Emits an update as bar has been "updated to 6"
|
||||||
|
|
||||||
|
col.bufferChanges(true);
|
||||||
|
col.update(obj).content = 7; // Short writing without knowing ID
|
||||||
|
// WARNING, do not change ID after adding ...
|
||||||
|
|
||||||
|
col.bufferChanges(false);
|
||||||
|
col.flush();
|
||||||
|
// No event emitted ... exception thrown ?...
|
||||||
|
col.bufferChanges(true);
|
||||||
|
col.update(obj);
|
||||||
|
col.flush();
|
||||||
|
// Emits an update event as bar has been "updated to 7"
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Special cases :
|
||||||
|
let foo = {id: 'foo'};
|
||||||
|
let bar = {id: 'bar'};
|
||||||
|
col.add(foo);
|
||||||
|
|
||||||
co.insert('a', 1000);
|
|
||||||
console.log('a', co.get('a'));
|
|
||||||
co.update('a', 2000);
|
|
||||||
console.log('a', co.get('a'));
|
|
||||||
co.delete('a');
|
|
||||||
try {
|
try {
|
||||||
console.log(co.get('a'));
|
col.update(foo, bar);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
// Throws an instant exception on ID violation
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(co.commit());
|
|
||||||
|
|
||||||
console.log('=====');
|
|
||||||
|
|
||||||
co.insert('b', 100);
|
|
||||||
console.log('b', co.get('b'));
|
|
||||||
co.begin();
|
|
||||||
co.update('b', 200);
|
|
||||||
console.log('b', co.get('b'));
|
|
||||||
co.insert('c', 300);
|
|
||||||
co.update('b', 400);
|
|
||||||
console.log('b', co.get('b'), 'c', co.get('c'));
|
|
||||||
co.delete('b');
|
|
||||||
try {
|
try {
|
||||||
console.log(co.get('b'));
|
col.udpate('foo', bar);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
// Same
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
co.rollback();
|
|
||||||
|
|
||||||
console.log('b', co.get('b'));
|
|
||||||
try {
|
try {
|
||||||
console.log(co.get('c'));
|
col.update(foo).id = 'bar';
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
|
// Throws an exception at Event emission (key !== content.id)
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('=====');
|
col.bufferChanges(true);
|
||||||
|
col.remove(foo);
|
||||||
|
col.add(foo);
|
||||||
|
col.bufferChanges(false);
|
||||||
|
// Silent...(No events)
|
||||||
|
|
||||||
var coa = new Collection.Collection();
|
col.bufferChanges(true);
|
||||||
coa.insert('x', 999);
|
col.update(foo).id = 'bar';
|
||||||
coa.begin();
|
// Nothing happens
|
||||||
coa.insert('a', 100);
|
|
||||||
coa.update('a', 150);
|
|
||||||
coa.insert('b', 200);
|
|
||||||
console.log('a', coa.get('a'), 'b', coa.get('b'), 'x', coa.get('x'));
|
|
||||||
var log = coa.commit();
|
|
||||||
var cob = new Collection.Collection();
|
|
||||||
cob.replay(log);
|
|
||||||
console.log('a', cob.get('a'), 'b', cob.get('b'));
|
|
||||||
try {
|
try {
|
||||||
console.log(cob.get('x'));
|
col.flush();
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
console.error(e);
|
// Throws
|
||||||
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
process.exit(0);
|
|
||||||
|
Loading…
Reference in New Issue
Block a user