_ _ _
| |__ ___ ___ | | _____| |_ ___ _ __ ___
| '_ \ / _ \ / _ \| |/ / __| __/ _ \| '__/ _ \
| |_) | (_) | (_) | <\__ \ || (_) | | | __/
|_.__/ \___/ \___/|_|\_\___/\__\___/|_| \___|
A small data framework for collaborative list editing using Azure Blob Storage and Redis.
What is different than traditional CRUD + cache?
- Content and summary
- Summary is computed from content, via a summarizer function
- When content is updated, only summary is broadcasted via Redis to other nodes
- List will fetch all summaries, without content
- List is cheap,
O(1)
- Update is done thru lock-update-unlock pattern with updater function
- Updater function can be programmed as optimistic or pessimistic concurrency
- We believe this model makes concurrency issues a little bit easier to handle
bookstore
is designed to be exposed over Web Socket and Server-Sent Events.
How to use
For production build, npm install bookstore
. For development build, npm install bookstore@master
.
For peer dependencies, you will also need npm install azure-storage@2 redis
.
const { createBlobService } = require('azure-storage');
const { createClient } = require('redis');
const updateIn = require('simple-update-in');
const createBook, { createPubSubUsingRedis, createStorageUsingAzureStorage } = require('bookstore');
const blobService = createBlobService(
process.env.AZURE_STORAGE_ACCOUNT,
process.env.AZURE_STORAGE_ACCESS_KEY
);
const publishRedis = createClient();
const subscribeRedis = publishRedis.duplicate();
const book = createBook(
({ x, y }) => ({ sum: x + y }),
{
...createPubSubUsingRedis(publishRedis, subscribeRedis, 'blob-container-name'),
...createStorageUsingAzureStorage(blobService, 'blob-container-name')
}
);
await book.create('page-0', { x: 1, y: 2 });
await book.list();
await book.get('page-0');
await book.update('page-0', content => updateIn(content, ['x'], () => 3));
book.subscribe(({ id, summary }) => {
console.log(id);
console.log(summary);
});
await book.list();
await book.del('page-0');
Peer requirements
Instead of using Blob via Azure Storage and Pub-sub via Redis, you can also use other services as long as they met the requirements:
- Storage
create(id, content, summary)
: Create a new blob with content and summarydel(id)
: Delete a blobget(id)
: Read a blob contentlist()
: List all blob summaries, without reading the actual contentupdate(id, updater)
: Update an existing blob via an updater function, using lock to prevent dirty read
updater: ({ content, summary }) => ({ content, summary })
- Pub-sub
publish(content)
: Publish to a predefined topicsubscribe(callback: content => void): () => void
: Subscribe to a predefined topic via callback, will return a function for unsubscribe
callback
will be called if content is updated (via Object.is
) and summary has kept the same
Contributions
Like us? Star us.
Want to make it better? File us an issue.
Don't like something you see? Submit a pull request.