{"version":3,"file":"index.cjs","names":[],"sources":["../../../src/core/helpers.ts","../../../src/core/Emitter.ts","../../../src/core/factories.ts"],"sourcesContent":["/**\n * Extracts the own enumerable keys of a mapped object, typed as its key union.\n *\n * @remarks\n * `Object.keys` widens its result to `string[]`, which breaks the key↔value\n * correlation a mapped type (like `EmitterHooks<TMap>`) otherwise guarantees.\n * A `for…in` push into a `keyof`-typed array narrows the result back,\n * type-safely and with no assertion. Inherited enumerable keys are excluded, as\n * `Object.keys` excludes them.\n *\n * @typeParam T - The object shape whose keys are extracted.\n * @param object - The object to read keys from.\n * @returns The object's own enumerable keys, typed as `ReadonlyArray<keyof T>`.\n *\n * @example\n * ```ts\n * import { extractKeys } from '@src/core'\n *\n * const hooks = { tick: () => {}, done: () => {} }\n * extractKeys(hooks) // ['tick', 'done']\n * extractKeys({}) // []\n * ```\n */\nexport function extractKeys<T extends object>(object: T): ReadonlyArray<keyof T> {\n\tconst collected: Array<keyof T> = []\n\tfor (const key in object) if (Object.hasOwn(object, key)) collected.push(key)\n\treturn collected\n}\n","import type {\n\tEmitterErrorHandler,\n\tEmitterHandler,\n\tEmitterHooks,\n\tEmitterInterface,\n\tEmitterOptions,\n\tEventMap,\n} from './types.js'\nimport { isFunction } from '@orkestrel/contract'\nimport { extractKeys } from './helpers.js'\n\n/**\n * Implements `EmitterInterface` over one listener `Set` per event, so every public method is\n * precisely typed with no assertion. A stateful entity owns one as a `#emitter` field and\n * exposes it through `readonly emitter`; it never inherits from it.\n *\n * @typeParam TMap - The event map: each event name to the argument tuple its\n *   listeners receive.\n *\n * @remarks\n * - **Synchronous.** `emit` invokes listeners in registration order, in the\n *   current tick.\n * - **Listener isolation.** A throwing listener never stops its siblings: every\n *   listener runs, and a throw is routed to the `error` handler\n *   ({@link EmitterOptions.error}) — never rethrown. Every throwing listener\n *   surfaces (not only the first), and with no `error` handler a throw is swallowed\n *   silently. The `error` handler runs inside its own try/catch, so a throwing\n *   error-handler is swallowed too (anti-recursion — it cannot escape or re-enter).\n * - **Destroyed → no-op.** After `destroy()`, `on` / `once` / `emit` do nothing\n *   and `destroyed` is `true`.\n *\n * @example\n * ```ts\n * type CounterEventMap = {\n * \ttick: readonly [count: number]\n * \tdone: readonly []\n * }\n *\n * const emitter = new Emitter<CounterEventMap>({\n * \ton: { done: () => stop() },\n * \terror: (error, event) => log(`listener for ${event} threw`, error),\n * })\n * emitter.on('tick', (count) => render(count))\n * emitter.emit('tick', 1)\n * ```\n */\nexport class Emitter<TMap extends EventMap> implements EmitterInterface<TMap> {\n\t#destroyed = false\n\t#listeners: { [K in keyof TMap]?: Set<EmitterHandler<TMap[K]>> } = {}\n\t// Each original handler may have MULTIPLE pending once-wrappers (repeated `once(event, h)`\n\t// calls before any of them fire), so the value is a Set of wrappers, not a single wrapper.\n\t#wrappers: { [K in keyof TMap]?: Map<EmitterHandler<TMap[K]>, Set<EmitterHandler<TMap[K]>>> } = {}\n\t// The emitter's own listener-error handler — a listener throw is routed here, never\n\t// rethrown. Held opaquely so an isolated throw becomes the entity's concern, not the loop's.\n\t#error: EmitterErrorHandler | undefined\n\n\t// Construction is the validation boundary: `error` and each `on` hook are defensively\n\t// guarded with `isFunction` here so a malformed options bag is skipped rather than blowing\n\t// up at first `emit` — `emit()` itself stays assertion-free and dependency-free (the hot\n\t// path), trusting only what construction has already let through.\n\tconstructor(options?: EmitterOptions<TMap>) {\n\t\tconst error = options?.error\n\t\tthis.#error = isFunction(error) ? error : undefined\n\t\tconst hooks = options?.on\n\t\tif (hooks !== undefined) {\n\t\t\tthis.#wire(hooks)\n\t\t}\n\t}\n\n\tget destroyed(): boolean {\n\t\treturn this.#destroyed\n\t}\n\n\ton<K extends keyof TMap>(event: K, handler: EmitterHandler<TMap[K]>): void {\n\t\tif (this.#destroyed) return\n\t\t;(this.#listeners[event] ??= new Set()).add(handler)\n\t}\n\n\tonce<K extends keyof TMap>(event: K, handler: EmitterHandler<TMap[K]>): void {\n\t\tif (this.#destroyed) return\n\t\t// The wrapper removes ITSELF from the listener Set (captured through its reference) instead\n\t\t// of routing through `off` — routing through `off` would look the handler up by the\n\t\t// original handler, which a second `once(event, handler)` registration keeps alongside\n\t\t// this one (both pending wrappers share the same original handler), orphaning whichever\n\t\t// wrapper `off` doesn't happen to pick if only a single wrapper were tracked.\n\t\tconst pending = (this.#wrappers[event] ??= new Map())\n\t\tconst reference = new Set<EmitterHandler<TMap[K]>>()\n\t\tconst wrapper = this.#wrap(event, handler, pending, reference)\n\t\treference.add(wrapper)\n\t\tconst wrappers = pending.get(handler) ?? new Set<EmitterHandler<TMap[K]>>()\n\t\twrappers.add(wrapper)\n\t\tpending.set(handler, wrappers)\n\t\tthis.on(event, wrapper)\n\t}\n\n\toff<K extends keyof TMap>(event: K, handler: EmitterHandler<TMap[K]>): void {\n\t\tconst listeners = this.#listeners[event]\n\t\tconst wrappers = this.#wrappers[event]\n\t\tconst pending = wrappers?.get(handler)\n\t\tif (pending !== undefined) {\n\t\t\tfor (const wrapper of pending) listeners?.delete(wrapper)\n\t\t\twrappers?.delete(handler)\n\t\t}\n\t\t// Remove the plain handler too — `on(event, h)` + `once(event, h)` registers `h` twice,\n\t\t// and one `off(event, h)` call is meant to clear both registrations.\n\t\tlisteners?.delete(handler)\n\t}\n\n\t// Snapshot semantics: the listener list is copied before iterating, so a listener added\n\t// DURING this emit does not fire this round, while a listener removed (or an emitter\n\t// destroyed) during this emit STILL fires this round if it was already in the snapshot.\n\temit<K extends keyof TMap>(event: K, ...args: TMap[K]): void {\n\t\tif (this.#destroyed) return\n\t\tconst listeners = this.#listeners[event]\n\t\tif (listeners === undefined) return\n\t\t// Every listener runs; a throw is isolated and routed to the `error` handler — never\n\t\t// rethrown, never stopping a sibling. EVERY throwing listener surfaces, not only the first.\n\t\tfor (const handler of [...listeners]) {\n\t\t\ttry {\n\t\t\t\thandler(...args)\n\t\t\t} catch (error) {\n\t\t\t\tthis.#surface(error, event)\n\t\t\t}\n\t\t}\n\t}\n\n\tcount(event?: keyof TMap): number {\n\t\tif (event !== undefined) return this.#listeners[event]?.size ?? 0\n\t\tlet total = 0\n\t\tfor (const set of Object.values(this.#listeners)) total += set?.size ?? 0\n\t\treturn total\n\t}\n\n\tclear(event?: keyof TMap): void {\n\t\tif (event !== undefined) {\n\t\t\tdelete this.#listeners[event]\n\t\t\tdelete this.#wrappers[event]\n\t\t\treturn\n\t\t}\n\t\tthis.#listeners = {}\n\t\tthis.#wrappers = {}\n\t}\n\n\tdestroy(): void {\n\t\tthis.#listeners = {}\n\t\tthis.#wrappers = {}\n\t\tthis.#error = undefined\n\t\tthis.#destroyed = true\n\t}\n\n\t#wrap<K extends keyof TMap>(\n\t\tevent: K,\n\t\thandler: EmitterHandler<TMap[K]>,\n\t\tpending: Map<EmitterHandler<TMap[K]>, Set<EmitterHandler<TMap[K]>>>,\n\t\treference: Set<EmitterHandler<TMap[K]>>,\n\t): EmitterHandler<TMap[K]> {\n\t\treturn (...args) => {\n\t\t\tfor (const wrapper of reference) {\n\t\t\t\tthis.#listeners[event]?.delete(wrapper)\n\t\t\t\tconst wrappers = pending.get(handler)\n\t\t\t\twrappers?.delete(wrapper)\n\t\t\t\tif (wrappers !== undefined && wrappers.size === 0) pending.delete(handler)\n\t\t\t}\n\t\t\thandler(...args)\n\t\t}\n\t}\n\n\t// Route an isolated listener throw to the `error` handler, inside its OWN try/catch:\n\t// a throwing error-handler is swallowed (anti-recursion — it can neither escape the emit loop\n\t// nor re-enter it), and with no handler the throw is dropped silently. NEVER rethrows.\n\t#surface(error: unknown, event: keyof TMap): void {\n\t\tconst handler = this.#error\n\t\tif (handler === undefined) return\n\t\ttry {\n\t\t\thandler(error, String(event))\n\t\t} catch {\n\t\t\t// The error handler itself threw — the end of the line. Swallow it: rethrowing would\n\t\t\t// corrupt the emit loop, and re-surfacing would recurse.\n\t\t}\n\t}\n\n\t// Register the initial `on` hooks. Each key is an event name whose value is the\n\t// matching handler, so registering through `on` preserves the correlation the\n\t// mapped `EmitterHooks` already guarantees — no assertion needed.\n\t// Defensively guards each hook value with `isFunction` — a non-function entry is skipped\n\t// rather than registered, so a malformed `on` bag fails safe at construction instead of\n\t// throwing later when the bad \"handler\" is invoked in `emit`.\n\t#wire(hooks: EmitterHooks<TMap>): void {\n\t\tfor (const event of extractKeys(hooks)) {\n\t\t\tconst handler = hooks[event]\n\t\t\tif (isFunction(handler)) this.on(event, handler)\n\t\t}\n\t}\n}\n","import type { EmitterInterface, EmitterOptions, EventMap } from './types.js'\nimport { Emitter } from './Emitter.js'\n\n/**\n * Creates a typed synchronous event emitter and returns it as an `EmitterInterface<TMap>`,\n * wiring the initial `on` hooks and the `error` handler its options carry.\n *\n * @remarks\n * Entities that own an emitter construct `new Emitter(...)` for their `#emitter`\n * field directly; this factory is the standalone entry point.\n *\n * @typeParam TMap - The event map: each event name to its listener argument tuple.\n * @param options - Optional `on` hooks (initial listeners wired at construction) and\n *   an optional `error` handler for a listener's throw\n * @returns A typed {@link EmitterInterface}\n *\n * @example Standalone emitter\n * ```ts\n * import { createEmitter } from '@orkestrel/emitter'\n *\n * type DownloadEventMap = {\n * \tchunk: readonly [bytes: number]\n * \tdone: readonly []\n * }\n *\n * const emitter = createEmitter<DownloadEventMap>()\n * emitter.on('chunk', (bytes) => accumulate(bytes))\n * emitter.once('done', () => finish())\n * emitter.emit('chunk', 1024)\n * emitter.emit('done')\n * ```\n */\nexport function createEmitter<TMap extends EventMap>(\n\toptions?: EmitterOptions<TMap>,\n): EmitterInterface<TMap> {\n\treturn new Emitter(options)\n}\n"],"mappings":"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"}