key-value-store
v0.2.1
Published
Transactional key-value store on top of any database
Downloads
7
Readme
KeyValueStore
Transactional key-value store on top of any database.
Why?
I needed a key-value store supporting ACID transactions. Since currently there are not many solutions out there, I created a layer above SQL databases like MySQL. My hope is that in the near future I will be able to replace the SQL database with something doing just what I want.
Features
- Simple API.
- If a key is an array it is serialized in a way that sort element-wise.
- Easy transactions with automatic begin/commit/rollback.
- Asynchronous functions return promises, feel free to handle them with ES7 async/await feature.
Supported databases
- Every databases supported by AnySQL.
- More to come...
Installation
npm install --save key-value-store
Usage
Simple operations
import KeyValueStore from 'key-value-store';
let store = new KeyValueStore('mysql://test@localhost/test');
async function simple() {
let key = ['users', 'abcde12345'];
// Create
await store.put(key, { firstName: 'Manu', age: 42 });
// Read
let user = await store.get(key);
// Update
await store.put(key, { firstName: 'Manu', age: 43 });
// Delete
await store.delete(key);
}
Range queries
import KeyValueStore from 'key-value-store';
let store = new KeyValueStore('mysql://test@localhost/test');
async function query() {
return await store.find({
prefix: 'users',
startAfter: 'abcde12345',
limit: 30
});
}
Transactions
import KeyValueStore from 'key-value-store';
let store = new KeyValueStore('mysql://test@localhost/test');
async function criticalOperation() {
await store.transaction(async function(transaction) {
let key = ['users', 'abcde12345'];
let user = await transaction.get(key);
user.age++;
await transaction.put(key, user);
// ...
// if no error has been thrown, the transaction is automatically committed
});
}
Basic concepts
Keys and values
Keys and values can be any kind of data.
If a key is an array it is automatically serialized in a way that sort element-wise. Thanks to that, keys can easily represent "path" (e.g.: ['users', 'abcde12345']).
Values are serialized with JSON.stringify
. If you need to customize the serialization of your objects, you can implement a toJSON()
method on them.
Promise based API
Every asynchronous operation returns a promise. It is a good idea to handle them with the great ES7 async/await feature. Since ES7 is not really there yet, you should compile your code with something like Babel.
API
new KeyValueStore(url)
Create a store for the specified URL.
import KeyValueStore from 'key-value-store';
let store = new KeyValueStore('mysql://test@localhost/test');
store.get(key, [options])
Get an item from the store.
let user = await store.get(['users', 'abcde12345']);
options
errorIfMissing
(default:true
): iftrue
, an error is thrown if the specifiedkey
is missing from the store. Iffalse
, the method returnsundefined
when thekey
is missing.
store.put(key, value, [options])
Put an item in the store.
await store.put(['users', 'abcde12345'], { firstName: 'Manu', age: 42 });
options
createIfMissing
(default:true
): iffalse
, an error is thrown if the specifiedkey
is missing from the store. This way you can ensure an "update" semantic.errorIfExists
(default:false
): iftrue
, an error is thrown if the specifiedkey
is already present in the store. This way you can ensure a "create" semantic.
store.delete(key, [options])
Delete an item from the store.
await store.delete(['users', 'abcde12345']);
options
errorIfMissing
(default:true
): iftrue
, an error is thrown if the specifiedkey
is missing from the store. Iffalse
, the method returnsfalse
when thekey
is missing.
store.getMany(keys, [options])
Get several items from the store. Return an array of objects composed of two properties: key
and value
. The order of the specified keys
is preserved in the result.
let users = await store.getMany([
['users', 'abcde12345'],
['users', 'abcde67890'],
// ...
]);
options
errorIfMissing
(default:true
): iftrue
, an error is thrown if one of the specifiedkeys
is missing from the store.returnValues
(default:true
): iffalse
, only keys found in the store are returned (novalue
property).
store.putMany(items, [options])
Not implemented yet.
store.deleteMany(keys, [options])
Not implemented yet.
store.find([options])
Fetch items matching the specified criteria. Return an array of objects composed of two properties: key
and value
. The returned items are ordered by key.
// Fetch all users
let users = await store.find({ prefix: 'users' });
// Fetch 30 users after the 'abcde12345' key
let users = await store.find({
prefix: 'users',
startAfter: 'abcde12345',
limit: 30
});
options
prefix
: fetch items with keys starting with the specified value.start
,startAfter
: fetch items with keys greater than (or equal to if you use thestart
option) the specified value.end
,endBefore
: fetch items with keys less than (or equal to if you use theend
option) the specified value.reverse
(default:false
): iftrue
, reverse the order of returned items.limit
(default:50000
): limit the number of fetched items to the specified value.returnValues
(default:true
): iffalse
, only keys found in the store are returned (novalue
property).
store.count([options])
Count items matching the specified criteria.
let users = await store.count({
prefix: 'users',
startAfter: 'abcde12345'
});
options
prefix
: count items with keys starting with the specified value.start
,startAfter
: count items with keys greater than (or equal to if you use thestart
option) the specified value.end
,endBefore
: count items with keys less than (or equal to if you use theend
option) the specified value.
store.findAndDelete([options])
Delete items matching the specified criteria. Return the number of deleted items.
let deletedItemsCount = await store.findAndDelete({
prefix: 'users',
startAfter: 'abcde12345'
});
options
prefix
: delete items with keys starting with the specified value.start
,startAfter
: delete items with keys greater than (or equal to if you use thestart
option) the specified value.end
,endBefore
: delete items with keys less than (or equal to if you use theend
option) the specified value.
store.transaction(fun)
Run the specified function inside a transaction. The function receives a transaction handler as first argument. This handler should be used as a replacement of the store for every operation made during the execution of the transaction. If any error occurs, the transaction is aborted and the store is automatically rolled back.
// Increment a counter
await store.transaction(async function(transaction) {
let value = await transaction.get('counter');
value++;
await transaction.put('counter', value);
});
store.close()
Close all connections to the store.
License
MIT