import { inject, BindingEngine, Disposable } from 'aurelia-framework'; import { isString, isObject, get, isThenable, isFunction } from './utils'; export type ActionCreator = (...args: any[]) => Dispatchable; export type StoreSelector = (state: S) => T; export type Thunk = (dispatch: (action: Dispatchable) => T, getState: () => S) => T; export type Dispatchable = T|Promise|Thunk; export interface ReduxPluginConfig { store?: Redux.Store; async?: boolean; } @inject(BindingEngine) export class Store { static instance: Store; static _queue: Function[] = []; private store: Redux.Store; private _changeId: number = 0; constructor( private bindingEngine: BindingEngine, private config: ReduxPluginConfig ) { Store.instance = this; this.config = Object.assign({ async: false }, this.config); if (this.config.store) { this.provideStore(this.config.store); } } get changeId(): number { return this._changeId; } provideStore(store: Redux.Store): void { this.store = store; while (Store._queue.length) { const observerHandler = Store._queue.shift(); if (observerHandler) { observerHandler(); } } } dispatch(action: Dispatchable): T|Promise { this._changeId++; if (this.config.async) { if (isThenable(action)) { return action.then(this.dispatch.bind(this)); } if (isFunction(action)) { return (>action)(this.dispatch.bind(this), this.store.getState.bind(this.store)); } } return this.store.dispatch(action); } getState(): S { return this.store.getState(); } subscribe(listener: () => void): Redux.Unsubscribe { return this.store.subscribe(listener); } replaceReducer(nextReducer: Redux.Reducer): void { this.store.replaceReducer(nextReducer); } observe(target, property, handler): Disposable { return this.bindingEngine.propertyObserver(target, property).subscribe(handler); } select(selector: string|string[]|StoreSelector, instance?: any, options: { invoke?: boolean } = {}): T { if (isString(selector)) { if (options.invoke) { const instanceSelector = get(instance, selector); if (isFunction(instanceSelector)) { return instanceSelector.call(instance, this.getState()); } } return get(this.getState(), selector); } else if (Array.isArray(selector)) { return get(this.getState(), selector); } return (>selector)(this.getState()); } static queue(fn: Function): void { if (this.instance) { fn(); } else { this._queue.push(fn); } } }