UNPKG

opendash

Version:

open.DASH data visualization framework

288 lines (222 loc) 7.69 kB
import produce from "immer"; import { AppInterface, ServicesInterface, BaseService, DataAdapterInterface, DataAdapterContext, DataItemInterface, DataItemValueInterface, equals, DataItemHistoryOptionsInterface, DataItemIdentifierInterface, DataItemDimensionHistoryOptionsInterface, DataItemDimensionValueInterface, } from ".."; type SubscriptionCallback = () => void; export class DataService extends BaseService { private app: AppInterface; private services: ServicesInterface; private adapter: DataAdapterInterface; private context: DataAdapterContext; private itemStore = new Map<string, DataItemInterface>(); private itemSubscriptions = new Map<string, Set<SubscriptionCallback>>(); private itemListSubscriptions: Set<SubscriptionCallback> = new Set(); private valueStore = new Map<string, DataItemValueInterface>(); private valueSubscriptions = new Map<string, Set<SubscriptionCallback>>(); constructor(app: AppInterface, adapter: DataAdapterInterface) { super(); this.app = app; this.services = app.services; this.adapter = adapter; this.context = new DataAdapterContext(this, this.app); this.initAdapter(adapter, this.context, this.services); } private notifyDataSubscribers( subscribers: Set<SubscriptionCallback>, clear: boolean = false ): void { subscribers.forEach((callback) => { try { callback(); } catch (error) { console.error("Error in subscription:", error); } }); if (clear) { subscribers.clear(); } } public subscribeItemList(callback: SubscriptionCallback) { this.itemListSubscriptions.add(callback); return () => { this.itemListSubscriptions.delete(callback); }; } public subscribeItem( item: DataItemInterface, callback: SubscriptionCallback ) { const key = keyForItem(item); if (!this.itemSubscriptions.has(key)) { this.itemSubscriptions.set(key, new Set()); } this.itemSubscriptions.get(key).add(callback); return () => { this.itemSubscriptions.get(key).delete(callback); }; } public subscribeValue( item: DataItemInterface, callback: SubscriptionCallback ) { const key = keyForItem(item); if (!this.valueSubscriptions.has(key)) { this.valueSubscriptions.set(key, new Set()); } this.valueSubscriptions.get(key).add(callback); return () => { this.valueSubscriptions.get(key).delete(callback); }; } public async get(source: string, id: string): Promise<DataItemInterface> { return this._getOrThrowSync(source, id); } public _getOrThrowSync(source: string, id: string): DataItemInterface { const item = this.itemStore.get(keyForIdentifier([source, id])); if (!item) { throw new Error(`Item with id "${id}" not found`); } return item; } public async getValue( item: DataItemInterface ): Promise<DataItemValueInterface> { return this._getValueOrThrowSync(item); } public _getValueOrThrowSync(item: DataItemInterface): DataItemValueInterface { return this.valueStore.get(keyForItem(item)); } public async list(): Promise<DataItemInterface[]> { return this._listOrThrowSync(); } public _listOrThrowSync(): DataItemInterface[] { return Array.from(this.itemStore.values()); } public async setItem(item: DataItemInterface): Promise<void> { const key = keyForItem(item); if (!this.itemSubscriptions.has(key)) { this.itemSubscriptions.set(key, new Set()); } if (!this.itemStore.has(key) || equals(this.itemStore.get(key), item)) { this.itemStore.set(key, item); this.notifyDataSubscribers(this.itemListSubscriptions); this.notifyDataSubscribers(this.itemSubscriptions.get(key)); } } public async setItems(items: DataItemInterface[]): Promise<void> { for (const item of items) { const key = keyForItem(item); if (!this.itemSubscriptions.has(key)) { this.itemSubscriptions.set(key, new Set()); } if (!this.itemStore.has(key) || !equals(this.itemStore.get(key), item)) { this.itemStore.set(key, item); this.notifyDataSubscribers(this.itemSubscriptions.get(key)); } } this.notifyDataSubscribers(this.itemListSubscriptions); } public async setValue( item: DataItemInterface, value: DataItemValueInterface ): Promise<void> { const key = keyForItem(item); if (!this.valueSubscriptions.has(key)) { this.valueSubscriptions.set(key, new Set()); } if (!this.valueStore.has(key) || !equals(this.valueStore.get(key), value)) { this.valueStore.set(key, value); this.notifyDataSubscribers(this.valueSubscriptions.get(key)); } } public async removeItem(item: DataItemInterface): Promise<void> { const key = keyForItem(item); this.itemStore.delete(key); this.valueStore.delete(key); this.notifyDataSubscribers(this.itemListSubscriptions); this.notifyDataSubscribers(this.itemSubscriptions.get(key)); this.notifyDataSubscribers(this.valueSubscriptions.get(key)); } public async clear() { this.setLoading(true); // clear stores this.itemStore.clear(); this.valueStore.clear(); this.notifyDataSubscribers(this.itemListSubscriptions); Array.from(this.itemSubscriptions.keys()).forEach((key) => { this.notifyDataSubscribers(this.itemSubscriptions.get(key)); }); Array.from(this.valueSubscriptions.keys()).forEach((key) => { this.notifyDataSubscribers(this.valueSubscriptions.get(key)); }); } public async fetchValues( item: DataItemInterface, options: DataItemHistoryOptionsInterface ): Promise<DataItemValueInterface[]> { await this.wait(); return await this.adapter.fetchValues(item, options); } public async fetchValuesMultiItem( items: DataItemInterface[], options: DataItemHistoryOptionsInterface ): Promise<[DataItemInterface, DataItemValueInterface[]][]> { await this.wait(); const historyRequests = items.map((item) => this.adapter.fetchValues(item, options) ); const histories = await Promise.all(historyRequests); const resultEntries = items.map( (item, i) => [item, histories[i]] as [DataItemInterface, DataItemValueInterface[]] ); return resultEntries; } public async fetchDimensionValues( item: DataItemInterface, dimension: number, options: DataItemDimensionHistoryOptionsInterface ): Promise<DataItemDimensionValueInterface[]> { await this.wait(); return await this.adapter.fetchDimensionValues(item, dimension, options); } public async fetchDimensionValuesMultiItem( items: [DataItemInterface, number][], options: DataItemHistoryOptionsInterface ): Promise<[DataItemInterface, number, DataItemDimensionValueInterface[]][]> { await this.wait(); const historyRequests = items.map(([item, dimension]) => this.adapter.fetchDimensionValues(item, dimension, options) ); const histories = await Promise.all(historyRequests); const resultEntries = items.map( ([item, dimension], i) => [item, dimension, histories[i]] as [ DataItemInterface, number, DataItemDimensionValueInterface[] ] ); return resultEntries; } public async update(item: DataItemInterface): Promise<void> { return await this.adapter.update(item); } } function keyForIdentifier(item: DataItemIdentifierInterface): string { return JSON.stringify(item); } function keyForItem(item: DataItemInterface): string { return JSON.stringify([item.source, item.id]); }