Ctrl K
ComunidadGuías…

src/state_machines/keyed_host.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
export interface KeyedController<Snapshot = unknown> {
  getSnapshot(): Snapshot;
  subscribe(listener: () => void): () => void;
  dispose(): void;
}

/** Owns the lifecycle and subscriptions for controllers keyed by an entity. */
export class KeyedControllerHost<K, C extends KeyedController> {
  private readonly controllers = new Map<K, C>();
  private readonly controllerUnsubscribes = new Map<K, () => void>();
  private readonly keyListeners = new Map<K, Set<() => void>>();
  private readonly anyListeners = new Set<() => void>();
  private disposed = false;

  constructor(private readonly createController: (key: K) => C) {}

  ensure(key: K): C {
    const existing = this.controllers.get(key);
    if (existing) return existing;
    if (this.disposed) {
      throw new Error("Cannot create a controller on a disposed host");
    }
    const controller = this.createController(key);
    this.controllers.set(key, controller);
    this.controllerUnsubscribes.set(
      key,
      controller.subscribe(() => this.notify(key)),
    );
    return controller;
  }

  get(key: K): C | undefined {
    return this.controllers.get(key);
  }

  keys(): K[] {
    return [...this.controllers.keys()];
  }

  subscribeKey(key: K, listener: () => void): () => void {
    if (this.disposed) return () => undefined;
    let listeners = this.keyListeners.get(key);
    if (!listeners) {
      listeners = new Set();
      this.keyListeners.set(key, listeners);
    }
    listeners.add(listener);
    return () => {
      listeners?.delete(listener);
      if (listeners?.size === 0) this.keyListeners.delete(key);
    };
  }

  subscribeAny(listener: () => void): () => void {
    if (this.disposed) return () => undefined;
    this.anyListeners.add(listener);
    return () => this.anyListeners.delete(listener);
  }

  disposeKey(key: K): void {
    const controller = this.controllers.get(key);
    if (!controller) return;
    this.controllers.delete(key);
    this.controllerUnsubscribes.get(key)?.();
    this.controllerUnsubscribes.delete(key);
    controller.dispose();
    this.notify(key);
  }

  dispose(): void {
    if (this.disposed) return;
    this.disposed = true;
    for (const unsubscribe of this.controllerUnsubscribes.values()) {
      unsubscribe();
    }
    for (const controller of this.controllers.values()) controller.dispose();
    this.controllers.clear();
    this.controllerUnsubscribes.clear();
    this.keyListeners.clear();
    this.anyListeners.clear();
  }

  private notify(key: K): void {
    for (const listener of this.keyListeners.get(key) ?? []) listener();
    for (const listener of this.anyListeners) listener();
  }
}