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/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
- [![npm version](https://img.shields.io/npm/v/aieventjs.svg)](https://www.npmjs.com/package/aieventjs)
17
- [![CI](https://github.com/islumina/aieventjs/actions/workflows/ci.yml/badge.svg)](https://github.com/islumina/aieventjs/actions/workflows/ci.yml)
18
- [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
19
- [![AI Generated](https://img.shields.io/badge/AI_Generated-Claude_Code_Opus_4.7_Max-blueviolet.svg)](https://www.anthropic.com/claude-code)
20
- [![繁體中文](https://img.shields.io/badge/lang-繁體中文-red.svg)](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
- > 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.
18
+ > **Status: 0.5.8 - stable 1.0-track surface.** The root entry is the public API.
23
19
 
24
- Part of the [ai\*js micro-runtime ecosystem](https://github.com/islumina) — see also [aifsmjs](https://github.com/islumina/aifsmjs) (FSM), [aiecsjs](https://github.com/islumina/aiecsjs) (ECS), [aibridgejs](https://github.com/islumina/aibridgejs) (cross-context RPC), [aipooljs](https://github.com/islumina/aipooljs) (object pool), [aiquadtreejs](https://github.com/islumina/aiquadtreejs) (spatial partitioning), and [aiaudiojs](https://github.com/islumina/aiaudiojs) (Web Audio shell).
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
- ```typescript
26
+ ```ts
58
27
  import { createEmitter } from "aieventjs";
28
+ ```
59
29
 
30
+ ## Quick Start
31
+
32
+ ```ts
60
33
  type Events = {
61
- "user:login": { id: string };
62
- "user:logout": void;
63
- "score:tick": { delta: number };
34
+ "score/change": { value: number };
35
+ "scene/end": void;
64
36
  };
65
37
 
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));
38
+ const events = createEmitter<Events>();
70
39
 
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" });
40
+ const off = events.on("score/change", ({ value }) => {
41
+ console.log(value);
42
+ });
80
43
 
81
- // 5. Tear down.
44
+ events.on("*", (type, payload) => console.log(type, payload), { sampleRate: 0.1 });
45
+ events.emit("score/change", { value: 10 });
82
46
  off();
83
- ctrl.abort();
84
- bus.dispose(); // idempotent; post-dispose calls throw EmitterDisposedError
47
+ events.dispose();
85
48
  ```
86
49
 
87
- `createEmitter()` returns a plain object whose methods do not depend on `this` — `const { on, emit } = bus` works fine.
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
- Full JSDoc lives in [`src/index.ts`](src/index.ts).
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
- ## Roadmap
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
- | Version | Adds |
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
- [MIT](LICENSE).
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 this project are documented here. The format follows
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.6] - 2026-06-10
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
- ### Docs
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
- - `STABILITY.md` freeze table now records `OnOptions.throttleMs` as **typed or wildcard since 0.5.3** (the table previously only listed the 0.3.0 wildcard-only surface). (EVT-B-01.)
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
- ## [0.3.0] - 2026-05-29
293
- ### Added
294
- - `EmitterOptions.captureHandlerErrors`: opt-in emitter-level error policy. Accepts `true` (swallow) or `(err, type, payload) => void` callback. Default behaviour (first throw aborts dispatch) is unchanged.
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
- ### Changed
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
- ### Notes
305
- - v0.2 was skipped; v0.3.0 directly supersedes the v0.2 roadmap entry for `captureHandlerErrors`. Existing v0.1 callers that did not pass the option see no behavioural change.
306
-
307
- ## [0.1.1] - 2026-05-28
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
- Tags follow Node's stability index conventions:
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 | Stability | Since |
381
- |---|---|---|
382
- | `createEmitter<Events>(opts?)` | [stable] | 0.1.0 |
383
- | `on / once / off / emit / clear / dispose / disposed` | [stable] | 0.1.0 |
384
- | `on(type, handler, { signal })` (AbortSignal) | [stable] | 0.1.0 |
385
- | Wildcard `"*"` handler | [stable] | 0.1.0 |
386
- | `EmitterOptions.captureHandlerErrors` (boolean) | [stable] | 0.3.0 |
387
- | `EmitterOptions.captureHandlerErrors` (callback) | [stable] | 0.3.0 |
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
- > **Stability freeze (0.4.0).** Every `[stable]` row above is frozen for the 1.x line: once 1.0 ships, these signatures, error names, and default behaviours will not change without a major version bump. v0.4.0 adds no runtime API — it formalises the 0.3.x surface as 1.0-track.
127
+ ## Behavior
395
128
 
396
- ## Drafts (not yet implemented)
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
- ### Async handler tracking — [experimental] placeholder
135
+ ## Drafts
399
136
 
400
- Targeted for v0.6+. Concept sketch (subject to change):
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
- Thanks for taking the time to look. aieventjs is a deliberately small library
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
- ## Quick start
148
+ ## Local workflow
426
149
 
427
150
  ```bash
428
151
  pnpm install
429
- pnpm test # vitest
430
- pnpm coverage # vitest with v0.1.0 thresholds (95/90/100/100)
431
- pnpm typecheck # tsc --noEmit on strict mode
432
- pnpm lint # biome check
433
- pnpm build # tsup; dual ESM/CJS + .d.ts
434
- pnpm verify:exports # ensures package.json#exports matches dist/
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
- ## What gets in easily
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
- ## Reporting issues
162
+ ## Rules
481
163
 
482
- - Minimal reproduction welcome (paste the smallest `createEmitter` + on /
483
- emit sequence that shows the bug).
484
- - For security issues, please email the maintainer rather than filing
485
- publicly.
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
- By contributing, you agree your changes will be licensed under the MIT
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
- > 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.
3
+ Typed synchronous emitter with wildcard handlers, AbortSignal cleanup, once, throttle, sample, and dispose.
4
4
 
5
- For the full LLM context (README + CHANGELOG + CONTRIBUTING concatenated), fetch [llms-full.txt](llms-full.txt).
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.6",
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",