UNPKG

immunid

Version:

Opinionated relational data storage

264 lines (198 loc) 5.83 kB
/* @flow weak */ var compact = require('./utils/compact'); var inflector = require('./utils/inflector'); var merge = require('./utils/merge'); var without = require('./utils/without'); var Events = require('./Events'); var Model = require('./Model'); var Promise = require('es6-promise').Promise; var NullAdapter = require('./NullAdapter'); var ACTIONS = { create: 'create', destroy: 'destroy', fetch: 'fetch', reload: 'reload', update: 'update' }; var Store = function(adapter, mapping) { this.adapter = adapter || new NullAdapter(); this.mapping = mapping || {}; this.buckets = {}; }; merge(Store.prototype, Events, { build: function(namespace, object) { var model = vivify(this, namespace, object); return model; }, add: function(namespace, object) { var bucket = getBucket(this, namespace), model = this.get(namespace, object.id); if (!!model) { model.set(object); } else { model = vivify(this, namespace, object) bucket[object.id] = model; } return model; }, find: function(namespace, id) { if (id === undefined) { return findAll(this, namespace); } else { return findOne(this, namespace, id); } }, get: function(namespace, id) { var bucket = getBucket(this, namespace); return bucket[id]; }, all: function(namespace) { var bucket = getBucket(this, namespace); return Object.keys(bucket).map(function(key) { return bucket[key]; }); }, some: function(namespace, ids) { var bucket = getBucket(this, namespace); if (ids && ids.length) { return compact(ids.map(function(id) { return bucket[id]; })); } else { return []; } }, where: function(namespace, props) { var bucket = getBucket(this, namespace); return collectMatches(bucket, props); }, count: function(namespace) { var bucket = getBucket(this, namespace); return Object.keys(bucket).length; }, clear: function(namespace) { if (namespace === undefined) { this.buckets = {}; } else { this.buckets = without(this.buckets, namespace); } return this; }, parse: function(payload, options) { parsePayload(this, payload, options); return self; }, remove: function(model) { var namespace = model.namespace, bucket = getBucket(this, namespace), prop = String(model.id); this.buckets[namespace] = without(bucket, prop); return this; }, destroy: function(model) { var self = this; this.remove(model); return this.adapter.destroy(model) .then(function() { emitEvent(self, model.namespace, ACTIONS.destroy, model); }); }, reload: function(model) { var self = this; return readModel(self, model).then(function(response) { self.parse(response.body, { action: ACTIONS.reload }); return self.get(model.namespace, model.id); }); }, save: function(model) { var self = this, method = model.id ? ACTIONS.update : ACTIONS.create; return self.adapter[method](model).then(function(response) { self.parse(response.body, { action: method }); return self.get( model.namespace, scrapeId(model.namespace, response.body) ); }); } }); function collectMatches(bucket, props) { return Object.keys(bucket).reduce(function(memo, key) { var match = true, model = bucket[key]; for (var prop in props) { match = match && model.get(prop) === props[prop]; } if (match) { memo.push(model); } return memo; }, []); } function collectAddedModels(store, namespace, values) { return values.reduce(function(memo, value) { memo.push(store.add(namespace, value)); return memo; }, []); } function parsePayload(store, payload, options) { var models, namespace, values; Object.keys(payload).forEach(function(key) { namespace = key; values = payload[key]; if (!Array.isArray(values)) { namespace = inflector.pluralize(key); values = [values]; } models = collectAddedModels(store, namespace, values); if (options && options.action) { emitEvent(store, namespace, options.action, models); } }); } function getBucket(store, namespace) { var bucket = store.buckets[namespace] = store.buckets[namespace] || {}; return bucket; } function resolve(mapping, namespace) { var key = inflector.capitalize(inflector.singularize(namespace)); return mapping[key] || Model; } function vivify(store, namespace, object) { var Constructor = resolve(store.mapping, namespace); return new Constructor(object, { namespace: namespace, store: store }); } function findAll(store, namespace) { var ModelClass = resolve(store.mapping, namespace); return readModel(store, ModelClass).then(function(response) { store.parse(response.body, { action: ACTIONS.fetch }); return store.all(namespace); }); } function findOne(store, namespace, id) { var bucket = getBucket(store, namespace), model = bucket[id], promise; if (model) { promise = Promise.resolve(model); } else { model = vivify(store, namespace, { id: id }); promise = readModel(store, model).then(function(response) { store.parse(response.body, { action: ACTIONS.fetch }); return bucket[id]; }); } return promise; } function scrapeId(key, object) { var namespace = inflector.singularize(key); return object[namespace]['id']; } function readModel(store, model) { return store.adapter.read(model); } function emitEvent(emitter, namespace, action, payload) { var name = [namespace, inflector.preterite(action)].join(':'); emitter.emit(name, payload); } module.exports = Store;