A first version without unit tests, but a gross test script
This commit is contained in:
parent
c3a01c240b
commit
cdb9c661bd
@ -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
|
||||
};
|
15
packages/xo-collection/package.json
Normal file
15
packages/xo-collection/package.json
Normal 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"
|
||||
}
|
65
packages/xo-collection/test.js
Normal file
65
packages/xo-collection/test.js
Normal 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);
|
Loading…
Reference in New Issue
Block a user