type Observer = () => void; type ObservableRecord>> = { [K in keyof T]: T[K] extends Observable ? U : never; }; class Observable { #observers: Observer[] = []; #data?: Promise; #loader: (current?: T) => Promise; constructor(loader: () => Promise) { this.#loader = loader; } public get ready() { return this.#data; } public get data() { if (!this.#data) { this.#data = this.#loader(this.#data); } return this.#data; } public recreate = () => { this.#data = undefined; this.notify(); }; public set(loader: (current?: T) => Promise) { this.#data = undefined; this.#loader = loader; this.notify(); } public notify = () => { this.#observers.forEach((observer) => observer()); }; subscribe = (observer: Observer) => { this.#observers.push(observer); return () => this.unsubscribe(observer); }; unsubscribe = (observer: Observer) => { this.#observers = this.#observers.filter((o) => o !== observer); }; pipe = (fn: (data: T) => Promise) => { const loader = async () => fn(await this.data); const observable = new Observable(loader); this.subscribe(() => { observable.set(loader); }); return observable; }; static combine = >>( record: U, ): Observable> => { const loader = () => Object.entries(record).reduce( async (accP, [key, value]) => ({ ...(await accP), [key]: await value.data, }), {} as any, ); const observable = new Observable>(loader); Object.values(record).forEach((item) => { item.subscribe(async () => { observable.set(loader); }); }); return observable; }; static link = (observables: Observable[], generate: () => Promise) => { const observable = new Observable(generate); observables.forEach((item) => { item.subscribe(() => { observable.recreate(); }); }); return observable; }; } export { Observable };