aieventjs 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
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
12
 
13
- > **Status: 0.5.1.** Full implementation shipped; all methods are live. Coverage ≥ 95/90/100/100; ~1050 B gzip (budget 1100 B).
13
+ > **Status: 0.5.3.** Full implementation shipped; all methods are live. Coverage ≥ 95/90/100/100; ~1050 B gzip (budget 1100 B).
14
14
 
15
15
  ---
16
16
 
@@ -73,6 +73,18 @@ bus.dispose(); // idempotent; post-dispose calls throw EmitterDisposedError
73
73
 
74
74
  `createEmitter()` returns a plain object whose methods do not depend on `this` — `const { on, emit } = bus` works fine.
75
75
 
76
+ > **Declare the event map with `type`, not `interface`.** The `Events` generic is constrained to `Record<string, unknown>`. A *plain* TypeScript `interface` has no implicit index signature, so passing one fails the constraint with *"Index signature for type 'string' is missing in type ..."*. A `type` object literal satisfies it structurally. (An `interface` with an explicit index signature — or one that `extends Record<string, unknown>` — also compiles, but widens `keyof Events` to `string` and loses strict event-name checking, so prefer `type`.)
77
+ >
78
+ > ```typescript
79
+ > // ❌ interface — fails the Record<string, unknown> constraint (TS2344)
80
+ > interface Events { "user:login": { id: string } }
81
+ > const bus = createEmitter<Events>();
82
+ >
83
+ > // ✅ type — satisfies the constraint
84
+ > type Events = { "user:login": { id: string } };
85
+ > const bus = createEmitter<Events>();
86
+ > ```
87
+
76
88
  ---
77
89
 
78
90
  ## Capabilities / Limitations
@@ -88,7 +100,7 @@ bus.dispose(); // idempotent; post-dispose calls throw EmitterDisposedError
88
100
  | Handler-array snapshot on `emit` (safe re-entrancy) | Persistent storage / replay (not its job) |
89
101
  | Destructurable methods (`const { on, emit } = bus`) | Zero-allocation `emit` (one snapshot per dispatch is required for re-entrancy) |
90
102
  | `on('*', fn, { sampleRate })` — probabilistic delivery for debug subscribers (wildcard only) | |
91
- | `on('*', fn, { throttleMs })` — leading-edge throttle for debug subscribers (wildcard only) | |
103
+ | `on(type, fn, { throttleMs })` — per-handler leading-edge throttle, on typed **and** wildcard subscriptions (e.g. a per-frame `credits/change` HUD event) | |
92
104
  | `createEmitter({ captureHandlerErrors })` — opt-in error policy; per-handler override via `OnOptions.captureErrors` | |
93
105
 
94
106
  ---
@@ -106,7 +118,7 @@ interface OnOptions {
106
118
  once?: boolean;
107
119
  captureErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void); // typed only
108
120
  sampleRate?: number; // wildcard "*" only — probability in (0, 1]
109
- throttleMs?: number; // wildcard "*" only — leading-edge throttle, uses Date.now()
121
+ throttleMs?: number; // typed or wildcard — per-handler leading-edge throttle, uses Date.now()
110
122
  }
111
123
 
112
124
  interface EmitterOptions {
package/README_ZHTW.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
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
12
 
13
- > **狀態:0.5.1。** 完整實作已上線、所有 method 皆可用。Coverage ≥ 95/90/100/100;~1050 B gzip(budget 1100 B)。
13
+ > **狀態:0.5.3。** 完整實作已上線、所有 method 皆可用。Coverage ≥ 95/90/100/100;~1050 B gzip(budget 1100 B)。
14
14
 
15
15
  ---
16
16
 
@@ -73,6 +73,18 @@ bus.dispose(); // 冪等;dispose 後再呼叫拋 EmitterDisposedError
73
73
 
74
74
  `createEmitter()` 回傳的 method 不抓 `this` ── `const { on, emit } = bus` 解構沒問題。
75
75
 
76
+ > **event map 要用 `type` 宣告、不要用 `interface`。** `Events` generic 受限於 `Record<string, unknown>`。**純** TypeScript `interface` 沒有隱含的 index signature,傳進去會違反 constraint、報 *"Index signature for type 'string' is missing in type ..."*;`type` 物件字面值則在結構上滿足它。(加了顯式 index signature、或 `extends Record<string, unknown>` 的 `interface` 也能通過,但會把 `keyof Events` 擴成 `string`、失去嚴格的事件名稱檢查,故建議用 `type`。)
77
+ >
78
+ > ```typescript
79
+ > // ❌ interface ── 違反 Record<string, unknown> constraint(TS2344)
80
+ > interface Events { "user:login": { id: string } }
81
+ > const bus = createEmitter<Events>();
82
+ >
83
+ > // ✅ type ── 滿足 constraint
84
+ > type Events = { "user:login": { id: string } };
85
+ > const bus = createEmitter<Events>();
86
+ > ```
87
+
76
88
  ---
77
89
 
78
90
  ## 能做 / 不做
@@ -88,7 +100,7 @@ bus.dispose(); // 冪等;dispose 後再呼叫拋 EmitterDisposedError
88
100
  | `emit` 走訪前 snapshot handler array(reentrant 安全) | 持久化 / replay(不是它的工作) |
89
101
  | Method 可解構(`const { on, emit } = bus`) | 零配置 `emit`(每次派送需 snapshot,re-entrancy 安全所需)|
90
102
  | `on('*', fn, { sampleRate })` ── 機率性派送(wildcard only)| |
91
- | `on('*', fn, { throttleMs })` ── leading-edge throttle(wildcard only)| |
103
+ | `on(type, fn, { throttleMs })` ── per-handler leading-edge throttle,typed **與** wildcard 皆可(如每幀觸發的 `credits/change` HUD 事件)| |
92
104
  | `createEmitter({ captureHandlerErrors })` ── opt-in 錯誤策略;`OnOptions.captureErrors` 個別 handler 覆蓋 | |
93
105
 
94
106
  ---
@@ -106,7 +118,7 @@ interface OnOptions {
106
118
  once?: boolean;
107
119
  captureErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void); // 僅限 typed handler
108
120
  sampleRate?: number; // wildcard "*" only — 機率 (0, 1]
109
- throttleMs?: number; // wildcard "*" only — leading-edge throttle,使用 Date.now()
121
+ throttleMs?: number; // typed 或 wildcard — per-handler leading-edge throttle,使用 Date.now()
110
122
  }
111
123
 
