aieventjs 0.1.0
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/LICENSE +21 -0
- package/README.md +149 -0
- package/README_ZHTW.md +149 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +141 -0
- package/dist/index.d.ts +141 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/llms-full.txt +303 -0
- package/llms.txt +5 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ysl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# aieventjs
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/aieventjs)
|
|
4
|
+
[](https://github.com/yshengliao/aieventjs/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://www.anthropic.com/claude-code)
|
|
7
|
+
[](README_ZHTW.md)
|
|
8
|
+
|
|
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.
|
|
10
|
+
|
|
11
|
+
Part of the [ai\*js micro-runtime ecosystem](https://github.com/yshengliao) — see also [aifsmjs](https://github.com/yshengliao/aifsmjs) (FSM), [aiecsjs](https://github.com/yshengliao/aiecsjs) (ECS), [aibridgejs](https://github.com/yshengliao/aibridgejs) (cross-context RPC), [aipooljs](https://github.com/yshengliao/aipooljs) (object pool), [aiquadtreejs](https://github.com/yshengliao/aiquadtreejs) (spatial partitioning), and [aiaudiojs](https://github.com/yshengliao/aiaudiojs) (Web Audio shell).
|
|
12
|
+
|
|
13
|
+
> **Status: 0.1.0.** First npm release. Full implementation shipped; all methods are live. Coverage ≥ 95/90/100/100; ≤ 800 B gzip.
|
|
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 (full evaluation in [LEARNINGS.md](../LEARNINGS.md)):
|
|
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
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm add aieventjs
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { createEmitter } from "aieventjs";
|
|
46
|
+
|
|
47
|
+
type Events = {
|
|
48
|
+
"user:login": { id: string };
|
|
49
|
+
"user:logout": void;
|
|
50
|
+
"score:tick": { delta: number };
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const bus = createEmitter<Events>();
|
|
54
|
+
|
|
55
|
+
// 1. Subscribe; capture the unsubscribe handle.
|
|
56
|
+
const off = bus.on("user:login", (u) => console.log("hi", u.id));
|
|
57
|
+
|
|
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.
|
|
69
|
+
off();
|
|
70
|
+
ctrl.abort();
|
|
71
|
+
bus.dispose(); // idempotent; post-dispose calls throw EmitterDisposedError
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`createEmitter()` returns a plain object whose methods do not depend on `this` — `const { on, emit } = bus` works fine.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Capabilities / Limitations
|
|
79
|
+
|
|
80
|
+
| Will do (v1) | Won't do |
|
|
81
|
+
| --------------------------------------------------------- | ----------------------------------------------------- |
|
|
82
|
+
| Typed `createEmitter<Events>()` | Untyped string-key bus (the type is the point) |
|
|
83
|
+
| `on()` returns unsubscribe function | Async / promise-returning handlers (sync only) |
|
|
84
|
+
| `once(type, handler)` + `on(..., { once: true })` | Namespaced wildcards (`"user.*"`) — out of scope |
|
|
85
|
+
| `on(..., { signal })` — `AbortSignal` cleanup | Priority / weight / ordering hints |
|
|
86
|
+
| Wildcard `"*"` handler — `(type, payload)` | Cross-context transport (use `aibridgejs` for that) |
|
|
87
|
+
| `dispose()` idempotent; post-dispose calls throw | Error-event special casing (Node EventEmitter style) |
|
|
88
|
+
| Handler-array snapshot on `emit` (safe re-entrancy) | Persistent storage / replay (not its job) |
|
|
89
|
+
| Destructurable methods (`const { on, emit } = bus`) | Zero-allocation `emit` (one snapshot per dispatch is required for re-entrancy) |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## API sketch
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
type EventHandler<P> = (payload: P) => void;
|
|
97
|
+
|
|
98
|
+
type WildcardHandler<Events extends Record<string, unknown>> =
|
|
99
|
+
<K extends keyof Events>(type: K, payload: Events[K]) => void;
|
|
100
|
+
|
|
101
|
+
interface OnOptions {
|
|
102
|
+
signal?: AbortSignal;
|
|
103
|
+
once?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface EmitterOptions {
|
|
107
|
+
// Reserved for 0.2.0 — collect throwing handlers into an AggregateError
|
|
108
|
+
// instead of aborting the dispatch. Ignored in 0.1.0.
|
|
109
|
+
captureHandlerErrors?: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface Emitter<Events extends Record<string, unknown>> {
|
|
113
|
+
on<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>, opts?: OnOptions): () => void;
|
|
114
|
+
on(type: "*", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;
|
|
115
|
+
once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;
|
|
116
|
+
off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;
|
|
117
|
+
off(type: "*", handler?: WildcardHandler<Events>): void;
|
|
118
|
+
emit<K extends keyof Events>(type: K, payload: Events[K]): void;
|
|
119
|
+
clear(): void;
|
|
120
|
+
dispose(): void;
|
|
121
|
+
readonly disposed: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
class EmitterError extends Error {}
|
|
125
|
+
class EmitterDisposedError extends Error {}
|
|
126
|
+
|
|
127
|
+
function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(
|
|
128
|
+
opts?: EmitterOptions,
|
|
129
|
+
): Emitter<Events>;
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Full JSDoc lives in [`src/index.ts`](src/index.ts).
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Roadmap
|
|
137
|
+
|
|
138
|
+
| Version | Adds |
|
|
139
|
+
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
140
|
+
| **0.0.1** | Scaffold landed — frozen API surface as a `throw` stub; full config + CI walk clean. |
|
|
141
|
+
| **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). |
|
|
142
|
+
| **0.2.0** | `captureHandlerErrors` option — collect throwing handlers into an `AggregateError` instead of aborting the dispatch. Opt-in. |
|
|
143
|
+
| **0.3+** | TBD — driven by integration feedback. Candidates: typed channel groups, structured-clone payload check, batch `emit`. |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
[MIT](LICENSE).
|
package/README_ZHTW.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# aieventjs
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/aieventjs)
|
|
4
|
+
[](https://github.com/yshengliao/aieventjs/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://www.anthropic.com/claude-code)
|
|
7
|
+
[](README.md)
|
|
8
|
+
|
|
9
|
+
> 一個小而嚴格的 typed event emitter ── `on()` 回傳 unsubscribe function、內建 `once`、`AbortSignal` 一級公民、`dispose()` 冪等、保留 wildcard `*` handler。形似 mitt 的 API、但裡裡外外都是 ai\*js convention。
|
|
10
|
+
|
|
11
|
+
隸屬 [ai\*js micro-runtime 生態系](https://github.com/yshengliao) ─ 另見 [aifsmjs](https://github.com/yshengliao/aifsmjs)(FSM)、[aiecsjs](https://github.com/yshengliao/aiecsjs)(ECS)、[aibridgejs](https://github.com/yshengliao/aibridgejs)(cross-context RPC)、[aipooljs](https://github.com/yshengliao/aipooljs)(物件池)、[aiquadtreejs](https://github.com/yshengliao/aiquadtreejs)(空間分割)、[aiaudiojs](https://github.com/yshengliao/aiaudiojs)(Web Audio 薄殼)。
|
|
12
|
+
|
|
13
|
+
> **狀態:0.0.1 scaffold。** 下方 API surface 已凍結;實作在 0.1.0 落地。目前 `createEmitter` 被呼叫會直接 `throw "not implemented"`。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 為什麼有 aieventjs
|
|
18
|
+
|
|
19
|
+
為什麼不直接用 `mitt`?老實說 `mitt` 對很多專案來說是正確選擇 ── MIT、~282 bytes gzip、API 本身造型很好。我們評估過之後選擇自寫。三個理由(完整評估在 [LEARNINGS.md](../LEARNINGS.md)):
|
|
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
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm add aieventjs
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { createEmitter } from "aieventjs";
|
|
46
|
+
|
|
47
|
+
type Events = {
|
|
48
|
+
"user:login": { id: string };
|
|
49
|
+
"user:logout": void;
|
|
50
|
+
"score:tick": { delta: number };
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const bus = createEmitter<Events>();
|
|
54
|
+
|
|
55
|
+
// 1. 訂閱;拿到 unsubscribe handle。
|
|
56
|
+
const off = bus.on("user:login", (u) => console.log("hi", u.id));
|
|
57
|
+
|
|
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. 拆除。
|
|
69
|
+
off();
|
|
70
|
+
ctrl.abort();
|
|
71
|
+
bus.dispose(); // 冪等;dispose 後再呼叫拋 EmitterDisposedError
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`createEmitter()` 回傳的 method 不抓 `this` ── `const { on, emit } = bus` 解構沒問題。
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 能做 / 不做
|
|
79
|
+
|
|
80
|
+
| 會做(v1) | 不會做 |
|
|
81
|
+
| ---------------------------------------------------------- | ----------------------------------------------------- |
|
|
82
|
+
| Typed `createEmitter<Events>()` | 無型別字串 key bus(型別本身就是賣點) |
|
|
83
|
+
| `on()` 回傳 unsubscribe function | Async / promise-returning handler(同步 only) |
|
|
84
|
+
| `once(type, handler)` + `on(..., { once: true })` | Namespaced wildcard(`"user.*"`)── 不在範圍 |
|
|
85
|
+
| `on(..., { signal })` ── `AbortSignal` cleanup | Priority / weight / ordering 提示 |
|
|
86
|
+
| Wildcard `"*"` handler ── `(type, payload)` | Cross-context transport(去用 `aibridgejs`) |
|
|
87
|
+
| `dispose()` 冪等;dispose 後呼叫拋錯 | Error-event 特殊處理(Node EventEmitter 風格不做) |
|
|
88
|
+
| `emit` 走訪前 snapshot handler array(reentrant 安全) | 持久化 / replay(不是它的工作) |
|
|
89
|
+
| Method 可解構(`const { on, emit } = bus`) | 零配置 `emit`(每次派送需 snapshot,re-entrancy 安全所需)|
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## API 草稿
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
type EventHandler<P> = (payload: P) => void;
|
|
97
|
+
|
|
98
|
+
type WildcardHandler<Events extends Record<string, unknown>> =
|
|
99
|
+
<K extends keyof Events>(type: K, payload: Events[K]) => void;
|
|
100
|
+
|
|
101
|
+
interface OnOptions {
|
|
102
|
+
signal?: AbortSignal;
|
|
103
|
+
once?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface EmitterOptions {
|
|
107
|
+
// 預留給 0.2.0 ── 把拋錯的 handler 收進 AggregateError,
|
|
108
|
+
// 不中斷派送。0.1.0 忽略此欄位。
|
|
109
|
+
captureHandlerErrors?: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface Emitter<Events extends Record<string, unknown>> {
|
|
113
|
+
on<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>, opts?: OnOptions): () => void;
|
|
114
|
+
on(type: "*", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;
|
|
115
|
+
once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;
|
|
116
|
+
off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;
|
|
117
|
+
off(type: "*", handler?: WildcardHandler<Events>): void;
|
|
118
|
+
emit<K extends keyof Events>(type: K, payload: Events[K]): void;
|
|
119
|
+
clear(): void;
|
|
120
|
+
dispose(): void;
|
|
121
|
+
readonly disposed: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
class EmitterError extends Error {}
|
|
125
|
+
class EmitterDisposedError extends Error {}
|
|
126
|
+
|
|
127
|
+
function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(
|
|
128
|
+
opts?: EmitterOptions,
|
|
129
|
+
): Emitter<Events>;
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
完整 JSDoc 在 [`src/index.ts`](src/index.ts)。
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Roadmap
|
|
137
|
+
|
|
138
|
+
| 版本 | 加入內容 |
|
|
139
|
+
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
140
|
+
| **0.0.1** | Scaffold 落地 ── 凍結 API surface 為 `throw` stub;完整配置 + CI 跑得起來。 |
|
|
141
|
+
| **0.1.0** | 第一個 npm release。`on` / `once` / `off` / `emit` / `clear` / `dispose` 實作完;coverage ≥ 95/90/100/100;≤ 800 B gzip(strict-TS 額外負擔實測落在 ~747 B)。 |
|
|
142
|
+
| **0.2.0** | `captureHandlerErrors` option ── 把拋錯的 handler 收進 `AggregateError`,不中斷派送。Opt-in。 |
|
|
143
|
+
| **0.3+** | TBD ── 由整合回饋驅動。候選:typed channel group、structured-clone payload 驗證、batch `emit`。 |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
[MIT](LICENSE)。
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var y=class extends Error{name="EmitterError"},H=class extends Error{name="EmitterDisposedError"};function K(s,e){let o=s.findIndex(i=>i.u===e);if(o>=0){let i=s[o];i!==void 0&&(i.c?.(),i.c=void 0),s.splice(o,1);}}function v(s){for(let e of s)e.c?.(),e.c=void 0;}function E(s,e,o){s.push(e);let i=()=>{let r=s.indexOf(e);r>=0&&s.splice(r,1),e.c?.(),e.c=void 0;};if(o!==void 0){let r=()=>i();o.addEventListener("abort",r,{once:true}),e.c=()=>o.removeEventListener("abort",r);}return i}function O(s){let e=new Map,o=[],i=false;function r(){if(i)throw new H("aieventjs: emitter has been disposed")}function p(n){let t=e.get(n);return t===void 0&&(t=[],e.set(n,t)),t}function g(n,t,c){r();let f=c?.signal;if(f?.aborted)return ()=>{};if(n==="*"){let d=t;if(c?.once){let u=E(o,{h:(w,A)=>{u(),d(w,A);},u:d,c:void 0},f);return u}return E(o,{h:d,u:d,c:void 0},f)}let a=t;if(c?.once){let d={h:u=>{l(),a(u);},u:a,c:void 0},l=E(p(n),d,f);return l}return E(p(n),{h:a,u:a,c:void 0},f)}function m(n,t){return g(n,t,{once:true})}function k(n,t){if(r(),n==="*"){t===void 0?(v(o),o.length=0):K(o,t);return}let c=e.get(n);c!==void 0&&(t===void 0?(v(c),e.delete(n)):K(c,t));}function h(n,t){r();let c=n,f=(e.get(c)??[]).slice(),a=o.slice();for(let d of f)d.h(t);for(let d of a)d.h(n,t);}function x(){for(let n of e.values())v(n);v(o),e.clear(),o.length=0;}return {on:g,once:m,off:k,emit:h,clear(){r(),x();},dispose(){i||(x(),i=true);},get disposed(){return i}}}exports.EmitterDisposedError=H;exports.EmitterError=y;exports.createEmitter=O;//# sourceMappingURL=index.cjs.map
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["EmitterError","EmitterDisposedError","rmByUser","arr","user","i","e","flush","sub","sig","rm","fn","createEmitter","opts","t","w","d","ck","ga","k","a","on","type","handler","o","tp","p","once","off","emit","payload","ts","ws","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}. Reserved for future options\n * (e.g. handler error policy); the scaffold accepts an empty object.\n *\n * @public\n */\nexport interface EmitterOptions {\n /**\n * If true, throwing handlers do not abort the remaining dispatch — the\n * error is collected and re-thrown as a single `AggregateError` after\n * all handlers ran. Default `false` (mitt-compatible: first throw wins).\n *\n * Reserved for 0.2.0; ignored in 0.1.0.\n */\n captureHandlerErrors?: boolean;\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/**\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 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 list is snapshotted before\n * iteration, so removing a handler inside its own callback does not\n * skip subsequent handlers. The first throwing handler aborts the\n * dispatch; remaining handlers (typed and wildcard) do not fire.\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. Reserved for future precondition violations.\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.\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}\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 * @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 void opts;\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 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 };\n const rm = sub(w, e, sig);\n return rm;\n }\n return sub(w, { h: fn, u: fn, c: undefined }, sig);\n }\n\n const fn = handler as AH;\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 };\n const rm = sub(ga(type), e, sig);\n return rm;\n }\n return sub(ga(type), { h: fn, u: fn, c: undefined }, 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 t.delete(type);\n } else {\n rmByUser(arr, handler as AH);\n }\n }\n\n function emit<K extends keyof Events>(type: K, payload: Events[K]): void {\n ck();\n // Snapshot BOTH arrays before either dispatch loop, so a typed handler\n // that adds/removes a wildcard handler during dispatch does not affect\n // the wildcards that fire in this same emit (spec §2 / §5).\n const k = type as string;\n const ts = (t.get(k) ?? []).slice();\n const ws = w.slice();\n for (const e of ts) e.h(payload as unknown);\n for (const e of ws) e.h(type as string, payload);\n }\n\n function purge(): void {\n for (const a of t.values()) flush(a);\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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for {@link createEmitter}. Reserved for future options
|
|
3
|
+
* (e.g. handler error policy); the scaffold accepts an empty object.
|
|
4
|
+
*
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
interface EmitterOptions {
|
|
8
|
+
/**
|
|
9
|
+
* If true, throwing handlers do not abort the remaining dispatch — the
|
|
10
|
+
* error is collected and re-thrown as a single `AggregateError` after
|
|
11
|
+
* all handlers ran. Default `false` (mitt-compatible: first throw wins).
|
|
12
|
+
*
|
|
13
|
+
* Reserved for 0.2.0; ignored in 0.1.0.
|
|
14
|
+
*/
|
|
15
|
+
captureHandlerErrors?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Handler invoked for a single typed event.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
type EventHandler<Payload> = (payload: Payload) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Handler invoked for the wildcard `"*"` subscription. Receives the actual
|
|
25
|
+
* event type alongside the payload.
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
type WildcardHandler<Events extends Record<string, unknown>> = <K extends keyof Events>(type: K, payload: Events[K]) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Subscription options accepted by {@link Emitter.on}.
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
interface OnOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Aborting this signal removes the handler. The same effect as calling
|
|
38
|
+
* the returned unsubscribe function. Pre-aborted signals never register.
|
|
39
|
+
*/
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
/** Auto-remove the handler after the first dispatch. Equivalent to `once()`. */
|
|
42
|
+
once?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Strongly-typed event emitter. Subscribe with {@link Emitter.on} (returns
|
|
46
|
+
* an unsubscribe function), dispatch with {@link Emitter.emit}, dispose
|
|
47
|
+
* with {@link Emitter.dispose} when finished.
|
|
48
|
+
*
|
|
49
|
+
* @typeParam Events — a string-keyed map from event name to payload type.
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
interface Emitter<Events extends Record<string, unknown>> {
|
|
53
|
+
/**
|
|
54
|
+
* Subscribe to a single event type. Returns an unsubscribe function;
|
|
55
|
+
* calling it (or aborting `opts.signal`) removes the handler.
|
|
56
|
+
*/
|
|
57
|
+
on<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>, opts?: OnOptions): () => void;
|
|
58
|
+
/**
|
|
59
|
+
* Subscribe to every event with a single handler that receives
|
|
60
|
+
* `(type, payload)`. Wildcard handlers fire AFTER type-matched
|
|
61
|
+
* handlers — same ordering as `mitt`.
|
|
62
|
+
*/
|
|
63
|
+
on(type: "*", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;
|
|
64
|
+
/**
|
|
65
|
+
* Subscribe and auto-remove after the first dispatch. Equivalent to
|
|
66
|
+
* `on(type, handler, { once: true })`.
|
|
67
|
+
*/
|
|
68
|
+
once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;
|
|
69
|
+
/**
|
|
70
|
+
* Imperative unsubscribe. Prefer the unsubscribe function returned by
|
|
71
|
+
* `on()` — it's faster (no reference lookup) and survives renames.
|
|
72
|
+
* If `handler` is omitted, removes every handler for `type`.
|
|
73
|
+
*/
|
|
74
|
+
off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;
|
|
75
|
+
/**
|
|
76
|
+
* Imperative wildcard unsubscribe.
|
|
77
|
+
*/
|
|
78
|
+
off(type: "*", handler?: WildcardHandler<Events>): void;
|
|
79
|
+
/**
|
|
80
|
+
* Dispatch synchronously. Handlers receive `payload`; wildcard handlers
|
|
81
|
+
* receive `(type, payload)`. Handler list is snapshotted before
|
|
82
|
+
* iteration, so removing a handler inside its own callback does not
|
|
83
|
+
* skip subsequent handlers. The first throwing handler aborts the
|
|
84
|
+
* dispatch; remaining handlers (typed and wildcard) do not fire.
|
|
85
|
+
*/
|
|
86
|
+
emit<K extends keyof Events>(type: K, payload: Events[K]): void;
|
|
87
|
+
/**
|
|
88
|
+
* Remove every handler for every event (including wildcards). The
|
|
89
|
+
* emitter remains usable. Use {@link dispose} for permanent teardown.
|
|
90
|
+
*/
|
|
91
|
+
clear(): void;
|
|
92
|
+
/**
|
|
93
|
+
* Idempotent teardown. Drops every handler; subsequent `on` / `once` /
|
|
94
|
+
* `emit` / `off` / `clear` throw {@link EmitterDisposedError}.
|
|
95
|
+
*/
|
|
96
|
+
dispose(): void;
|
|
97
|
+
/** `true` once {@link dispose} has been called. */
|
|
98
|
+
readonly disposed: boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Recoverable emitter error. Reserved for future precondition violations.
|
|
102
|
+
*
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
declare class EmitterError extends Error {
|
|
106
|
+
readonly name = "EmitterError";
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Thrown by any emitter method called after {@link Emitter.dispose}.
|
|
110
|
+
*
|
|
111
|
+
* @public
|
|
112
|
+
*/
|
|
113
|
+
declare class EmitterDisposedError extends Error {
|
|
114
|
+
readonly name = "EmitterDisposedError";
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Construct a strongly-typed event emitter.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* import { createEmitter } from "aieventjs";
|
|
122
|
+
*
|
|
123
|
+
* type Events = {
|
|
124
|
+
* "user:login": { id: string };
|
|
125
|
+
* "user:logout": void;
|
|
126
|
+
* };
|
|
127
|
+
*
|
|
128
|
+
* const bus = createEmitter<Events>();
|
|
129
|
+
*
|
|
130
|
+
* const off = bus.on("user:login", (u) => console.log("hi", u.id));
|
|
131
|
+
* bus.emit("user:login", { id: "alice" });
|
|
132
|
+
* off();
|
|
133
|
+
*
|
|
134
|
+
* bus.on("*", (type, payload) => console.log("event", type, payload));
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* @public
|
|
138
|
+
*/
|
|
139
|
+
declare function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(opts?: EmitterOptions): Emitter<Events>;
|
|
140
|
+
|
|
141
|
+
export { type Emitter, EmitterDisposedError, EmitterError, type EmitterOptions, type EventHandler, type OnOptions, type WildcardHandler, createEmitter };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for {@link createEmitter}. Reserved for future options
|
|
3
|
+
* (e.g. handler error policy); the scaffold accepts an empty object.
|
|
4
|
+
*
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
interface EmitterOptions {
|
|
8
|
+
/**
|
|
9
|
+
* If true, throwing handlers do not abort the remaining dispatch — the
|
|
10
|
+
* error is collected and re-thrown as a single `AggregateError` after
|
|
11
|
+
* all handlers ran. Default `false` (mitt-compatible: first throw wins).
|
|
12
|
+
*
|
|
13
|
+
* Reserved for 0.2.0; ignored in 0.1.0.
|
|
14
|
+
*/
|
|
15
|
+
captureHandlerErrors?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Handler invoked for a single typed event.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
type EventHandler<Payload> = (payload: Payload) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Handler invoked for the wildcard `"*"` subscription. Receives the actual
|
|
25
|
+
* event type alongside the payload.
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
type WildcardHandler<Events extends Record<string, unknown>> = <K extends keyof Events>(type: K, payload: Events[K]) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Subscription options accepted by {@link Emitter.on}.
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
interface OnOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Aborting this signal removes the handler. The same effect as calling
|
|
38
|
+
* the returned unsubscribe function. Pre-aborted signals never register.
|
|
39
|
+
*/
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
/** Auto-remove the handler after the first dispatch. Equivalent to `once()`. */
|
|
42
|
+
once?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Strongly-typed event emitter. Subscribe with {@link Emitter.on} (returns
|
|
46
|
+
* an unsubscribe function), dispatch with {@link Emitter.emit}, dispose
|
|
47
|
+
* with {@link Emitter.dispose} when finished.
|
|
48
|
+
*
|
|
49
|
+
* @typeParam Events — a string-keyed map from event name to payload type.
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
interface Emitter<Events extends Record<string, unknown>> {
|
|
53
|
+
/**
|
|
54
|
+
* Subscribe to a single event type. Returns an unsubscribe function;
|
|
55
|
+
* calling it (or aborting `opts.signal`) removes the handler.
|
|
56
|
+
*/
|
|
57
|
+
on<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>, opts?: OnOptions): () => void;
|
|
58
|
+
/**
|
|
59
|
+
* Subscribe to every event with a single handler that receives
|
|
60
|
+
* `(type, payload)`. Wildcard handlers fire AFTER type-matched
|
|
61
|
+
* handlers — same ordering as `mitt`.
|
|
62
|
+
*/
|
|
63
|
+
on(type: "*", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;
|
|
64
|
+
/**
|
|
65
|
+
* Subscribe and auto-remove after the first dispatch. Equivalent to
|
|
66
|
+
* `on(type, handler, { once: true })`.
|
|
67
|
+
*/
|
|
68
|
+
once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;
|
|
69
|
+
/**
|
|
70
|
+
* Imperative unsubscribe. Prefer the unsubscribe function returned by
|
|
71
|
+
* `on()` — it's faster (no reference lookup) and survives renames.
|
|
72
|
+
* If `handler` is omitted, removes every handler for `type`.
|
|
73
|
+
*/
|
|
74
|
+
off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;
|
|
75
|
+
/**
|
|
76
|
+
* Imperative wildcard unsubscribe.
|
|
77
|
+
*/
|
|
78
|
+
off(type: "*", handler?: WildcardHandler<Events>): void;
|
|
79
|
+
/**
|
|
80
|
+
* Dispatch synchronously. Handlers receive `payload`; wildcard handlers
|
|
81
|
+
* receive `(type, payload)`. Handler list is snapshotted before
|
|
82
|
+
* iteration, so removing a handler inside its own callback does not
|
|
83
|
+
* skip subsequent handlers. The first throwing handler aborts the
|
|
84
|
+
* dispatch; remaining handlers (typed and wildcard) do not fire.
|
|
85
|
+
*/
|
|
86
|
+
emit<K extends keyof Events>(type: K, payload: Events[K]): void;
|
|
87
|
+
/**
|
|
88
|
+
* Remove every handler for every event (including wildcards). The
|
|
89
|
+
* emitter remains usable. Use {@link dispose} for permanent teardown.
|
|
90
|
+
*/
|
|
91
|
+
clear(): void;
|
|
92
|
+
/**
|
|
93
|
+
* Idempotent teardown. Drops every handler; subsequent `on` / `once` /
|
|
94
|
+
* `emit` / `off` / `clear` throw {@link EmitterDisposedError}.
|
|
95
|
+
*/
|
|
96
|
+
dispose(): void;
|
|
97
|
+
/** `true` once {@link dispose} has been called. */
|
|
98
|
+
readonly disposed: boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Recoverable emitter error. Reserved for future precondition violations.
|
|
102
|
+
*
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
declare class EmitterError extends Error {
|
|
106
|
+
readonly name = "EmitterError";
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Thrown by any emitter method called after {@link Emitter.dispose}.
|
|
110
|
+
*
|
|
111
|
+
* @public
|
|
112
|
+
*/
|
|
113
|
+
declare class EmitterDisposedError extends Error {
|
|
114
|
+
readonly name = "EmitterDisposedError";
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Construct a strongly-typed event emitter.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* import { createEmitter } from "aieventjs";
|
|
122
|
+
*
|
|
123
|
+
* type Events = {
|
|
124
|
+
* "user:login": { id: string };
|
|
125
|
+
* "user:logout": void;
|
|
126
|
+
* };
|
|
127
|
+
*
|
|
128
|
+
* const bus = createEmitter<Events>();
|
|
129
|
+
*
|
|
130
|
+
* const off = bus.on("user:login", (u) => console.log("hi", u.id));
|
|
131
|
+
* bus.emit("user:login", { id: "alice" });
|
|
132
|
+
* off();
|
|
133
|
+
*
|
|
134
|
+
* bus.on("*", (type, payload) => console.log("event", type, payload));
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* @public
|
|
138
|
+
*/
|
|
139
|
+
declare function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(opts?: EmitterOptions): Emitter<Events>;
|
|
140
|
+
|
|
141
|
+
export { type Emitter, EmitterDisposedError, EmitterError, type EmitterOptions, type EventHandler, type OnOptions, type WildcardHandler, createEmitter };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var y=class extends Error{name="EmitterError"},H=class extends Error{name="EmitterDisposedError"};function K(s,e){let o=s.findIndex(i=>i.u===e);if(o>=0){let i=s[o];i!==void 0&&(i.c?.(),i.c=void 0),s.splice(o,1);}}function v(s){for(let e of s)e.c?.(),e.c=void 0;}function E(s,e,o){s.push(e);let i=()=>{let r=s.indexOf(e);r>=0&&s.splice(r,1),e.c?.(),e.c=void 0;};if(o!==void 0){let r=()=>i();o.addEventListener("abort",r,{once:true}),e.c=()=>o.removeEventListener("abort",r);}return i}function O(s){let e=new Map,o=[],i=false;function r(){if(i)throw new H("aieventjs: emitter has been disposed")}function p(n){let t=e.get(n);return t===void 0&&(t=[],e.set(n,t)),t}function g(n,t,c){r();let f=c?.signal;if(f?.aborted)return ()=>{};if(n==="*"){let d=t;if(c?.once){let u=E(o,{h:(w,A)=>{u(),d(w,A);},u:d,c:void 0},f);return u}return E(o,{h:d,u:d,c:void 0},f)}let a=t;if(c?.once){let d={h:u=>{l(),a(u);},u:a,c:void 0},l=E(p(n),d,f);return l}return E(p(n),{h:a,u:a,c:void 0},f)}function m(n,t){return g(n,t,{once:true})}function k(n,t){if(r(),n==="*"){t===void 0?(v(o),o.length=0):K(o,t);return}let c=e.get(n);c!==void 0&&(t===void 0?(v(c),e.delete(n)):K(c,t));}function h(n,t){r();let c=n,f=(e.get(c)??[]).slice(),a=o.slice();for(let d of f)d.h(t);for(let d of a)d.h(n,t);}function x(){for(let n of e.values())v(n);v(o),e.clear(),o.length=0;}return {on:g,once:m,off:k,emit:h,clear(){r(),x();},dispose(){i||(x(),i=true);},get disposed(){return i}}}export{H as EmitterDisposedError,y as EmitterError,O as createEmitter};//# sourceMappingURL=index.js.map
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["EmitterError","EmitterDisposedError","rmByUser","arr","user","i","e","flush","sub","sig","rm","fn","createEmitter","opts","t","w","d","ck","ga","k","a","on","type","handler","o","tp","p","once","off","emit","payload","ts","ws","purge"],"mappings":"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","file":"index.js","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}. Reserved for future options\n * (e.g. handler error policy); the scaffold accepts an empty object.\n *\n * @public\n */\nexport interface EmitterOptions {\n /**\n * If true, throwing handlers do not abort the remaining dispatch — the\n * error is collected and re-thrown as a single `AggregateError` after\n * all handlers ran. Default `false` (mitt-compatible: first throw wins).\n *\n * Reserved for 0.2.0; ignored in 0.1.0.\n */\n captureHandlerErrors?: boolean;\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/**\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 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 list is snapshotted before\n * iteration, so removing a handler inside its own callback does not\n * skip subsequent handlers. The first throwing handler aborts the\n * dispatch; remaining handlers (typed and wildcard) do not fire.\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. Reserved for future precondition violations.\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.\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}\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 * @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 void opts;\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 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 };\n const rm = sub(w, e, sig);\n return rm;\n }\n return sub(w, { h: fn, u: fn, c: undefined }, sig);\n }\n\n const fn = handler as AH;\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 };\n const rm = sub(ga(type), e, sig);\n return rm;\n }\n return sub(ga(type), { h: fn, u: fn, c: undefined }, 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 t.delete(type);\n } else {\n rmByUser(arr, handler as AH);\n }\n }\n\n function emit<K extends keyof Events>(type: K, payload: Events[K]): void {\n ck();\n // Snapshot BOTH arrays before either dispatch loop, so a typed handler\n // that adds/removes a wildcard handler during dispatch does not affect\n // the wildcards that fire in this same emit (spec §2 / §5).\n const k = type as string;\n const ts = (t.get(k) ?? []).slice();\n const ws = w.slice();\n for (const e of ts) e.h(payload as unknown);\n for (const e of ws) e.h(type as string, payload);\n }\n\n function purge(): void {\n for (const a of t.values()) flush(a);\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"]}
|
package/llms-full.txt
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# aieventjs — full LLM context
|
|
2
|
+
|
|
3
|
+
This file is auto-generated by `scripts/build-llms-full.mjs`. Do not edit
|
|
4
|
+
manually; instead edit the underlying source documents and re-run the
|
|
5
|
+
script. The file concatenates the canonical English documentation surface
|
|
6
|
+
so an LLM agent can ingest the full project context in a single fetch.
|
|
7
|
+
|
|
8
|
+
The short index lives at `llms.txt` (see https://llmstxt.org/).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<!-- ===== README.md ===== -->
|
|
13
|
+
|
|
14
|
+
# aieventjs
|
|
15
|
+
|
|
16
|
+
[](https://www.npmjs.com/package/aieventjs)
|
|
17
|
+
[](https://github.com/yshengliao/aieventjs/actions/workflows/ci.yml)
|
|
18
|
+
[](LICENSE)
|
|
19
|
+
[](https://www.anthropic.com/claude-code)
|
|
20
|
+
[](README_ZHTW.md)
|
|
21
|
+
|
|
22
|
+
> 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.
|
|
23
|
+
|
|
24
|
+
Part of the [ai\*js micro-runtime ecosystem](https://github.com/yshengliao) — see also [aifsmjs](https://github.com/yshengliao/aifsmjs) (FSM), [aiecsjs](https://github.com/yshengliao/aiecsjs) (ECS), [aibridgejs](https://github.com/yshengliao/aibridgejs) (cross-context RPC), [aipooljs](https://github.com/yshengliao/aipooljs) (object pool), [aiquadtreejs](https://github.com/yshengliao/aiquadtreejs) (spatial partitioning), and [aiaudiojs](https://github.com/yshengliao/aiaudiojs) (Web Audio shell).
|
|
25
|
+
|
|
26
|
+
> **Status: 0.1.0.** First npm release. Full implementation shipped; all methods are live. Coverage ≥ 95/90/100/100; ≤ 800 B gzip.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Why aieventjs
|
|
31
|
+
|
|
32
|
+
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 (full evaluation in [LEARNINGS.md](../LEARNINGS.md)):
|
|
33
|
+
|
|
34
|
+
- **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.
|
|
35
|
+
- **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.
|
|
36
|
+
- **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.
|
|
37
|
+
|
|
38
|
+
So `aieventjs` is the ai\*js-shaped event emitter:
|
|
39
|
+
|
|
40
|
+
- **`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.
|
|
41
|
+
- **`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.
|
|
42
|
+
- **`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.
|
|
43
|
+
- **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.
|
|
44
|
+
- **Handler-array snapshot on `emit`.** Removing a handler inside its own callback does not skip subsequent handlers (mitt has this since 2.x; preserved).
|
|
45
|
+
- **Functional, destructurable.** `const { on, emit } = bus` works — no `this` capture anywhere.
|
|
46
|
+
|
|
47
|
+
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.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm add aieventjs
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { createEmitter } from "aieventjs";
|
|
59
|
+
|
|
60
|
+
type Events = {
|
|
61
|
+
"user:login": { id: string };
|
|
62
|
+
"user:logout": void;
|
|
63
|
+
"score:tick": { delta: number };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const bus = createEmitter<Events>();
|
|
67
|
+
|
|
68
|
+
// 1. Subscribe; capture the unsubscribe handle.
|
|
69
|
+
const off = bus.on("user:login", (u) => console.log("hi", u.id));
|
|
70
|
+
|
|
71
|
+
// 2. Or wire to an AbortSignal for framework-native cleanup.
|
|
72
|
+
const ctrl = new AbortController();
|
|
73
|
+
bus.on("score:tick", (e) => render(e.delta), { signal: ctrl.signal });
|
|
74
|
+
|
|
75
|
+
// 3. Wildcard receives (type, payload) — fires AFTER type-matched handlers.
|
|
76
|
+
bus.on("*", (type, payload) => trace(type, payload));
|
|
77
|
+
|
|
78
|
+
// 4. Dispatch.
|
|
79
|
+
bus.emit("user:login", { id: "alice" });
|
|
80
|
+
|
|
81
|
+
// 5. Tear down.
|
|
82
|
+
off();
|
|
83
|
+
ctrl.abort();
|
|
84
|
+
bus.dispose(); // idempotent; post-dispose calls throw EmitterDisposedError
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`createEmitter()` returns a plain object whose methods do not depend on `this` — `const { on, emit } = bus` works fine.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Capabilities / Limitations
|
|
92
|
+
|
|
93
|
+
| Will do (v1) | Won't do |
|
|
94
|
+
| --------------------------------------------------------- | ----------------------------------------------------- |
|
|
95
|
+
| Typed `createEmitter<Events>()` | Untyped string-key bus (the type is the point) |
|
|
96
|
+
| `on()` returns unsubscribe function | Async / promise-returning handlers (sync only) |
|
|
97
|
+
| `once(type, handler)` + `on(..., { once: true })` | Namespaced wildcards (`"user.*"`) — out of scope |
|
|
98
|
+
| `on(..., { signal })` — `AbortSignal` cleanup | Priority / weight / ordering hints |
|
|
99
|
+
| Wildcard `"*"` handler — `(type, payload)` | Cross-context transport (use `aibridgejs` for that) |
|
|
100
|
+
| `dispose()` idempotent; post-dispose calls throw | Error-event special casing (Node EventEmitter style) |
|
|
101
|
+
| Handler-array snapshot on `emit` (safe re-entrancy) | Persistent storage / replay (not its job) |
|
|
102
|
+
| Destructurable methods (`const { on, emit } = bus`) | Zero-allocation `emit` (one snapshot per dispatch is required for re-entrancy) |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## API sketch
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
type EventHandler<P> = (payload: P) => void;
|
|
110
|
+
|
|
111
|
+
type WildcardHandler<Events extends Record<string, unknown>> =
|
|
112
|
+
<K extends keyof Events>(type: K, payload: Events[K]) => void;
|
|
113
|
+
|
|
114
|
+
interface OnOptions {
|
|
115
|
+
signal?: AbortSignal;
|
|
116
|
+
once?: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface EmitterOptions {
|
|
120
|
+
// Reserved for 0.2.0 — collect throwing handlers into an AggregateError
|
|
121
|
+
// instead of aborting the dispatch. Ignored in 0.1.0.
|
|
122
|
+
captureHandlerErrors?: boolean;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface Emitter<Events extends Record<string, unknown>> {
|
|
126
|
+
on<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>, opts?: OnOptions): () => void;
|
|
127
|
+
on(type: "*", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;
|
|
128
|
+
once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;
|
|
129
|
+
off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;
|
|
130
|
+
off(type: "*", handler?: WildcardHandler<Events>): void;
|
|
131
|
+
emit<K extends keyof Events>(type: K, payload: Events[K]): void;
|
|
132
|
+
clear(): void;
|
|
133
|
+
dispose(): void;
|
|
134
|
+
readonly disposed: boolean;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
class EmitterError extends Error {}
|
|
138
|
+
class EmitterDisposedError extends Error {}
|
|
139
|
+
|
|
140
|
+
function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(
|
|
141
|
+
opts?: EmitterOptions,
|
|
142
|
+
): Emitter<Events>;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Full JSDoc lives in [`src/index.ts`](src/index.ts).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Roadmap
|
|
150
|
+
|
|
151
|
+
| Version | Adds |
|
|
152
|
+
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
153
|
+
| **0.0.1** | Scaffold landed — frozen API surface as a `throw` stub; full config + CI walk clean. |
|
|
154
|
+
| **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). |
|
|
155
|
+
| **0.2.0** | `captureHandlerErrors` option — collect throwing handlers into an `AggregateError` instead of aborting the dispatch. Opt-in. |
|
|
156
|
+
| **0.3+** | TBD — driven by integration feedback. Candidates: typed channel groups, structured-clone payload check, batch `emit`. |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
[MIT](LICENSE).
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
<!-- ===== CHANGELOG.md ===== -->
|
|
167
|
+
|
|
168
|
+
# Changelog
|
|
169
|
+
|
|
170
|
+
All notable changes to this project are documented here. The format follows
|
|
171
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
172
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
173
|
+
|
|
174
|
+
## [Unreleased]
|
|
175
|
+
|
|
176
|
+
## [0.1.0] - 2026-05-28
|
|
177
|
+
|
|
178
|
+
### Added
|
|
179
|
+
|
|
180
|
+
- Strict typed `createEmitter<Events>()` with `on` returning an unsubscribe
|
|
181
|
+
function, `once`, wildcard `*` handler, `off`, `emit`, `clear`, `dispose`.
|
|
182
|
+
- `on(type, handler, { signal?, once? })` — `AbortSignal` integration so
|
|
183
|
+
framework code (Svelte 5 `$effect`, Vue `onScopeDispose`) cleans up listeners
|
|
184
|
+
via the standard cancellation primitive.
|
|
185
|
+
- `dispose()` idempotent; post-dispose `on` / `emit` / `once` throw
|
|
186
|
+
`EmitterDisposedError`.
|
|
187
|
+
- Handler-array snapshot on `emit` so removing a handler during dispatch does
|
|
188
|
+
not skip its successor (mitt has had this property since 2.x — preserved).
|
|
189
|
+
- Functional — methods are destructurable (`const { on, emit } = bus`).
|
|
190
|
+
- Test coverage ≥95% statements / lines / functions / ≥90% branches.
|
|
191
|
+
- Size budget: ≤ 550 B gzip.
|
|
192
|
+
- Dual ESM + CJS via `tsup`; `sideEffects: false`; zero runtime dependencies.
|
|
193
|
+
|
|
194
|
+
## [0.0.1] - 2026-05-28
|
|
195
|
+
|
|
196
|
+
### Added (scaffold)
|
|
197
|
+
|
|
198
|
+
- Full package scaffold landed (`package.json`, `tsconfig.json`,
|
|
199
|
+
`tsconfig.test.json`, `tsup.config.ts`, `vitest.config.ts`, `biome.json`,
|
|
200
|
+
`scripts/{verify-exports,check-size,build-llms-full}.mjs`,
|
|
201
|
+
`test/scaffold.test.ts`, `examples/.gitkeep`, `.github/workflows/{ci,publish}.yml`,
|
|
202
|
+
`README.md`, `README_ZHTW.md`, `CHANGELOG.md`, `CONTRIBUTING.md`,
|
|
203
|
+
`LICENSE`, `llms.txt`, `llms-full.txt`).
|
|
204
|
+
- `src/index.ts` is a `throw` stub exposing the frozen 0.1.0 API surface
|
|
205
|
+
(`createEmitter`, `Emitter<Events>`, wildcard `*` handler signature, `once`,
|
|
206
|
+
`AbortSignal`-aware `on`, `dispose`, `EmitterError`, `EmitterDisposedError`).
|
|
207
|
+
- `pnpm typecheck && pnpm lint && pnpm coverage && pnpm build &&
|
|
208
|
+
pnpm verify:exports && pnpm verify:llms && pnpm check:size` walks clean
|
|
209
|
+
against a single placeholder test.
|
|
210
|
+
- Coverage thresholds temporarily set to `0/0/0/0`; tightened to
|
|
211
|
+
`95/90/100/100` in 0.1.0.
|
|
212
|
+
- Size budget temporarily set to 3 KB gzip; tightened to the 550 B README
|
|
213
|
+
target in 0.1.0.
|
|
214
|
+
- Publish workflow exists but trigger is `workflow_dispatch` only — no
|
|
215
|
+
accidental npm release until 0.1.0.
|
|
216
|
+
|
|
217
|
+
### Decision log (carried over from LEARNINGS.md v0.3.0 cycle 預備區)
|
|
218
|
+
|
|
219
|
+
- **Not a `mitt` fork.** `mitt@3.0.1` is MIT-fork-friendly but is ~35 lines of
|
|
220
|
+
pure logic and has been unmaintained since 2023-07. Forking is equivalent
|
|
221
|
+
to rewriting, and the upstream copyright notice would carry no benefit.
|
|
222
|
+
Cleaner to write from scratch with the ai\*js conventions baked in.
|
|
223
|
+
- **Wildcard `*` is kept.** It is `mitt`'s signature feature; keeping it
|
|
224
|
+
preserves migration ergonomics for existing `mitt` users at ~80 B gzip cost.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
<!-- ===== CONTRIBUTING.md ===== -->
|
|
229
|
+
|
|
230
|
+
# Contributing to aieventjs
|
|
231
|
+
|
|
232
|
+
Thanks for taking the time to look. aieventjs is a deliberately small library
|
|
233
|
+
(target ≤ 550 B gzip); contributions that keep the surface narrow are easier
|
|
234
|
+
to accept than ones that expand it.
|
|
235
|
+
|
|
236
|
+
## Quick start
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
pnpm install
|
|
240
|
+
pnpm test # vitest
|
|
241
|
+
pnpm coverage # vitest with v0.1.0 thresholds (95/90/100/100)
|
|
242
|
+
pnpm typecheck # tsc --noEmit on strict mode
|
|
243
|
+
pnpm lint # biome check
|
|
244
|
+
pnpm build # tsup; dual ESM/CJS + .d.ts
|
|
245
|
+
pnpm verify:exports # ensures package.json#exports matches dist/
|
|
246
|
+
pnpm verify:llms # ensures llms-full.txt is in sync with README + CHANGELOG
|
|
247
|
+
pnpm check:size # gzip per subpath against the size budget
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## What gets in easily
|
|
251
|
+
|
|
252
|
+
- Bug fixes with a failing test added first
|
|
253
|
+
- README / typing corrections
|
|
254
|
+
- Tests that lock down existing behaviour (especially the re-entrancy
|
|
255
|
+
invariant on `emit`)
|
|
256
|
+
- Performance work that keeps `on()` / `emit()` O(1) on the dispatch path
|
|
257
|
+
|
|
258
|
+
## What needs discussion first
|
|
259
|
+
|
|
260
|
+
- Anything that changes the public surface (`createEmitter`, `Emitter<Events>`,
|
|
261
|
+
`OnOptions`, error classes)
|
|
262
|
+
- Namespaced wildcards (`user.*`) — explicit non-goal; bring `eventemitter2`
|
|
263
|
+
if you need that
|
|
264
|
+
- Async / promise-returning handlers — explicit non-goal (handlers are
|
|
265
|
+
synchronous; resolve promises in user-land)
|
|
266
|
+
- Anything that pushes the core gzip past 550 B
|
|
267
|
+
|
|
268
|
+
## Design principles
|
|
269
|
+
|
|
270
|
+
aieventjs follows the ai*js library-core priority order:
|
|
271
|
+
|
|
272
|
+
> Security > Correctness > Simplicity > YAGNI > Performance
|
|
273
|
+
|
|
274
|
+
Key invariants:
|
|
275
|
+
|
|
276
|
+
- `on()` returns a callable unsubscribe; calling it (or aborting
|
|
277
|
+
`opts.signal`) removes the handler in O(1).
|
|
278
|
+
- `emit()` snapshots the handler array before iterating — handlers added
|
|
279
|
+
during dispatch do NOT fire this round; handlers removed during dispatch
|
|
280
|
+
do NOT skip their successor.
|
|
281
|
+
- Wildcard `*` handlers fire AFTER type-matched handlers.
|
|
282
|
+
- `dispose()` is idempotent.
|
|
283
|
+
- All methods are destructurable: `const { on, emit } = bus` works.
|
|
284
|
+
|
|
285
|
+
## Commit & PR style
|
|
286
|
+
|
|
287
|
+
- Commit messages: imperative subject under 70 chars; body explains *why*.
|
|
288
|
+
- PRs: keep scope to one topic. Link the issue if any.
|
|
289
|
+
- Tests required for any behaviour change.
|
|
290
|
+
|
|
291
|
+
## Reporting issues
|
|
292
|
+
|
|
293
|
+
- Minimal reproduction welcome (paste the smallest `createEmitter` + on /
|
|
294
|
+
emit sequence that shows the bug).
|
|
295
|
+
- For security issues, please email the maintainer rather than filing
|
|
296
|
+
publicly.
|
|
297
|
+
|
|
298
|
+
## License
|
|
299
|
+
|
|
300
|
+
By contributing, you agree your changes will be licensed under the MIT
|
|
301
|
+
license that covers this project.
|
|
302
|
+
|
|
303
|
+
---
|
package/llms.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# aieventjs
|
|
2
|
+
|
|
3
|
+
> A small, strict, typed event emitter — on() returns an unsubscribe function, once is built-in, AbortSignal is first-class, dispose() is idempotent, wildcard '*' handlers preserved. Mitt-shaped API where it counts; ai*js conventions everywhere else. Zero runtime dependencies. Part of the ai*js micro-runtime ecosystem.
|
|
4
|
+
|
|
5
|
+
For the full LLM context (README + CHANGELOG + CONTRIBUTING concatenated), fetch [llms-full.txt](llms-full.txt).
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aieventjs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Small, strict, typed event emitter — on() returns an unsubscribe function, once is built-in, AbortSignal is first-class, dispose() is idempotent, wildcard '*' handlers preserved. Mitt-shaped API; ai*js conventions everywhere else.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"event-emitter",
|
|
7
|
+
"emitter",
|
|
8
|
+
"events",
|
|
9
|
+
"mitt-alternative",
|
|
10
|
+
"abort-signal",
|
|
11
|
+
"typed-events",
|
|
12
|
+
"ai-readable",
|
|
13
|
+
"typescript",
|
|
14
|
+
"esm"
|
|
15
|
+
],
|
|
16
|
+
"author": "ysl <ysl@sheng.page>",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"homepage": "https://github.com/yshengliao/aieventjs#readme",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/yshengliao/aieventjs.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/yshengliao/aieventjs/issues"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"main": "./dist/index.cjs",
|
|
29
|
+
"module": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js",
|
|
35
|
+
"require": "./dist/index.cjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md",
|
|
41
|
+
"README_ZHTW.md",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"llms.txt",
|
|
44
|
+
"llms-full.txt"
|
|
45
|
+
],
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@biomejs/biome": "^1.9.0",
|
|
48
|
+
"@types/node": "^22.0.0",
|
|
49
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
50
|
+
"tsup": "^8.3.0",
|
|
51
|
+
"tsx": "^4.22.3",
|
|
52
|
+
"typescript": "^5.6.0",
|
|
53
|
+
"vite": "^8.0.14",
|
|
54
|
+
"vitest": "^4.1.7"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18.0.0"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsup",
|
|
64
|
+
"test": "vitest run",
|
|
65
|
+
"test:watch": "vitest",
|
|
66
|
+
"lint": "biome check src test",
|
|
67
|
+
"format": "biome format --write src test",
|
|
68
|
+
"typecheck": "tsc --noEmit",
|
|
69
|
+
"verify:exports": "node scripts/verify-exports.mjs",
|
|
70
|
+
"check:size": "node scripts/check-size.mjs",
|
|
71
|
+
"build:llms": "node scripts/build-llms-full.mjs",
|
|
72
|
+
"verify:llms": "node scripts/build-llms-full.mjs --check",
|
|
73
|
+
"coverage": "vitest run --coverage"
|
|
74
|
+
}
|
|
75
|
+
}
|