Skip to content
This repository has been archived by the owner on Dec 22, 2020. It is now read-only.

Latest commit

 

History

History
254 lines (169 loc) · 7.48 KB

README.md

File metadata and controls

254 lines (169 loc) · 7.48 KB

KeyValueStore Build Status

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): if true, an error is thrown if the specified key is missing from the store. If false, the method returns undefined when the key 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): if false, an error is thrown if the specified key is missing from the store. This way you can ensure an "update" semantic.
  • errorIfExists (default: false): if true, an error is thrown if the specified key 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): if true, an error is thrown if the specified key is missing from the store. If false, the method returns false when the key 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): if true, an error is thrown if one of the specified keys is missing from the store.
  • returnValues (default: true): if false, only keys found in the store are returned (no value 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 the start option) the specified value.
  • end, endBefore: fetch items with keys less than (or equal to if you use the end option) the specified value.
  • reverse (default: false): if true, reverse the order of returned items.
  • limit (default: 50000): limit the number of fetched items to the specified value.
  • returnValues (default: true): if false, only keys found in the store are returned (no value 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 the start option) the specified value.
  • end, endBefore: count items with keys less than (or equal to if you use the end 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 the start option) the specified value.
  • end, endBefore: delete items with keys less than (or equal to if you use the end 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