aieventjs 0.5.6 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,177 +1,66 @@
1
1
  # aieventjs
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/aieventjs.svg)](https://www.npmjs.com/package/aieventjs)
4
- [![CI](https://github.com/islumina/aieventjs/actions/workflows/ci.yml/badge.svg)](https://github.com/islumina/aieventjs/actions/workflows/ci.yml)
5
- [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
6
- [![AI Generated](https://img.shields.io/badge/AI_Generated-Claude_Code_Opus_4.7_Max-blueviolet.svg)](https://www.anthropic.com/claude-code)
7
- [![繁體中文](https://img.shields.io/badge/lang-繁體中文-red.svg)](README_ZHTW.md)
3
+ Small, strict, typed event emitter with ai*js lifecycle conventions: `on()` returns unsubscribe, `once` is built in, `AbortSignal` is first-class, wildcard handlers are supported, and `dispose()` is idempotent.
8
4
 
9
- > A small, strict, typed event emitter — `on()` returns an unsubscribe function, `once` is built-in, `AbortSignal` is first-class, `dispose()` is idempotent, wildcard `*` handlers are preserved. Mitt-shaped API where it counts; ai\*js conventions everywhere else.
5
+ > **Status: 0.5.8 - stable 1.0-track surface.** The root entry is the public API.
10
6
 
11
- Part of the [ai\*js micro-runtime ecosystem](https://github.com/islumina) — see also [aifsmjs](https://github.com/islumina/aifsmjs) (FSM), [aiecsjs](https://github.com/islumina/aiecsjs) (ECS), [aibridgejs](https://github.com/islumina/aibridgejs) (cross-context RPC), [aipooljs](https://github.com/islumina/aipooljs) (object pool), [aiquadtreejs](https://github.com/islumina/aiquadtreejs) (spatial partitioning), and [aiaudiojs](https://github.com/islumina/aiaudiojs) (Web Audio shell).
12
-
13
- > **Status: 0.5.6.** Full implementation shipped; all methods are live. Coverage ≥ 95/90/100/100; ~1050 B gzip (budget 1100 B).
14
-
15
- ---
16
-
17
- ## Why aieventjs
18
-
19
- Why not just use `mitt`? Honest answer: `mitt` is the right choice for many projects — it's MIT, ~282 B gzipped, and the API is genuinely well-shaped. We evaluated it and chose to write from scratch instead. Three reasons:
20
-
21
- - **mitt has been unmaintained since 2023-07-04.** The PRs the community most wants — `unsubscribe`-returning `on()`, `AbortSignal`, `sideEffects: false`, nodenext compatibility — are all open and untouched. Forking would mean shipping a copy with our name on it; the upstream couldn't accept improvements back even if we wanted.
22
- - **The implementation is ~35 lines of pure logic.** "Fork and improve" doesn't really exist at that size class — any non-trivial change is a rewrite, and the cost of carrying the upstream copyright notice exceeds the benefit.
23
- - **ai\*js conventions are pervasive enough that fitting them onto mitt's API surface would change every method signature.** `on()` returning `void` vs. returning an unsubscribe is the visible difference; the strict TypeScript posture (`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, no `!` non-null assertions) is the invisible one that touches every line.
24
-
25
- So `aieventjs` is the ai\*js-shaped event emitter:
26
-
27
- - **`on()` returns an unsubscribe function.** Cleanup via the standard "call this to undo" idiom — closes over nothing, survives handler renames, drops in to `$effect()` / `onScopeDispose()` / `useEffect` cleanup without ceremony.
28
- - **`AbortSignal` everywhere it makes sense.** `on(type, handler, { signal })` removes the handler when the signal aborts. Pre-aborted signals never register. The whole library uses the same cancellation primitive as `fetch` and the rest of the platform.
29
- - **`dispose()` is idempotent.** Post-dispose `on` / `emit` / `once` throw `EmitterDisposedError`. This is the family-wide convention; an emitter that "looks alive but does nothing" after teardown is the canonical leak vector and we refuse to ship it.
30
- - **Wildcard `*` is preserved.** `bus.on("*", (type, payload) => ...)` works exactly like in `mitt`; wildcard handlers fire AFTER type-matched handlers. ~80 B gzip cost; kept to make migration mechanical.
31
- - **Handler-array snapshot on `emit`.** Removing a handler inside its own callback does not skip subsequent handlers (mitt has this since 2.x; preserved).
32
- - **Functional, destructurable.** `const { on, emit } = bus` works — no `this` capture anywhere.
33
-
34
- What this is **not**: not an async event bus (handlers are synchronous), not a namespaced bus (`user.*` style wildcards are out), not a priority queue, not a transport. It is the in-process synchronous fan-out primitive — nothing more.
35
-
36
- ---
37
-
38
- ## Quick Start
7
+ ## Install
39
8
 
40
9
  ```bash
41
10
  pnpm add aieventjs
42
11
  ```
43
12
 
44
- ```typescript
13
+ ```ts
45
14
  import { createEmitter } from "aieventjs";
15
+ ```
16
+
17
+ ## Quick Start
46
18
 
19
+ ```ts
47
20
  type Events = {
48
- "user:login": { id: string };
49
- "user:logout": void;
50
- "score:tick": { delta: number };
21
+ "score/change": { value: number };
22
+ "scene/end": void;
51
23
  };
52
24
 
53
- const bus = createEmitter<Events>();
25
+ const events = createEmitter<Events>();
54
26
 
55
- // 1. Subscribe; capture the unsubscribe handle.
56
- const off = bus.on("user:login", (u) => console.log("hi", u.id));
27
+ const off = events.on("score/change", ({ value }) => {
28
+ console.log(value);
29
+ });
57
30
 
58
- // 2. Or wire to an AbortSignal for framework-native cleanup.
59
- const ctrl = new AbortController();
60
- bus.on("score:tick", (e) => render(e.delta), { signal: ctrl.signal });
61
-
62
- // 3. Wildcard receives (type, payload) — fires AFTER type-matched handlers.
63
- bus.on("*", (type, payload) => trace(type, payload));
64
-
65
- // 4. Dispatch.
66
- bus.emit("user:login", { id: "alice" });
67
-
68
- // 5. Tear down.
31
+ events.on("*", (type, payload) => console.log(type, payload), { sampleRate: 0.1 });
32
+ events.emit("score/change", { value: 10 });
69
33
  off();
70
- ctrl.abort();
71
- bus.dispose(); // idempotent; post-dispose calls throw EmitterDisposedError
34
+ events.dispose();
72
35
  ```
73
36
 
74
- `createEmitter()` returns a plain object whose methods do not depend on `this` — `const { on, emit } = bus` works fine.
75
-
76
- > **Declare the event map with `type`, not `interface`.** The `Events` generic is constrained to `Record<string, unknown>`. A *plain* TypeScript `interface` has no implicit index signature, so passing one fails the constraint with *"Index signature for type 'string' is missing in type ..."*. A `type` object literal satisfies it structurally. (An `interface` with an explicit index signature — or one that `extends Record<string, unknown>` — also compiles, but widens `keyof Events` to `string` and loses strict event-name checking, so prefer `type`.)
77
- >
78
- > ```typescript
79
- > // ❌ interface — fails the Record<string, unknown> constraint (TS2344)
80
- > interface Events { "user:login": { id: string } }
81
- > const bus = createEmitter<Events>();
82
- >
83
- > // ✅ type — satisfies the constraint
84
- > type Events = { "user:login": { id: string } };
85
- > const bus = createEmitter<Events>();
86
- > ```
87
-
88
- ---
89
-
90
- ## Capabilities / Limitations
91
-
92
- | Will do (v1) | Won't do |
93
- | --------------------------------------------------------- | ----------------------------------------------------- |
94
- | Typed `createEmitter<Events>()` | Untyped string-key bus (the type is the point) |
95
- | `on()` returns unsubscribe function | Async / promise-returning handlers (sync only) |
96
- | `once(type, handler)` + `on(..., { once: true })` | Namespaced wildcards (`"user.*"`) — out of scope |
97
- | `on(..., { signal })` — `AbortSignal` cleanup | Priority / weight / ordering hints |
98
- | Wildcard `"*"` handler — `(type, payload)` | Cross-context transport (use `aibridgejs` for that) |
99
- | `dispose()` idempotent; post-dispose calls throw | Error-event special casing (Node EventEmitter style) |
100
- | Handler-array snapshot on `emit` (safe re-entrancy) | Persistent storage / replay (not its job) |
101
- | Destructurable methods (`const { on, emit } = bus`) | Zero-allocation `emit` (one snapshot per dispatch is required for re-entrancy) |
102
- | `on('*', fn, { sampleRate })` — probabilistic delivery for debug subscribers (wildcard only) | |
103
- | `on(type, fn, { throttleMs })` — per-handler leading-edge throttle, on typed **and** wildcard subscriptions (e.g. a per-frame `credits/change` HUD event) | |
104
- | `createEmitter({ captureHandlerErrors })` — opt-in error policy; per-handler override via `OnOptions.captureErrors` | |
105
-
106
- ---
107
-
108
- ## API sketch
109
-
110
- ```typescript
111
- type EventHandler<P> = (payload: P) => void;
112
-
113
- type WildcardHandler<Events extends Record<string, unknown>> =
114
- <K extends keyof Events>(type: K, payload: Events[K]) => void;
115
-
116
- interface OnOptions {
117
- signal?: AbortSignal;
118
- once?: boolean;
119
- captureErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void); // typed only
120
- sampleRate?: number; // wildcard "*" only — probability in (0, 1]
121
- throttleMs?: number; // typed or wildcard — per-handler leading-edge throttle, uses Date.now()
122
- // Note: Date.now() is not monotonic; a system-clock regression silently
123
- // mutes the handler until wall time re-passes the stored timestamp.
124
- // Switching to performance.now() is deferred to the next minor. (EVT-R-02)
125
- }
126
-
127
- interface EmitterOptions {
128
- // Default error policy. undefined/false (default): first throw aborts dispatch.
129
- // true: swallow errors and continue dispatch over all handlers.
130
- // (err, type, payload) => void: callback invoked per throwing handler
131
- // (if the callback itself throws, that error is silently ignored and dispatch continues).
132
- // Per-handler OnOptions.captureErrors overrides this for individual subscriptions.
133
- captureHandlerErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);
134
- }
135
-
136
- interface Emitter<Events extends Record<string, unknown>> {
137
- on<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>, opts?: OnOptions): () => void;
138
- on(type: "*", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;
139
- once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;
140
- // Note: once() only accepts typed event keys. For wildcard-once semantics use
141
- // on("*", handler, { once: true }) — the handler receives (type, payload) as
142
- // WildcardHandler, not (payload) as EventHandler. (EVT-B-02)
143
- off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;
144
- off(type: "*", handler?: WildcardHandler<Events>): void;
145
- emit<K extends keyof Events>(type: K, payload: Events[K]): void;
146
- clear(): void;
147
- dispose(): void;
148
- readonly disposed: boolean;
149
- }
150
-
151
- class EmitterError extends Error {}
152
- class EmitterDisposedError extends Error {}
153
-
154
- function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(
155
- opts?: EmitterOptions,
156
- ): Emitter<Events>;
157
- ```
37
+ ## Core API
158
38
 
159
- Full JSDoc lives in [`src/index.ts`](src/index.ts).
39
+ - `createEmitter<Events>(options?)` creates a typed emitter.
40
+ - `on(type, handler, options?)` subscribes and returns an unsubscribe function.
41
+ - `on("*", wildcard, options?)` subscribes to every event after type-matched handlers.
42
+ - `once(type, handler)` is shorthand for a one-shot typed handler.
43
+ - `off(type, handler?)`, `clear()`, and `dispose()` remove handlers at different scopes.
44
+ - `emit(type, payload)` dispatches synchronously over a snapshot of handlers.
45
+ - Options: `signal`, `once`, `captureErrors`, `sampleRate` for wildcard, `throttleMs` for typed and wildcard.
160
46
 
161
- ---
47
+ ## Sharp Edges
162
48
 
163
- ## Roadmap
49
+ - Default error policy is mitt-like: the first throwing handler aborts dispatch. Use `captureHandlerErrors` or per-handler `captureErrors` to swallow/report and continue.
50
+ - Wildcard handlers receive `(type, payload)`, not just payload.
51
+ - Use `on("*", handler, { once: true })` for wildcard-once. `once("*")` is intentionally not part of the typed public overload.
52
+ - `throttleMs` uses `Date.now()`. If the system clock moves backward, a throttled handler can be muted until wall time catches up.
53
+ - `sampleRate` is wildcard-only and uses `Math.random()` per dispatch.
54
+ - `dispose()` is permanent; post-dispose APIs throw `EmitterDisposedError` except cleanup calls that are no-ops by design.
164
55
 
165
- | Version | Adds |
166
- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------- |
167
- | **0.0.1** | Scaffold landed — frozen API surface as a `throw` stub; full config + CI walk clean. |
168
- | **0.1.0** | First npm release. `on` / `once` / `off` / `emit` / `clear` / `dispose` implemented; coverage ≥ 95/90/100/100; ≤ 800 B gzip (strict-TS overhead lands at ~747 B). |
169
- | **0.3.0** | `captureHandlerErrors` + wildcard sampling/throttling. The v0.2 number was skipped to align with the four-package v0.3 release cohort. |
170
- | **0.4.0** | Dependency hygiene + stability freeze: removed the unused `tsx` devDependency, aligned `fast-check` to `^4.8.0`, and froze the 0.3.x public surface for the 1.x line. No runtime API change; bundle byte-identical to 0.3.1. |
171
- | **0.6+** | Async handler tracking (draft) — see [STABILITY.md](STABILITY.md). |
56
+ ## AI Context
172
57
 
173
- ---
58
+ - Short index: [`llms.txt`](llms.txt)
59
+ - Full generated context: [`llms-full.txt`](llms-full.txt)
60
+ - Stability contract: [`STABILITY.md`](STABILITY.md)
61
+ - Current review backlog: [`REVIEW.md`](REVIEW.md)
62
+ - Release history: [`CHANGELOG.md`](CHANGELOG.md)
174
63
 
175
64
  ## License
176
65
 
177
- [MIT](LICENSE).
66
+ MIT
package/README_ZHTW.md CHANGED
@@ -1,176 +1,66 @@
1
1
  # aieventjs
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/aieventjs.svg)](https://www.npmjs.com/package/aieventjs)
4
- [![CI](https://github.com/islumina/aieventjs/actions/workflows/ci.yml/badge.svg)](https://github.com/islumina/aieventjs/actions/workflows/ci.yml)
5
- [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
6
- [![AI Generated](https://img.shields.io/badge/AI_Generated-Claude_Code_Opus_4.7_Max-blueviolet.svg)](https://www.anthropic.com/claude-code)
7
- [![English](https://img.shields.io/badge/lang-English-blue.svg)](README.md)
3
+ 小而嚴格的 typed event emitter,具備 ai*js lifecycle 慣例:`on()` 回傳 unsubscribe、內建 `once`、支援 `AbortSignal`、wildcard handlers,以及可重複呼叫的 `dispose()`。
8
4
 
9
- > 一個小而嚴格的 typed event emitter ── `on()` 回傳 unsubscribe function、內建 `once`、`AbortSignal` 一級公民、`dispose()` 冪等、保留 wildcard `*` handler。形似 mitt 的 API、但裡裡外外都是 ai\*js convention。
5
+ > **狀態:0.5.8 - 穩定 1.0 軌道 API。** root entry 是公開 API。
10
6
 
11
- 隸屬 [ai\*js micro-runtime 生態系](https://github.com/islumina) ─ 另見 [aifsmjs](https://github.com/islumina/aifsmjs)(FSM)、[aiecsjs](https://github.com/islumina/aiecsjs)(ECS)、[aibridgejs](https://github.com/islumina/aibridgejs)(cross-context RPC)、[aipooljs](https://github.com/islumina/aipooljs)(物件池)、[aiquadtreejs](https://github.com/islumina/aiquadtreejs)(空間分割)、[aiaudiojs](https://github.com/islumina/aiaudiojs)(Web Audio 薄殼)。
12
-
13
- > **狀態:0.5.6。** 完整實作已上線、所有 method 皆可用。Coverage ≥ 95/90/100/100;~1050 B gzip(budget 1100 B)。
14
-
15
- ---
16
-
17
- ## 為什麼有 aieventjs
18
-
19
- 為什麼不直接用 `mitt`?老實說 `mitt` 對很多專案來說是正確選擇 ── MIT、~282 bytes gzip、API 本身造型很好。我們評估過之後選擇自寫。三個理由:
20
-
21
- - **mitt 自 2023-07-04 起停止維護。** 社群最想要的 PR ── `on()` 回傳 unsubscribe、`AbortSignal`、`sideEffects: false`、nodenext 相容 ── 全部 open 但沒人處理。fork 等於我們扛一份「上面寫人家名字」的副本,且就算改進了上游也接不回去。
22
- - **實作是 ~35 行純邏輯。** 那種尺寸沒有「fork 然後改良」這回事 ── 任何非 trivial 修改都等於重寫;繼承上游 copyright notice 的成本超過效益。
23
- - **ai\*js convention 太多,硬塞進 mitt API 等於每個 method 都要改 signature。** `on()` 回傳 `void` vs 回傳 unsubscribe 是看得到的差異;strict TS(`noUncheckedIndexedAccess`、`exactOptionalPropertyTypes`、不用 `!` non-null assertion)是看不到但每一行都會碰到的差異。
24
-
25
- 所以 `aieventjs` 就是 ai\*js 形狀的 event emitter:
26
-
27
- - **`on()` 回傳 unsubscribe function。** 用「呼叫它就撤銷」的標準 idiom 清理 ── 不 close over 任何東西、handler 改名不會壞、直接餵進 `$effect()` / `onScopeDispose()` / `useEffect` cleanup 不需要儀式。
28
- - **`AbortSignal` 用在每個合理的位置。** `on(type, handler, { signal })`,signal abort 即解綁。Pre-aborted 的 signal 永遠不註冊。整個 library 用 `fetch` 等 platform API 一樣的取消原語。
29
- - **`dispose()` 冪等。** dispose 後 `on` / `emit` / `once` 拋 `EmitterDisposedError`。這是家族通用 convention;「看起來還活著但其實 no-op」的 emitter 是經典 leak 來源,我們拒絕出貨那種東西。
30
- - **保留 wildcard `*`。** `bus.on("*", (type, payload) => ...)` 行為與 `mitt` 完全相同;wildcard handler 在 type-matched handler 之後觸發。~80 bytes gzip 成本,保留以讓 mitt 用戶遷移成本歸零。
31
- - **`emit` 走訪前先 snapshot handler array。** Handler 內 unsubscribe 自己不會跳過下一個兄弟(mitt 自 2.x 就有,保留)。
32
- - **Functional、可解構。** `const { on, emit } = bus` 可行 ── 任何地方都不抓 `this`。
33
-
34
- 明確**不做**的:不做 async event bus(handler 同步)、不做 namespaced bus(`user.*` 那種 wildcard 不在)、不做 priority queue、不做 transport。它就是 in-process 同步 fan-out 原語 ── 不多不少。
35
-
36
- ---
37
-
38
- ## Quick Start
7
+ ## 安裝
39
8
 
40
9
  ```bash
41
10
  pnpm add aieventjs
42
11
  ```
43
12
 
44
- ```typescript
13
+ ```ts
45
14
  import { createEmitter } from "aieventjs";
15
+ ```
16
+
17
+ ## 快速開始
46
18
 
19
+ ```ts
47
20
  type Events = {
48
- "user:login": { id: string };
49
- "user:logout": void;
50
- "score:tick": { delta: number };
21
+ "score/change": { value: number };
22
+ "scene/end": void;
51
23
  };
52
24
 
53
- const bus = createEmitter<Events>();
25
+ const events = createEmitter<Events>();
54
26
 
55
- // 1. 訂閱;拿到 unsubscribe handle。
56
- const off = bus.on("user:login", (u) => console.log("hi", u.id));
27
+ const off = events.on("score/change", ({ value }) => {
28
+ console.log(value);
29
+ });
57
30
 
58
- // 2. 或接到 AbortSignal 走 framework 原生 cleanup。
59
- const ctrl = new AbortController();
60
- bus.on("score:tick", (e) => render(e.delta), { signal: ctrl.signal });
61
-
62
- // 3. Wildcard 收 (type, payload) ── 在 type-matched handler 之後觸發。
63
- bus.on("*", (type, payload) => trace(type, payload));
64
-
65
- // 4. 派送。
66
- bus.emit("user:login", { id: "alice" });
67
-
68
- // 5. 拆除。
31
+ events.on("*", (type, payload) => console.log(type, payload), { sampleRate: 0.1 });
32
+ events.emit("score/change", { value: 10 });
69
33
  off();
70
- ctrl.abort();
71
- bus.dispose(); // 冪等;dispose 後再呼叫拋 EmitterDisposedError
34
+ events.dispose();
72
35
  ```
73
36
 
74
- `createEmitter()` 回傳的 method 不抓 `this` ── `const { on, emit } = bus` 解構沒問題。
75
-
76
- > **event map 要用 `type` 宣告、不要用 `interface`。** `Events` generic 受限於 `Record<string, unknown>`。**純** TypeScript `interface` 沒有隱含的 index signature,傳進去會違反 constraint、報 *"Index signature for type 'string' is missing in type ..."*;`type` 物件字面值則在結構上滿足它。(加了顯式 index signature、或 `extends Record<string, unknown>` 的 `interface` 也能通過,但會把 `keyof Events` 擴成 `string`、失去嚴格的事件名稱檢查,故建議用 `type`。)
77
- >
78
- > ```typescript
79
- > // ❌ interface ── 違反 Record<string, unknown> constraint(TS2344)
80
- > interface Events { "user:login": { id: string } }
81
- > const bus = createEmitter<Events>();
82
- >
83
- > // ✅ type ── 滿足 constraint
84
- > type Events = { "user:login": { id: string } };
85
- > const bus = createEmitter<Events>();
86
- > ```
87
-
88
- ---
89
-
90
- ## 能做 / 不做
91
-
92
- | 會做(v1) | 不會做 |
93
- | ---------------------------------------------------------- | ----------------------------------------------------- |
94
- | Typed `createEmitter<Events>()` | 無型別字串 key bus(型別本身就是賣點) |
95
- | `on()` 回傳 unsubscribe function | Async / promise-returning handler(同步 only) |
96
- | `once(type, handler)` + `on(..., { once: true })` | Namespaced wildcard(`"user.*"`)── 不在範圍 |
97
- | `on(..., { signal })` ── `AbortSignal` cleanup | Priority / weight / ordering 提示 |
98
- | Wildcard `"*"` handler ── `(type, payload)` | Cross-context transport(去用 `aibridgejs`) |
99
- | `dispose()` 冪等;dispose 後呼叫拋錯 | Error-event 特殊處理(Node EventEmitter 風格不做) |
100
- | `emit` 走訪前 snapshot handler array(reentrant 安全) | 持久化 / replay(不是它的工作) |
101
- | Method 可解構(`const { on, emit } = bus`) | 零配置 `emit`(每次派送需 snapshot,re-entrancy 安全所需)|
102
- | `on('*', fn, { sampleRate })` ── 機率性派送(wildcard only)| |
103
- | `on(type, fn, { throttleMs })` ── per-handler leading-edge throttle,typed **與** wildcard 皆可(如每幀觸發的 `credits/change` HUD 事件)| |
104
- | `createEmitter({ captureHandlerErrors })` ── opt-in 錯誤策略;`OnOptions.captureErrors` 個別 handler 覆蓋 | |
105
-
106
- ---
107
-
108
- ## API 草稿
109
-
110
- ```typescript
111
- type EventHandler<P> = (payload: P) => void;
112
-
113
- type WildcardHandler<Events extends Record<string, unknown>> =
114
- <K extends keyof Events>(type: K, payload: Events[K]) => void;
115
-
116
- interface OnOptions {
117
- signal?: AbortSignal;
118
- once?: boolean;
119
- captureErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void); // 僅限 typed handler
120
- sampleRate?: number; // wildcard "*" only — 機率 (0, 1]
121
- throttleMs?: number; // typed 或 wildcard — per-handler leading-edge throttle,使用 Date.now()
122
- // 注意:Date.now() 非單調時鐘;系統時鐘往回跳時,handler 會被靜默
123
- // mute 直到 wall time 再次超過紀錄的時間戳。切換 performance.now()
124
- // 延至下個 minor。(EVT-R-02)
125
- }
126
-
127
- interface EmitterOptions {
128
- // 預設錯誤策略。undefined/false(預設):第一個拋錯即中斷派送。
129
- // true:吞掉錯誤並繼續走完所有 handler。
130
- // (err, type, payload) => void:每個拋錯的 handler 呼叫一次 callback(若此 callback 自身拋錯,會被靜默忽略並繼續派送)。
131
- // OnOptions.captureErrors 可覆蓋個別 handler 的策略。
132
- captureHandlerErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);
133
- }
134
-
135
- interface Emitter<Events extends Record<string, unknown>> {
136
- on<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>, opts?: OnOptions): () => void;
137
- on(type: "*", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;
138
- once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;
139
- // 注意:once() 只接受 typed 事件 key。wildcard-once 請改用
140
- // on("*", handler, { once: true })——handler 收 (type, payload)
141
- // 而非 (payload)。型別層修正延至下個 minor。(EVT-B-02)
142
- off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;
143
- off(type: "*", handler?: WildcardHandler<Events>): void;
144
- emit<K extends keyof Events>(type: K, payload: Events[K]): void;
145
- clear(): void;
146
- dispose(): void;
147
- readonly disposed: boolean;
148
- }
149
-
150
- class EmitterError extends Error {}
151
- class EmitterDisposedError extends Error {}
152
-
153
- function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(
154
- opts?: EmitterOptions,
155
- ): Emitter<Events>;
156
- ```
37
+ ## 核心 API
157
38
 
158
- 完整 JSDoc 在 [`src/index.ts`](src/index.ts)。
39
+ - `createEmitter<Events>(options?)` 建立 typed emitter。
40
+ - `on(type, handler, options?)` 訂閱並回傳 unsubscribe。
41
+ - `on("*", wildcard, options?)` 訂閱所有事件,且在 typed handlers 之後呼叫。
42
+ - `once(type, handler)` 是 typed one-shot handler 的 shorthand。
43
+ - `off(type, handler?)`、`clear()`、`dispose()` 用不同 scope 移除 handlers。
44
+ - `emit(type, payload)` 同步 dispatch,且會先 snapshot handler list。
45
+ - Options:`signal`、`once`、`captureErrors`、wildcard-only `sampleRate`、typed/wildcard `throttleMs`。
159
46
 
160
- ---
47
+ ## 注意事項
161
48
 
162
- ## Roadmap
49
+ - 預設錯誤策略與 mitt 類似:第一個 throw 的 handler 會中止 dispatch。可用 `captureHandlerErrors` 或單一 handler 的 `captureErrors` 改成吞掉/回報後繼續。
50
+ - Wildcard handler 收到 `(type, payload)`,不是只有 payload。
51
+ - wildcard once 請用 `on("*", handler, { once: true })`。`once("*")` 不屬於 typed public overload。
52
+ - `throttleMs` 使用 `Date.now()`。若系統時間往回跳,throttled handler 可能靜默到 wall time 追上為止。
53
+ - `sampleRate` 只支援 wildcard,且每次 dispatch 以 `Math.random()` 取樣。
54
+ - `dispose()` 是永久 teardown;dispose 後多數 API 會丟 `EmitterDisposedError`,cleanup 類呼叫則維持 no-op。
163
55
 
164
- | 版本 | 加入內容 |
165
- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------- |
166
- | **0.0.1** | Scaffold 落地 ── 凍結 API surface 為 `throw` stub;完整配置 + CI 跑得起來。 |
167
- | **0.1.0** | 第一個 npm release。`on` / `once` / `off` / `emit` / `clear` / `dispose` 實作完;coverage ≥ 95/90/100/100;≤ 800 B gzip(strict-TS 額外負擔實測落在 ~747 B)。 |
168
- | **0.3.0** | `captureHandlerErrors` + wildcard sampling/throttling。v0.2 編號跳過以配合四套件 v0.3 同步釋出。 |
169
- | **0.4.0** | 相依整理 + 穩定凍結:移除未使用的 `tsx`、`fast-check` 對齊 `^4.8.0`、凍結 0.3.x 公開 API surface。無 runtime API 異動;bundle 與 0.3.1 完全相同。 |
170
- | **0.6+** | Async handler 追蹤(草案)── 詳見 [STABILITY.md](STABILITY.md)。 |
56
+ ## AI Context
171
57
 
172
- ---
58
+ - 短索引:[`llms.txt`](llms.txt)
59
+ - 完整生成內容:[`llms-full.txt`](llms-full.txt)
60
+ - 穩定度契約:[`STABILITY.md`](STABILITY.md)
61
+ - 目前 review backlog:[`REVIEW.md`](REVIEW.md)
62
+ - 版本紀錄:[`CHANGELOG.md`](CHANGELOG.md)
173
63
 
174
64
  ## License
175
65
 
176
- [MIT](LICENSE)。
66
+ MIT
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var E=class extends Error{name="EmitterError"},g=class extends Error{name="EmitterDisposedError"};function K(d,c){let i=d.findIndex(r=>r.u===c);if(i>=0){let r=d[i];r!==void 0&&(r.c?.(),r.c=void 0),d.splice(i,1);}}function H(d){for(let c of d)c.c?.(),c.c=void 0;}function y(d,c,i){d.push(c);let r=()=>{let f=d.indexOf(c);f>=0&&d.splice(f,1),c.c?.(),c.c=void 0;};if(i!==void 0){let f=()=>r();i.addEventListener("abort",f,{once:true}),c.c=()=>i.removeEventListener("abort",f);}return r}function j(d){let c=d?.captureHandlerErrors,i=new Map,r=[],f=false;function p(){if(f)throw new g("aieventjs: emitter has been disposed")}function h(n){let t=i.get(n);return t===void 0&&(t=[],i.set(n,t)),t}function k(n,t,o){p();let s=o?.sampleRate,u=o?.throttleMs;if(n==="*"){if(o?.captureErrors!==void 0)throw new E("aieventjs: captureErrors invalid on *")}else if(s!==void 0)throw new E("aieventjs: sampleRate wildcard-only");if(s!==void 0&&(!Number.isFinite(s)||s<=0||s>1))throw new E("aieventjs: sampleRate must be in (0,1]");if(u!==void 0&&(!Number.isFinite(u)||u<0))throw new E("aieventjs: throttleMs must be >= 0");let l=o?.signal;if(l?.aborted)return ()=>{};if(n==="*"){let v=t;if(o?.once){let m=y(r,{h:(R,M)=>{m(),v(R,M);},u:v,c:void 0,r:s,tm:u},l);return m}return y(r,{h:v,u:v,c:void 0,r:s,tm:u},l)}let e=t,a=o?.captureErrors;if(o?.once){let v={h:m=>{w(),e(m);},u:e,c:void 0,ce:a,tm:u},w=y(h(n),v,l);return w}return y(h(n),{h:e,u:e,c:void 0,ce:a,tm:u},l)}function A(n,t){return k(n,t,{once:true})}function O(n,t){if(p(),n==="*"){t===void 0?(H(r),r.length=0):K(r,t);return}let o=i.get(n);o!==void 0&&(t===void 0?(H(o),o.length=0,i.delete(n)):K(o,t));}function x(n,t,o,s){if(n===void 0||n===false)throw t;if(typeof n=="function")try{n(t,o,s);}catch{}}function W(n,t){p();let o=n,s=t,u=(i.get(o)??[]).slice(),l=r.slice();for(let e of u){if(e.tm){let a=Date.now();if(e.ts!==void 0&&a-e.ts<e.tm)continue;e.ts=a;}try{e.h(s);}catch(a){x(e.ce!==void 0?e.ce:c,a,o,s);}}for(let e of l)if(!(e.r!==void 0&&Math.random()>=e.r)){if(e.tm){let a=Date.now();if(e.ts!==void 0&&a-e.ts<e.tm)continue;e.ts=a;}try{e.h(o,s);}catch(a){x(c,a,o,s);}}}function b(){for(let n of i.values())H(n),n.length=0;H(r),i.clear(),r.length=0;}return {on:k,once:A,off:O,emit:W,clear(){p(),b();},dispose(){f||(b(),f=true);},get disposed(){return f}}}exports.EmitterDisposedError=g;exports.EmitterError=E;exports.createEmitter=j;//# sourceMappingURL=index.cjs.map
1
+ 'use strict';var E=class extends Error{name="EmitterError"},h=class extends Error{name="EmitterDisposedError"};function K(d,c){let i=d.findIndex(r=>r.u===c);if(i>=0){let r=d[i];r!==void 0&&(r.c?.(),r.c=void 0),d.splice(i,1);}}function H(d){for(let c of d)c.c?.(),c.c=void 0;}function y(d,c,i){d.push(c);let r=()=>{let f=d.indexOf(c);f>=0&&d.splice(f,1),c.c?.(),c.c=void 0;};if(i!==void 0){let f=()=>r();i.addEventListener("abort",f,{once:true}),c.c=()=>i.removeEventListener("abort",f);}return r}function j(d){let c=d?.captureHandlerErrors,i=new Map,r=[],f=false;function p(){if(f)throw new h("aieventjs: emitter has been disposed")}function g(e){let t=i.get(e);return t===void 0&&(t=[],i.set(e,t)),t}function k(e,t,o){p();let s=o?.sampleRate,u=o?.throttleMs;if(e==="*"){if(o?.captureErrors!==void 0)throw new E("aieventjs: captureErrors invalid on *")}else if(s!==void 0)throw new E("aieventjs: sampleRate wildcard-only");if(s!==void 0&&(!Number.isFinite(s)||s<=0||s>1))throw new E("aieventjs: sampleRate must be in (0,1]");if(u!==void 0&&(!Number.isFinite(u)||u<0))throw new E("aieventjs: throttleMs must be >= 0");let l=o?.signal;if(l?.aborted)return ()=>{};if(e==="*"){let v=t;if(o?.once){let m=y(r,{h:(R,M)=>{m(),v(R,M);},u:v,c:void 0,r:s,tm:u},l);return m}return y(r,{h:v,u:v,c:void 0,r:s,tm:u},l)}let n=t,a=o?.captureErrors;if(o?.once){let v={h:m=>{w(),n(m);},u:n,c:void 0,ce:a,tm:u},w=y(g(e),v,l);return w}return y(g(e),{h:n,u:n,c:void 0,ce:a,tm:u},l)}function A(e,t){return k(e,t,{once:true})}function O(e,t){if(p(),e==="*"){t===void 0?(H(r),r.length=0):K(r,t);return}let o=i.get(e);o!==void 0&&(t===void 0?(H(o),o.length=0,i.delete(e)):K(o,t));}function x(e,t,o,s){if(e===void 0||e===false)throw t;if(typeof e=="function")try{e(t,o,s);}catch{}}function W(e,t){p();let o=e,s=t,u=(i.get(o)??[]).slice(),l=r.slice();for(let n of u){if(n.tm){let a=performance.now();if(n.ts!==void 0&&a-n.ts<n.tm)continue;n.ts=a;}try{n.h(s);}catch(a){x(n.ce!==void 0?n.ce:c,a,o,s);}}for(let n of l)if(!(n.r!==void 0&&Math.random()>=n.r)){if(n.tm){let a=performance.now();if(n.ts!==void 0&&a-n.ts<n.tm)continue;n.ts=a;}try{n.h(o,s);}catch(a){x(c,a,o,s);}}}function b(){for(let e of i.values())H(e),e.length=0;H(r),i.clear(),r.length=0;}return {on:k,once:A,off:O,emit:W,clear(){p(),b();},dispose(){f||(b(),f=true);},get disposed(){return f}}}exports.EmitterDisposedError=h;exports.EmitterError=E;exports.createEmitter=j;//# sourceMappingURL=index.cjs.map
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["EmitterError","EmitterDisposedError","rmByUser","arr","user","e","flush","sub","sig","rm","i","fn","createEmitter","opts","cap","t","w","d","ck","ga","k","a","on","type","handler","sr","tm2","tp","p","ce","once","off","ap","pol","err","emit","payload","ts","ws","now","purge"],"mappings":"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","file":"index.cjs","sourcesContent":["// aieventjs — small, strict, typed event emitter for the ai*js family.\n//\n// v0.1.0: full implementation of the frozen API surface. Mitt-compatible\n// snapshot semantics, wildcard \"*\" handler, AbortSignal integration, once,\n// idempotent dispose, destructurable methods (no `this`).\n\n/**\n * Configuration for {@link createEmitter}. Controls the default error\n * policy for handlers thrown during `emit()`; per-handler\n * {@link OnOptions.captureErrors} overrides this default.\n *\n * @public\n */\nexport interface EmitterOptions {\n /**\n * Default error policy for all handlers when they throw during emit().\n *\n * - undefined / false (default) — first throw aborts dispatch (mitt-compatible).\n * - true — swallow; dispatch continues over all handlers in the snapshot.\n * - (err, type, payload) => void — invoked with the unknown error, the\n * event name as string, and the payload as unknown. If this callback\n * itself throws, the error is silently ignored.\n *\n * Per-subscription OnOptions.captureErrors overrides this for that handler.\n */\n captureHandlerErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);\n}\n\n/**\n * Handler invoked for a single typed event.\n *\n * @public\n */\nexport type EventHandler<Payload> = (payload: Payload) => void;\n\n/**\n * Handler invoked for the wildcard `\"*\"` subscription. Receives the actual\n * event type alongside the payload.\n *\n * @public\n */\nexport type WildcardHandler<Events extends Record<string, unknown>> = <K extends keyof Events>(\n type: K,\n payload: Events[K],\n) => void;\n\n/**\n * Subscription options accepted by {@link Emitter.on}.\n *\n * @public\n */\nexport interface OnOptions {\n /**\n * Aborting this signal removes the handler. The same effect as calling\n * the returned unsubscribe function. Pre-aborted signals never register.\n */\n signal?: AbortSignal;\n\n /** Auto-remove the handler after the first dispatch. Equivalent to `once()`. */\n once?: boolean;\n\n /**\n * Override emitter-level captureHandlerErrors for this handler.\n * - undefined — fall through to emitter-level.\n * - false — force re-throw, even when emitter-level is true / callback.\n * - true — swallow.\n * - (err, type, payload) => void — same semantics as the emitter-level callback.\n *\n * Throws EmitterError if set on a wildcard \"*\" subscription.\n * @invariant does not break snapshot-before-iterate semantics.\n */\n captureErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);\n\n /**\n * Wildcard \"*\" only. Probability in (0, 1] that a dispatch reaches this\n * handler. Math.random() is sampled per dispatch. Values <= 0 or > 1 are\n * rejected at on() time.\n *\n * Throws EmitterError if set on a typed handler.\n */\n sampleRate?: number;\n\n /**\n * Per-handler leading-edge throttle. Minimum milliseconds between successive\n * calls to this handler. The first dispatch after subscription always fires;\n * subsequent dispatches within `throttleMs` are dropped (not queued).\n * Uses `Date.now()`. 0 = no throttle. Non-finite or negative values are\n * rejected at `on()` time.\n *\n * Valid on both typed and wildcard `\"*\"` subscriptions (since v0.5.3); each\n * handler keeps its own throttle clock. Useful for per-event HUD throttling,\n * e.g. a `credits/change` event that fires every frame.\n *\n * @remarks\n * **Wall-clock limitation (EVT-R-02):** the throttle clock uses\n * `Date.now()`, which is not monotonic. If the system clock regresses (NTP\n * correction, manual change) by Δ ms after a dispatch, `now - e.ts` will be\n * negative and every subsequent dispatch will be dropped until wall time\n * re-passes the stored timestamp — up to Δ ms of silence with no error.\n * For most web/game use cases this is acceptable; switching to\n * `performance.now()` (monotonic) would be a surface-level behaviour change\n * and is deferred to the next minor release window. Document this trade-off\n * at integration if clock stability is a concern.\n */\n throttleMs?: number;\n}\n\n/**\n * Strongly-typed event emitter. Subscribe with {@link Emitter.on} (returns\n * an unsubscribe function), dispatch with {@link Emitter.emit}, dispose\n * with {@link Emitter.dispose} when finished.\n *\n * @typeParam Events — a string-keyed map from event name to payload type.\n * @public\n */\nexport interface Emitter<Events extends Record<string, unknown>> {\n /**\n * Subscribe to a single event type. Returns an unsubscribe function;\n * calling it (or aborting `opts.signal`) removes the handler.\n */\n on<K extends keyof Events>(\n type: K,\n handler: EventHandler<Events[K]>,\n opts?: OnOptions,\n ): () => void;\n\n /**\n * Subscribe to every event with a single handler that receives\n * `(type, payload)`. Wildcard handlers fire AFTER type-matched\n * handlers — same ordering as `mitt`.\n */\n on(type: \"*\", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;\n\n /**\n * Subscribe and auto-remove after the first dispatch. Equivalent to\n * `on(type, handler, { once: true })`.\n *\n * @remarks\n * **`\"*\"` is not a valid `type` argument for `once()`.** The public\n * overload only accepts `K extends keyof Events`; the wildcard `\"*\"` is\n * handled by the `on(\"*\", handler, { once: true })` overload instead.\n * Passing `\"*\"` to `once()` would route through `on()` with the wildcard\n * branch and invoke the handler as `(type, payload)` — the first positional\n * argument would be the event *name*, not the payload — which diverges from\n * the `EventHandler<payload>` type implied by the `once` signature. Use\n * `on(\"*\", handler, { once: true })` explicitly for wildcard-once semantics.\n * This behaviour is intentional and deferred for a type-level fix to the\n * next minor that can introduce a breaking overload change. (EVT-B-02)\n */\n once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;\n\n /**\n * Imperative unsubscribe. Prefer the unsubscribe function returned by\n * `on()` — it's faster (no reference lookup) and survives renames.\n * If `handler` is omitted, removes every handler for `type`.\n */\n off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;\n\n /**\n * Imperative wildcard unsubscribe.\n */\n off(type: \"*\", handler?: WildcardHandler<Events>): void;\n\n /**\n * Dispatch synchronously. Handlers receive `payload`; wildcard handlers\n * receive `(type, payload)`. Handler lists are snapshotted before iteration,\n * so removing a handler inside its own callback does not skip subsequent\n * handlers. By default, the first throwing handler aborts the dispatch;\n * set EmitterOptions.captureHandlerErrors (or per-handler OnOptions.captureErrors)\n * to swallow or report errors and continue.\n */\n emit<K extends keyof Events>(type: K, payload: Events[K]): void;\n\n /**\n * Remove every handler for every event (including wildcards). The\n * emitter remains usable. Use {@link dispose} for permanent teardown.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops every handler; subsequent `on` / `once` /\n * `emit` / `off` / `clear` throw {@link EmitterDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable emitter error. Thrown by `on()` when `OnOptions` violates a\n * precondition: `captureErrors` set on a wildcard `\"*\"` subscription;\n * `sampleRate` set on a typed subscription; `sampleRate` outside `(0, 1]`; or\n * `throttleMs` non-finite or negative.\n *\n * @public\n */\nexport class EmitterError extends Error {\n override readonly name = \"EmitterError\";\n}\n\n/**\n * Thrown by any emitter method called after {@link Emitter.dispose}.\n *\n * @public\n */\nexport class EmitterDisposedError extends Error {\n override readonly name = \"EmitterDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\n// Mutable `c` field (not optional `?:`) avoids exactOptionalPropertyTypes TS2412\n// when assigning undefined. Short field names reduce minified output size.\ntype ErrorPolicy = boolean | ((err: unknown, type: string, payload: unknown) => void);\n\ninterface E<H> {\n h: H; // handler (may be a once-wrapper)\n c: (() => void) | undefined; // abortCleanup\n u: H; // user-provided handler (off matching)\n // v0.3.0: per-handler error policy and throttle/sample state.\n // Fields typed as `T | undefined` (not just `T`) so that exactOptionalPropertyTypes\n // permits assigning `undefined` in object literals (avoids TS2375).\n ce?: ErrorPolicy | undefined; // captureErrors override (typed only)\n r?: number | undefined; // sampleRate (wildcard only)\n tm?: number | undefined; // throttleMs (typed or wildcard; v0.5.3)\n ts?: number | undefined; // last call timestamp — mutated during dispatch (throttle clock)\n}\n\ntype AH = EventHandler<unknown>;\ntype WH = WildcardHandler<Record<string, unknown>>;\n\n// Remove one entry by user-identity from an array; run its abort cleanup.\nfunction rmByUser<H>(arr: E<H>[], user: H): void {\n const i = arr.findIndex((e) => e.u === user);\n if (i >= 0) {\n const e = arr[i];\n if (e !== undefined) {\n e.c?.();\n e.c = undefined;\n }\n arr.splice(i, 1);\n }\n}\n\n// Flush all abort cleanups from an array (for clear / dispose).\nfunction flush<H>(arr: E<H>[]): void {\n for (const e of arr) {\n e.c?.();\n e.c = undefined;\n }\n}\n\n// Push entry onto arr, wire AbortSignal, return unsubscribe.\nfunction sub<H>(arr: E<H>[], e: E<H>, sig: AbortSignal | undefined): () => void {\n arr.push(e);\n const rm = () => {\n const i = arr.indexOf(e);\n if (i >= 0) arr.splice(i, 1);\n e.c?.();\n e.c = undefined;\n };\n if (sig !== undefined) {\n const fn = () => rm();\n sig.addEventListener(\"abort\", fn, { once: true });\n e.c = () => sig.removeEventListener(\"abort\", fn);\n }\n return rm;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a strongly-typed event emitter.\n *\n * @remarks\n * Declare the event map with a `type` alias, not an `interface`. The `Events`\n * generic is constrained to `Record<string, unknown>`, and a *plain* TypeScript\n * `interface` has no implicit index signature, so it fails the constraint with\n * *\"Index signature for type 'string' is missing in type ...\"*. A `type` object\n * literal satisfies the constraint structurally. (An `interface` with an explicit\n * index signature or `extends Record<string, unknown>` also compiles, but widens\n * `keyof Events` to `string`, losing strict event-name checking.)\n *\n * ```ts\n * // ❌ interface — fails the Record<string, unknown> constraint\n * interface Events { \"user:login\": { id: string } }\n * const bus = createEmitter<Events>(); // TS2344\n *\n * // ✅ type — satisfies the constraint\n * type Events = { \"user:login\": { id: string } };\n * const bus = createEmitter<Events>();\n * ```\n *\n * @example\n * ```ts\n * import { createEmitter } from \"aieventjs\";\n *\n * type Events = {\n * \"user:login\": { id: string };\n * \"user:logout\": void;\n * };\n *\n * const bus = createEmitter<Events>();\n *\n * const off = bus.on(\"user:login\", (u) => console.log(\"hi\", u.id));\n * bus.emit(\"user:login\", { id: \"alice\" });\n * off();\n *\n * bus.on(\"*\", (type, payload) => console.log(\"event\", type, payload));\n * ```\n *\n * @public\n */\nexport function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(\n opts?: EmitterOptions,\n): Emitter<Events> {\n const cap = opts?.captureHandlerErrors;\n\n const t: Map<string, E<AH>[]> = new Map();\n const w: E<WH>[] = [];\n let d = false;\n\n function ck(): void {\n if (d) throw new EmitterDisposedError(\"aieventjs: emitter has been disposed\");\n }\n\n // Get or create typed handler array for a key.\n function ga(k: string): E<AH>[] {\n let a = t.get(k);\n if (a === undefined) {\n a = [];\n t.set(k, a);\n }\n return a;\n }\n\n function on(type: string | \"*\", handler: AH | WH, o?: OnOptions): () => void {\n ck();\n // v0.3.0 guards: cross-domain options + range checks.\n // v0.5.3: throttleMs is now valid on typed handlers too (per-handler clock);\n // sampleRate remains wildcard-only.\n const sr = o?.sampleRate;\n const tm2 = o?.throttleMs;\n if (type === \"*\") {\n if (o?.captureErrors !== undefined)\n throw new EmitterError(\"aieventjs: captureErrors invalid on *\");\n } else {\n if (sr !== undefined) throw new EmitterError(\"aieventjs: sampleRate wildcard-only\");\n }\n if (sr !== undefined && (!Number.isFinite(sr) || sr <= 0 || sr > 1))\n throw new EmitterError(\"aieventjs: sampleRate must be in (0,1]\");\n if (tm2 !== undefined && (!Number.isFinite(tm2) || tm2 < 0))\n throw new EmitterError(\"aieventjs: throttleMs must be >= 0\");\n const sig = o?.signal;\n if (sig?.aborted) return () => {};\n\n if (type === \"*\") {\n const fn = handler as WH;\n if (o?.once) {\n const e: E<WH> = {\n h: (tp, p) => {\n rm();\n fn(tp, p);\n },\n u: fn,\n c: undefined,\n r: sr,\n tm: tm2,\n };\n const rm = sub(w, e, sig);\n return rm;\n }\n return sub(w, { h: fn, u: fn, c: undefined, r: sr, tm: tm2 }, sig);\n }\n\n const fn = handler as AH;\n const ce = o?.captureErrors;\n if (o?.once) {\n const e: E<AH> = {\n h: (p) => {\n rm();\n fn(p);\n },\n u: fn,\n c: undefined,\n ce: ce,\n tm: tm2,\n };\n const rm = sub(ga(type), e, sig);\n return rm;\n }\n return sub(ga(type), { h: fn, u: fn, c: undefined, ce: ce, tm: tm2 }, sig);\n }\n\n function once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void {\n return on(type as string, handler as AH, { once: true });\n }\n\n function off(type: string | \"*\", handler?: AH | WH): void {\n ck();\n if (type === \"*\") {\n if (handler === undefined) {\n flush(w);\n w.length = 0;\n } else {\n rmByUser(w, handler as WH);\n }\n return;\n }\n const arr = t.get(type);\n if (arr === undefined) return;\n if (handler === undefined) {\n flush(arr);\n arr.length = 0;\n t.delete(type);\n } else {\n rmByUser(arr, handler as AH);\n }\n }\n\n // Inline error policy handler — policy undefined/false → re-throw; true → swallow;\n // function → invoke and swallow; if callback throws, ignore silently.\n function ap(pol: ErrorPolicy | undefined, err: unknown, k: string, p: unknown): void {\n if (pol === undefined || pol === false) throw err;\n if (typeof pol === \"function\")\n try {\n pol(err, k, p);\n } catch {\n /* silent */\n }\n }\n\n function emit<K extends keyof Events>(type: K, payload: Events[K]): void {\n ck();\n // Both slices happen BEFORE any handler call (snapshot-before-iterate).\n const k = type as string;\n const p = payload as unknown;\n const ts = (t.get(k) ?? []).slice();\n const ws = w.slice();\n for (const e of ts) {\n if (e.tm) {\n const now = Date.now();\n if (e.ts !== undefined && now - e.ts < e.tm) continue;\n e.ts = now;\n }\n try {\n e.h(p);\n } catch (err) {\n ap(e.ce !== undefined ? e.ce : cap, err, k, p);\n }\n }\n for (const e of ws) {\n if (e.r !== undefined && Math.random() >= e.r) continue;\n if (e.tm) {\n const now = Date.now();\n if (e.ts !== undefined && now - e.ts < e.tm) continue;\n e.ts = now;\n }\n try {\n e.h(k, p as never);\n } catch (err) {\n ap(cap, err, k, p);\n }\n }\n }\n\n function purge(): void {\n for (const a of t.values()) {\n flush(a);\n a.length = 0;\n }\n flush(w);\n t.clear();\n w.length = 0;\n }\n\n return {\n on: on as Emitter<Events>[\"on\"],\n once,\n off: off as Emitter<Events>[\"off\"],\n emit,\n clear() {\n ck();\n purge();\n },\n dispose() {\n if (!d) {\n purge();\n d = true;\n }\n },\n get disposed() {\n return d;\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["EmitterError","EmitterDisposedError","rmByUser","arr","user","e","flush","sub","sig","rm","i","fn","createEmitter","opts","cap","t","w","d","ck","ga","k","a","on","type","handler","sr","tm2","tp","p","ce","once","off","ap","pol","err","emit","payload","ts","ws","now","purge"],"mappings":"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","file":"index.cjs","sourcesContent":["// aieventjs — small, strict, typed event emitter for the ai*js family.\n//\n// v0.1.0: full implementation of the frozen API surface. Mitt-compatible\n// snapshot semantics, wildcard \"*\" handler, AbortSignal integration, once,\n// idempotent dispose, destructurable methods (no `this`).\n\n/**\n * Configuration for {@link createEmitter}. Controls the default error\n * policy for handlers thrown during `emit()`; per-handler\n * {@link OnOptions.captureErrors} overrides this default.\n *\n * @public\n */\nexport interface EmitterOptions {\n /**\n * Default error policy for all handlers when they throw during emit().\n *\n * - undefined / false (default) — first throw aborts dispatch (mitt-compatible).\n * - true — swallow; dispatch continues over all handlers in the snapshot.\n * - (err, type, payload) => void — invoked with the unknown error, the\n * event name as string, and the payload as unknown. If this callback\n * itself throws, the error is silently ignored.\n *\n * Per-subscription OnOptions.captureErrors overrides this for that handler.\n */\n captureHandlerErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);\n}\n\n/**\n * Handler invoked for a single typed event.\n *\n * @public\n */\nexport type EventHandler<Payload> = (payload: Payload) => void;\n\n/**\n * Handler invoked for the wildcard `\"*\"` subscription. Receives the actual\n * event type alongside the payload.\n *\n * @public\n */\nexport type WildcardHandler<Events extends Record<string, unknown>> = <K extends keyof Events>(\n type: K,\n payload: Events[K],\n) => void;\n\n/**\n * Subscription options accepted by {@link Emitter.on}.\n *\n * @public\n */\nexport interface OnOptions {\n /**\n * Aborting this signal removes the handler. The same effect as calling\n * the returned unsubscribe function. Pre-aborted signals never register.\n */\n signal?: AbortSignal;\n\n /** Auto-remove the handler after the first dispatch. Equivalent to `once()`. */\n once?: boolean;\n\n /**\n * Override emitter-level captureHandlerErrors for this handler.\n * - undefined — fall through to emitter-level.\n * - false — force re-throw, even when emitter-level is true / callback.\n * - true — swallow.\n * - (err, type, payload) => void — same semantics as the emitter-level callback.\n *\n * Throws EmitterError if set on a wildcard \"*\" subscription.\n * @invariant does not break snapshot-before-iterate semantics.\n */\n captureErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);\n\n /**\n * Wildcard \"*\" only. Probability in (0, 1] that a dispatch reaches this\n * handler. Math.random() is sampled per dispatch. Values <= 0 or > 1 are\n * rejected at on() time.\n *\n * Throws EmitterError if set on a typed handler.\n */\n sampleRate?: number;\n\n /**\n * Per-handler leading-edge throttle. Minimum milliseconds between successive\n * calls to this handler. The first dispatch after subscription always fires;\n * subsequent dispatches within `throttleMs` are dropped (not queued).\n * Uses `performance.now()` (monotonic). 0 = no throttle. Non-finite or\n * negative values are rejected at `on()` time.\n *\n * Valid on both typed and wildcard `\"*\"` subscriptions (since v0.5.3); each\n * handler keeps its own throttle clock. Useful for per-event HUD throttling,\n * e.g. a `credits/change` event that fires every frame.\n *\n * @remarks\n * The throttle clock uses `performance.now()`, which is monotonic and\n * unaffected by system-clock corrections (NTP step-backs, manual adjustments).\n * This ensures handlers are never silently muted by a wall-clock regression.\n */\n throttleMs?: number;\n}\n\n/**\n * Strongly-typed event emitter. Subscribe with {@link Emitter.on} (returns\n * an unsubscribe function), dispatch with {@link Emitter.emit}, dispose\n * with {@link Emitter.dispose} when finished.\n *\n * @typeParam Events — a string-keyed map from event name to payload type.\n * @public\n */\nexport interface Emitter<Events extends Record<string, unknown>> {\n /**\n * Subscribe to a single event type. Returns an unsubscribe function;\n * calling it (or aborting `opts.signal`) removes the handler.\n */\n on<K extends keyof Events>(\n type: K,\n handler: EventHandler<Events[K]>,\n opts?: OnOptions,\n ): () => void;\n\n /**\n * Subscribe to every event with a single handler that receives\n * `(type, payload)`. Wildcard handlers fire AFTER type-matched\n * handlers — same ordering as `mitt`.\n */\n on(type: \"*\", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;\n\n /**\n * Subscribe and auto-remove after the first dispatch. Equivalent to\n * `on(type, handler, { once: true })`.\n *\n * @remarks\n * **`\"*\"` is not a valid `type` argument for `once()`.** The wildcard is\n * handled by the `on(\"*\", handler, { once: true })` overload instead.\n * The explicit rejection overload below ensures `once(\"*\", ...)` is a\n * compile-time error (handler typed as `never`). (EVT-B-02)\n */\n /** @internal — compile-time rejection: `once(\"*\", handler)` is a type error. */\n once(type: \"*\", handler: never): never;\n once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;\n\n /**\n * Imperative unsubscribe. Prefer the unsubscribe function returned by\n * `on()` — it's faster (no reference lookup) and survives renames.\n * If `handler` is omitted, removes every handler for `type`.\n */\n off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;\n\n /**\n * Imperative wildcard unsubscribe.\n */\n off(type: \"*\", handler?: WildcardHandler<Events>): void;\n\n /**\n * Dispatch synchronously. Handlers receive `payload`; wildcard handlers\n * receive `(type, payload)`. Handler lists are snapshotted before iteration,\n * so removing a handler inside its own callback does not skip subsequent\n * handlers. By default, the first throwing handler aborts the dispatch;\n * set EmitterOptions.captureHandlerErrors (or per-handler OnOptions.captureErrors)\n * to swallow or report errors and continue.\n */\n emit<K extends keyof Events>(type: K, payload: Events[K]): void;\n\n /**\n * Remove every handler for every event (including wildcards). The\n * emitter remains usable. Use {@link dispose} for permanent teardown.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops every handler; subsequent `on` / `once` /\n * `emit` / `off` / `clear` throw {@link EmitterDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable emitter error. Thrown by `on()` when `OnOptions` violates a\n * precondition: `captureErrors` set on a wildcard `\"*\"` subscription;\n * `sampleRate` set on a typed subscription; `sampleRate` outside `(0, 1]`; or\n * `throttleMs` non-finite or negative.\n *\n * @public\n */\nexport class EmitterError extends Error {\n override readonly name = \"EmitterError\";\n}\n\n/**\n * Thrown by any emitter method called after {@link Emitter.dispose}.\n *\n * @public\n */\nexport class EmitterDisposedError extends Error {\n override readonly name = \"EmitterDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\n// Mutable `c` field (not optional `?:`) avoids exactOptionalPropertyTypes TS2412\n// when assigning undefined. Short field names reduce minified output size.\ntype ErrorPolicy = boolean | ((err: unknown, type: string, payload: unknown) => void);\n\ninterface E<H> {\n h: H; // handler (may be a once-wrapper)\n c: (() => void) | undefined; // abortCleanup\n u: H; // user-provided handler (off matching)\n // v0.3.0: per-handler error policy and throttle/sample state.\n // Fields typed as `T | undefined` (not just `T`) so that exactOptionalPropertyTypes\n // permits assigning `undefined` in object literals (avoids TS2375).\n ce?: ErrorPolicy | undefined; // captureErrors override (typed only)\n r?: number | undefined; // sampleRate (wildcard only)\n tm?: number | undefined; // throttleMs (typed or wildcard; v0.5.3)\n ts?: number | undefined; // last call timestamp — mutated during dispatch (throttle clock)\n}\n\ntype AH = EventHandler<unknown>;\ntype WH = WildcardHandler<Record<string, unknown>>;\n\n// Remove one entry by user-identity from an array; run its abort cleanup.\nfunction rmByUser<H>(arr: E<H>[], user: H): void {\n const i = arr.findIndex((e) => e.u === user);\n if (i >= 0) {\n const e = arr[i];\n if (e !== undefined) {\n e.c?.();\n e.c = undefined;\n }\n arr.splice(i, 1);\n }\n}\n\n// Flush all abort cleanups from an array (for clear / dispose).\nfunction flush<H>(arr: E<H>[]): void {\n for (const e of arr) {\n e.c?.();\n e.c = undefined;\n }\n}\n\n// Push entry onto arr, wire AbortSignal, return unsubscribe.\nfunction sub<H>(arr: E<H>[], e: E<H>, sig: AbortSignal | undefined): () => void {\n arr.push(e);\n const rm = () => {\n const i = arr.indexOf(e);\n if (i >= 0) arr.splice(i, 1);\n e.c?.();\n e.c = undefined;\n };\n if (sig !== undefined) {\n const fn = () => rm();\n sig.addEventListener(\"abort\", fn, { once: true });\n e.c = () => sig.removeEventListener(\"abort\", fn);\n }\n return rm;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a strongly-typed event emitter.\n *\n * @remarks\n * Declare the event map with a `type` alias, not an `interface`. The `Events`\n * generic is constrained to `Record<string, unknown>`, and a *plain* TypeScript\n * `interface` has no implicit index signature, so it fails the constraint with\n * *\"Index signature for type 'string' is missing in type ...\"*. A `type` object\n * literal satisfies the constraint structurally. (An `interface` with an explicit\n * index signature or `extends Record<string, unknown>` also compiles, but widens\n * `keyof Events` to `string`, losing strict event-name checking.)\n *\n * ```ts\n * // ❌ interface — fails the Record<string, unknown> constraint\n * interface Events { \"user:login\": { id: string } }\n * const bus = createEmitter<Events>(); // TS2344\n *\n * // ✅ type — satisfies the constraint\n * type Events = { \"user:login\": { id: string } };\n * const bus = createEmitter<Events>();\n * ```\n *\n * @example\n * ```ts\n * import { createEmitter } from \"aieventjs\";\n *\n * type Events = {\n * \"user:login\": { id: string };\n * \"user:logout\": void;\n * };\n *\n * const bus = createEmitter<Events>();\n *\n * const off = bus.on(\"user:login\", (u) => console.log(\"hi\", u.id));\n * bus.emit(\"user:login\", { id: \"alice\" });\n * off();\n *\n * bus.on(\"*\", (type, payload) => console.log(\"event\", type, payload));\n * ```\n *\n * @public\n */\nexport function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(\n opts?: EmitterOptions,\n): Emitter<Events> {\n const cap = opts?.captureHandlerErrors;\n\n const t: Map<string, E<AH>[]> = new Map();\n const w: E<WH>[] = [];\n let d = false;\n\n function ck(): void {\n if (d) throw new EmitterDisposedError(\"aieventjs: emitter has been disposed\");\n }\n\n // Get or create typed handler array for a key.\n function ga(k: string): E<AH>[] {\n let a = t.get(k);\n if (a === undefined) {\n a = [];\n t.set(k, a);\n }\n return a;\n }\n\n function on(type: string | \"*\", handler: AH | WH, o?: OnOptions): () => void {\n ck();\n // v0.3.0 guards: cross-domain options + range checks.\n // v0.5.3: throttleMs is now valid on typed handlers too (per-handler clock);\n // sampleRate remains wildcard-only.\n const sr = o?.sampleRate;\n const tm2 = o?.throttleMs;\n if (type === \"*\") {\n if (o?.captureErrors !== undefined)\n throw new EmitterError(\"aieventjs: captureErrors invalid on *\");\n } else {\n if (sr !== undefined) throw new EmitterError(\"aieventjs: sampleRate wildcard-only\");\n }\n if (sr !== undefined && (!Number.isFinite(sr) || sr <= 0 || sr > 1))\n throw new EmitterError(\"aieventjs: sampleRate must be in (0,1]\");\n if (tm2 !== undefined && (!Number.isFinite(tm2) || tm2 < 0))\n throw new EmitterError(\"aieventjs: throttleMs must be >= 0\");\n const sig = o?.signal;\n if (sig?.aborted) return () => {};\n\n if (type === \"*\") {\n const fn = handler as WH;\n if (o?.once) {\n const e: E<WH> = {\n h: (tp, p) => {\n rm();\n fn(tp, p);\n },\n u: fn,\n c: undefined,\n r: sr,\n tm: tm2,\n };\n const rm = sub(w, e, sig);\n return rm;\n }\n return sub(w, { h: fn, u: fn, c: undefined, r: sr, tm: tm2 }, sig);\n }\n\n const fn = handler as AH;\n const ce = o?.captureErrors;\n if (o?.once) {\n const e: E<AH> = {\n h: (p) => {\n rm();\n fn(p);\n },\n u: fn,\n c: undefined,\n ce: ce,\n tm: tm2,\n };\n const rm = sub(ga(type), e, sig);\n return rm;\n }\n return sub(ga(type), { h: fn, u: fn, c: undefined, ce: ce, tm: tm2 }, sig);\n }\n\n function once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void {\n return on(type as string, handler as AH, { once: true });\n }\n\n function off(type: string | \"*\", handler?: AH | WH): void {\n ck();\n if (type === \"*\") {\n if (handler === undefined) {\n flush(w);\n w.length = 0;\n } else {\n rmByUser(w, handler as WH);\n }\n return;\n }\n const arr = t.get(type);\n if (arr === undefined) return;\n if (handler === undefined) {\n flush(arr);\n arr.length = 0;\n t.delete(type);\n } else {\n rmByUser(arr, handler as AH);\n }\n }\n\n // Inline error policy handler — policy undefined/false → re-throw; true → swallow;\n // function → invoke and swallow; if callback throws, ignore silently.\n function ap(pol: ErrorPolicy | undefined, err: unknown, k: string, p: unknown): void {\n if (pol === undefined || pol === false) throw err;\n if (typeof pol === \"function\")\n try {\n pol(err, k, p);\n } catch {\n /* silent */\n }\n }\n\n function emit<K extends keyof Events>(type: K, payload: Events[K]): void {\n ck();\n // Both slices happen BEFORE any handler call (snapshot-before-iterate).\n const k = type as string;\n const p = payload as unknown;\n const ts = (t.get(k) ?? []).slice();\n const ws = w.slice();\n for (const e of ts) {\n if (e.tm) {\n const now = performance.now();\n if (e.ts !== undefined && now - e.ts < e.tm) continue;\n e.ts = now;\n }\n try {\n e.h(p);\n } catch (err) {\n ap(e.ce !== undefined ? e.ce : cap, err, k, p);\n }\n }\n for (const e of ws) {\n if (e.r !== undefined && Math.random() >= e.r) continue;\n if (e.tm) {\n const now = performance.now();\n if (e.ts !== undefined && now - e.ts < e.tm) continue;\n e.ts = now;\n }\n try {\n e.h(k, p as never);\n } catch (err) {\n ap(cap, err, k, p);\n }\n }\n }\n\n function purge(): void {\n for (const a of t.values()) {\n flush(a);\n a.length = 0;\n }\n flush(w);\n t.clear();\n w.length = 0;\n }\n\n return {\n on: on as Emitter<Events>[\"on\"],\n once: once as Emitter<Events>[\"once\"],\n off: off as Emitter<Events>[\"off\"],\n emit,\n clear() {\n ck();\n purge();\n },\n dispose() {\n if (!d) {\n purge();\n d = true;\n }\n },\n get disposed() {\n return d;\n },\n };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -68,23 +68,17 @@ interface OnOptions {
68
68
  * Per-handler leading-edge throttle. Minimum milliseconds between successive
69
69
  * calls to this handler. The first dispatch after subscription always fires;
70
70
  * subsequent dispatches within `throttleMs` are dropped (not queued).
71
- * Uses `Date.now()`. 0 = no throttle. Non-finite or negative values are
72
- * rejected at `on()` time.
71
+ * Uses `performance.now()` (monotonic). 0 = no throttle. Non-finite or
72
+ * negative values are rejected at `on()` time.
73
73
  *
74
74
  * Valid on both typed and wildcard `"*"` subscriptions (since v0.5.3); each
75
75
  * handler keeps its own throttle clock. Useful for per-event HUD throttling,
76
76
  * e.g. a `credits/change` event that fires every frame.
77
77
  *
78
78
  * @remarks
79
- * **Wall-clock limitation (EVT-R-02):** the throttle clock uses
80
- * `Date.now()`, which is not monotonic. If the system clock regresses (NTP
81
- * correction, manual change) by Δ ms after a dispatch, `now - e.ts` will be
82
- * negative and every subsequent dispatch will be dropped until wall time
83
- * re-passes the stored timestamp — up to Δ ms of silence with no error.
84
- * For most web/game use cases this is acceptable; switching to
85
- * `performance.now()` (monotonic) would be a surface-level behaviour change
86
- * and is deferred to the next minor release window. Document this trade-off
87
- * at integration if clock stability is a concern.
79
+ * The throttle clock uses `performance.now()`, which is monotonic and
80
+ * unaffected by system-clock corrections (NTP step-backs, manual adjustments).
81
+ * This ensures handlers are never silently muted by a wall-clock regression.
88
82
  */
89
83
  throttleMs?: number;
90
84
  }
@@ -113,17 +107,13 @@ interface Emitter<Events extends Record<string, unknown>> {
113
107
  * `on(type, handler, { once: true })`.
114
108
  *
115
109
  * @remarks
116
- * **`"*"` is not a valid `type` argument for `once()`.** The public
117
- * overload only accepts `K extends keyof Events`; the wildcard `"*"` is
110
+ * **`"*"` is not a valid `type` argument for `once()`.** The wildcard is
118
111
  * handled by the `on("*", handler, { once: true })` overload instead.
119
- * Passing `"*"` to `once()` would route through `on()` with the wildcard
120
- * branch and invoke the handler as `(type, payload)` — the first positional
121
- * argument would be the event *name*, not the payload — which diverges from
122
- * the `EventHandler<payload>` type implied by the `once` signature. Use
123
- * `on("*", handler, { once: true })` explicitly for wildcard-once semantics.
124
- * This behaviour is intentional and deferred for a type-level fix to the
125
- * next minor that can introduce a breaking overload change. (EVT-B-02)
112
+ * The explicit rejection overload below ensures `once("*", ...)` is a
113
+ * compile-time error (handler typed as `never`). (EVT-B-02)
126
114
  */
115
+ /** @internal — compile-time rejection: `once("*", handler)` is a type error. */
116
+ once(type: "*", handler: never): never;
127
117
  once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;
128
118
  /**
129
119
  * Imperative unsubscribe. Prefer the unsubscribe function returned by