112
124
  interface EmitterOptions {
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var v=class extends Error{name="EmitterError"},h=class extends Error{name="EmitterDisposedError"};function K(d,c){let i=d.findIndex(o=>o.u===c);if(i>=0){let o=d[i];o!==void 0&&(o.c?.(),o.c=void 0),d.splice(i,1);}}function y(d){for(let c of d)c.c?.(),c.c=void 0;}function m(d,c,i){d.push(c);let o=()=>{let a=d.indexOf(c);a>=0&&d.splice(a,1),c.c?.(),c.c=void 0;};if(i!==void 0){let a=()=>o();i.addEventListener("abort",a,{once:true}),c.c=()=>i.removeEventListener("abort",a);}return o}function j(d){let c=d?.captureHandlerErrors,i=new Map,o=[],a=false;function p(){if(a)throw new h("aieventjs: emitter has been disposed")}function g(e){let n=i.get(e);return n===void 0&&(n=[],i.set(e,n)),n}function k(e,n,t){p();let s=t?.sampleRate,u=t?.throttleMs;if(e==="*"){if(t?.captureErrors!==void 0)throw new v("aieventjs: captureErrors invalid on *")}else {if(s!==void 0)throw new v("aieventjs: sampleRate wildcard-only");if(u!==void 0)throw new v("aieventjs: throttleMs wildcard-only")}if(s!==void 0&&(!Number.isFinite(s)||s<=0||s>1))throw new v("aieventjs: sampleRate must be in (0,1]");if(u!==void 0&&(!Number.isFinite(u)||u<0))throw new v("aieventjs: throttleMs must be >= 0");let l=t?.signal;if(l?.aborted)return ()=>{};if(e==="*"){let E=n;if(t?.once){let H=m(o,{h:(R,M)=>{H(),E(R,M);},u:E,c:void 0,r:s,tm:u},l);return H}return m(o,{h:E,u:E,c:void 0,r:s,tm:u},l)}let r=n,f=t?.captureErrors;if(t?.once){let E={h:H=>{w(),r(H);},u:r,c:void 0,ce:f},w=m(g(e),E,l);return w}return m(g(e),{h:r,u:r,c:void 0,ce:f},l)}function A(e,n){return k(e,n,{once:true})}function O(e,n){if(p(),e==="*"){n===void 0?(y(o),o.length=0):K(o,n);return}let t=i.get(e);t!==void 0&&(n===void 0?(y(t),i.delete(e)):K(t,n));}function x(e,n,t,s){if(e===void 0||e===false)throw n;if(typeof e=="function")try{e(n,t,s);}catch{}}function W(e,n){p();let t=e,s=n,u=(i.get(t)??[]).slice(),l=o.slice();for(let r of u)try{r.h(s);}catch(f){x(r.ce!==void 0?r.ce:c,f,t,s);}for(let r of l)if(!(r.r!==void 0&&Math.random()>=r.r)){if(r.tm){let f=Date.now();if(r.ts!==void 0&&f-r.ts<r.tm)continue;r.ts=f;}try{r.h(t,s);}catch(f){x(c,f,t,s);}}}function b(){for(let e of i.values())y(e);y(o),i.clear(),o.length=0;}return {on:k,once:A,off:O,emit:W,clear(){p(),b();},dispose(){a||(b(),a=true);},get disposed(){return a}}}exports.EmitterDisposedError=h;exports.EmitterError=v;exports.createEmitter=j;//# sourceMappingURL=index.cjs.map
1
+ 'use strict';var E=class extends Error{name="EmitterError"},g=class extends Error{name="EmitterDisposedError"};function K(d,c){let i=d.findIndex(r=>r.u===c);if(i>=0){let r=d[i];r!==void 0&&(r.c?.(),r.c=void 0),d.splice(i,1);}}function H(d){for(let c of d)c.c?.(),c.c=void 0;}function y(d,c,i){d.push(c);let r=()=>{let f=d.indexOf(c);f>=0&&d.splice(f,1),c.c?.(),c.c=void 0;};if(i!==void 0){let f=()=>r();i.addEventListener("abort",f,{once:true}),c.c=()=>i.removeEventListener("abort",f);}return r}function j(d){let c=d?.captureHandlerErrors,i=new Map,r=[],f=false;function p(){if(f)throw new g("aieventjs: emitter has been disposed")}function h(n){let t=i.get(n);return t===void 0&&(t=[],i.set(n,t)),t}function k(n,t,o){p();let s=o?.sampleRate,u=o?.throttleMs;if(n==="*"){if(o?.captureErrors!==void 0)throw new E("aieventjs: captureErrors invalid on *")}else if(s!==void 0)throw new E("aieventjs: sampleRate wildcard-only");if(s!==void 0&&(!Number.isFinite(s)||s<=0||s>1))throw new E("aieventjs: sampleRate must be in (0,1]");if(u!==void 0&&(!Number.isFinite(u)||u<0))throw new E("aieventjs: throttleMs must be >= 0");let l=o?.signal;if(l?.aborted)return ()=>{};if(n==="*"){let v=t;if(o?.once){let m=y(r,{h:(R,M)=>{m(),v(R,M);},u:v,c:void 0,r:s,tm:u},l);return m}return y(r,{h:v,u:v,c:void 0,r:s,tm:u},l)}let e=t,a=o?.captureErrors;if(o?.once){let v={h:m=>{w(),e(m);},u:e,c:void 0,ce:a,tm:u},w=y(h(n),v,l);return w}return y(h(n),{h:e,u:e,c:void 0,ce:a,tm:u},l)}function A(n,t){return k(n,t,{once:true})}function O(n,t){if(p(),n==="*"){t===void 0?(H(r),r.length=0):K(r,t);return}let o=i.get(n);o!==void 0&&(t===void 0?(H(o),i.delete(n)):K(o,t));}function x(n,t,o,s){if(n===void 0||n===false)throw t;if(typeof n=="function")try{n(t,o,s);}catch{}}function W(n,t){p();let o=n,s=t,u=(i.get(o)??[]).slice(),l=r.slice();for(let e of u){if(e.tm){let a=Date.now();if(e.ts!==void 0&&a-e.ts<e.tm)continue;e.ts=a;}try{e.h(s);}catch(a){x(e.ce!==void 0?e.ce:c,a,o,s);}}for(let e of l)if(!(e.r!==void 0&&Math.random()>=e.r)){if(e.tm){let a=Date.now();if(e.ts!==void 0&&a-e.ts<e.tm)continue;e.ts=a;}try{e.h(o,s);}catch(a){x(c,a,o,s);}}}function b(){for(let n of i.values())H(n);H(r),i.clear(),r.length=0;}return {on:k,once:A,off:O,emit:W,clear(){p(),b();},dispose(){f||(b(),f=true);},get disposed(){return f}}}exports.EmitterDisposedError=g;exports.EmitterError=E;exports.createEmitter=j;//# sourceMappingURL=index.cjs.map
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["EmitterError","EmitterDisposedError","rmByUser","arr","user","e","flush","sub","sig","rm","i","fn","createEmitter","opts","cap","t","w","d","ck","ga","k","a","on","type","handler","o","sr","tm2","tp","p","ce","once","off","ap","pol","err","emit","payload","ts","ws","now","purge"],"mappings":"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","file":"index.cjs","sourcesContent":["// aieventjs — small, strict, typed event emitter for the ai*js family.\n//\n// v0.1.0: full implementation of the frozen API surface. Mitt-compatible\n// snapshot semantics, wildcard \"*\" handler, AbortSignal integration, once,\n// idempotent dispose, destructurable methods (no `this`).\n\n/**\n * Configuration for {@link createEmitter}. Controls the default error\n * policy for handlers thrown during `emit()`; per-handler\n * {@link OnOptions.captureErrors} overrides this default.\n *\n * @public\n */\nexport interface EmitterOptions {\n /**\n * Default error policy for all handlers when they throw during emit().\n *\n * - undefined / false (default) — first throw aborts dispatch (mitt-compatible).\n * - true — swallow; dispatch continues over all handlers in the snapshot.\n * - (err, type, payload) => void — invoked with the unknown error, the\n * event name as string, and the payload as unknown. If this callback\n * itself throws, the error is silently ignored.\n *\n * Per-subscription OnOptions.captureErrors overrides this for that handler.\n */\n captureHandlerErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);\n}\n\n/**\n * Handler invoked for a single typed event.\n *\n * @public\n */\nexport type EventHandler<Payload> = (payload: Payload) => void;\n\n/**\n * Handler invoked for the wildcard `\"*\"` subscription. Receives the actual\n * event type alongside the payload.\n *\n * @public\n */\nexport type WildcardHandler<Events extends Record<string, unknown>> = <K extends keyof Events>(\n type: K,\n payload: Events[K],\n) => void;\n\n/**\n * Subscription options accepted by {@link Emitter.on}.\n *\n * @public\n */\nexport interface OnOptions {\n /**\n * Aborting this signal removes the handler. The same effect as calling\n * the returned unsubscribe function. Pre-aborted signals never register.\n */\n signal?: AbortSignal;\n\n /** Auto-remove the handler after the first dispatch. Equivalent to `once()`. */\n once?: boolean;\n\n /**\n * Override emitter-level captureHandlerErrors for this handler.\n * - undefined — fall through to emitter-level.\n * - false — force re-throw, even when emitter-level is true / callback.\n * - true — swallow.\n * - (err, type, payload) => void — same semantics as the emitter-level callback.\n *\n * Throws EmitterError if set on a wildcard \"*\" subscription.\n * @invariant does not break snapshot-before-iterate semantics.\n */\n captureErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);\n\n /**\n * Wildcard \"*\" only. Probability in (0, 1] that a dispatch reaches this\n * handler. Math.random() is sampled per dispatch. Values <= 0 or > 1 are\n * rejected at on() time.\n *\n * Throws EmitterError if set on a typed handler.\n */\n sampleRate?: number;\n\n /**\n * Wildcard \"*\" only. Minimum milliseconds between successive calls.\n * Leading-edge: the first dispatch after subscription always fires;\n * subsequent dispatches within `throttleMs` are dropped (not queued).\n * Uses Date.now(). 0 = no throttle. Negative values are rejected.\n *\n * Throws EmitterError if set on a typed handler.\n */\n throttleMs?: number;\n}\n\n/**\n * Strongly-typed event emitter. Subscribe with {@link Emitter.on} (returns\n * an unsubscribe function), dispatch with {@link Emitter.emit}, dispose\n * with {@link Emitter.dispose} when finished.\n *\n * @typeParam Events — a string-keyed map from event name to payload type.\n * @public\n */\nexport interface Emitter<Events extends Record<string, unknown>> {\n /**\n * Subscribe to a single event type. Returns an unsubscribe function;\n * calling it (or aborting `opts.signal`) removes the handler.\n */\n on<K extends keyof Events>(\n type: K,\n handler: EventHandler<Events[K]>,\n opts?: OnOptions,\n ): () => void;\n\n /**\n * Subscribe to every event with a single handler that receives\n * `(type, payload)`. Wildcard handlers fire AFTER type-matched\n * handlers — same ordering as `mitt`.\n */\n on(type: \"*\", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;\n\n /**\n * Subscribe and auto-remove after the first dispatch. Equivalent to\n * `on(type, handler, { once: true })`.\n */\n once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;\n\n /**\n * Imperative unsubscribe. Prefer the unsubscribe function returned by\n * `on()` — it's faster (no reference lookup) and survives renames.\n * If `handler` is omitted, removes every handler for `type`.\n */\n off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;\n\n /**\n * Imperative wildcard unsubscribe.\n */\n off(type: \"*\", handler?: WildcardHandler<Events>): void;\n\n /**\n * Dispatch synchronously. Handlers receive `payload`; wildcard handlers\n * receive `(type, payload)`. Handler lists are snapshotted before iteration,\n * so removing a handler inside its own callback does not skip subsequent\n * handlers. By default, the first throwing handler aborts the dispatch;\n * set EmitterOptions.captureHandlerErrors (or per-handler OnOptions.captureErrors)\n * to swallow or report errors and continue.\n */\n emit<K extends keyof Events>(type: K, payload: Events[K]): void;\n\n /**\n * Remove every handler for every event (including wildcards). The\n * emitter remains usable. Use {@link dispose} for permanent teardown.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops every handler; subsequent `on` / `once` /\n * `emit` / `off` / `clear` throw {@link EmitterDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable emitter error. Thrown by `on()` when `OnOptions` violates a\n * precondition: `captureErrors` set on a wildcard `\"*\"` subscription;\n * `sampleRate` / `throttleMs` set on a typed subscription; `sampleRate`\n * outside `(0, 1]`; or `throttleMs` negative.\n *\n * @public\n */\nexport class EmitterError extends Error {\n override readonly name = \"EmitterError\";\n}\n\n/**\n * Thrown by any emitter method called after {@link Emitter.dispose}.\n *\n * @public\n */\nexport class EmitterDisposedError extends Error {\n override readonly name = \"EmitterDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\n// Mutable `c` field (not optional `?:`) avoids exactOptionalPropertyTypes TS2412\n// when assigning undefined. Short field names reduce minified output size.\ntype ErrorPolicy = boolean | ((err: unknown, type: string, payload: unknown) => void);\n\ninterface E<H> {\n h: H; // handler (may be a once-wrapper)\n c: (() => void) | undefined; // abortCleanup\n u: H; // user-provided handler (off matching)\n // v0.3.0: per-handler error policy and wildcard throttle/sample state.\n // Fields typed as `T | undefined` (not just `T`) so that exactOptionalPropertyTypes\n // permits assigning `undefined` in object literals (avoids TS2375).\n ce?: ErrorPolicy | undefined; // captureErrors override (typed only)\n r?: number | undefined; // sampleRate (wildcard only)\n tm?: number | undefined; // throttleMs (wildcard only)\n ts?: number | undefined; // last call timestamp — mutated during dispatch (wildcard only)\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 const cap = opts?.captureHandlerErrors;\n\n const t: Map<string, E<AH>[]> = new Map();\n const w: E<WH>[] = [];\n let d = false;\n\n function ck(): void {\n if (d) throw new EmitterDisposedError(\"aieventjs: emitter has been disposed\");\n }\n\n // Get or create typed handler array for a key.\n function ga(k: string): E<AH>[] {\n let a = t.get(k);\n if (a === undefined) {\n a = [];\n t.set(k, a);\n }\n return a;\n }\n\n function on(type: string | \"*\", handler: AH | WH, o?: OnOptions): () => void {\n ck();\n // v0.3.0 guards: cross-domain options + range checks\n const sr = o?.sampleRate;\n const tm2 = o?.throttleMs;\n if (type === \"*\") {\n if (o?.captureErrors !== undefined)\n throw new EmitterError(\"aieventjs: captureErrors invalid on *\");\n } else {\n if (sr !== undefined) throw new EmitterError(\"aieventjs: sampleRate wildcard-only\");\n if (tm2 !== undefined) throw new EmitterError(\"aieventjs: throttleMs wildcard-only\");\n }\n if (sr !== undefined && (!Number.isFinite(sr) || sr <= 0 || sr > 1))\n throw new EmitterError(\"aieventjs: sampleRate must be in (0,1]\");\n if (tm2 !== undefined && (!Number.isFinite(tm2) || tm2 < 0))\n throw new EmitterError(\"aieventjs: throttleMs must be >= 0\");\n const sig = o?.signal;\n if (sig?.aborted) return () => {};\n\n if (type === \"*\") {\n const fn = handler as WH;\n if (o?.once) {\n const e: E<WH> = {\n h: (tp, p) => {\n rm();\n fn(tp, p);\n },\n u: fn,\n c: undefined,\n r: sr,\n tm: tm2,\n };\n const rm = sub(w, e, sig);\n return rm;\n }\n return sub(w, { h: fn, u: fn, c: undefined, r: sr, tm: tm2 }, sig);\n }\n\n const fn = handler as AH;\n const ce = o?.captureErrors;\n if (o?.once) {\n const e: E<AH> = {\n h: (p) => {\n rm();\n fn(p);\n },\n u: fn,\n c: undefined,\n ce: ce,\n };\n const rm = sub(ga(type), e, sig);\n return rm;\n }\n return sub(ga(type), { h: fn, u: fn, c: undefined, ce: ce }, 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 // Inline error policy handler — policy undefined/false → re-throw; true → swallow;\n // function → invoke and swallow; if callback throws, ignore silently.\n function ap(pol: ErrorPolicy | undefined, err: unknown, k: string, p: unknown): void {\n if (pol === undefined || pol === false) throw err;\n if (typeof pol === \"function\")\n try {\n pol(err, k, p);\n } catch {\n /* silent */\n }\n }\n\n function emit<K extends keyof Events>(type: K, payload: Events[K]): void {\n ck();\n // Both slices happen BEFORE any handler call (snapshot-before-iterate).\n const k = type as string;\n const p = payload as unknown;\n const ts = (t.get(k) ?? []).slice();\n const ws = w.slice();\n for (const e of ts) {\n try {\n e.h(p);\n } catch (err) {\n ap(e.ce !== undefined ? e.ce : cap, err, k, p);\n }\n }\n for (const e of ws) {\n if (e.r !== undefined && Math.random() >= e.r) continue;\n if (e.tm) {\n const now = Date.now();\n if (e.ts !== undefined && now - e.ts < e.tm) continue;\n e.ts = now;\n }\n try {\n e.h(k, p as never);\n } catch (err) {\n ap(cap, err, k, p);\n }\n }\n }\n\n function purge(): void {\n for (const a of t.values()) 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"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["EmitterError","EmitterDisposedError","rmByUser","arr","user","e","flush","sub","sig","rm","i","fn","createEmitter","opts","cap","t","w","d","ck","ga","k","a","on","type","handler","sr","tm2","tp","p","ce","once","off","ap","pol","err","emit","payload","ts","ws","now","purge"],"mappings":"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","file":"index.cjs","sourcesContent":["// aieventjs — small, strict, typed event emitter for the ai*js family.\n//\n// v0.1.0: full implementation of the frozen API surface. Mitt-compatible\n// snapshot semantics, wildcard \"*\" handler, AbortSignal integration, once,\n// idempotent dispose, destructurable methods (no `this`).\n\n/**\n * Configuration for {@link createEmitter}. Controls the default error\n * policy for handlers thrown during `emit()`; per-handler\n * {@link OnOptions.captureErrors} overrides this default.\n *\n * @public\n */\nexport interface EmitterOptions {\n /**\n * Default error policy for all handlers when they throw during emit().\n *\n * - undefined / false (default) — first throw aborts dispatch (mitt-compatible).\n * - true — swallow; dispatch continues over all handlers in the snapshot.\n * - (err, type, payload) => void — invoked with the unknown error, the\n * event name as string, and the payload as unknown. If this callback\n * itself throws, the error is silently ignored.\n *\n * Per-subscription OnOptions.captureErrors overrides this for that handler.\n */\n captureHandlerErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);\n}\n\n/**\n * Handler invoked for a single typed event.\n *\n * @public\n */\nexport type EventHandler<Payload> = (payload: Payload) => void;\n\n/**\n * Handler invoked for the wildcard `\"*\"` subscription. Receives the actual\n * event type alongside the payload.\n *\n * @public\n */\nexport type WildcardHandler<Events extends Record<string, unknown>> = <K extends keyof Events>(\n type: K,\n payload: Events[K],\n) => void;\n\n/**\n * Subscription options accepted by {@link Emitter.on}.\n *\n * @public\n */\nexport interface OnOptions {\n /**\n * Aborting this signal removes the handler. The same effect as calling\n * the returned unsubscribe function. Pre-aborted signals never register.\n */\n signal?: AbortSignal;\n\n /** Auto-remove the handler after the first dispatch. Equivalent to `once()`. */\n once?: boolean;\n\n /**\n * Override emitter-level captureHandlerErrors for this handler.\n * - undefined — fall through to emitter-level.\n * - false — force re-throw, even when emitter-level is true / callback.\n * - true — swallow.\n * - (err, type, payload) => void — same semantics as the emitter-level callback.\n *\n * Throws EmitterError if set on a wildcard \"*\" subscription.\n * @invariant does not break snapshot-before-iterate semantics.\n */\n captureErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);\n\n /**\n * Wildcard \"*\" only. Probability in (0, 1] that a dispatch reaches this\n * handler. Math.random() is sampled per dispatch. Values <= 0 or > 1 are\n * rejected at on() time.\n *\n * Throws EmitterError if set on a typed handler.\n */\n sampleRate?: number;\n\n /**\n * Per-handler leading-edge throttle. Minimum milliseconds between successive\n * calls to this handler. The first dispatch after subscription always fires;\n * subsequent dispatches within `throttleMs` are dropped (not queued).\n * Uses Date.now(). 0 = no throttle. Non-finite or negative values are rejected.\n *\n * Valid on both typed and wildcard `\"*\"` subscriptions (since v0.5.3); each\n * handler keeps its own throttle clock. Useful for per-event HUD throttling,\n * e.g. a `credits/change` event that fires every frame.\n */\n throttleMs?: number;\n}\n\n/**\n * Strongly-typed event emitter. Subscribe with {@link Emitter.on} (returns\n * an unsubscribe function), dispatch with {@link Emitter.emit}, dispose\n * with {@link Emitter.dispose} when finished.\n *\n * @typeParam Events — a string-keyed map from event name to payload type.\n * @public\n */\nexport interface Emitter<Events extends Record<string, unknown>> {\n /**\n * Subscribe to a single event type. Returns an unsubscribe function;\n * calling it (or aborting `opts.signal`) removes the handler.\n */\n on<K extends keyof Events>(\n type: K,\n handler: EventHandler<Events[K]>,\n opts?: OnOptions,\n ): () => void;\n\n /**\n * Subscribe to every event with a single handler that receives\n * `(type, payload)`. Wildcard handlers fire AFTER type-matched\n * handlers — same ordering as `mitt`.\n */\n on(type: \"*\", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;\n\n /**\n * Subscribe and auto-remove after the first dispatch. Equivalent to\n * `on(type, handler, { once: true })`.\n */\n once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;\n\n /**\n * Imperative unsubscribe. Prefer the unsubscribe function returned by\n * `on()` — it's faster (no reference lookup) and survives renames.\n * If `handler` is omitted, removes every handler for `type`.\n */\n off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;\n\n /**\n * Imperative wildcard unsubscribe.\n */\n off(type: \"*\", handler?: WildcardHandler<Events>): void;\n\n /**\n * Dispatch synchronously. Handlers receive `payload`; wildcard handlers\n * receive `(type, payload)`. Handler lists are snapshotted before iteration,\n * so removing a handler inside its own callback does not skip subsequent\n * handlers. By default, the first throwing handler aborts the dispatch;\n * set EmitterOptions.captureHandlerErrors (or per-handler OnOptions.captureErrors)\n * to swallow or report errors and continue.\n */\n emit<K extends keyof Events>(type: K, payload: Events[K]): void;\n\n /**\n * Remove every handler for every event (including wildcards). The\n * emitter remains usable. Use {@link dispose} for permanent teardown.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops every handler; subsequent `on` / `once` /\n * `emit` / `off` / `clear` throw {@link EmitterDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable emitter error. Thrown by `on()` when `OnOptions` violates a\n * precondition: `captureErrors` set on a wildcard `\"*\"` subscription;\n * `sampleRate` set on a typed subscription; `sampleRate` outside `(0, 1]`; or\n * `throttleMs` non-finite or negative.\n *\n * @public\n */\nexport class EmitterError extends Error {\n override readonly name = \"EmitterError\";\n}\n\n/**\n * Thrown by any emitter method called after {@link Emitter.dispose}.\n *\n * @public\n */\nexport class EmitterDisposedError extends Error {\n override readonly name = \"EmitterDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\n// Mutable `c` field (not optional `?:`) avoids exactOptionalPropertyTypes TS2412\n// when assigning undefined. Short field names reduce minified output size.\ntype ErrorPolicy = boolean | ((err: unknown, type: string, payload: unknown) => void);\n\ninterface E<H> {\n h: H; // handler (may be a once-wrapper)\n c: (() => void) | undefined; // abortCleanup\n u: H; // user-provided handler (off matching)\n // v0.3.0: per-handler error policy and throttle/sample state.\n // Fields typed as `T | undefined` (not just `T`) so that exactOptionalPropertyTypes\n // permits assigning `undefined` in object literals (avoids TS2375).\n ce?: ErrorPolicy | undefined; // captureErrors override (typed only)\n r?: number | undefined; // sampleRate (wildcard only)\n tm?: number | undefined; // throttleMs (typed or wildcard; v0.5.3)\n ts?: number | undefined; // last call timestamp — mutated during dispatch (throttle clock)\n}\n\ntype AH = EventHandler<unknown>;\ntype WH = WildcardHandler<Record<string, unknown>>;\n\n// Remove one entry by user-identity from an array; run its abort cleanup.\nfunction rmByUser<H>(arr: E<H>[], user: H): void {\n const i = arr.findIndex((e) => e.u === user);\n if (i >= 0) {\n const e = arr[i];\n if (e !== undefined) {\n e.c?.();\n e.c = undefined;\n }\n arr.splice(i, 1);\n }\n}\n\n// Flush all abort cleanups from an array (for clear / dispose).\nfunction flush<H>(arr: E<H>[]): void {\n for (const e of arr) {\n e.c?.();\n e.c = undefined;\n }\n}\n\n// Push entry onto arr, wire AbortSignal, return unsubscribe.\nfunction sub<H>(arr: E<H>[], e: E<H>, sig: AbortSignal | undefined): () => void {\n arr.push(e);\n const rm = () => {\n const i = arr.indexOf(e);\n if (i >= 0) arr.splice(i, 1);\n e.c?.();\n e.c = undefined;\n };\n if (sig !== undefined) {\n const fn = () => rm();\n sig.addEventListener(\"abort\", fn, { once: true });\n e.c = () => sig.removeEventListener(\"abort\", fn);\n }\n return rm;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a strongly-typed event emitter.\n *\n * @remarks\n * Declare the event map with a `type` alias, not an `interface`. The `Events`\n * generic is constrained to `Record<string, unknown>`, and a *plain* TypeScript\n * `interface` has no implicit index signature, so it fails the constraint with\n * *\"Index signature for type 'string' is missing in type ...\"*. A `type` object\n * literal satisfies the constraint structurally. (An `interface` with an explicit\n * index signature or `extends Record<string, unknown>` also compiles, but widens\n * `keyof Events` to `string`, losing strict event-name checking.)\n *\n * ```ts\n * // ❌ interface — fails the Record<string, unknown> constraint\n * interface Events { \"user:login\": { id: string } }\n * const bus = createEmitter<Events>(); // TS2344\n *\n * // ✅ type — satisfies the constraint\n * type Events = { \"user:login\": { id: string } };\n * const bus = createEmitter<Events>();\n * ```\n *\n * @example\n * ```ts\n * import { createEmitter } from \"aieventjs\";\n *\n * type Events = {\n * \"user:login\": { id: string };\n * \"user:logout\": void;\n * };\n *\n * const bus = createEmitter<Events>();\n *\n * const off = bus.on(\"user:login\", (u) => console.log(\"hi\", u.id));\n * bus.emit(\"user:login\", { id: \"alice\" });\n * off();\n *\n * bus.on(\"*\", (type, payload) => console.log(\"event\", type, payload));\n * ```\n *\n * @public\n */\nexport function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(\n opts?: EmitterOptions,\n): Emitter<Events> {\n const cap = opts?.captureHandlerErrors;\n\n const t: Map<string, E<AH>[]> = new Map();\n const w: E<WH>[] = [];\n let d = false;\n\n function ck(): void {\n if (d) throw new EmitterDisposedError(\"aieventjs: emitter has been disposed\");\n }\n\n // Get or create typed handler array for a key.\n function ga(k: string): E<AH>[] {\n let a = t.get(k);\n if (a === undefined) {\n a = [];\n t.set(k, a);\n }\n return a;\n }\n\n function on(type: string | \"*\", handler: AH | WH, o?: OnOptions): () => void {\n ck();\n // v0.3.0 guards: cross-domain options + range checks.\n // v0.5.3: throttleMs is now valid on typed handlers too (per-handler clock);\n // sampleRate remains wildcard-only.\n const sr = o?.sampleRate;\n const tm2 = o?.throttleMs;\n if (type === \"*\") {\n if (o?.captureErrors !== undefined)\n throw new EmitterError(\"aieventjs: captureErrors invalid on *\");\n } else {\n if (sr !== undefined) throw new EmitterError(\"aieventjs: sampleRate wildcard-only\");\n }\n if (sr !== undefined && (!Number.isFinite(sr) || sr <= 0 || sr > 1))\n throw new EmitterError(\"aieventjs: sampleRate must be in (0,1]\");\n if (tm2 !== undefined && (!Number.isFinite(tm2) || tm2 < 0))\n throw new EmitterError(\"aieventjs: throttleMs must be >= 0\");\n const sig = o?.signal;\n if (sig?.aborted) return () => {};\n\n if (type === \"*\") {\n const fn = handler as WH;\n if (o?.once) {\n const e: E<WH> = {\n h: (tp, p) => {\n rm();\n fn(tp, p);\n },\n u: fn,\n c: undefined,\n r: sr,\n tm: tm2,\n };\n const rm = sub(w, e, sig);\n return rm;\n }\n return sub(w, { h: fn, u: fn, c: undefined, r: sr, tm: tm2 }, sig);\n }\n\n const fn = handler as AH;\n const ce = o?.captureErrors;\n if (o?.once) {\n const e: E<AH> = {\n h: (p) => {\n rm();\n fn(p);\n },\n u: fn,\n c: undefined,\n ce: ce,\n tm: tm2,\n };\n const rm = sub(ga(type), e, sig);\n return rm;\n }\n return sub(ga(type), { h: fn, u: fn, c: undefined, ce: ce, tm: tm2 }, sig);\n }\n\n function once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void {\n return on(type as string, handler as AH, { once: true });\n }\n\n function off(type: string | \"*\", handler?: AH | WH): void {\n ck();\n if (type === \"*\") {\n if (handler === undefined) {\n flush(w);\n w.length = 0;\n } else {\n rmByUser(w, handler as WH);\n }\n return;\n }\n const arr = t.get(type);\n if (arr === undefined) return;\n if (handler === undefined) {\n flush(arr);\n t.delete(type);\n } else {\n rmByUser(arr, handler as AH);\n }\n }\n\n // Inline error policy handler — policy undefined/false → re-throw; true → swallow;\n // function → invoke and swallow; if callback throws, ignore silently.\n function ap(pol: ErrorPolicy | undefined, err: unknown, k: string, p: unknown): void {\n if (pol === undefined || pol === false) throw err;\n if (typeof pol === \"function\")\n try {\n pol(err, k, p);\n } catch {\n /* silent */\n }\n }\n\n function emit<K extends keyof Events>(type: K, payload: Events[K]): void {\n ck();\n // Both slices happen BEFORE any handler call (snapshot-before-iterate).\n const k = type as string;\n const p = payload as unknown;\n const ts = (t.get(k) ?? []).slice();\n const ws = w.slice();\n for (const e of ts) {\n if (e.tm) {\n const now = Date.now();\n if (e.ts !== undefined && now - e.ts < e.tm) continue;\n e.ts = now;\n }\n try {\n e.h(p);\n } catch (err) {\n ap(e.ce !== undefined ? e.ce : cap, err, k, p);\n }\n }\n for (const e of ws) {\n if (e.r !== undefined && Math.random() >= e.r) continue;\n if (e.tm) {\n const now = Date.now();\n if (e.ts !== undefined && now - e.ts < e.tm) continue;\n e.ts = now;\n }\n try {\n e.h(k, p as never);\n } catch (err) {\n ap(cap, err, k, p);\n }\n }\n }\n\n function purge(): void {\n for (const a of t.values()) 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 CHANGED
@@ -65,12 +65,14 @@ interface OnOptions {
65
65
  */
66
66
  sampleRate?: number;
67
67
  /**
68
- * Wildcard "*" only. Minimum milliseconds between successive calls.
69
- * Leading-edge: the first dispatch after subscription always fires;
68
+ * Per-handler leading-edge throttle. Minimum milliseconds between successive
69
+ * calls to this handler. The first dispatch after subscription always fires;
70
70
  * subsequent dispatches within `throttleMs` are dropped (not queued).
71
- * Uses Date.now(). 0 = no throttle. Negative values are rejected.
71
+ * Uses Date.now(). 0 = no throttle. Non-finite or negative values are rejected.
72
72
  *
73
- * Throws EmitterError if set on a typed handler.
73
+ * Valid on both typed and wildcard `"*"` subscriptions (since v0.5.3); each
74
+ * handler keeps its own throttle clock. Useful for per-event HUD throttling,
75
+ * e.g. a `credits/change` event that fires every frame.
74
76
  */
75
77
  throttleMs?: number;
76
78
  }
@@ -134,8 +136,8 @@ interface Emitter<Events extends Record<string, unknown>> {
134
136
  /**
135
137
  * Recoverable emitter error. Thrown by `on()` when `OnOptions` violates a
136
138
  * precondition: `captureErrors` set on a wildcard `"*"` subscription;
137
- * `sampleRate` / `throttleMs` set on a typed subscription; `sampleRate`
138
- * outside `(0, 1]`; or `throttleMs` negative.
139
+ * `sampleRate` set on a typed subscription; `sampleRate` outside `(0, 1]`; or
140
+ * `throttleMs` non-finite or negative.
139
141
  *
140
142
  * @public
141
143
  */
@@ -153,6 +155,25 @@ declare class EmitterDisposedError extends Error {
153
155
  /**
154
156
  * Construct a strongly-typed event emitter.
155
157
  *
158
+ * @remarks
159
+ * Declare the event map with a `type` alias, not an `interface`. The `Events`
160
+ * generic is constrained to `Record<string, unknown>`, and a *plain* TypeScript
161
+ * `interface` has no implicit index signature, so it fails the constraint with
162
+ * *"Index signature for type 'string' is missing in type ..."*. A `type` object
163
+ * literal satisfies the constraint structurally. (An `interface` with an explicit
164
+ * index signature or `extends Record<string, unknown>` also compiles, but widens
165
+ * `keyof Events` to `string`, losing strict event-name checking.)
166
+ *
167
+ * ```ts
168
+ * // ❌ interface — fails the Record<string, unknown> constraint
169
+ * interface Events { "user:login": { id: string } }
170
+ * const bus = createEmitter<Events>(); // TS2344
171
+ *
172
+ * // ✅ type — satisfies the constraint
173
+ * type Events = { "user:login": { id: string } };
174
+ * const bus = createEmitter<Events>();
175
+ * ```
176
+ *
156
177
  * @example
157
178
  * ```ts
158
179
  * import { createEmitter } from "aieventjs";
package/dist/index.d.ts CHANGED
@@ -65,12 +65,14 @@ interface OnOptions {
65
65
  */
66
66
  sampleRate?: number;
67
67
  /**
68
- * Wildcard "*" only. Minimum milliseconds between successive calls.
69
- * Leading-edge: the first dispatch after subscription always fires;
68
+ * Per-handler leading-edge throttle. Minimum milliseconds between successive
69
+ * calls to this handler. The first dispatch after subscription always fires;
70
70
  * subsequent dispatches within `throttleMs` are dropped (not queued).
71
- * Uses Date.now(). 0 = no throttle. Negative values are rejected.
71
+ * Uses Date.now(). 0 = no throttle. Non-finite or negative values are rejected.
72
72
  *
73
- * Throws EmitterError if set on a typed handler.
73
+ * Valid on both typed and wildcard `"*"` subscriptions (since v0.5.3); each
74
+ * handler keeps its own throttle clock. Useful for per-event HUD throttling,
75
+ * e.g. a `credits/change` event that fires every frame.
74
76
  */
75
77
  throttleMs?: number;
76
78
  }
@@ -134,8 +136,8 @@ interface Emitter<Events extends Record<string, unknown>> {
134
136
  /**
135
137
  * Recoverable emitter error. Thrown by `on()` when `OnOptions` violates a
136
138
  * precondition: `captureErrors` set on a wildcard `"*"` subscription;
137
- * `sampleRate` / `throttleMs` set on a typed subscription; `sampleRate`
138
- * outside `(0, 1]`; or `throttleMs` negative.
139
+ * `sampleRate` set on a typed subscription; `sampleRate` outside `(0, 1]`; or
140
+ * `throttleMs` non-finite or negative.
139
141
  *
140
142
  * @public
141
143
  */
@@ -153,6 +155,25 @@ declare class EmitterDisposedError extends Error {
153
155
  /**
154
156
  * Construct a strongly-typed event emitter.
155
157
  *
158
+ * @remarks
159
+ * Declare the event map with a `type` alias, not an `interface`. The `Events`
160
+ * generic is constrained to `Record<string, unknown>`, and a *plain* TypeScript
161
+ * `interface` has no implicit index signature, so it fails the constraint with
162
+ * *"Index signature for type 'string' is missing in type ..."*. A `type` object
163
+ * literal satisfies the constraint structurally. (An `interface` with an explicit
164
+ * index signature or `extends Record<string, unknown>` also compiles, but widens
165
+ * `keyof Events` to `string`, losing strict event-name checking.)
166
+ *
167
+ * ```ts
168
+ * // ❌ interface — fails the Record<string, unknown> constraint
169
+ * interface Events { "user:login": { id: string } }
170
+ * const bus = createEmitter<Events>(); // TS2344
171
+ *
172
+ * // ✅ type — satisfies the constraint
173
+ * type Events = { "user:login": { id: string } };
174
+ * const bus = createEmitter<Events>();
175
+ * ```
176
+ *
156
177
  * @example
157
178
  * ```ts
158
179
  * import { createEmitter } from "aieventjs";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var v=class extends Error{name="EmitterError"},h=class extends Error{name="EmitterDisposedError"};function K(d,c){let i=d.findIndex(o=>o.u===c);if(i>=0){let o=d[i];o!==void 0&&(o.c?.(),o.c=void 0),d.splice(i,1);}}function y(d){for(let c of d)c.c?.(),c.c=void 0;}function m(d,c,i){d.push(c);let o=()=>{let a=d.indexOf(c);a>=0&&d.splice(a,1),c.c?.(),c.c=void 0;};if(i!==void 0){let a=()=>o();i.addEventListener("abort",a,{once:true}),c.c=()=>i.removeEventListener("abort",a);}return o}function j(d){let c=d?.captureHandlerErrors,i=new Map,o=[],a=false;function p(){if(a)throw new h("aieventjs: emitter has been disposed")}function g(e){let n=i.get(e);return n===void 0&&(n=[],i.set(e,n)),n}function k(e,n,t){p();let s=t?.sampleRate,u=t?.throttleMs;if(e==="*"){if(t?.captureErrors!==void 0)throw new v("aieventjs: captureErrors invalid on *")}else {if(s!==void 0)throw new v("aieventjs: sampleRate wildcard-only");if(u!==void 0)throw new v("aieventjs: throttleMs wildcard-only")}if(s!==void 0&&(!Number.isFinite(s)||s<=0||s>1))throw new v("aieventjs: sampleRate must be in (0,1]");if(u!==void 0&&(!Number.isFinite(u)||u<0))throw new v("aieventjs: throttleMs must be >= 0");let l=t?.signal;if(l?.aborted)return ()=>{};if(e==="*"){let E=n;if(t?.once){let H=m(o,{h:(R,M)=>{H(),E(R,M);},u:E,c:void 0,r:s,tm:u},l);return H}return m(o,{h:E,u:E,c:void 0,r:s,tm:u},l)}let r=n,f=t?.captureErrors;if(t?.once){let E={h:H=>{w(),r(H);},u:r,c:void 0,ce:f},w=m(g(e),E,l);return w}return m(g(e),{h:r,u:r,c:void 0,ce:f},l)}function A(e,n){return k(e,n,{once:true})}function O(e,n){if(p(),e==="*"){n===void 0?(y(o),o.length=0):K(o,n);return}let t=i.get(e);t!==void 0&&(n===void 0?(y(t),i.delete(e)):K(t,n));}function x(e,n,t,s){if(e===void 0||e===false)throw n;if(typeof e=="function")try{e(n,t,s);}catch{}}function W(e,n){p();let t=e,s=n,u=(i.get(t)??[]).slice(),l=o.slice();for(let r of u)try{r.h(s);}catch(f){x(r.ce!==void 0?r.ce:c,f,t,s);}for(let r of l)if(!(r.r!==void 0&&Math.random()>=r.r)){if(r.tm){let f=Date.now();if(r.ts!==void 0&&f-r.ts<r.tm)continue;r.ts=f;}try{r.h(t,s);}catch(f){x(c,f,t,s);}}}function b(){for(let e of i.values())y(e);y(o),i.clear(),o.length=0;}return {on:k,once:A,off:O,emit:W,clear(){p(),b();},dispose(){a||(b(),a=true);},get disposed(){return a}}}export{h as EmitterDisposedError,v as EmitterError,j as createEmitter};//# sourceMappingURL=index.js.map
1
+ var E=class extends Error{name="EmitterError"},g=class extends Error{name="EmitterDisposedError"};function K(d,c){let i=d.findIndex(r=>r.u===c);if(i>=0){let r=d[i];r!==void 0&&(r.c?.(),r.c=void 0),d.splice(i,1);}}function H(d){for(let c of d)c.c?.(),c.c=void 0;}function y(d,c,i){d.push(c);let r=()=>{let f=d.indexOf(c);f>=0&&d.splice(f,1),c.c?.(),c.c=void 0;};if(i!==void 0){let f=()=>r();i.addEventListener("abort",f,{once:true}),c.c=()=>i.removeEventListener("abort",f);}return r}function j(d){let c=d?.captureHandlerErrors,i=new Map,r=[],f=false;function p(){if(f)throw new g("aieventjs: emitter has been disposed")}function h(n){let t=i.get(n);return t===void 0&&(t=[],i.set(n,t)),t}function k(n,t,o){p();let s=o?.sampleRate,u=o?.throttleMs;if(n==="*"){if(o?.captureErrors!==void 0)throw new E("aieventjs: captureErrors invalid on *")}else if(s!==void 0)throw new E("aieventjs: sampleRate wildcard-only");if(s!==void 0&&(!Number.isFinite(s)||s<=0||s>1))throw new E("aieventjs: sampleRate must be in (0,1]");if(u!==void 0&&(!Number.isFinite(u)||u<0))throw new E("aieventjs: throttleMs must be >= 0");let l=o?.signal;if(l?.aborted)return ()=>{};if(n==="*"){let v=t;if(o?.once){let m=y(r,{h:(R,M)=>{m(),v(R,M);},u:v,c:void 0,r:s,tm:u},l);return m}return y(r,{h:v,u:v,c:void 0,r:s,tm:u},l)}let e=t,a=o?.captureErrors;if(o?.once){let v={h:m=>{w(),e(m);},u:e,c:void 0,ce:a,tm:u},w=y(h(n),v,l);return w}return y(h(n),{h:e,u:e,c:void 0,ce:a,tm:u},l)}function A(n,t){return k(n,t,{once:true})}function O(n,t){if(p(),n==="*"){t===void 0?(H(r),r.length=0):K(r,t);return}let o=i.get(n);o!==void 0&&(t===void 0?(H(o),i.delete(n)):K(o,t));}function x(n,t,o,s){if(n===void 0||n===false)throw t;if(typeof n=="function")try{n(t,o,s);}catch{}}function W(n,t){p();let o=n,s=t,u=(i.get(o)??[]).slice(),l=r.slice();for(let e of u){if(e.tm){let a=Date.now();if(e.ts!==void 0&&a-e.ts<e.tm)continue;e.ts=a;}try{e.h(s);}catch(a){x(e.ce!==void 0?e.ce:c,a,o,s);}}for(let e of l)if(!(e.r!==void 0&&Math.random()>=e.r)){if(e.tm){let a=Date.now();if(e.ts!==void 0&&a-e.ts<e.tm)continue;e.ts=a;}try{e.h(o,s);}catch(a){x(c,a,o,s);}}}function b(){for(let n of i.values())H(n);H(r),i.clear(),r.length=0;}return {on:k,once:A,off:O,emit:W,clear(){p(),b();},dispose(){f||(b(),f=true);},get disposed(){return f}}}export{g as EmitterDisposedError,E as EmitterError,j as createEmitter};//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["EmitterError","EmitterDisposedError","rmByUser","arr","user","e","flush","sub","sig","rm","i","fn","createEmitter","opts","cap","t","w","d","ck","ga","k","a","on","type","handler","o","sr","tm2","tp","p","ce","once","off","ap","pol","err","emit","payload","ts","ws","now","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}. Controls the default error\n * policy for handlers thrown during `emit()`; per-handler\n * {@link OnOptions.captureErrors} overrides this default.\n *\n * @public\n */\nexport interface EmitterOptions {\n /**\n * Default error policy for all handlers when they throw during emit().\n *\n * - undefined / false (default) — first throw aborts dispatch (mitt-compatible).\n * - true — swallow; dispatch continues over all handlers in the snapshot.\n * - (err, type, payload) => void — invoked with the unknown error, the\n * event name as string, and the payload as unknown. If this callback\n * itself throws, the error is silently ignored.\n *\n * Per-subscription OnOptions.captureErrors overrides this for that handler.\n */\n captureHandlerErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);\n}\n\n/**\n * Handler invoked for a single typed event.\n *\n * @public\n */\nexport type EventHandler<Payload> = (payload: Payload) => void;\n\n/**\n * Handler invoked for the wildcard `\"*\"` subscription. Receives the actual\n * event type alongside the payload.\n *\n * @public\n */\nexport type WildcardHandler<Events extends Record<string, unknown>> = <K extends keyof Events>(\n type: K,\n payload: Events[K],\n) => void;\n\n/**\n * Subscription options accepted by {@link Emitter.on}.\n *\n * @public\n */\nexport interface OnOptions {\n /**\n * Aborting this signal removes the handler. The same effect as calling\n * the returned unsubscribe function. Pre-aborted signals never register.\n */\n signal?: AbortSignal;\n\n /** Auto-remove the handler after the first dispatch. Equivalent to `once()`. */\n once?: boolean;\n\n /**\n * Override emitter-level captureHandlerErrors for this handler.\n * - undefined — fall through to emitter-level.\n * - false — force re-throw, even when emitter-level is true / callback.\n * - true — swallow.\n * - (err, type, payload) => void — same semantics as the emitter-level callback.\n *\n * Throws EmitterError if set on a wildcard \"*\" subscription.\n * @invariant does not break snapshot-before-iterate semantics.\n */\n captureErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);\n\n /**\n * Wildcard \"*\" only. Probability in (0, 1] that a dispatch reaches this\n * handler. Math.random() is sampled per dispatch. Values <= 0 or > 1 are\n * rejected at on() time.\n *\n * Throws EmitterError if set on a typed handler.\n */\n sampleRate?: number;\n\n /**\n * Wildcard \"*\" only. Minimum milliseconds between successive calls.\n * Leading-edge: the first dispatch after subscription always fires;\n * subsequent dispatches within `throttleMs` are dropped (not queued).\n * Uses Date.now(). 0 = no throttle. Negative values are rejected.\n *\n * Throws EmitterError if set on a typed handler.\n */\n throttleMs?: number;\n}\n\n/**\n * Strongly-typed event emitter. Subscribe with {@link Emitter.on} (returns\n * an unsubscribe function), dispatch with {@link Emitter.emit}, dispose\n * with {@link Emitter.dispose} when finished.\n *\n * @typeParam Events — a string-keyed map from event name to payload type.\n * @public\n */\nexport interface Emitter<Events extends Record<string, unknown>> {\n /**\n * Subscribe to a single event type. Returns an unsubscribe function;\n * calling it (or aborting `opts.signal`) removes the handler.\n */\n on<K extends keyof Events>(\n type: K,\n handler: EventHandler<Events[K]>,\n opts?: OnOptions,\n ): () => void;\n\n /**\n * Subscribe to every event with a single handler that receives\n * `(type, payload)`. Wildcard handlers fire AFTER type-matched\n * handlers — same ordering as `mitt`.\n */\n on(type: \"*\", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;\n\n /**\n * Subscribe and auto-remove after the first dispatch. Equivalent to\n * `on(type, handler, { once: true })`.\n */\n once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;\n\n /**\n * Imperative unsubscribe. Prefer the unsubscribe function returned by\n * `on()` — it's faster (no reference lookup) and survives renames.\n * If `handler` is omitted, removes every handler for `type`.\n */\n off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;\n\n /**\n * Imperative wildcard unsubscribe.\n */\n off(type: \"*\", handler?: WildcardHandler<Events>): void;\n\n /**\n * Dispatch synchronously. Handlers receive `payload`; wildcard handlers\n * receive `(type, payload)`. Handler lists are snapshotted before iteration,\n * so removing a handler inside its own callback does not skip subsequent\n * handlers. By default, the first throwing handler aborts the dispatch;\n * set EmitterOptions.captureHandlerErrors (or per-handler OnOptions.captureErrors)\n * to swallow or report errors and continue.\n */\n emit<K extends keyof Events>(type: K, payload: Events[K]): void;\n\n /**\n * Remove every handler for every event (including wildcards). The\n * emitter remains usable. Use {@link dispose} for permanent teardown.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops every handler; subsequent `on` / `once` /\n * `emit` / `off` / `clear` throw {@link EmitterDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable emitter error. Thrown by `on()` when `OnOptions` violates a\n * precondition: `captureErrors` set on a wildcard `\"*\"` subscription;\n * `sampleRate` / `throttleMs` set on a typed subscription; `sampleRate`\n * outside `(0, 1]`; or `throttleMs` negative.\n *\n * @public\n */\nexport class EmitterError extends Error {\n override readonly name = \"EmitterError\";\n}\n\n/**\n * Thrown by any emitter method called after {@link Emitter.dispose}.\n *\n * @public\n */\nexport class EmitterDisposedError extends Error {\n override readonly name = \"EmitterDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\n// Mutable `c` field (not optional `?:`) avoids exactOptionalPropertyTypes TS2412\n// when assigning undefined. Short field names reduce minified output size.\ntype ErrorPolicy = boolean | ((err: unknown, type: string, payload: unknown) => void);\n\ninterface E<H> {\n h: H; // handler (may be a once-wrapper)\n c: (() => void) | undefined; // abortCleanup\n u: H; // user-provided handler (off matching)\n // v0.3.0: per-handler error policy and wildcard throttle/sample state.\n // Fields typed as `T | undefined` (not just `T`) so that exactOptionalPropertyTypes\n // permits assigning `undefined` in object literals (avoids TS2375).\n ce?: ErrorPolicy | undefined; // captureErrors override (typed only)\n r?: number | undefined; // sampleRate (wildcard only)\n tm?: number | undefined; // throttleMs (wildcard only)\n ts?: number | undefined; // last call timestamp — mutated during dispatch (wildcard only)\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 const cap = opts?.captureHandlerErrors;\n\n const t: Map<string, E<AH>[]> = new Map();\n const w: E<WH>[] = [];\n let d = false;\n\n function ck(): void {\n if (d) throw new EmitterDisposedError(\"aieventjs: emitter has been disposed\");\n }\n\n // Get or create typed handler array for a key.\n function ga(k: string): E<AH>[] {\n let a = t.get(k);\n if (a === undefined) {\n a = [];\n t.set(k, a);\n }\n return a;\n }\n\n function on(type: string | \"*\", handler: AH | WH, o?: OnOptions): () => void {\n ck();\n // v0.3.0 guards: cross-domain options + range checks\n const sr = o?.sampleRate;\n const tm2 = o?.throttleMs;\n if (type === \"*\") {\n if (o?.captureErrors !== undefined)\n throw new EmitterError(\"aieventjs: captureErrors invalid on *\");\n } else {\n if (sr !== undefined) throw new EmitterError(\"aieventjs: sampleRate wildcard-only\");\n if (tm2 !== undefined) throw new EmitterError(\"aieventjs: throttleMs wildcard-only\");\n }\n if (sr !== undefined && (!Number.isFinite(sr) || sr <= 0 || sr > 1))\n throw new EmitterError(\"aieventjs: sampleRate must be in (0,1]\");\n if (tm2 !== undefined && (!Number.isFinite(tm2) || tm2 < 0))\n throw new EmitterError(\"aieventjs: throttleMs must be >= 0\");\n const sig = o?.signal;\n if (sig?.aborted) return () => {};\n\n if (type === \"*\") {\n const fn = handler as WH;\n if (o?.once) {\n const e: E<WH> = {\n h: (tp, p) => {\n rm();\n fn(tp, p);\n },\n u: fn,\n c: undefined,\n r: sr,\n tm: tm2,\n };\n const rm = sub(w, e, sig);\n return rm;\n }\n return sub(w, { h: fn, u: fn, c: undefined, r: sr, tm: tm2 }, sig);\n }\n\n const fn = handler as AH;\n const ce = o?.captureErrors;\n if (o?.once) {\n const e: E<AH> = {\n h: (p) => {\n rm();\n fn(p);\n },\n u: fn,\n c: undefined,\n ce: ce,\n };\n const rm = sub(ga(type), e, sig);\n return rm;\n }\n return sub(ga(type), { h: fn, u: fn, c: undefined, ce: ce }, 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 // Inline error policy handler — policy undefined/false → re-throw; true → swallow;\n // function → invoke and swallow; if callback throws, ignore silently.\n function ap(pol: ErrorPolicy | undefined, err: unknown, k: string, p: unknown): void {\n if (pol === undefined || pol === false) throw err;\n if (typeof pol === \"function\")\n try {\n pol(err, k, p);\n } catch {\n /* silent */\n }\n }\n\n function emit<K extends keyof Events>(type: K, payload: Events[K]): void {\n ck();\n // Both slices happen BEFORE any handler call (snapshot-before-iterate).\n const k = type as string;\n const p = payload as unknown;\n const ts = (t.get(k) ?? []).slice();\n const ws = w.slice();\n for (const e of ts) {\n try {\n e.h(p);\n } catch (err) {\n ap(e.ce !== undefined ? e.ce : cap, err, k, p);\n }\n }\n for (const e of ws) {\n if (e.r !== undefined && Math.random() >= e.r) continue;\n if (e.tm) {\n const now = Date.now();\n if (e.ts !== undefined && now - e.ts < e.tm) continue;\n e.ts = now;\n }\n try {\n e.h(k, p as never);\n } catch (err) {\n ap(cap, err, k, p);\n }\n }\n }\n\n function purge(): void {\n for (const a of t.values()) 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"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["EmitterError","EmitterDisposedError","rmByUser","arr","user","e","flush","sub","sig","rm","i","fn","createEmitter","opts","cap","t","w","d","ck","ga","k","a","on","type","handler","sr","tm2","tp","p","ce","once","off","ap","pol","err","emit","payload","ts","ws","now","purge"],"mappings":"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","file":"index.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}. Controls the default error\n * policy for handlers thrown during `emit()`; per-handler\n * {@link OnOptions.captureErrors} overrides this default.\n *\n * @public\n */\nexport interface EmitterOptions {\n /**\n * Default error policy for all handlers when they throw during emit().\n *\n * - undefined / false (default) — first throw aborts dispatch (mitt-compatible).\n * - true — swallow; dispatch continues over all handlers in the snapshot.\n * - (err, type, payload) => void — invoked with the unknown error, the\n * event name as string, and the payload as unknown. If this callback\n * itself throws, the error is silently ignored.\n *\n * Per-subscription OnOptions.captureErrors overrides this for that handler.\n */\n captureHandlerErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);\n}\n\n/**\n * Handler invoked for a single typed event.\n *\n * @public\n */\nexport type EventHandler<Payload> = (payload: Payload) => void;\n\n/**\n * Handler invoked for the wildcard `\"*\"` subscription. Receives the actual\n * event type alongside the payload.\n *\n * @public\n */\nexport type WildcardHandler<Events extends Record<string, unknown>> = <K extends keyof Events>(\n type: K,\n payload: Events[K],\n) => void;\n\n/**\n * Subscription options accepted by {@link Emitter.on}.\n *\n * @public\n */\nexport interface OnOptions {\n /**\n * Aborting this signal removes the handler. The same effect as calling\n * the returned unsubscribe function. Pre-aborted signals never register.\n */\n signal?: AbortSignal;\n\n /** Auto-remove the handler after the first dispatch. Equivalent to `once()`. */\n once?: boolean;\n\n /**\n * Override emitter-level captureHandlerErrors for this handler.\n * - undefined — fall through to emitter-level.\n * - false — force re-throw, even when emitter-level is true / callback.\n * - true — swallow.\n * - (err, type, payload) => void — same semantics as the emitter-level callback.\n *\n * Throws EmitterError if set on a wildcard \"*\" subscription.\n * @invariant does not break snapshot-before-iterate semantics.\n */\n captureErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);\n\n /**\n * Wildcard \"*\" only. Probability in (0, 1] that a dispatch reaches this\n * handler. Math.random() is sampled per dispatch. Values <= 0 or > 1 are\n * rejected at on() time.\n *\n * Throws EmitterError if set on a typed handler.\n */\n sampleRate?: number;\n\n /**\n * Per-handler leading-edge throttle. Minimum milliseconds between successive\n * calls to this handler. The first dispatch after subscription always fires;\n * subsequent dispatches within `throttleMs` are dropped (not queued).\n * Uses Date.now(). 0 = no throttle. Non-finite or negative values are rejected.\n *\n * Valid on both typed and wildcard `\"*\"` subscriptions (since v0.5.3); each\n * handler keeps its own throttle clock. Useful for per-event HUD throttling,\n * e.g. a `credits/change` event that fires every frame.\n */\n throttleMs?: number;\n}\n\n/**\n * Strongly-typed event emitter. Subscribe with {@link Emitter.on} (returns\n * an unsubscribe function), dispatch with {@link Emitter.emit}, dispose\n * with {@link Emitter.dispose} when finished.\n *\n * @typeParam Events — a string-keyed map from event name to payload type.\n * @public\n */\nexport interface Emitter<Events extends Record<string, unknown>> {\n /**\n * Subscribe to a single event type. Returns an unsubscribe function;\n * calling it (or aborting `opts.signal`) removes the handler.\n */\n on<K extends keyof Events>(\n type: K,\n handler: EventHandler<Events[K]>,\n opts?: OnOptions,\n ): () => void;\n\n /**\n * Subscribe to every event with a single handler that receives\n * `(type, payload)`. Wildcard handlers fire AFTER type-matched\n * handlers — same ordering as `mitt`.\n */\n on(type: \"*\", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;\n\n /**\n * Subscribe and auto-remove after the first dispatch. Equivalent to\n * `on(type, handler, { once: true })`.\n */\n once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;\n\n /**\n * Imperative unsubscribe. Prefer the unsubscribe function returned by\n * `on()` — it's faster (no reference lookup) and survives renames.\n * If `handler` is omitted, removes every handler for `type`.\n */\n off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;\n\n /**\n * Imperative wildcard unsubscribe.\n */\n off(type: \"*\", handler?: WildcardHandler<Events>): void;\n\n /**\n * Dispatch synchronously. Handlers receive `payload`; wildcard handlers\n * receive `(type, payload)`. Handler lists are snapshotted before iteration,\n * so removing a handler inside its own callback does not skip subsequent\n * handlers. By default, the first throwing handler aborts the dispatch;\n * set EmitterOptions.captureHandlerErrors (or per-handler OnOptions.captureErrors)\n * to swallow or report errors and continue.\n */\n emit<K extends keyof Events>(type: K, payload: Events[K]): void;\n\n /**\n * Remove every handler for every event (including wildcards). The\n * emitter remains usable. Use {@link dispose} for permanent teardown.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops every handler; subsequent `on` / `once` /\n * `emit` / `off` / `clear` throw {@link EmitterDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable emitter error. Thrown by `on()` when `OnOptions` violates a\n * precondition: `captureErrors` set on a wildcard `\"*\"` subscription;\n * `sampleRate` set on a typed subscription; `sampleRate` outside `(0, 1]`; or\n * `throttleMs` non-finite or negative.\n *\n * @public\n */\nexport class EmitterError extends Error {\n override readonly name = \"EmitterError\";\n}\n\n/**\n * Thrown by any emitter method called after {@link Emitter.dispose}.\n *\n * @public\n */\nexport class EmitterDisposedError extends Error {\n override readonly name = \"EmitterDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\n// Mutable `c` field (not optional `?:`) avoids exactOptionalPropertyTypes TS2412\n// when assigning undefined. Short field names reduce minified output size.\ntype ErrorPolicy = boolean | ((err: unknown, type: string, payload: unknown) => void);\n\ninterface E<H> {\n h: H; // handler (may be a once-wrapper)\n c: (() => void) | undefined; // abortCleanup\n u: H; // user-provided handler (off matching)\n // v0.3.0: per-handler error policy and throttle/sample state.\n // Fields typed as `T | undefined` (not just `T`) so that exactOptionalPropertyTypes\n // permits assigning `undefined` in object literals (avoids TS2375).\n ce?: ErrorPolicy | undefined; // captureErrors override (typed only)\n r?: number | undefined; // sampleRate (wildcard only)\n tm?: number | undefined; // throttleMs (typed or wildcard; v0.5.3)\n ts?: number | undefined; // last call timestamp — mutated during dispatch (throttle clock)\n}\n\ntype AH = EventHandler<unknown>;\ntype WH = WildcardHandler<Record<string, unknown>>;\n\n// Remove one entry by user-identity from an array; run its abort cleanup.\nfunction rmByUser<H>(arr: E<H>[], user: H): void {\n const i = arr.findIndex((e) => e.u === user);\n if (i >= 0) {\n const e = arr[i];\n if (e !== undefined) {\n e.c?.();\n e.c = undefined;\n }\n arr.splice(i, 1);\n }\n}\n\n// Flush all abort cleanups from an array (for clear / dispose).\nfunction flush<H>(arr: E<H>[]): void {\n for (const e of arr) {\n e.c?.();\n e.c = undefined;\n }\n}\n\n// Push entry onto arr, wire AbortSignal, return unsubscribe.\nfunction sub<H>(arr: E<H>[], e: E<H>, sig: AbortSignal | undefined): () => void {\n arr.push(e);\n const rm = () => {\n const i = arr.indexOf(e);\n if (i >= 0) arr.splice(i, 1);\n e.c?.();\n e.c = undefined;\n };\n if (sig !== undefined) {\n const fn = () => rm();\n sig.addEventListener(\"abort\", fn, { once: true });\n e.c = () => sig.removeEventListener(\"abort\", fn);\n }\n return rm;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a strongly-typed event emitter.\n *\n * @remarks\n * Declare the event map with a `type` alias, not an `interface`. The `Events`\n * generic is constrained to `Record<string, unknown>`, and a *plain* TypeScript\n * `interface` has no implicit index signature, so it fails the constraint with\n * *\"Index signature for type 'string' is missing in type ...\"*. A `type` object\n * literal satisfies the constraint structurally. (An `interface` with an explicit\n * index signature or `extends Record<string, unknown>` also compiles, but widens\n * `keyof Events` to `string`, losing strict event-name checking.)\n *\n * ```ts\n * // ❌ interface — fails the Record<string, unknown> constraint\n * interface Events { \"user:login\": { id: string } }\n * const bus = createEmitter<Events>(); // TS2344\n *\n * // ✅ type — satisfies the constraint\n * type Events = { \"user:login\": { id: string } };\n * const bus = createEmitter<Events>();\n * ```\n *\n * @example\n * ```ts\n * import { createEmitter } from \"aieventjs\";\n *\n * type Events = {\n * \"user:login\": { id: string };\n * \"user:logout\": void;\n * };\n *\n * const bus = createEmitter<Events>();\n *\n * const off = bus.on(\"user:login\", (u) => console.log(\"hi\", u.id));\n * bus.emit(\"user:login\", { id: \"alice\" });\n * off();\n *\n * bus.on(\"*\", (type, payload) => console.log(\"event\", type, payload));\n * ```\n *\n * @public\n */\nexport function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(\n opts?: EmitterOptions,\n): Emitter<Events> {\n const cap = opts?.captureHandlerErrors;\n\n const t: Map<string, E<AH>[]> = new Map();\n const w: E<WH>[] = [];\n let d = false;\n\n function ck(): void {\n if (d) throw new EmitterDisposedError(\"aieventjs: emitter has been disposed\");\n }\n\n // Get or create typed handler array for a key.\n function ga(k: string): E<AH>[] {\n let a = t.get(k);\n if (a === undefined) {\n a = [];\n t.set(k, a);\n }\n return a;\n }\n\n function on(type: string | \"*\", handler: AH | WH, o?: OnOptions): () => void {\n ck();\n // v0.3.0 guards: cross-domain options + range checks.\n // v0.5.3: throttleMs is now valid on typed handlers too (per-handler clock);\n // sampleRate remains wildcard-only.\n const sr = o?.sampleRate;\n const tm2 = o?.throttleMs;\n if (type === \"*\") {\n if (o?.captureErrors !== undefined)\n throw new EmitterError(\"aieventjs: captureErrors invalid on *\");\n } else {\n if (sr !== undefined) throw new EmitterError(\"aieventjs: sampleRate wildcard-only\");\n }\n if (sr !== undefined && (!Number.isFinite(sr) || sr <= 0 || sr > 1))\n throw new EmitterError(\"aieventjs: sampleRate must be in (0,1]\");\n if (tm2 !== undefined && (!Number.isFinite(tm2) || tm2 < 0))\n throw new EmitterError(\"aieventjs: throttleMs must be >= 0\");\n const sig = o?.signal;\n if (sig?.aborted) return () => {};\n\n if (type === \"*\") {\n const fn = handler as WH;\n if (o?.once) {\n const e: E<WH> = {\n h: (tp, p) => {\n rm();\n fn(tp, p);\n },\n u: fn,\n c: undefined,\n r: sr,\n tm: tm2,\n };\n const rm = sub(w, e, sig);\n return rm;\n }\n return sub(w, { h: fn, u: fn, c: undefined, r: sr, tm: tm2 }, sig);\n }\n\n const fn = handler as AH;\n const ce = o?.captureErrors;\n if (o?.once) {\n const e: E<AH> = {\n h: (p) => {\n rm();\n fn(p);\n },\n u: fn,\n c: undefined,\n ce: ce,\n tm: tm2,\n };\n const rm = sub(ga(type), e, sig);\n return rm;\n }\n return sub(ga(type), { h: fn, u: fn, c: undefined, ce: ce, tm: tm2 }, sig);\n }\n\n function once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void {\n return on(type as string, handler as AH, { once: true });\n }\n\n function off(type: string | \"*\", handler?: AH | WH): void {\n ck();\n if (type === \"*\") {\n if (handler === undefined) {\n flush(w);\n w.length = 0;\n } else {\n rmByUser(w, handler as WH);\n }\n return;\n }\n const arr = t.get(type);\n if (arr === undefined) return;\n if (handler === undefined) {\n flush(arr);\n t.delete(type);\n } else {\n rmByUser(arr, handler as AH);\n }\n }\n\n // Inline error policy handler — policy undefined/false → re-throw; true → swallow;\n // function → invoke and swallow; if callback throws, ignore silently.\n function ap(pol: ErrorPolicy | undefined, err: unknown, k: string, p: unknown): void {\n if (pol === undefined || pol === false) throw err;\n if (typeof pol === \"function\")\n try {\n pol(err, k, p);\n } catch {\n /* silent */\n }\n }\n\n function emit<K extends keyof Events>(type: K, payload: Events[K]): void {\n ck();\n // Both slices happen BEFORE any handler call (snapshot-before-iterate).\n const k = type as string;\n const p = payload as unknown;\n const ts = (t.get(k) ?? []).slice();\n const ws = w.slice();\n for (const e of ts) {\n if (e.tm) {\n const now = Date.now();\n if (e.ts !== undefined && now - e.ts < e.tm) continue;\n e.ts = now;\n }\n try {\n e.h(p);\n } catch (err) {\n ap(e.ce !== undefined ? e.ce : cap, err, k, p);\n }\n }\n for (const e of ws) {\n if (e.r !== undefined && Math.random() >= e.r) continue;\n if (e.tm) {\n const now = Date.now();\n if (e.ts !== undefined && now - e.ts < e.tm) continue;\n e.ts = now;\n }\n try {\n e.h(k, p as never);\n } catch (err) {\n ap(cap, err, k, p);\n }\n }\n }\n\n function purge(): void {\n for (const a of t.values()) 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 CHANGED
@@ -23,7 +23,7 @@ The short index lives at `llms.txt` (see https://llmstxt.org/).
23
23
 
24
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
25
 
26
- > **Status: 0.5.1.** Full implementation shipped; all methods are live. Coverage ≥ 95/90/100/100; ~1050 B gzip (budget 1100 B).
26
+ > **Status: 0.5.3.** Full implementation shipped; all methods are live. Coverage ≥ 95/90/100/100; ~1050 B gzip (budget 1100 B).
27
27
 
28
28
  ---
29
29
 
@@ -86,6 +86,18 @@ bus.dispose(); // idempotent; post-dispose calls throw EmitterDisposedError
86
86
 
87
87
  `createEmitter()` returns a plain object whose methods do not depend on `this` — `const { on, emit } = bus` works fine.
88
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
+
89
101
  ---
90
102
 
91
103
  ## Capabilities / Limitations
@@ -101,7 +113,7 @@ bus.dispose(); // idempotent; post-dispose calls throw EmitterDisposedError
101
113
  | Handler-array snapshot on `emit` (safe re-entrancy) | Persistent storage / replay (not its job) |
102
114
  | Destructurable methods (`const { on, emit } = bus`) | Zero-allocation `emit` (one snapshot per dispatch is required for re-entrancy) |
103
115
  | `on('*', fn, { sampleRate })` — probabilistic delivery for debug subscribers (wildcard only) | |
104
- | `on('*', fn, { throttleMs })` — leading-edge throttle 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) | |
105
117
  | `createEmitter({ captureHandlerErrors })` — opt-in error policy; per-handler override via `OnOptions.captureErrors` | |
106
118
 
107
119
  ---
@@ -119,7 +131,7 @@ interface OnOptions {
119
131
  once?: boolean;
120
132
  captureErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void); // typed only
121
133
  sampleRate?: number; // wildcard "*" only — probability in (0, 1]
122
- throttleMs?: number; // wildcard "*" only — leading-edge throttle, uses Date.now()
134
+ throttleMs?: number; // typed or wildcard — per-handler leading-edge throttle, uses Date.now()
123
135
  }
124
136
 
125
137
  interface EmitterOptions {
@@ -183,6 +195,14 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
183
195
 
184
196
  ## [Unreleased]
185
197
 
198
+ ## [0.5.3] - 2026-06-05
199
+
200
+ ### Added
201
+ - `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.
202
+
203
+ ### Docs
204
+ - 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`.
205
+
186
206
  ## [0.5.2] - 2026-06-05
187
207
 
188
208
  ### Docs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aieventjs",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
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",