{"version":3,"sources":["../src/listener-registry.ts"],"sourcesContent":["import { Action, Fn, MaybePromise } from './types.js';\nimport { err, DispatchResultItem } from './dispatch-result.js';\n\n/**\n * @internal\n * A lightweight registry for managing listener functions with stable dispatch order.\n *\n * Key characteristics:\n * - O(1) add/remove/has using an internal Map\n * - Snapshot-based dispatch to avoid reallocating arrays when the set is unchanged\n * - Supports one-time listeners via `once`\n * - Returns booleans for idempotent add/remove operations\n *\n * @template P Tuple of argument types passed to listeners\n * @template R Return type of listeners\n *\n * @example\n * ```typescript\n * const registry = new ListenerRegistry<[number], void>();\n * const listener = (value: number) => console.log(value);\n *\n * registry.on(listener);           // true\n * registry.on(listener);           // false (already registered)\n * registry.dispatch(1);            // calls listener\n * registry.off(listener);          // true\n * registry.dispatch(2);            // no listeners called\n * ```\n */\ninterface ListenerEntry {\n  once: boolean;\n  onRemove?: Action;\n}\n\n/**\n * @internal\n */\nexport class ListenerRegistry<P extends unknown[], R> {\n  #listeners = new Map<Fn<P, R>, ListenerEntry>();\n  #snapshot: Fn<P, R>[] | null = null;\n\n  readonly [Symbol.toStringTag] = 'ListenerRegistry';\n\n  /**\n   * The number of listeners currently registered.\n   */\n  get size(): number {\n    return this.#listeners.size;\n  }\n\n  /**\n   * Checks whether the listener is currently registered.\n   */\n  has(listener: Fn<P, R>): boolean {\n    return this.#listeners.has(listener);\n  }\n\n  /**\n   * Convenience inverse of `has`.\n   */\n  lacks(listener: Fn<P, R>): boolean {\n    return !this.has(listener);\n  }\n\n  /**\n   * Removes a listener if present.\n   *\n   * @returns `true` if removed, `false` if it was not registered.\n   */\n  off(listener: Fn<P, R>): boolean {\n    const entry = this.#listeners.get(listener);\n    if (!entry) {\n      return false;\n    }\n    this.#listeners.delete(listener);\n    this.#snapshot = null;\n    entry.onRemove?.();\n    return true;\n  }\n\n  /**\n   * Registers a listener if not already present.\n   *\n   * @param onRemove Optional callback invoked when this listener is removed.\n   * @returns `true` if added, `false` if it was already registered.\n   */\n  on(listener: Fn<P, R>, onRemove?: Action): boolean {\n    if (this.has(listener)) {\n      return false;\n    }\n    this.#listeners.set(listener, { once: false, onRemove });\n    this.#snapshot = null;\n    return true;\n  }\n\n  /**\n   * Registers a listener that will automatically unregister after its next dispatch.\n   *\n   * @param onRemove Optional callback invoked when this listener is removed.\n   * @returns `true` if added, `false` if it was already registered.\n   */\n  once(listener: Fn<P, R>, onRemove?: Action): boolean {\n    if (this.has(listener)) {\n      return false;\n    }\n    this.#listeners.set(listener, { once: true, onRemove });\n    this.#snapshot = null;\n    return true;\n  }\n\n  /**\n   * Removes all listeners and clears the dispatch snapshot.\n   */\n  clear(): void {\n    for (const entry of this.#listeners.values()) {\n      entry.onRemove?.();\n    }\n    this.#listeners.clear();\n    this.#snapshot = null;\n  }\n\n  /**\n   * Dispatches to all listeners in snapshot order.\n   * One-time listeners are removed before invocation.\n   * Exceptions are captured as ResultError so dispatch continues (unwrap to surface rejections).\n   *\n   * @param values Arguments forwarded to each listener.\n   * @returns Array of listener results or promises, one per listener.\n   */\n  dispatch(...values: P): DispatchResultItem<R | void>[] {\n    const listeners = this.#listeners;\n    if (listeners.size === 0) {\n      return [];\n    }\n    let ordered = this.#snapshot;\n    if (!ordered) {\n      const snapshot = new Array<Fn<P, R>>(listeners.size);\n      let index = 0;\n      for (const fn of listeners.keys()) {\n        snapshot[index++] = fn;\n      }\n      snapshot.length = index;\n      ordered = snapshot;\n      this.#snapshot = snapshot;\n    }\n    if (ordered.length === 0) {\n      return [];\n    }\n    const results = new Array<DispatchResultItem<R | void>>(ordered.length);\n    const argCount = values.length;\n    for (let index = 0; index < ordered.length; index++) {\n      const fn = ordered[index];\n      if (listeners.get(fn)?.once) {\n        this.off(fn);\n      }\n      const invoke = fn as (...args: unknown[]) => MaybePromise<R | void>;\n      try {\n        switch (argCount) {\n          case 0:\n            results[index] = invoke();\n            break;\n          case 1:\n            results[index] = invoke(values[0]);\n            break;\n          case 2:\n            results[index] = invoke(values[0], values[1]);\n            break;\n          default:\n            results[index] = invoke(...values);\n            break;\n        }\n      } catch (error) {\n        results[index] = err(error);\n      }\n    }\n\n    return results;\n  }\n}\n"],"names":["ListenerRegistry","Map","Symbol","toStringTag","size","has","listener","lacks","off","entry","get","delete","onRemove","on","set","once","clear","values","dispatch","listeners","ordered","snapshot","Array","index","fn","keys","length","results","argCount","invoke","error","err"],"mappings":"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"}