README
Store
Indexed, resource-based, evented data storage.
Somewhere between Backbone.Models/Collections and Redux reducers without ORM.
Flatten, index and subscribe to your data through a minimal interface.
Changes:
- 0.8.0
- remove
Resource#create()
andResource#upsert()
in favour ofResource#store()
- remove
Resource#_add()
(private) Resource#all()
now provides all arguments fromArray#map()
"create"
event payload on aResource
is: [instance
,index
]
- remove
Quick start:
// create a data space for your app
const dataSpace = sktwrx_store.namespace('organisation.application');
// create a singleton resource collection with default Resouce#idAttribute 'id'
const users = dataSpace.resource('users');
// tell resource which keys to index on the instances to look up their positions
users.index('name', 'age');
/**
* optional - tell the resource what namespace to use for indexing on an Instance
* this is useful when an API returns something similar to this:
*
* {
* href: '/ai/v1/users/5/',
* meta: {
* type: 'model',
* ...
* },
* body: {
* id: 5,
* name: 'bob',
* age: 5,
* ...
* }
* }
*
*/
users.body('body');
// specifying default schema with values when creating an instance
users.defaults({
name: '',
age: 0,
dickMoves: Array,
...
});
// store resource (collection) specific attributes - not indexed
users.set('limit', 20);
users.change({ page: 1, skip: 2 });
// store an instance
let bob = users.store({ id: 1, name: 'bob', age: 45 });
// update bill
bill.update({ age: 33 });
//or
users.update(bill, { age: 33 });
// reading properties from bill
bill.get('age'); // 33
// or get all props
bill.getProps():
// finding an item in the store - returns a Query
users.find('name', 'bill').first().getProps(); // { name: 'bill', ... }
// or return a query with instances that match 'name' or 'age' with their values.
users.query({ name: 'bill', age: 24 });
module
require('sktwrx.store');
or import { namespace, Resource, Instance } from 'sktwrx.store';
or window.sktwrx_store
.
sktwrx_store.Namespace
Namespaces provide a way to group Resources into singleton data-spaces.
Creating and accessing a namespace:
import { namespace, Namespace } from 'sktwrx.store';
const myNS = namespace('myOrg.MyApp');
// or
const myOtherNS = new Namespace('myOrg.MyApp');
sktwrx_store.Resource
Resources store data items (Instances) in an ordered and linked fashion. It also contains the Index allow easy and fast data-access/lookup.
Creating and accessing a store via a namespace:
const Users = myNS.resource('users');
Creating and accessing a store globally:
import { resource } from 'sktwrx.store';
const Users = resource('users');
Resource indexing
By default the Resource will try to index an item by its Resource#idAttribute
property which defaults to "id"
.
This id attribute is used to determine if an item already exists or not. You can specify your own id field name when you create a resource:
import { resource } from 'sktwrx.store';
const Users = resource('users', '_id'); // MongoDB ObjectId
Or you can specify it on a Namespace to pass it as a default when creating a Resource within the namespace:
const { namespace } from 'sktwrx.store';
const NS = namespace('org.app');
NS.id('_id');
Once the Resource is created you can specify what attributes need indexing when storing/updating an item:
Add as many fields as you like, because if these fields are not specified the Resource will fall back to iterating through all items in the list which has major performance downsides on large-scale UIs (at least to my experience).
Users.index('name', 'age', 'dob', ...);
Or you can specify it on a Namespace to pass it as a default when creating a Resource within the namespace:
const { namespace } from 'sktwrx.store';
const NS = namespace('org.app');
NS.index('name', 'age', 'dob', ...);
You may find yourself in a situation where you are dealing with nested, more advanced data-structure containing metadata near your resource. You can specify which field (only one) to use as the resource item's body:
Users.body('body');
Or you can specify it on a Namespace to pass it as a default when creating a Resource within the namespace:
const { namespace } from 'sktwrx.store';
const NS = namespace('org.app');
NS.body('body');
Additionally, you can specify default values when storing an item
Users.defaults({ name: '', age: 0, friends: Array, ... });
Storing resource instances
Storing (creating) an item will add it to the end of the list:
const keira = Users.store({ name: 'keira' });
You can store instances at a specified position:
const bob = Users.store({ id: 3, name: 'bob' }, 2); // insert as the 3rd element
Creating an Instance will publish the create
event on the Resource.
Inserting before/after an instance
Users.store([
{ id: 0, name: 'bill' },
{ id: 1, name: 'bob' },
{ id: 2, name: 'betty' },
]);
const bob = Users.at(1);
const amelia = Users.before(bob, { id: 3, name: 'amelia' });
const anastasia = Users.after(bob, { id: 4, name: 'anastasia' });
Users.all(i => console.log(i.get('name')); // [ 'bill', 'amelia', 'bob', 'anastasia', 'betty' ];
Getting the position(s) of a key and value
You can return the position(s) if you want to find where to insert new items:
Users.position('name', 'bob'); // [3, 7, ...]
Updating resource instances
When updating an item, the index will be rebuilt around the item.
bob.update({ age: 33 });
// or
Users.update(bob, { age: 33 });
Updating an Instance will publish the update
event on the Instance first and then on the Resource.
Removing resource instances
bob.remove();
// or
Users.remove(bob);
Updating an Instance will publish the remove
event on the Resource.
Querying the Resource
There are 2 ways of finding resource items: find or query.
Find works for only one key - value combination:
// return all resource instances that have the name 'bob'
Users.find('name', 'bob');
Query will look up based on the provided object:
// return items that have either the name 'bob' or the age 45
Users.query({
name: 'bob',
age: 45
});
Additionally, you might want to be able to query the Resource for the available values first (for example available categories of a Resource)
import { resource } from 'sktwrx.store';
const Books = resource('books');
Books.index('category');
Books.store({ id: 0, category: 'scary' });
Books.store({ id: 1, category: 'romantic' });
Books.store({ id: 2, category: 'drama' });
Books.store({ id: 3, category: 'drama' });
Books.store({ id: 4, category: 'scary' });
Books.values('category'); // [ 'scary', 'romantic', 'drama' ]
Resetting the Resource
Cleans up the Resource, removes resource instances and destructs the Resource index.
Users.reset();
Resetting the Resource will publish the reset
event on the Resource.
Storing list specific data
Resources might have other data describing them, such as paging or page size. You can store these keys and values on the Resource:
Users.set('page', 5);
// or
Users.change({ page: 5, limit: 20 });
Setting/changing on a Resource will publish the change
event on the Resource.
sktwrx_store.Instance
Instances are evented resource items
const bob = Users.store({ id: 0, name: 'bob', age: 45 });
Changing an Instance
There are 2 ways of modifying an Instance: changing it or updating it.
Change is registering the key-values in a different lookup but doesn't change the underlying data source.
bob.set('age', 25);
// or
bob.change({ age: 25 });
Update will take all changes from before and merge it together with the given updates then merge it onto the underlying data source.
bob.update({ age: 33 });
Events
Resource: create
, change
, update
, remove
, reset
Instance: change
, update
Event constants:
import { events } from 'sktwrx.store;
events.CREATE // 'create'
events.CHANGE // 'change'
events.UPDATE // 'update'
events.REMOVE // 'remove'
events.RESET // 'reset'
Subscribing to events: bob.subscribe('update', ... );
Unsubscribe from events: Users.unsubscribe('update', ... );