A first version without unit tests, but a gross test script

This commit is contained in:
Fabrice Marsaud 2015-03-27 15:33:21 +01:00
parent c3a01c240b
commit cdb9c661bd
3 changed files with 346 additions and 32 deletions

View File

@ -1,17 +1,21 @@
import makeError from 'make-error';
var IllegalKey = makeError('IllegalKey');
var IllegalId = makeError('IllegalId');
var OverrideViolation = makeError('OverrideViolation');
var NotFound = makeError('NotFound');
var TransactionAlreadyOpened = makeError('TransactionAlreadyOpened');
var NoTransactionOpened = makeError('NoTransactionOpened');
var FailedRollback = makeError('FailedRollback');
var FailedReplay = makeError('FailedReplay');
var UnrecognizedTransactionItem = makeError('UnrecognizedTransactionItem');
var UnexpectedLogFormat = makeError('UnexpectedLogFormat');
var UnexpectedLogItemData = makeError('UnexpectedLogItemData');
class Collection {
constructor(strictMode = true) {
constructor() {
this._collection = {};
this._strictMode = strictMode;
this._transaction = null;
}
@ -20,11 +24,11 @@ class Collection {
try {
if (this._strictMode && null !== this._transaction) {
if (null !== this._transaction) {
throw new TransactionAlreadyOpened('A transaction is already opened');
}
this._transaction = this._transaction || [];
this._transaction = [];
if ('function' === typeof callback) {
callback(null, this);
@ -49,11 +53,11 @@ class Collection {
try {
if (this._strictMode && null === this._transaction) {
if (null === this._transaction) {
throw new NoTransactionOpened('No opened transaction to commit.');
}
let transactionLog = this._transaction || [];
let transactionLog = this._transaction;
this._transaction = null;
if ('function' === typeof callback) {
@ -75,20 +79,226 @@ class Collection {
}
rollback(callback = undefined) {
try {
if (null === this._transaction) {
throw new NoTransactionOpened('No opened transaction to rollback.');
}
let log = this._transaction;
this._transaction = null;
return this._rollback(log, callback);
} catch(err) {
if ('function' === typeof callback) {
callback(err);
} else {
throw err;
}
}
}
_rollback(log, callback = undefined) {
try {
if (!Array.isArray(log)) {
throw new UnexpectedLogFormat('A transaction log must be an Array.');
}
let item;
let done = [];
while(item = log.pop()) {
try {
this.checkLogItem(item);
switch (item.action) {
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) {
let 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;
}
}
}
replay(log, callback = undefined) {
try {
if (!Array.isArray(log)) {
throw new UnexpectedLogFormat('A transaction log must be an Array.');
}
let item;
let 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) {
let 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) {
if (!item.hasOwnProperty('id')) {
throw new UnexpectedLogItemData(
'Missing id for ' + item.action + ' object.'
);
}
let checkFormer = () => {
if (!item.hasOwnProperty('former')) {
throw new UnexpectedLogItemData(
'Missing former item in ' + item.action + ' object.'
);
}
};
let checkItem = () => {
if (!item.hasOwnProperty('item')) {
throw new UnexpectedLogItemData(
'Missing item in ' + item.action + ' object.'
);
}
};
switch (item.action) {
case 'update':
checkItem();
case 'delete':
checkFormer();
break;
case 'insert':
checkItem();
break;
default:
throw new UnrecognizedTransactionItem(
'Unrecognizes item action : "' + item.action + '.'
);
}
}
insert(id, item, callback = undefined) {
try {
id = this.checkId(id);
this.checkId(id);
if (this._strictMode && this._has(id)) {
if (this._has(id)) {
throw new OverrideViolation(
'An insertion must not override the pre-existing key ' + id + '. ' +
'An insertion must not override the pre-existing id ' + id + '. ' +
'Consider using update instead depending on your use case.'
);
}
this._collection[id] = item;
if (this._transaction) {
this._transaction.push({
action: 'insert',
id,
item
});
}
if ('function' === typeof callback) {
callback(null, this);
@ -112,16 +322,28 @@ class Collection {
try {
id = this.checkId(id);
this.checkId(id);
if (this._strictMode && !this._has(id)) {
if (!this._has(id)) {
throw new NotFound(
'No item to update at this key ' + id + '. ' +
'No item to update at id ' + id + '. ' +
'Consider using insert instead depending on your usecase.'
);
}
let former = this._collection[id];
this._collection[id] = item;
if (this._transaction) {
this._transaction.push({
action: 'update',
id,
former,
item
});
}
if ('function' === typeof callback) {
callback(null, this);
@ -145,15 +367,25 @@ class Collection {
try {
id = this.checkId(id);
this.checkId(id);
if (this._strictMode && !this._has(id)) {
if (!this._has(id)) {
throw new NotFound(
'No item to remove at key' + id + '.'
'No item to remove at id' + id + '.'
);
}
let former = this._collection[id];
delete this._collection[id];
if (this._transaction) {
this._transaction.push({
action:'delete',
id,
former
});
}
if ('function' === typeof callback) {
callback(null, this);
@ -177,11 +409,11 @@ class Collection {
try {
id = this.checkId(id);
this.checkId(id);
if (this._strictMode && !this._has(id)) {
if (!this._has(id)) {
throw new NotFound(
'No item found at key ' + id
'No item found at id ' + id
);
}
@ -205,15 +437,8 @@ class Collection {
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');
if ('string' !== typeof id || id.length < 1) {
throw new IllegalId('id must be a non-empty string');
}
}
@ -232,7 +457,16 @@ class Collection {
}
export var Collection = Collection;
export var IllegalKey = IllegalKey;
export var OverrideViolation = OverrideViolation;
export var NotFound = NotFound;
export default {
Collection,
IllegalId,
OverrideViolation,
NotFound,
TransactionAlreadyOpened,
NoTransactionOpened,
FailedRollback,
FailedReplay,
UnrecognizedTransactionItem,
UnexpectedLogFormat,
UnexpectedLogItemData
};

View File

@ -0,0 +1,15 @@
{
"name": "xo-collection",
"version": "0.0.0",
"description": "A generice batch collection attempt",
"main": "collection.js",
"dependencies": {
"make-error": "^0.3.0"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Fabrice Marsaud <fabrice.marsaud@vates.fr>",
"license": "aGPLv3"
}

View File

@ -0,0 +1,65 @@
import xoCollection from './collection';
var co = new xoCollection.Collection();
co.begin();
co.insert('a', 1000);
console.log('a', co.get('a'));
co.update('a', 2000);
console.log('a', co.get('a'));
co.delete('a');
try {
console.log(co.get('a'));
} catch(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 {
console.log(co.get('b'));
} catch(e) {
console.error(e);
}
co.rollback();
console.log('b', co.get('b'));
try {
console.log(co.get('c'));
} catch(e) {
console.error(e);
}
console.log('=====');
var coa = new xoCollection.Collection();
coa.insert('x', 999);
coa.begin();
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 xoCollection.Collection();
cob.replay(log);
console.log('a', cob.get('a'), 'b', cob.get('b'));
try {
console.log(cob.get('x'));
} catch(e) {
console.error(e);
}
process.exit(0);