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 +39 -150
- package/README_ZHTW.md +39 -149
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -20
- package/dist/index.d.ts +10 -20
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/llms-full.txt +86 -405
- package/llms.txt +6 -2
- package/package.json +1 -1
package/llms-full.txt
CHANGED
|
@@ -13,181 +13,70 @@ The short index lives at `llms.txt` (see https://llmstxt.org/).
|
|
|
13
13
|
|
|
14
14
|
# aieventjs
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
[](https://github.com/islumina/aieventjs/actions/workflows/ci.yml)
|
|
18
|
-
[](LICENSE)
|
|
19
|
-
[](https://www.anthropic.com/claude-code)
|
|
20
|
-
[](README_ZHTW.md)
|
|
16
|
+
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.
|
|
21
17
|
|
|
22
|
-
>
|
|
18
|
+
> **Status: 0.5.8 - stable 1.0-track surface.** The root entry is the public API.
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
> **Status: 0.5.6.** Full implementation shipped; all methods are live. Coverage ≥ 95/90/100/100; ~1050 B gzip (budget 1100 B).
|
|
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:
|
|
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
|
|
20
|
+
## Install
|
|
52
21
|
|
|
53
22
|
```bash
|
|
54
23
|
pnpm add aieventjs
|
|
55
24
|
```
|
|
56
25
|
|
|
57
|
-
```
|
|
26
|
+
```ts
|
|
58
27
|
import { createEmitter } from "aieventjs";
|
|
28
|
+
```
|
|
59
29
|
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```ts
|
|
60
33
|
type Events = {
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"score:tick": { delta: number };
|
|
34
|
+
"score/change": { value: number };
|
|
35
|
+
"scene/end": void;
|
|
64
36
|
};
|
|
65
37
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
// 1. Subscribe; capture the unsubscribe handle.
|
|
69
|
-
const off = bus.on("user:login", (u) => console.log("hi", u.id));
|
|
38
|
+
const events = createEmitter<Events>();
|
|
70
39
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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" });
|
|
40
|
+
const off = events.on("score/change", ({ value }) => {
|
|
41
|
+
console.log(value);
|
|
42
|
+
});
|
|
80
43
|
|
|
81
|
-
|
|
44
|
+
events.on("*", (type, payload) => console.log(type, payload), { sampleRate: 0.1 });
|
|
45
|
+
events.emit("score/change", { value: 10 });
|
|
82
46
|
off();
|
|
83
|
-
|
|
84
|
-
bus.dispose(); // idempotent; post-dispose calls throw EmitterDisposedError
|
|
47
|
+
events.dispose();
|
|
85
48
|
```
|
|
86
49
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
> **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`.)
|
|
90
|
-
>
|
|
91
|
-
> ```typescript
|
|
92
|
-
> // ❌ interface — fails the Record<string, unknown> constraint (TS2344)
|
|
93
|
-
> interface Events { "user:login": { id: string } }
|
|
94
|
-
> const bus = createEmitter<Events>();
|
|
95
|
-
>
|
|
96
|
-
> // ✅ type — satisfies the constraint
|
|
97
|
-
> type Events = { "user:login": { id: string } };
|
|
98
|
-
> const bus = createEmitter<Events>();
|
|
99
|
-
> ```
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## Capabilities / Limitations
|
|
104
|
-
|
|
105
|
-
| Will do (v1) | Won't do |
|
|
106
|
-
| --------------------------------------------------------- | ----------------------------------------------------- |
|
|
107
|
-
| Typed `createEmitter<Events>()` | Untyped string-key bus (the type is the point) |
|
|
108
|
-
| `on()` returns unsubscribe function | Async / promise-returning handlers (sync only) |
|
|
109
|
-
| `once(type, handler)` + `on(..., { once: true })` | Namespaced wildcards (`"user.*"`) — out of scope |
|
|
110
|
-
| `on(..., { signal })` — `AbortSignal` cleanup | Priority / weight / ordering hints |
|
|
111
|
-
| Wildcard `"*"` handler — `(type, payload)` | Cross-context transport (use `aibridgejs` for that) |
|
|
112
|
-
| `dispose()` idempotent; post-dispose calls throw | Error-event special casing (Node EventEmitter style) |
|
|
113
|
-
| Handler-array snapshot on `emit` (safe re-entrancy) | Persistent storage / replay (not its job) |
|
|
114
|
-
| Destructurable methods (`const { on, emit } = bus`) | Zero-allocation `emit` (one snapshot per dispatch is required for re-entrancy) |
|
|
115
|
-
| `on('*', fn, { sampleRate })` — probabilistic delivery for debug subscribers (wildcard only) | |
|
|
116
|
-
| `on(type, fn, { throttleMs })` — per-handler leading-edge throttle, on typed **and** wildcard subscriptions (e.g. a per-frame `credits/change` HUD event) | |
|
|
117
|
-
| `createEmitter({ captureHandlerErrors })` — opt-in error policy; per-handler override via `OnOptions.captureErrors` | |
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## API sketch
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
type EventHandler<P> = (payload: P) => void;
|
|
125
|
-
|
|
126
|
-
type WildcardHandler<Events extends Record<string, unknown>> =
|
|
127
|
-
<K extends keyof Events>(type: K, payload: Events[K]) => void;
|
|
128
|
-
|
|
129
|
-
interface OnOptions {
|
|
130
|
-
signal?: AbortSignal;
|
|
131
|
-
once?: boolean;
|
|
132
|
-
captureErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void); // typed only
|
|
133
|
-
sampleRate?: number; // wildcard "*" only — probability in (0, 1]
|
|
134
|
-
throttleMs?: number; // typed or wildcard — per-handler leading-edge throttle, uses Date.now()
|
|
135
|
-
// Note: Date.now() is not monotonic; a system-clock regression silently
|
|
136
|
-
// mutes the handler until wall time re-passes the stored timestamp.
|
|
137
|
-
// Switching to performance.now() is deferred to the next minor. (EVT-R-02)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
interface EmitterOptions {
|
|
141
|
-
// Default error policy. undefined/false (default): first throw aborts dispatch.
|
|
142
|
-
// true: swallow errors and continue dispatch over all handlers.
|
|
143
|
-
// (err, type, payload) => void: callback invoked per throwing handler
|
|
144
|
-
// (if the callback itself throws, that error is silently ignored and dispatch continues).
|
|
145
|
-
// Per-handler OnOptions.captureErrors overrides this for individual subscriptions.
|
|
146
|
-
captureHandlerErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
interface Emitter<Events extends Record<string, unknown>> {
|
|
150
|
-
on<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>, opts?: OnOptions): () => void;
|
|
151
|
-
on(type: "*", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;
|
|
152
|
-
once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;
|
|
153
|
-
// Note: once() only accepts typed event keys. For wildcard-once semantics use
|
|
154
|
-
// on("*", handler, { once: true }) — the handler receives (type, payload) as
|
|
155
|
-
// WildcardHandler, not (payload) as EventHandler. (EVT-B-02)
|
|
156
|
-
off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;
|
|
157
|
-
off(type: "*", handler?: WildcardHandler<Events>): void;
|
|
158
|
-
emit<K extends keyof Events>(type: K, payload: Events[K]): void;
|
|
159
|
-
clear(): void;
|
|
160
|
-
dispose(): void;
|
|
161
|
-
readonly disposed: boolean;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
class EmitterError extends Error {}
|
|
165
|
-
class EmitterDisposedError extends Error {}
|
|
166
|
-
|
|
167
|
-
function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(
|
|
168
|
-
opts?: EmitterOptions,
|
|
169
|
-
): Emitter<Events>;
|
|
170
|
-
```
|
|
50
|
+
## Core API
|
|
171
51
|
|
|
172
|
-
|
|
52
|
+
- `createEmitter<Events>(options?)` creates a typed emitter.
|
|
53
|
+
- `on(type, handler, options?)` subscribes and returns an unsubscribe function.
|
|
54
|
+
- `on("*", wildcard, options?)` subscribes to every event after type-matched handlers.
|
|
55
|
+
- `once(type, handler)` is shorthand for a one-shot typed handler.
|
|
56
|
+
- `off(type, handler?)`, `clear()`, and `dispose()` remove handlers at different scopes.
|
|
57
|
+
- `emit(type, payload)` dispatches synchronously over a snapshot of handlers.
|
|
58
|
+
- Options: `signal`, `once`, `captureErrors`, `sampleRate` for wildcard, `throttleMs` for typed and wildcard.
|
|
173
59
|
|
|
174
|
-
|
|
60
|
+
## Sharp Edges
|
|
175
61
|
|
|
176
|
-
|
|
62
|
+
- Default error policy is mitt-like: the first throwing handler aborts dispatch. Use `captureHandlerErrors` or per-handler `captureErrors` to swallow/report and continue.
|
|
63
|
+
- Wildcard handlers receive `(type, payload)`, not just payload.
|
|
64
|
+
- Use `on("*", handler, { once: true })` for wildcard-once. `once("*")` is intentionally not part of the typed public overload.
|
|
65
|
+
- `throttleMs` uses `Date.now()`. If the system clock moves backward, a throttled handler can be muted until wall time catches up.
|
|
66
|
+
- `sampleRate` is wildcard-only and uses `Math.random()` per dispatch.
|
|
67
|
+
- `dispose()` is permanent; post-dispose APIs throw `EmitterDisposedError` except cleanup calls that are no-ops by design.
|
|
177
68
|
|
|
178
|
-
|
|
179
|
-
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
180
|
-
| **0.0.1** | Scaffold landed — frozen API surface as a `throw` stub; full config + CI walk clean. |
|
|
181
|
-
| **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). |
|
|
182
|
-
| **0.3.0** | `captureHandlerErrors` + wildcard sampling/throttling. The v0.2 number was skipped to align with the four-package v0.3 release cohort. |
|
|
183
|
-
| **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. |
|
|
184
|
-
| **0.6+** | Async handler tracking (draft) — see [STABILITY.md](STABILITY.md). |
|
|
69
|
+
## AI Context
|
|
185
70
|
|
|
186
|
-
|
|
71
|
+
- Short index: [`llms.txt`](llms.txt)
|
|
72
|
+
- Full generated context: [`llms-full.txt`](llms-full.txt)
|
|
73
|
+
- Stability contract: [`STABILITY.md`](STABILITY.md)
|
|
74
|
+
- Current review backlog: [`REVIEW.md`](REVIEW.md)
|
|
75
|
+
- Release history: [`CHANGELOG.md`](CHANGELOG.md)
|
|
187
76
|
|
|
188
77
|
## License
|
|
189
78
|
|
|
190
|
-
|
|
79
|
+
MIT
|
|
191
80
|
|
|
192
81
|
---
|
|
193
82
|
|
|
@@ -195,173 +84,28 @@ Full JSDoc lives in [`src/index.ts`](src/index.ts).
|
|
|
195
84
|
|
|
196
85
|
# Changelog
|
|
197
86
|
|
|
198
|
-
All notable changes to
|
|
199
|
-
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
200
|
-
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
87
|
+
All notable changes to aieventjs are summarized here.
|
|
201
88
|
|
|
202
89
|
## [Unreleased]
|
|
203
90
|
|
|
204
|
-
## [0.5.
|
|
205
|
-
|
|
206
|
-
### Fixed
|
|
207
|
-
|
|
208
|
-
- `purge()` and `off(type)` now truncate the typed handler arrays they flush (mirroring the wildcard array's existing handling), so a retained unsubscribe closure no longer pins every sibling handler entry against GC. (Review wave 2026-06-10, EVT-R-01.)
|
|
209
|
-
|
|
210
|
-
### Changed
|
|
211
|
-
|
|
212
|
-
- Supply-chain and release hardening: CI/publish actions SHA-pinned, npm CLI pinned (`11.16.0`) in the OIDC publish job, `permissions: contents: read` on CI, job timeouts, tag↔package.json version guard, `npm publish --ignore-scripts`, and **manual publish dispatch now defaults to dry-run** (it previously performed a real publish). New `verify:docs` banner gate; `typecheck` now also type-checks the test suite; `llms-full.txt` embeds `STABILITY.md`.
|
|
91
|
+
## [0.5.8] - 2026-06-14
|
|
213
92
|
|
|
214
|
-
|
|
93
|
+
- Fixed: per-handler `throttleMs` now uses the monotonic `performance.now()` clock instead of `Date.now()`, so a wall-clock regression can no longer silently mute throttled handlers.
|
|
94
|
+
- Changed: `once("*")` is now a compile-time type error; use `on("*", handler, { once: true })` for wildcard-once semantics.
|
|
95
|
+
- Documentation-only slimming pass across README, stability notes, review backlog, and LLM context.
|
|
215
96
|
|
|
216
|
-
|
|
217
|
-
- `once("*")` is documented (and pinned by test) as routing through the wildcard `on()` overload with `(type, payload)` handler arguments; the wall-clock (`Date.now()`) limitation of `throttleMs` under backwards clock steps is documented. New pins for abort-listener detach on `off()` paths and `once` × `sampleRate` interaction.
|
|
218
|
-
|
|
219
|
-
## [0.5.5] - 2026-06-08
|
|
220
|
-
|
|
221
|
-
### Changed
|
|
222
|
-
|
|
223
|
-
- Project home migrated to the [`islumina`](https://github.com/islumina) GitHub org; the package is now published from there via npm trusted publisher (OIDC + SLSA provenance). Family-wide version alignment at `0.5.5` — no runtime or API changes.
|
|
224
|
-
|
|
225
|
-
## [0.5.3] - 2026-06-05
|
|
226
|
-
|
|
227
|
-
### Added
|
|
228
|
-
- `throttleMs` is now accepted on typed `on()` (non-breaking; per-handler leading-edge throttle, e.g. a per-frame `credits/change` HUD event). Previously it threw `EmitterError` (wildcard-only); `sampleRate` stays wildcard-only.
|
|
229
|
-
|
|
230
|
-
### Docs
|
|
231
|
-
- Document that event maps must be declared with `type`, not `interface` (an `interface` lacks an index signature and fails the `Record<string, unknown>` constraint with TS2344); `createEmitter` JSDoc + README + `README_ZHTW.md` + regenerated `llms-full.txt`.
|
|
232
|
-
|
|
233
|
-
## [0.5.2] - 2026-06-05
|
|
234
|
-
|
|
235
|
-
### Docs
|
|
236
|
-
|
|
237
|
-
- Review-driven documentation fixes (`README.md`, `README_ZHTW.md`, `llms-full.txt`; plus repo-only `CONTRIBUTING.md`): clarity and accuracy from a cross-package code review. No runtime or API change; `dist` byte-identical to 0.5.1.
|
|
238
|
-
|
|
239
|
-
## [0.5.1] - 2026-06-02
|
|
240
|
-
|
|
241
|
-
### Fixed
|
|
242
|
-
- `NaN` sampleRate and `NaN` throttleMs now throw `EmitterError` at `on()` time.
|
|
243
|
-
Previously, `NaN <= 0` and `NaN > 1` both evaluate to `false`, so a `NaN`
|
|
244
|
-
sampleRate silently passed validation and degraded to always-fire semantics;
|
|
245
|
-
similarly `NaN < 0` is `false`, so a `NaN` throttleMs passed and degraded to
|
|
246
|
-
no-throttle. Both guards now include `Number.isFinite()` as the first check,
|
|
247
|
-
consistent with the documented contract that invalid values throw `EmitterError`.
|
|
248
|
-
|
|
249
|
-
### Tests
|
|
250
|
-
- Added regression tests locking in `NaN` rejection for both `sampleRate` and
|
|
251
|
-
`throttleMs` (wildcard-throttle.test.ts §A6, §B4).
|
|
252
|
-
- Added regression tests confirming that a `once` handler that throws is still
|
|
253
|
-
removed and does not re-fire on the next emit — for both typed and wildcard
|
|
254
|
-
once subscriptions (emitter.test.ts §C2a, §C2b).
|
|
255
|
-
- Added regression tests for post-dispose guard coverage: `off()` after
|
|
256
|
-
`dispose()` throws `EmitterDisposedError`; `clear()` after `dispose()` throws
|
|
257
|
-
`EmitterDisposedError`; the unsubscribe function returned by `on()` is a safe
|
|
258
|
-
no-op (does not throw) after `dispose()` (emitter.test.ts §H2a, §H2b, §H2c).
|
|
259
|
-
|
|
260
|
-
## [0.4.0] - 2026-05-29
|
|
261
|
-
|
|
262
|
-
Dependency hygiene + stability freeze. No runtime API addition; `dist/` is byte-identical to 0.3.1 (no `src/` change). Consumer-facing behaviour is unchanged.
|
|
263
|
-
|
|
264
|
-
### Changed
|
|
265
|
-
- Aligned the `fast-check` devDependency to `^4.8.0` (was `^3.23.0`), matching the newer ai*js family cohort. Test-only; never bundled; zero consumer impact.
|
|
266
|
-
- Declared the entire 0.3.x public API surface frozen for the 1.x line — see STABILITY.md. No signatures, error names, or default behaviours changed.
|
|
267
|
-
|
|
268
|
-
### Removed
|
|
269
|
-
- `tsx` devDependency — unused (no script, config, test, or CI step referenced it). Trims the lockfile subtree; no functional impact.
|
|
270
|
-
|
|
271
|
-
## [0.3.1] - 2026-05-29
|
|
272
|
-
### Added
|
|
273
|
-
- `test/emitter.prop.test.ts`: three fast-check property invariants — dispatch
|
|
274
|
-
order (typed before wildcard, registration order preserved), snapshot stability
|
|
275
|
-
under mid-dispatch `off()`, live-set accuracy after upfront unsubscribe.
|
|
276
|
-
`numRuns: 100`; inline `fc.assert` style matching family convention.
|
|
277
|
-
- `test/wildcard-throttle.test.ts` §F: five edge-case tests — pre-aborted signal
|
|
278
|
-
+ valid `sampleRate` never registers; `on()` guard-before-signal ordering;
|
|
279
|
-
`captureErrors` + signal leak check (`removeEventListener` called on abort);
|
|
280
|
-
throttled wildcard + mid-dispatch abort (snapshot-in-flight completes);
|
|
281
|
-
`sampleRate` exact boundary (`Math.random() === sampleRate` is a miss).
|
|
282
|
-
- `test/capture-errors.test.ts` §F: dispose-during-capturing-dispatch —
|
|
283
|
-
snapshot completes; `EmitterDisposedError` from re-entrant `emit()` is routed
|
|
284
|
-
through the capture callback; sibling handler still runs.
|
|
285
|
-
- `fast-check ^3.23.0` devDependency (family convention: aifsmjs `^3.20.0`,
|
|
286
|
-
aiquadtreejs `^3.23.0`).
|
|
287
|
-
|
|
288
|
-
### Changed
|
|
289
|
-
- No `src/index.ts` behaviour change. `dist/` is byte-identical to v0.3.0.
|
|
290
|
-
gzip 1050 B / 1100 B unchanged.
|
|
97
|
+
## [0.5.6] - 2026-06-10
|
|
291
98
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
-
|
|
295
|
-
- `OnOptions.captureErrors`: per-handler override of the emitter-level policy. `false` forces re-throw even when emitter-level swallows. Setting it on a wildcard `"*"` subscription throws `EmitterError`.
|
|
296
|
-
- `OnOptions.sampleRate` (wildcard only): probability in `(0, 1]` that a dispatch reaches the handler; uses `Math.random()`. Out-of-range values throw `EmitterError` at `on()` time.
|
|
297
|
-
- `OnOptions.throttleMs` (wildcard only): minimum ms between successive calls, leading-edge; uses `Date.now()`. Negative values throw `EmitterError` at `on()` time.
|
|
298
|
-
- `STABILITY.md`: stability index for all public API surface, plus `[experimental]` placeholder for async handler tracking (targeted v0.6+).
|
|
99
|
+
- Hardened wildcard/once documentation and wall-clock throttle caveats.
|
|
100
|
+
- Kept typed dispatch, wildcard dispatch, and AbortSignal behavior stable.
|
|
101
|
+
- Regenerated generated LLM context from canonical docs.
|
|
299
102
|
|
|
300
|
-
|
|
301
|
-
- Internal entry shape gains optional `ce` / `r` / `tm` / `ts` fields to carry per-handler error policy and wildcard throttle/sample state. Snapshot-before-iterate semantics in `emit()` are unchanged.
|
|
302
|
-
- `scripts/check-size.mjs` budget raised from 800 B to 1100 B. Actual v0.3.0 gzip lands at ~1050 B; the spec estimate of 900 B was optimistic (~100 B per feature accounting for guard message strings and try/catch frames).
|
|
103
|
+
## Older releases
|
|
303
104
|
|
|
304
|
-
|
|
305
|
-
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
### Changed (CI)
|
|
310
|
-
|
|
311
|
-
- **`publish.yml` now triggers on `push: tags: ["v*"]`** (was `workflow_dispatch` only). Aligns with the trigger used by `aifsmjs` / `aiecsjs` / `aibridgejs`. Tag push now automatically runs the OIDC trusted publish.
|
|
312
|
-
- **`npm publish --provenance --access public`** — the workflow now emits a [sigstore provenance attestation](https://docs.npmjs.com/generating-provenance-statements) so consumers can verify the tarball was built by this workflow on this commit.
|
|
313
|
-
|
|
314
|
-
No runtime / source / API changes. This is a CI-only patch to validate the GitHub Actions OIDC trusted-publisher pipeline now that the npm trusted publisher entry is configured. Production bundles are byte-identical to 0.1.0.
|
|
315
|
-
|
|
316
|
-
## [0.1.0] - 2026-05-28
|
|
317
|
-
|
|
318
|
-
### Added
|
|
319
|
-
|
|
320
|
-
- Strict typed `createEmitter<Events>()` with `on` returning an unsubscribe
|
|
321
|
-
function, `once`, wildcard `*` handler, `off`, `emit`, `clear`, `dispose`.
|
|
322
|
-
- `on(type, handler, { signal?, once? })` — `AbortSignal` integration so
|
|
323
|
-
framework code (Svelte 5 `$effect`, Vue `onScopeDispose`) cleans up listeners
|
|
324
|
-
via the standard cancellation primitive.
|
|
325
|
-
- `dispose()` idempotent; post-dispose `on` / `emit` / `once` throw
|
|
326
|
-
`EmitterDisposedError`.
|
|
327
|
-
- Handler-array snapshot on `emit` so removing a handler during dispatch does
|
|
328
|
-
not skip its successor (mitt has had this property since 2.x — preserved).
|
|
329
|
-
- Functional — methods are destructurable (`const { on, emit } = bus`).
|
|
330
|
-
- Test coverage ≥95% statements / lines / functions / ≥90% branches.
|
|
331
|
-
- Size budget: ≤ 550 B gzip.
|
|
332
|
-
- Dual ESM + CJS via `tsup`; `sideEffects: false`; zero runtime dependencies.
|
|
333
|
-
|
|
334
|
-
## [0.0.1] - 2026-05-28
|
|
335
|
-
|
|
336
|
-
### Added (scaffold)
|
|
337
|
-
|
|
338
|
-
- Full package scaffold landed (`package.json`, `tsconfig.json`,
|
|
339
|
-
`tsconfig.test.json`, `tsup.config.ts`, `vitest.config.ts`, `biome.json`,
|
|
340
|
-
`scripts/{verify-exports,check-size,build-llms-full}.mjs`,
|
|
341
|
-
`test/scaffold.test.ts`, `examples/.gitkeep`, `.github/workflows/{ci,publish}.yml`,
|
|
342
|
-
`README.md`, `README_ZHTW.md`, `CHANGELOG.md`, `CONTRIBUTING.md`,
|
|
343
|
-
`LICENSE`, `llms.txt`, `llms-full.txt`).
|
|
344
|
-
- `src/index.ts` is a `throw` stub exposing the frozen 0.1.0 API surface
|
|
345
|
-
(`createEmitter`, `Emitter<Events>`, wildcard `*` handler signature, `once`,
|
|
346
|
-
`AbortSignal`-aware `on`, `dispose`, `EmitterError`, `EmitterDisposedError`).
|
|
347
|
-
- `pnpm typecheck && pnpm lint && pnpm coverage && pnpm build &&
|
|
348
|
-
pnpm verify:exports && pnpm verify:llms && pnpm check:size` walks clean
|
|
349
|
-
against a single placeholder test.
|
|
350
|
-
- Coverage thresholds temporarily set to `0/0/0/0`; tightened to
|
|
351
|
-
`95/90/100/100` in 0.1.0.
|
|
352
|
-
- Size budget temporarily set to 3 KB gzip; tightened to the 550 B README
|
|
353
|
-
target in 0.1.0.
|
|
354
|
-
- Publish workflow exists but trigger is `workflow_dispatch` only — no
|
|
355
|
-
accidental npm release until 0.1.0.
|
|
356
|
-
|
|
357
|
-
### Decision log (carried over from LEARNINGS.md v0.3.0 cycle 預備區)
|
|
358
|
-
|
|
359
|
-
- **Not a `mitt` fork.** `mitt@3.0.1` is MIT-fork-friendly but is ~35 lines of
|
|
360
|
-
pure logic and has been unmaintained since 2023-07. Forking is equivalent
|
|
361
|
-
to rewriting, and the upstream copyright notice would carry no benefit.
|
|
362
|
-
Cleaner to write from scratch with the ai\*js conventions baked in.
|
|
363
|
-
- **Wildcard `*` is kept.** It is `mitt`'s signature feature; keeping it
|
|
364
|
-
preserves migration ergonomics for existing `mitt` users at ~80 B gzip cost.
|
|
105
|
+
- `0.5.5` through `0.5.1` focused on release hygiene, docs accuracy, and regression tests for wildcard/once/error behavior.
|
|
106
|
+
- `0.4.0` declared the stable ai*js surface.
|
|
107
|
+
- `0.3.x` added sampling, throttle, capture-error options, and public stability docs.
|
|
108
|
+
- `0.1.x` introduced `createEmitter`, typed `on/once/off/emit`, wildcard handlers, abort cleanup, and dispose semantics.
|
|
365
109
|
|
|
366
110
|
---
|
|
367
111
|
|
|
@@ -369,48 +113,29 @@ No runtime / source / API changes. This is a CI-only patch to validate the GitHu
|
|
|
369
113
|
|
|
370
114
|
# aieventjs Stability Index
|
|
371
115
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
- `[stable]` — frozen API, semver-protected.
|
|
375
|
-
- `[experimental]` — usable but may change without major bump in 0.x; will stabilise pre-1.0.
|
|
376
|
-
- `[draft]` — placeholder; API not yet shipped.
|
|
377
|
-
|
|
378
|
-
## Public API
|
|
116
|
+
## Stable API
|
|
379
117
|
|
|
380
|
-
| Surface |
|
|
381
|
-
|
|
382
|
-
| `createEmitter
|
|
383
|
-
| `on
|
|
384
|
-
| `on(
|
|
385
|
-
|
|
|
386
|
-
| `
|
|
387
|
-
|
|
|
388
|
-
| `OnOptions.captureErrors` | [stable] | 0.3.0 |
|
|
389
|
-
| `OnOptions.sampleRate` (wildcard only) | [stable] | 0.3.0 |
|
|
390
|
-
| `OnOptions.throttleMs` (wildcard only) | [stable] | 0.3.0 |
|
|
391
|
-
| `OnOptions.throttleMs` (typed or wildcard) | [stable] | 0.5.3 |
|
|
392
|
-
| `EmitterError` / `EmitterDisposedError` | [stable] | 0.1.0 |
|
|
118
|
+
| Surface | Status | Notes |
|
|
119
|
+
| --- | --- | --- |
|
|
120
|
+
| `createEmitter(options?)` | Stable | Generic typed event map. |
|
|
121
|
+
| `Emitter.on` | Stable | Typed and wildcard overloads; returns unsubscribe. |
|
|
122
|
+
| `Emitter.once` | Stable | Typed events only; wildcard once uses `on("*", ..., { once: true })`. |
|
|
123
|
+
| `Emitter.off`, `clear`, `dispose` | Stable | Cleanup methods; dispose is permanent and idempotent. |
|
|
124
|
+
| `Emitter.emit` | Stable | Synchronous snapshot dispatch. |
|
|
125
|
+
| Error classes | Stable | `EmitterError`, `EmitterDisposedError`. |
|
|
393
126
|
|
|
394
|
-
|
|
127
|
+
## Behavior
|
|
395
128
|
|
|
396
|
-
|
|
129
|
+
- Type-matched handlers run before wildcard handlers.
|
|
130
|
+
- Handler lists are snapshotted before dispatch.
|
|
131
|
+
- Default handler errors propagate; capture options can swallow/report.
|
|
132
|
+
- `AbortSignal` removes subscriptions and pre-aborted signals do not register.
|
|
133
|
+
- `throttleMs` uses `Date.now()` and is therefore wall-clock based.
|
|
397
134
|
|
|
398
|
-
|
|
135
|
+
## Drafts
|
|
399
136
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
```ts
|
|
403
|
-
// possibly via a new emitter option
|
|
404
|
-
createEmitter<Events>({
|
|
405
|
-
awaitAsyncHandlers: true,
|
|
406
|
-
});
|
|
407
|
-
// emit() may return a Promise that resolves after Promise.allSettled over
|
|
408
|
-
// any handler that returned a Promise. Sync handlers run synchronously
|
|
409
|
-
// before any awaiting begins. Snapshot semantics preserved.
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
Not part of v0.4.0; do not depend on this API. The current `emit()` remains
|
|
413
|
-
fully synchronous and ignores handler return values.
|
|
137
|
+
- Async handler tracking is not implemented.
|
|
138
|
+
- A future minor may switch throttle timing to monotonic time or add a runtime guard around wildcard `once()`.
|
|
414
139
|
|
|
415
140
|
---
|
|
416
141
|
|
|
@@ -418,75 +143,31 @@ fully synchronous and ignores handler return values.
|
|
|
418
143
|
|
|
419
144
|
# Contributing to aieventjs
|
|
420
145
|
|
|
421
|
-
|
|
422
|
-
(target ≤ 1100 B gzip); contributions that keep the surface narrow are easier
|
|
423
|
-
to accept than ones that expand it.
|
|
146
|
+
Keep the emitter small, synchronous, and predictable.
|
|
424
147
|
|
|
425
|
-
##
|
|
148
|
+
## Local workflow
|
|
426
149
|
|
|
427
150
|
```bash
|
|
428
151
|
pnpm install
|
|
429
|
-
pnpm
|
|
430
|
-
pnpm
|
|
431
|
-
pnpm
|
|
432
|
-
pnpm
|
|
433
|
-
pnpm
|
|
434
|
-
pnpm
|
|
435
|
-
pnpm verify:llms # ensures llms-full.txt is in sync with README + CHANGELOG
|
|
436
|
-
pnpm check:size # gzip per subpath against the size budget
|
|
152
|
+
pnpm typecheck
|
|
153
|
+
pnpm test
|
|
154
|
+
pnpm verify:docs
|
|
155
|
+
pnpm build:llms
|
|
156
|
+
pnpm verify:llms
|
|
157
|
+
pnpm check:size
|
|
437
158
|
```
|
|
438
159
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
- Bug fixes with a failing test added first
|
|
442
|
-
- README / typing corrections
|
|
443
|
-
- Tests that lock down existing behaviour (especially the re-entrancy
|
|
444
|
-
invariant on `emit`)
|
|
445
|
-
- Performance work that keeps `on()` / `emit()` O(1) on the dispatch path
|
|
446
|
-
|
|
447
|
-
## What needs discussion first
|
|
448
|
-
|
|
449
|
-
- Anything that changes the public surface (`createEmitter`, `Emitter<Events>`,
|
|
450
|
-
`OnOptions`, error classes)
|
|
451
|
-
- Namespaced wildcards (`user.*`) — explicit non-goal; bring `eventemitter2`
|
|
452
|
-
if you need that
|
|
453
|
-
- Async / promise-returning handlers — explicit non-goal (handlers are
|
|
454
|
-
synchronous; resolve promises in user-land)
|
|
455
|
-
- Anything that pushes the core gzip past 1100 B
|
|
456
|
-
|
|
457
|
-
## Design principles
|
|
458
|
-
|
|
459
|
-
aieventjs follows the ai*js library-core priority order:
|
|
460
|
-
|
|
461
|
-
> Security > Correctness > Simplicity > YAGNI > Performance
|
|
462
|
-
|
|
463
|
-
Key invariants:
|
|
464
|
-
|
|
465
|
-
- `on()` returns a callable unsubscribe; calling it (or aborting
|
|
466
|
-
`opts.signal`) removes the handler in O(1).
|
|
467
|
-
- `emit()` snapshots the handler array before iterating — handlers added
|
|
468
|
-
during dispatch do NOT fire this round; handlers removed during dispatch
|
|
469
|
-
do NOT skip their successor.
|
|
470
|
-
- Wildcard `*` handlers fire AFTER type-matched handlers.
|
|
471
|
-
- `dispose()` is idempotent.
|
|
472
|
-
- All methods are destructurable: `const { on, emit } = bus` works.
|
|
473
|
-
|
|
474
|
-
## Commit & PR style
|
|
475
|
-
|
|
476
|
-
- Commit messages: imperative subject under 70 chars; body explains *why*.
|
|
477
|
-
- PRs: keep scope to one topic. Link the issue if any.
|
|
478
|
-
- Tests required for any behaviour change.
|
|
160
|
+
Run `pnpm lint` before PRs. If docs change, regenerate `llms-full.txt`.
|
|
479
161
|
|
|
480
|
-
##
|
|
162
|
+
## Rules
|
|
481
163
|
|
|
482
|
-
-
|
|
483
|
-
|
|
484
|
-
-
|
|
485
|
-
|
|
164
|
+
- Preserve snapshot-before-iterate dispatch semantics.
|
|
165
|
+
- Keep wildcard ordering after typed handlers.
|
|
166
|
+
- Add tests for `AbortSignal`, `once`, wildcard, throttle, sample, and error policy changes.
|
|
167
|
+
- Do not add async queueing to the stable emitter without a separate design note.
|
|
486
168
|
|
|
487
169
|
## License
|
|
488
170
|
|
|
489
|
-
|
|
490
|
-
license that covers this project.
|
|
171
|
+
MIT
|
|
491
172
|
|
|
492
173
|
---
|
package/llms.txt
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# aieventjs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Typed synchronous emitter with wildcard handlers, AbortSignal cleanup, once, throttle, sample, and dispose.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- Start: README.md
|
|
6
|
+
- Stability: STABILITY.md
|
|
7
|
+
- Current backlog: REVIEW.md
|
|
8
|
+
- Changelog: CHANGELOG.md
|
|
9
|
+
- Full generated context: llms-full.txt
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aieventjs",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.8",
|
|
4
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
5
|
"keywords": [
|
|
6
6
|
"event-emitter",
|