opendash
Version:
open.DASH data visualization framework
74 lines (59 loc) • 1.72 kB
text/typescript
import produce from "immer";
type Selector<T, ReturnType> = (state: T) => ReturnType;
type SubscriptionCallback<T> = () => void;
type ProducerCallback<T> = (currentState: T) => T | void;
export class Store<T> {
private _state: T;
private watcher = new Set<SubscriptionCallback<T>>();
private selectorCache = new Map<Selector<T, any>, any>();
constructor(initialState: T) {
this._state = initialState;
}
state(): T {
return this._state;
}
select<ReturnType>(selector: Selector<T, ReturnType>): ReturnType {
return selector(this._state);
}
update(callbackOrState: T | ProducerCallback<T>): void {
if (typeof callbackOrState === "function") {
this._state = produce(
this._state,
callbackOrState as ProducerCallback<T>
) as T;
} else {
this._state = callbackOrState;
}
this.watcher.forEach(callback => {
try {
callback();
} catch (error) {
console.error("Error in Store subscription callback:", error);
}
});
}
subscribe(callback: SubscriptionCallback<T>) {
this.watcher.add(callback);
return () => {
this.watcher.delete(callback);
};
}
subscribeSelection(
selector: Selector<T, any>,
callback: SubscriptionCallback<T>
) {
const selectorCallback = () => {
const cache = this.selectorCache.get(selector);
const selection = selector(this._state);
if (!cache || cache !== selection) {
this.selectorCache.set(selector, selection);
callback();
}
};
this.watcher.add(selectorCallback);
return () => {
this.watcher.delete(selectorCallback);
this.selectorCache.delete(selector);
};
}
}