fluorine-orchestra
Version:
A data orchestration layer for Fluorine
205 lines (160 loc) • 5.06 kB
JavaScript
import invariant from 'invariant'
import { BehaviorSubject } from 'rxjs'
import toMap from './util/toMap'
import createReducerForStore from './util/createReducerForStore'
import { Collection } from './Collection'
import {
Iterable,
OrderedMap,
Set
} from 'immutable'
import {
STORE_INSERT,
STORE_REMOVE,
STORE_FILTER,
STORE_UPDATE
} from './constants/StoreConstants'
const missing = Symbol('missingSubject')
export class Store {
static isStore(obj) {
return typeof obj === 'object' && obj instanceof Store
}
constructor(identifier, opts = {}) {
invariant(typeof identifier === 'string', 'Store: `identifier` is expected to be a string.')
this[missing] = {
subject: new BehaviorSubject(),
cache: {}
}
this.identifier = identifier
this.opts = opts
this.dependencies = {}
this.hooks = {}
this.insert = this.insert.bind(this)
this.remove = this.remove.bind(this)
this.filter = this.filter.bind(this)
this.update = this.update.bind(this)
}
pre(transformer) {
invariant(typeof transformer === 'function', 'Store: `transformer` is expected to be a function.')
this.hooks.pre = transformer
return this
}
dependsOn(identifier, ...args) {
const { dependencies } = this
invariant(typeof identifier === 'string', 'Store: `identifier` is expected to be a string.')
invariant(!dependencies[identifier], `Store: There is already an existing dependency to the store \`${identifier}\`.`)
invariant(args.length <= 2 && args.length >= 1, 'Store: `dependsOn` is expected to receive `setter` or `getter` and `setter`.')
let dependency
if (args.length === 1) {
const [ setter ] = args
invariant(typeof setter === 'function', 'Store: `setter` is expected to be a function.')
dependency = { identifier, setter }
} else {
const [ getter, setter ] = args
invariant(typeof getter === 'function', 'Store: `getter` is expected to be a function.')
invariant(typeof setter === 'function', 'Store: `setter` is expected to be a function.')
dependency = { identifier, getter, setter }
}
dependencies[identifier] = dependency
return this
}
post(transformer) {
invariant(typeof transformer === 'function', 'Store: `transformer` is expected to be a function.')
this.hooks.post = transformer
return this
}
observeMissing() {
return this[missing].subject.asObservable()
}
useCollection(col) {
invariant(col && col.prototype instanceof Collection,
'Store: `col` is expected to be a Collection.')
this.collection = col
return this
}
getIdentifier() {
return this.identifier
}
getPre() {
return this.hooks.pre || (x => toMap(x))
}
getDependencies() {
return this.dependencies
}
getPost() {
return this.hooks.post
}
getReducer() {
if (this.reducer && typeof this.reducer === 'function') {
return this.reducer
}
this.reducer = createReducerForStore(this)
return this.reducer
}
createCollection() {
const CollectionClass = this.collection || Collection
const res = new CollectionClass()
res.dependencies = Object.keys(this.dependencies)
return res
}
_missing(ids, identifier = null) {
const { cache, subject } = this[missing]
invariant(
Set.isSet(ids) && ids.every(x => typeof x === 'string'),
'Store: `ids` is expected to be an Immutable.Set of ids (string).')
cache[identifier] = ids
const missingIds = Object
.keys(cache)
.reduce((acc, key) => {
const _set = cache[key]
return _set ? acc.union(_set) : acc
}, new Set())
subject.next(missingIds)
}
insert(payload, groupId) {
invariant(payload && (
Iterable.isKeyed(payload) ||
(Iterable.isIterable(payload) && payload.every && payload.every(Iterable.isKeyed))
), 'Store: `payload` is expected to be a keyed iterable or an iterable containing keyed iterables.')
const { identifier } = this
return {
type: STORE_INSERT,
identifier,
payload,
groupId
}
}
remove(payload) {
invariant(payload && (
typeof payload === 'string' ||
(Iterable.isKeyed(payload) && typeof payload.get('id') === 'string')
), 'Store: `payload` is expected to be an id or a keyed iterable containing an id.')
const { identifier } = this
return {
type: STORE_REMOVE,
identifier,
payload
}
}
filter(selector) {
invariant(typeof selector === 'function', 'Store: `selector` is expected to be a function.')
const { identifier } = this
return {
type: STORE_FILTER,
identifier,
selector
}
}
update(selector) {
invariant(typeof selector === 'function', 'Store: `selector` is expected to be a function.')
const { identifier } = this
return {
type: STORE_UPDATE,
identifier,
selector
}
}
}
export default function createStore(identifier, opts) {
return new Store(identifier, opts)
}