import { Observable, ReplaySubject, Subject, firstValueFrom, map, scan, startWith } from 'rxjs'; import { ExtensionsLog, log } from '../logs/log'; import { deepFreeze } from '../utils'; export const MSG_CANNOT_REGISTER_READ_ONLY = 'Cannot register to a read-only registry'; export type PluginExtensionConfigs = { pluginId: string; configs: T[]; }; export type RegistryType = Record; // This is the base-class used by the separate specific registries. export abstract class Registry { // Used in cases when we would like to pass a read-only registry to plugin. // In these cases we are passing in the `registrySubject` to the constructor. // (If TRUE `initialState` is ignored.) private isReadOnly: boolean; // This is the subject that receives extension configs for a loaded plugin. private resultSubject: Subject>; protected logger: ExtensionsLog; // This is the subject that we expose. // (It will buffer the last value on the stream - the registry - and emit it to new subscribers immediately.) protected registrySubject: ReplaySubject>; constructor(options: { registrySubject?: ReplaySubject>; initialState?: RegistryType; log?: ExtensionsLog; }) { this.resultSubject = new Subject>(); this.logger = options.log ?? log; this.isReadOnly = false; // If the registry subject (observable) is provided, it means that all the registry updates are taken care of outside of this class -> it is read-only. if (options.registrySubject) { this.registrySubject = options.registrySubject; this.isReadOnly = true; return; } this.registrySubject = new ReplaySubject>(1); this.resultSubject .pipe( scan(this.mapToRegistry.bind(this), options.initialState ?? {}), // Emit an empty registry to start the stream (it is only going to do it once during construction, and then just passes down the values) startWith(options.initialState ?? {}), map((registry) => deepFreeze(registry)) ) // Emitting the new registry to `this.registrySubject` .subscribe(this.registrySubject); } abstract mapToRegistry( registry: RegistryType, item: PluginExtensionConfigs ): RegistryType; register(result: PluginExtensionConfigs): void { if (this.isReadOnly) { throw new Error(MSG_CANNOT_REGISTER_READ_ONLY); } this.resultSubject.next(result); } asObservable(): Observable> { return this.registrySubject.asObservable(); } getState(): Promise> { return firstValueFrom(this.asObservable()); } }