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';
|
import makeError from 'make-error';
|
||||||
|
|
||||||
var IllegalKey = makeError('IllegalKey');
|
var IllegalId = makeError('IllegalId');
|
||||||
var OverrideViolation = makeError('OverrideViolation');
|
var OverrideViolation = makeError('OverrideViolation');
|
||||||
var NotFound = makeError('NotFound');
|
var NotFound = makeError('NotFound');
|
||||||
var TransactionAlreadyOpened = makeError('TransactionAlreadyOpened');
|
var TransactionAlreadyOpened = makeError('TransactionAlreadyOpened');
|
||||||
var NoTransactionOpened = makeError('NoTransactionOpened');
|
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 {
|
class Collection {
|
||||||
|
|
||||||
constructor(strictMode = true) {
|
constructor() {
|
||||||
|
|
||||||
this._collection = {};
|
this._collection = {};
|
||||||
this._strictMode = strictMode;
|
|
||||||
this._transaction = null;
|
this._transaction = null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -20,11 +24,11 @@ class Collection {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (this._strictMode && null !== this._transaction) {
|
if (null !== this._transaction) {
|
||||||
throw new TransactionAlreadyOpened('A transaction is already opened');
|
throw new TransactionAlreadyOpened('A transaction is already opened');
|
||||||
}
|
}
|
||||||
|
|
||||||
this._transaction = this._transaction || [];
|
this._transaction = [];
|
||||||
|
|
||||||
if ('function' === typeof callback) {
|
if ('function' === typeof callback) {
|
||||||
callback(null, this);
|
callback(null, this);
|
||||||
@ -49,11 +53,11 @@ class Collection {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (this._strictMode && null === this._transaction) {
|
if (null === this._transaction) {
|
||||||
throw new NoTransactionOpened('No opened transaction to commit.');
|
throw new NoTransactionOpened('No opened transaction to commit.');
|
||||||
}
|
}
|
||||||
|
|
||||||
let transactionLog = this._transaction || [];
|
let transactionLog = this._transaction;
|
||||||
this._transaction = null;
|
this._transaction = null;
|
||||||
|
|
||||||
if ('function' === typeof callback) {
|
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) {
|
insert(id, item, callback = undefined) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
id = this.checkId(id);
|
this.checkId(id);
|
||||||
|
|
||||||
if (this._strictMode && this._has(id)) {
|
if (this._has(id)) {
|
||||||
throw new OverrideViolation(
|
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.'
|
'Consider using update instead depending on your use case.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._collection[id] = item;
|
this._collection[id] = item;
|
||||||
|
|
||||||
|
if (this._transaction) {
|
||||||
|
this._transaction.push({
|
||||||
|
action: 'insert',
|
||||||
|
id,
|
||||||
|
item
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if ('function' === typeof callback) {
|
if ('function' === typeof callback) {
|
||||||
callback(null, this);
|
callback(null, this);
|
||||||
@ -112,16 +322,28 @@ class Collection {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
id = this.checkId(id);
|
this.checkId(id);
|
||||||
|
|
||||||
if (this._strictMode && !this._has(id)) {
|
if (!this._has(id)) {
|
||||||
throw new NotFound(
|
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.'
|
'Consider using insert instead depending on your usecase.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let former = this._collection[id];
|
||||||
|
|
||||||
this._collection[id] = item;
|
this._collection[id] = item;
|
||||||
|
|
||||||
|
if (this._transaction) {
|
||||||
|
this._transaction.push({
|
||||||
|
action: 'update',
|
||||||
|
id,
|
||||||
|
former,
|
||||||
|
item
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if ('function' === typeof callback) {
|
if ('function' === typeof callback) {
|
||||||
callback(null, this);
|
callback(null, this);
|
||||||
@ -145,15 +367,25 @@ class Collection {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
id = this.checkId(id);
|
this.checkId(id);
|
||||||
|
|
||||||
if (this._strictMode && !this._has(id)) {
|
if (!this._has(id)) {
|
||||||
throw new NotFound(
|
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];
|
delete this._collection[id];
|
||||||
|
|
||||||
|
if (this._transaction) {
|
||||||
|
this._transaction.push({
|
||||||
|
action:'delete',
|
||||||
|
id,
|
||||||
|
former
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if ('function' === typeof callback) {
|
if ('function' === typeof callback) {
|
||||||
callback(null, this);
|
callback(null, this);
|
||||||
@ -177,11 +409,11 @@ class Collection {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
id = this.checkId(id);
|
this.checkId(id);
|
||||||
|
|
||||||
if (this._strictMode && !this._has(id)) {
|
if (!this._has(id)) {
|
||||||
throw new NotFound(
|
throw new NotFound(
|
||||||
'No item found at key ' + id
|
'No item found at id ' + id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,15 +437,8 @@ class Collection {
|
|||||||
|
|
||||||
checkId (id) {
|
checkId (id) {
|
||||||
|
|
||||||
if (undefined === id || null === id) {
|
if ('string' !== typeof id || id.length < 1) {
|
||||||
throw new IllegalKey('Illegal key : ' + id);
|
throw new IllegalId('id must be a non-empty string');
|
||||||
}
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -232,7 +457,16 @@ class Collection {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export var Collection = Collection;
|
export default {
|
||||||
export var IllegalKey = IllegalKey;
|
Collection,
|
||||||
export var OverrideViolation = OverrideViolation;
|
IllegalId,
|
||||||
export var NotFound = NotFound;
|
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