aieventjs 0.5.5 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -144
- package/README_ZHTW.md +39 -143
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/llms-full.txt +93 -342
- package/llms.txt +6 -2
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var E=class extends Error{name="EmitterError"},
|
|
1
|
+
var E=class extends Error{name="EmitterError"},h=class extends Error{name="EmitterDisposedError"};function K(d,c){let i=d.findIndex(r=>r.u===c);if(i>=0){let r=d[i];r!==void 0&&(r.c?.(),r.c=void 0),d.splice(i,1);}}function H(d){for(let c of d)c.c?.(),c.c=void 0;}function y(d,c,i){d.push(c);let r=()=>{let f=d.indexOf(c);f>=0&&d.splice(f,1),c.c?.(),c.c=void 0;};if(i!==void 0){let f=()=>r();i.addEventListener("abort",f,{once:true}),c.c=()=>i.removeEventListener("abort",f);}return r}function j(d){let c=d?.captureHandlerErrors,i=new Map,r=[],f=false;function p(){if(f)throw new h("aieventjs: emitter has been disposed")}function g(e){let t=i.get(e);return t===void 0&&(t=[],i.set(e,t)),t}function k(e,t,o){p();let s=o?.sampleRate,u=o?.throttleMs;if(e==="*"){if(o?.captureErrors!==void 0)throw new E("aieventjs: captureErrors invalid on *")}else if(s!==void 0)throw new E("aieventjs: sampleRate wildcard-only");if(s!==void 0&&(!Number.isFinite(s)||s<=0||s>1))throw new E("aieventjs: sampleRate must be in (0,1]");if(u!==void 0&&(!Number.isFinite(u)||u<0))throw new E("aieventjs: throttleMs must be >= 0");let l=o?.signal;if(l?.aborted)return ()=>{};if(e==="*"){let v=t;if(o?.once){let m=y(r,{h:(R,M)=>{m(),v(R,M);},u:v,c:void 0,r:s,tm:u},l);return m}return y(r,{h:v,u:v,c:void 0,r:s,tm:u},l)}let n=t,a=o?.captureErrors;if(o?.once){let v={h:m=>{w(),n(m);},u:n,c:void 0,ce:a,tm:u},w=y(g(e),v,l);return w}return y(g(e),{h:n,u:n,c:void 0,ce:a,tm:u},l)}function A(e,t){return k(e,t,{once:true})}function O(e,t){if(p(),e==="*"){t===void 0?(H(r),r.length=0):K(r,t);return}let o=i.get(e);o!==void 0&&(t===void 0?(H(o),o.length=0,i.delete(e)):K(o,t));}function x(e,t,o,s){if(e===void 0||e===false)throw t;if(typeof e=="function")try{e(t,o,s);}catch{}}function W(e,t){p();let o=e,s=t,u=(i.get(o)??[]).slice(),l=r.slice();for(let n of u){if(n.tm){let a=performance.now();if(n.ts!==void 0&&a-n.ts<n.tm)continue;n.ts=a;}try{n.h(s);}catch(a){x(n.ce!==void 0?n.ce:c,a,o,s);}}for(let n of l)if(!(n.r!==void 0&&Math.random()>=n.r)){if(n.tm){let a=performance.now();if(n.ts!==void 0&&a-n.ts<n.tm)continue;n.ts=a;}try{n.h(o,s);}catch(a){x(c,a,o,s);}}}function b(){for(let e of i.values())H(e),e.length=0;H(r),i.clear(),r.length=0;}return {on:k,once:A,off:O,emit:W,clear(){p(),b();},dispose(){f||(b(),f=true);},get disposed(){return f}}}export{h 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","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"]}
|
|
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 `performance.now()` (monotonic). 0 = no throttle. Non-finite or\n * negative values are rejected at `on()` time.\n *\n * Valid on both typed and wildcard `\"*\"` subscriptions (since v0.5.3); each\n * handler keeps its own throttle clock. Useful for per-event HUD throttling,\n * e.g. a `credits/change` event that fires every frame.\n *\n * @remarks\n * The throttle clock uses `performance.now()`, which is monotonic and\n * unaffected by system-clock corrections (NTP step-backs, manual adjustments).\n * This ensures handlers are never silently muted by a wall-clock regression.\n */\n throttleMs?: number;\n}\n\n/**\n * Strongly-typed event emitter. Subscribe with {@link Emitter.on} (returns\n * an unsubscribe function), dispatch with {@link Emitter.emit}, dispose\n * with {@link Emitter.dispose} when finished.\n *\n * @typeParam Events — a string-keyed map from event name to payload type.\n * @public\n */\nexport interface Emitter<Events extends Record<string, unknown>> {\n /**\n * Subscribe to a single event type. Returns an unsubscribe function;\n * calling it (or aborting `opts.signal`) removes the handler.\n */\n on<K extends keyof Events>(\n type: K,\n handler: EventHandler<Events[K]>,\n opts?: OnOptions,\n ): () => void;\n\n /**\n * Subscribe to every event with a single handler that receives\n * `(type, payload)`. Wildcard handlers fire AFTER type-matched\n * handlers — same ordering as `mitt`.\n */\n on(type: \"*\", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;\n\n /**\n * Subscribe and auto-remove after the first dispatch. Equivalent to\n * `on(type, handler, { once: true })`.\n *\n * @remarks\n * **`\"*\"` is not a valid `type` argument for `once()`.** The wildcard is\n * handled by the `on(\"*\", handler, { once: true })` overload instead.\n * The explicit rejection overload below ensures `once(\"*\", ...)` is a\n * compile-time error (handler typed as `never`). (EVT-B-02)\n */\n /** @internal — compile-time rejection: `once(\"*\", handler)` is a type error. */\n once(type: \"*\", handler: never): never;\n once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;\n\n /**\n * Imperative unsubscribe. Prefer the unsubscribe function returned by\n * `on()` — it's faster (no reference lookup) and survives renames.\n * If `handler` is omitted, removes every handler for `type`.\n */\n off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;\n\n /**\n * Imperative wildcard unsubscribe.\n */\n off(type: \"*\", handler?: WildcardHandler<Events>): void;\n\n /**\n * Dispatch synchronously. Handlers receive `payload`; wildcard handlers\n * receive `(type, payload)`. Handler lists are snapshotted before iteration,\n * so removing a handler inside its own callback does not skip subsequent\n * handlers. By default, the first throwing handler aborts the dispatch;\n * set EmitterOptions.captureHandlerErrors (or per-handler OnOptions.captureErrors)\n * to swallow or report errors and continue.\n */\n emit<K extends keyof Events>(type: K, payload: Events[K]): void;\n\n /**\n * Remove every handler for every event (including wildcards). The\n * emitter remains usable. Use {@link dispose} for permanent teardown.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops every handler; subsequent `on` / `once` /\n * `emit` / `off` / `clear` throw {@link EmitterDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable emitter error. Thrown by `on()` when `OnOptions` violates a\n * precondition: `captureErrors` set on a wildcard `\"*\"` subscription;\n * `sampleRate` set on a typed subscription; `sampleRate` outside `(0, 1]`; or\n * `throttleMs` non-finite or negative.\n *\n * @public\n */\nexport class EmitterError extends Error {\n override readonly name = \"EmitterError\";\n}\n\n/**\n * Thrown by any emitter method called after {@link Emitter.dispose}.\n *\n * @public\n */\nexport class EmitterDisposedError extends Error {\n override readonly name = \"EmitterDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\n// Mutable `c` field (not optional `?:`) avoids exactOptionalPropertyTypes TS2412\n// when assigning undefined. Short field names reduce minified output size.\ntype ErrorPolicy = boolean | ((err: unknown, type: string, payload: unknown) => void);\n\ninterface E<H> {\n h: H; // handler (may be a once-wrapper)\n c: (() => void) | undefined; // abortCleanup\n u: H; // user-provided handler (off matching)\n // v0.3.0: per-handler error policy and throttle/sample state.\n // Fields typed as `T | undefined` (not just `T`) so that exactOptionalPropertyTypes\n // permits assigning `undefined` in object literals (avoids TS2375).\n ce?: ErrorPolicy | undefined; // captureErrors override (typed only)\n r?: number | undefined; // sampleRate (wildcard only)\n tm?: number | undefined; // throttleMs (typed or wildcard; v0.5.3)\n ts?: number | undefined; // last call timestamp — mutated during dispatch (throttle clock)\n}\n\ntype AH = EventHandler<unknown>;\ntype WH = WildcardHandler<Record<string, unknown>>;\n\n// Remove one entry by user-identity from an array; run its abort cleanup.\nfunction rmByUser<H>(arr: E<H>[], user: H): void {\n const i = arr.findIndex((e) => e.u === user);\n if (i >= 0) {\n const e = arr[i];\n if (e !== undefined) {\n e.c?.();\n e.c = undefined;\n }\n arr.splice(i, 1);\n }\n}\n\n// Flush all abort cleanups from an array (for clear / dispose).\nfunction flush<H>(arr: E<H>[]): void {\n for (const e of arr) {\n e.c?.();\n e.c = undefined;\n }\n}\n\n// Push entry onto arr, wire AbortSignal, return unsubscribe.\nfunction sub<H>(arr: E<H>[], e: E<H>, sig: AbortSignal | undefined): () => void {\n arr.push(e);\n const rm = () => {\n const i = arr.indexOf(e);\n if (i >= 0) arr.splice(i, 1);\n e.c?.();\n e.c = undefined;\n };\n if (sig !== undefined) {\n const fn = () => rm();\n sig.addEventListener(\"abort\", fn, { once: true });\n e.c = () => sig.removeEventListener(\"abort\", fn);\n }\n return rm;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a strongly-typed event emitter.\n *\n * @remarks\n * Declare the event map with a `type` alias, not an `interface`. The `Events`\n * generic is constrained to `Record<string, unknown>`, and a *plain* TypeScript\n * `interface` has no implicit index signature, so it fails the constraint with\n * *\"Index signature for type 'string' is missing in type ...\"*. A `type` object\n * literal satisfies the constraint structurally. (An `interface` with an explicit\n * index signature or `extends Record<string, unknown>` also compiles, but widens\n * `keyof Events` to `string`, losing strict event-name checking.)\n *\n * ```ts\n * // ❌ interface — fails the Record<string, unknown> constraint\n * interface Events { \"user:login\": { id: string } }\n * const bus = createEmitter<Events>(); // TS2344\n *\n * // ✅ type — satisfies the constraint\n * type Events = { \"user:login\": { id: string } };\n * const bus = createEmitter<Events>();\n * ```\n *\n * @example\n * ```ts\n * import { createEmitter } from \"aieventjs\";\n *\n * type Events = {\n * \"user:login\": { id: string };\n * \"user:logout\": void;\n * };\n *\n * const bus = createEmitter<Events>();\n *\n * const off = bus.on(\"user:login\", (u) => console.log(\"hi\", u.id));\n * bus.emit(\"user:login\", { id: \"alice\" });\n * off();\n *\n * bus.on(\"*\", (type, payload) => console.log(\"event\", type, payload));\n * ```\n *\n * @public\n */\nexport function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(\n opts?: EmitterOptions,\n): Emitter<Events> {\n const cap = opts?.captureHandlerErrors;\n\n const t: Map<string, E<AH>[]> = new Map();\n const w: E<WH>[] = [];\n let d = false;\n\n function ck(): void {\n if (d) throw new EmitterDisposedError(\"aieventjs: emitter has been disposed\");\n }\n\n // Get or create typed handler array for a key.\n function ga(k: string): E<AH>[] {\n let a = t.get(k);\n if (a === undefined) {\n a = [];\n t.set(k, a);\n }\n return a;\n }\n\n function on(type: string | \"*\", handler: AH | WH, o?: OnOptions): () => void {\n ck();\n // v0.3.0 guards: cross-domain options + range checks.\n // v0.5.3: throttleMs is now valid on typed handlers too (per-handler clock);\n // sampleRate remains wildcard-only.\n const sr = o?.sampleRate;\n const tm2 = o?.throttleMs;\n if (type === \"*\") {\n if (o?.captureErrors !== undefined)\n throw new EmitterError(\"aieventjs: captureErrors invalid on *\");\n } else {\n if (sr !== undefined) throw new EmitterError(\"aieventjs: sampleRate wildcard-only\");\n }\n if (sr !== undefined && (!Number.isFinite(sr) || sr <= 0 || sr > 1))\n throw new EmitterError(\"aieventjs: sampleRate must be in (0,1]\");\n if (tm2 !== undefined && (!Number.isFinite(tm2) || tm2 < 0))\n throw new EmitterError(\"aieventjs: throttleMs must be >= 0\");\n const sig = o?.signal;\n if (sig?.aborted) return () => {};\n\n if (type === \"*\") {\n const fn = handler as WH;\n if (o?.once) {\n const e: E<WH> = {\n h: (tp, p) => {\n rm();\n fn(tp, p);\n },\n u: fn,\n c: undefined,\n r: sr,\n tm: tm2,\n };\n const rm = sub(w, e, sig);\n return rm;\n }\n return sub(w, { h: fn, u: fn, c: undefined, r: sr, tm: tm2 }, sig);\n }\n\n const fn = handler as AH;\n const ce = o?.captureErrors;\n if (o?.once) {\n const e: E<AH> = {\n h: (p) => {\n rm();\n fn(p);\n },\n u: fn,\n c: undefined,\n ce: ce,\n tm: tm2,\n };\n const rm = sub(ga(type), e, sig);\n return rm;\n }\n return sub(ga(type), { h: fn, u: fn, c: undefined, ce: ce, tm: tm2 }, sig);\n }\n\n function once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void {\n return on(type as string, handler as AH, { once: true });\n }\n\n function off(type: string | \"*\", handler?: AH | WH): void {\n ck();\n if (type === \"*\") {\n if (handler === undefined) {\n flush(w);\n w.length = 0;\n } else {\n rmByUser(w, handler as WH);\n }\n return;\n }\n const arr = t.get(type);\n if (arr === undefined) return;\n if (handler === undefined) {\n flush(arr);\n arr.length = 0;\n t.delete(type);\n } else {\n rmByUser(arr, handler as AH);\n }\n }\n\n // Inline error policy handler — policy undefined/false → re-throw; true → swallow;\n // function → invoke and swallow; if callback throws, ignore silently.\n function ap(pol: ErrorPolicy | undefined, err: unknown, k: string, p: unknown): void {\n if (pol === undefined || pol === false) throw err;\n if (typeof pol === \"function\")\n try {\n pol(err, k, p);\n } catch {\n /* silent */\n }\n }\n\n function emit<K extends keyof Events>(type: K, payload: Events[K]): void {\n ck();\n // Both slices happen BEFORE any handler call (snapshot-before-iterate).\n const k = type as string;\n const p = payload as unknown;\n const ts = (t.get(k) ?? []).slice();\n const ws = w.slice();\n for (const e of ts) {\n if (e.tm) {\n const now = performance.now();\n if (e.ts !== undefined && now - e.ts < e.tm) continue;\n e.ts = now;\n }\n try {\n e.h(p);\n } catch (err) {\n ap(e.ce !== undefined ? e.ce : cap, err, k, p);\n }\n }\n for (const e of ws) {\n if (e.r !== undefined && Math.random() >= e.r) continue;\n if (e.tm) {\n const now = performance.now();\n if (e.ts !== undefined && now - e.ts < e.tm) continue;\n e.ts = now;\n }\n try {\n e.h(k, p as never);\n } catch (err) {\n ap(cap, err, k, p);\n }\n }\n }\n\n function purge(): void {\n for (const a of t.values()) {\n flush(a);\n a.length = 0;\n }\n flush(w);\n t.clear();\n w.length = 0;\n }\n\n return {\n on: on as Emitter<Events>[\"on\"],\n once: once as Emitter<Events>[\"once\"],\n off: off as Emitter<Events>[\"off\"],\n emit,\n clear() {\n ck();\n purge();\n },\n dispose() {\n if (!d) {\n purge();\n d = true;\n }\n },\n get disposed() {\n return d;\n },\n };\n}\n"]}
|
package/llms-full.txt
CHANGED
|
@@ -13,175 +13,70 @@ The short index lives at `llms.txt` (see https://llmstxt.org/).
|
|
|
13
13
|
|
|
14
14
|
# aieventjs
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
[](https://github.com/yshengliao/aieventjs/actions/workflows/ci.yml)
|
|
18
|
-
[](LICENSE)
|
|
19
|
-
[](https://www.anthropic.com/claude-code)
|
|
20
|
-
[](README_ZHTW.md)
|
|
16
|
+
Small, strict, typed event emitter with ai*js lifecycle conventions: `on()` returns unsubscribe, `once` is built in, `AbortSignal` is first-class, wildcard handlers are supported, and `dispose()` is idempotent.
|
|
21
17
|
|
|
22
|
-
>
|
|
18
|
+
> **Status: 0.5.8 - stable 1.0-track surface.** The root entry is the public API.
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
> **Status: 0.5.3.** Full implementation shipped; all methods are live. Coverage ≥ 95/90/100/100; ~1050 B gzip (budget 1100 B).
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## Why aieventjs
|
|
31
|
-
|
|
32
|
-
Why not just use `mitt`? Honest answer: `mitt` is the right choice for many projects — it's MIT, ~282 B gzipped, and the API is genuinely well-shaped. We evaluated it and chose to write from scratch instead. Three reasons:
|
|
33
|
-
|
|
34
|
-
- **mitt has been unmaintained since 2023-07-04.** The PRs the community most wants — `unsubscribe`-returning `on()`, `AbortSignal`, `sideEffects: false`, nodenext compatibility — are all open and untouched. Forking would mean shipping a copy with our name on it; the upstream couldn't accept improvements back even if we wanted.
|
|
35
|
-
- **The implementation is ~35 lines of pure logic.** "Fork and improve" doesn't really exist at that size class — any non-trivial change is a rewrite, and the cost of carrying the upstream copyright notice exceeds the benefit.
|
|
36
|
-
- **ai\*js conventions are pervasive enough that fitting them onto mitt's API surface would change every method signature.** `on()` returning `void` vs. returning an unsubscribe is the visible difference; the strict TypeScript posture (`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, no `!` non-null assertions) is the invisible one that touches every line.
|
|
37
|
-
|
|
38
|
-
So `aieventjs` is the ai\*js-shaped event emitter:
|
|
39
|
-
|
|
40
|
-
- **`on()` returns an unsubscribe function.** Cleanup via the standard "call this to undo" idiom — closes over nothing, survives handler renames, drops in to `$effect()` / `onScopeDispose()` / `useEffect` cleanup without ceremony.
|
|
41
|
-
- **`AbortSignal` everywhere it makes sense.** `on(type, handler, { signal })` removes the handler when the signal aborts. Pre-aborted signals never register. The whole library uses the same cancellation primitive as `fetch` and the rest of the platform.
|
|
42
|
-
- **`dispose()` is idempotent.** Post-dispose `on` / `emit` / `once` throw `EmitterDisposedError`. This is the family-wide convention; an emitter that "looks alive but does nothing" after teardown is the canonical leak vector and we refuse to ship it.
|
|
43
|
-
- **Wildcard `*` is preserved.** `bus.on("*", (type, payload) => ...)` works exactly like in `mitt`; wildcard handlers fire AFTER type-matched handlers. ~80 B gzip cost; kept to make migration mechanical.
|
|
44
|
-
- **Handler-array snapshot on `emit`.** Removing a handler inside its own callback does not skip subsequent handlers (mitt has this since 2.x; preserved).
|
|
45
|
-
- **Functional, destructurable.** `const { on, emit } = bus` works — no `this` capture anywhere.
|
|
46
|
-
|
|
47
|
-
What this is **not**: not an async event bus (handlers are synchronous), not a namespaced bus (`user.*` style wildcards are out), not a priority queue, not a transport. It is the in-process synchronous fan-out primitive — nothing more.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## Quick Start
|
|
20
|
+
## Install
|
|
52
21
|
|
|
53
22
|
```bash
|
|
54
23
|
pnpm add aieventjs
|
|
55
24
|
```
|
|
56
25
|
|
|
57
|
-
```
|
|
26
|
+
```ts
|
|
58
27
|
import { createEmitter } from "aieventjs";
|
|
28
|
+
```
|
|
59
29
|
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```ts
|
|
60
33
|
type Events = {
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"score:tick": { delta: number };
|
|
34
|
+
"score/change": { value: number };
|
|
35
|
+
"scene/end": void;
|
|
64
36
|
};
|
|
65
37
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
// 1. Subscribe; capture the unsubscribe handle.
|
|
69
|
-
const off = bus.on("user:login", (u) => console.log("hi", u.id));
|
|
38
|
+
const events = createEmitter<Events>();
|
|
70
39
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
40
|
+
const off = events.on("score/change", ({ value }) => {
|
|
41
|
+
console.log(value);
|
|
42
|
+
});
|
|
74
43
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// 4. Dispatch.
|
|
79
|
-
bus.emit("user:login", { id: "alice" });
|
|
80
|
-
|
|
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
|
-
|
|
84
|
-
bus.dispose(); // idempotent; post-dispose calls throw EmitterDisposedError
|
|
47
|
+
events.dispose();
|
|
85
48
|
```
|
|
86
49
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
> **Declare the event map with `type`, not `interface`.** The `Events` generic is constrained to `Record<string, unknown>`. A *plain* TypeScript `interface` has no implicit index signature, so passing one fails the constraint with *"Index signature for type 'string' is missing in type ..."*. A `type` object literal satisfies it structurally. (An `interface` with an explicit index signature — or one that `extends Record<string, unknown>` — also compiles, but widens `keyof Events` to `string` and loses strict event-name checking, so prefer `type`.)
|
|
90
|
-
>
|
|
91
|
-
> ```typescript
|
|
92
|
-
> // ❌ interface — fails the Record<string, unknown> constraint (TS2344)
|
|
93
|
-
> interface Events { "user:login": { id: string } }
|
|
94
|
-
> const bus = createEmitter<Events>();
|
|
95
|
-
>
|
|
96
|
-
> // ✅ type — satisfies the constraint
|
|
97
|
-
> type Events = { "user:login": { id: string } };
|
|
98
|
-
> const bus = createEmitter<Events>();
|
|
99
|
-
> ```
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## Capabilities / Limitations
|
|
104
|
-
|
|
105
|
-
| Will do (v1) | Won't do |
|
|
106
|
-
| --------------------------------------------------------- | ----------------------------------------------------- |
|
|
107
|
-
| Typed `createEmitter<Events>()` | Untyped string-key bus (the type is the point) |
|
|
108
|
-
| `on()` returns unsubscribe function | Async / promise-returning handlers (sync only) |
|
|
109
|
-
| `once(type, handler)` + `on(..., { once: true })` | Namespaced wildcards (`"user.*"`) — out of scope |
|
|
110
|
-
| `on(..., { signal })` — `AbortSignal` cleanup | Priority / weight / ordering hints |
|
|
111
|
-
| Wildcard `"*"` handler — `(type, payload)` | Cross-context transport (use `aibridgejs` for that) |
|
|
112
|
-
| `dispose()` idempotent; post-dispose calls throw | Error-event special casing (Node EventEmitter style) |
|
|
113
|
-
| Handler-array snapshot on `emit` (safe re-entrancy) | Persistent storage / replay (not its job) |
|
|
114
|
-
| Destructurable methods (`const { on, emit } = bus`) | Zero-allocation `emit` (one snapshot per dispatch is required for re-entrancy) |
|
|
115
|
-
| `on('*', fn, { sampleRate })` — probabilistic delivery for debug subscribers (wildcard only) | |
|
|
116
|
-
| `on(type, fn, { throttleMs })` — per-handler leading-edge throttle, on typed **and** wildcard subscriptions (e.g. a per-frame `credits/change` HUD event) | |
|
|
117
|
-
| `createEmitter({ captureHandlerErrors })` — opt-in error policy; per-handler override via `OnOptions.captureErrors` | |
|
|
118
|
-
|
|
119
|
-
---
|
|
50
|
+
## Core API
|
|
120
51
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
type
|
|
125
|
-
|
|
126
|
-
type
|
|
127
|
-
|
|
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
|
-
}
|
|
136
|
-
|
|
137
|
-
interface EmitterOptions {
|
|
138
|
-
// Default error policy. undefined/false (default): first throw aborts dispatch.
|
|
139
|
-
// true: swallow errors and continue dispatch over all handlers.
|
|
140
|
-
// (err, type, payload) => void: callback invoked per throwing handler
|
|
141
|
-
// (if the callback itself throws, that error is silently ignored and dispatch continues).
|
|
142
|
-
// Per-handler OnOptions.captureErrors overrides this for individual subscriptions.
|
|
143
|
-
captureHandlerErrors?: boolean | ((err: unknown, type: string, payload: unknown) => void);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
interface Emitter<Events extends Record<string, unknown>> {
|
|
147
|
-
on<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>, opts?: OnOptions): () => void;
|
|
148
|
-
on(type: "*", handler: WildcardHandler<Events>, opts?: OnOptions): () => void;
|
|
149
|
-
once<K extends keyof Events>(type: K, handler: EventHandler<Events[K]>): () => void;
|
|
150
|
-
off<K extends keyof Events>(type: K, handler?: EventHandler<Events[K]>): void;
|
|
151
|
-
off(type: "*", handler?: WildcardHandler<Events>): void;
|
|
152
|
-
emit<K extends keyof Events>(type: K, payload: Events[K]): void;
|
|
153
|
-
clear(): void;
|
|
154
|
-
dispose(): void;
|
|
155
|
-
readonly disposed: boolean;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
class EmitterError extends Error {}
|
|
159
|
-
class EmitterDisposedError extends Error {}
|
|
160
|
-
|
|
161
|
-
function createEmitter<Events extends Record<string, unknown> = Record<string, unknown>>(
|
|
162
|
-
opts?: EmitterOptions,
|
|
163
|
-
): Emitter<Events>;
|
|
164
|
-
```
|
|
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.
|
|
165
59
|
|
|
166
|
-
|
|
60
|
+
## Sharp Edges
|
|
167
61
|
|
|
168
|
-
|
|
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.
|
|
169
68
|
|
|
170
|
-
##
|
|
69
|
+
## AI Context
|
|
171
70
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
| **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. |
|
|
178
|
-
| **0.6+** | Async handler tracking (draft) — see [STABILITY.md](STABILITY.md). |
|
|
179
|
-
|
|
180
|
-
---
|
|
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)
|
|
181
76
|
|
|
182
77
|
## License
|
|
183
78
|
|
|
184
|
-
|
|
79
|
+
MIT
|
|
185
80
|
|
|
186
81
|
---
|
|
187
82
|
|
|
@@ -189,158 +84,58 @@ Full JSDoc lives in [`src/index.ts`](src/index.ts).
|
|
|
189
84
|
|
|
190
85
|
# Changelog
|
|
191
86
|
|
|
192
|
-
All notable changes to
|
|
193
|
-
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
194
|
-
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
87
|
+
All notable changes to aieventjs are summarized here.
|
|
195
88
|
|
|
196
89
|
## [Unreleased]
|
|
197
90
|
|
|
198
|
-
## [0.5.
|
|
91
|
+
## [0.5.8] - 2026-06-14
|
|
92
|
+
|
|
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.
|
|
96
|
+
|
|
97
|
+
## [0.5.6] - 2026-06-10
|
|
98
|
+
|
|
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.
|
|
102
|
+
|
|
103
|
+
## Older releases
|
|
199
104
|
|
|
200
|
-
|
|
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.
|
|
201
109
|
|
|
202
|
-
|
|
110
|
+
---
|
|
203
111
|
|
|
204
|
-
|
|
112
|
+
<!-- ===== STABILITY.md ===== -->
|
|
205
113
|
|
|
206
|
-
|
|
207
|
-
- `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.
|
|
114
|
+
# aieventjs Stability Index
|
|
208
115
|
|
|
209
|
-
|
|
210
|
-
- 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`.
|
|
116
|
+
## Stable API
|
|
211
117
|
|
|
212
|
-
|
|
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`. |
|
|
213
126
|
|
|
214
|
-
|
|
127
|
+
## Behavior
|
|
215
128
|
|
|
216
|
-
-
|
|
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.
|
|
217
134
|
|
|
218
|
-
##
|
|
135
|
+
## Drafts
|
|
219
136
|
|
|
220
|
-
|
|
221
|
-
-
|
|
222
|
-
Previously, `NaN <= 0` and `NaN > 1` both evaluate to `false`, so a `NaN`
|
|
223
|
-
sampleRate silently passed validation and degraded to always-fire semantics;
|
|
224
|
-
similarly `NaN < 0` is `false`, so a `NaN` throttleMs passed and degraded to
|
|
225
|
-
no-throttle. Both guards now include `Number.isFinite()` as the first check,
|
|
226
|
-
consistent with the documented contract that invalid values throw `EmitterError`.
|
|
227
|
-
|
|
228
|
-
### Tests
|
|
229
|
-
- Added regression tests locking in `NaN` rejection for both `sampleRate` and
|
|
230
|
-
`throttleMs` (wildcard-throttle.test.ts §A6, §B4).
|
|
231
|
-
- Added regression tests confirming that a `once` handler that throws is still
|
|
232
|
-
removed and does not re-fire on the next emit — for both typed and wildcard
|
|
233
|
-
once subscriptions (emitter.test.ts §C2a, §C2b).
|
|
234
|
-
- Added regression tests for post-dispose guard coverage: `off()` after
|
|
235
|
-
`dispose()` throws `EmitterDisposedError`; `clear()` after `dispose()` throws
|
|
236
|
-
`EmitterDisposedError`; the unsubscribe function returned by `on()` is a safe
|
|
237
|
-
no-op (does not throw) after `dispose()` (emitter.test.ts §H2a, §H2b, §H2c).
|
|
238
|
-
|
|
239
|
-
## [0.4.0] - 2026-05-29
|
|
240
|
-
|
|
241
|
-
Dependency hygiene + stability freeze. No runtime API addition; `dist/` is byte-identical to 0.3.1 (no `src/` change). Consumer-facing behaviour is unchanged.
|
|
242
|
-
|
|
243
|
-
### Changed
|
|
244
|
-
- 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.
|
|
245
|
-
- 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.
|
|
246
|
-
|
|
247
|
-
### Removed
|
|
248
|
-
- `tsx` devDependency — unused (no script, config, test, or CI step referenced it). Trims the lockfile subtree; no functional impact.
|
|
249
|
-
|
|
250
|
-
## [0.3.1] - 2026-05-29
|
|
251
|
-
### Added
|
|
252
|
-
- `test/emitter.prop.test.ts`: three fast-check property invariants — dispatch
|
|
253
|
-
order (typed before wildcard, registration order preserved), snapshot stability
|
|
254
|
-
under mid-dispatch `off()`, live-set accuracy after upfront unsubscribe.
|
|
255
|
-
`numRuns: 100`; inline `fc.assert` style matching family convention.
|
|
256
|
-
- `test/wildcard-throttle.test.ts` §F: five edge-case tests — pre-aborted signal
|
|
257
|
-
+ valid `sampleRate` never registers; `on()` guard-before-signal ordering;
|
|
258
|
-
`captureErrors` + signal leak check (`removeEventListener` called on abort);
|
|
259
|
-
throttled wildcard + mid-dispatch abort (snapshot-in-flight completes);
|
|
260
|
-
`sampleRate` exact boundary (`Math.random() === sampleRate` is a miss).
|
|
261
|
-
- `test/capture-errors.test.ts` §F: dispose-during-capturing-dispatch —
|
|
262
|
-
snapshot completes; `EmitterDisposedError` from re-entrant `emit()` is routed
|
|
263
|
-
through the capture callback; sibling handler still runs.
|
|
264
|
-
- `fast-check ^3.23.0` devDependency (family convention: aifsmjs `^3.20.0`,
|
|
265
|
-
aiquadtreejs `^3.23.0`).
|
|
266
|
-
|
|
267
|
-
### Changed
|
|
268
|
-
- No `src/index.ts` behaviour change. `dist/` is byte-identical to v0.3.0.
|
|
269
|
-
gzip 1050 B / 1100 B unchanged.
|
|
270
|
-
|
|
271
|
-
## [0.3.0] - 2026-05-29
|
|
272
|
-
### Added
|
|
273
|
-
- `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.
|
|
274
|
-
- `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`.
|
|
275
|
-
- `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.
|
|
276
|
-
- `OnOptions.throttleMs` (wildcard only): minimum ms between successive calls, leading-edge; uses `Date.now()`. Negative values throw `EmitterError` at `on()` time.
|
|
277
|
-
- `STABILITY.md`: stability index for all public API surface, plus `[experimental]` placeholder for async handler tracking (targeted v0.6+).
|
|
278
|
-
|
|
279
|
-
### Changed
|
|
280
|
-
- 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.
|
|
281
|
-
- `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).
|
|
282
|
-
|
|
283
|
-
### Notes
|
|
284
|
-
- 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.
|
|
285
|
-
|
|
286
|
-
## [0.1.1] - 2026-05-28
|
|
287
|
-
|
|
288
|
-
### Changed (CI)
|
|
289
|
-
|
|
290
|
-
- **`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.
|
|
291
|
-
- **`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.
|
|
292
|
-
|
|
293
|
-
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.
|
|
294
|
-
|
|
295
|
-
## [0.1.0] - 2026-05-28
|
|
296
|
-
|
|
297
|
-
### Added
|
|
298
|
-
|
|
299
|
-
- Strict typed `createEmitter<Events>()` with `on` returning an unsubscribe
|
|
300
|
-
function, `once`, wildcard `*` handler, `off`, `emit`, `clear`, `dispose`.
|
|
301
|
-
- `on(type, handler, { signal?, once? })` — `AbortSignal` integration so
|
|
302
|
-
framework code (Svelte 5 `$effect`, Vue `onScopeDispose`) cleans up listeners
|
|
303
|
-
via the standard cancellation primitive.
|
|
304
|
-
- `dispose()` idempotent; post-dispose `on` / `emit` / `once` throw
|
|
305
|
-
`EmitterDisposedError`.
|
|
306
|
-
- Handler-array snapshot on `emit` so removing a handler during dispatch does
|
|
307
|
-
not skip its successor (mitt has had this property since 2.x — preserved).
|
|
308
|
-
- Functional — methods are destructurable (`const { on, emit } = bus`).
|
|
309
|
-
- Test coverage ≥95% statements / lines / functions / ≥90% branches.
|
|
310
|
-
- Size budget: ≤ 550 B gzip.
|
|
311
|
-
- Dual ESM + CJS via `tsup`; `sideEffects: false`; zero runtime dependencies.
|
|
312
|
-
|
|
313
|
-
## [0.0.1] - 2026-05-28
|
|
314
|
-
|
|
315
|
-
### Added (scaffold)
|
|
316
|
-
|
|
317
|
-
- Full package scaffold landed (`package.json`, `tsconfig.json`,
|
|
318
|
-
`tsconfig.test.json`, `tsup.config.ts`, `vitest.config.ts`, `biome.json`,
|
|
319
|
-
`scripts/{verify-exports,check-size,build-llms-full}.mjs`,
|
|
320
|
-
`test/scaffold.test.ts`, `examples/.gitkeep`, `.github/workflows/{ci,publish}.yml`,
|
|
321
|
-
`README.md`, `README_ZHTW.md`, `CHANGELOG.md`, `CONTRIBUTING.md`,
|
|
322
|
-
`LICENSE`, `llms.txt`, `llms-full.txt`).
|
|
323
|
-
- `src/index.ts` is a `throw` stub exposing the frozen 0.1.0 API surface
|
|
324
|
-
(`createEmitter`, `Emitter<Events>`, wildcard `*` handler signature, `once`,
|
|
325
|
-
`AbortSignal`-aware `on`, `dispose`, `EmitterError`, `EmitterDisposedError`).
|
|
326
|
-
- `pnpm typecheck && pnpm lint && pnpm coverage && pnpm build &&
|
|
327
|
-
pnpm verify:exports && pnpm verify:llms && pnpm check:size` walks clean
|
|
328
|
-
against a single placeholder test.
|
|
329
|
-
- Coverage thresholds temporarily set to `0/0/0/0`; tightened to
|
|
330
|
-
`95/90/100/100` in 0.1.0.
|
|
331
|
-
- Size budget temporarily set to 3 KB gzip; tightened to the 550 B README
|
|
332
|
-
target in 0.1.0.
|
|
333
|
-
- Publish workflow exists but trigger is `workflow_dispatch` only — no
|
|
334
|
-
accidental npm release until 0.1.0.
|
|
335
|
-
|
|
336
|
-
### Decision log (carried over from LEARNINGS.md v0.3.0 cycle 預備區)
|
|
337
|
-
|
|
338
|
-
- **Not a `mitt` fork.** `mitt@3.0.1` is MIT-fork-friendly but is ~35 lines of
|
|
339
|
-
pure logic and has been unmaintained since 2023-07. Forking is equivalent
|
|
340
|
-
to rewriting, and the upstream copyright notice would carry no benefit.
|
|
341
|
-
Cleaner to write from scratch with the ai\*js conventions baked in.
|
|
342
|
-
- **Wildcard `*` is kept.** It is `mitt`'s signature feature; keeping it
|
|
343
|
-
preserves migration ergonomics for existing `mitt` users at ~80 B gzip cost.
|
|
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()`.
|
|
344
139
|
|
|
345
140
|
---
|
|
346
141
|
|
|
@@ -348,75 +143,31 @@ No runtime / source / API changes. This is a CI-only patch to validate the GitHu
|
|
|
348
143
|
|
|
349
144
|
# Contributing to aieventjs
|
|
350
145
|
|
|
351
|
-
|
|
352
|
-
(target ≤ 1100 B gzip); contributions that keep the surface narrow are easier
|
|
353
|
-
to accept than ones that expand it.
|
|
146
|
+
Keep the emitter small, synchronous, and predictable.
|
|
354
147
|
|
|
355
|
-
##
|
|
148
|
+
## Local workflow
|
|
356
149
|
|
|
357
150
|
```bash
|
|
358
151
|
pnpm install
|
|
359
|
-
pnpm
|
|
360
|
-
pnpm
|
|
361
|
-
pnpm
|
|
362
|
-
pnpm
|
|
363
|
-
pnpm
|
|
364
|
-
pnpm
|
|
365
|
-
pnpm verify:llms # ensures llms-full.txt is in sync with README + CHANGELOG
|
|
366
|
-
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
|
|
367
158
|
```
|
|
368
159
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
- Bug fixes with a failing test added first
|
|
372
|
-
- README / typing corrections
|
|
373
|
-
- Tests that lock down existing behaviour (especially the re-entrancy
|
|
374
|
-
invariant on `emit`)
|
|
375
|
-
- Performance work that keeps `on()` / `emit()` O(1) on the dispatch path
|
|
376
|
-
|
|
377
|
-
## What needs discussion first
|
|
378
|
-
|
|
379
|
-
- Anything that changes the public surface (`createEmitter`, `Emitter<Events>`,
|
|
380
|
-
`OnOptions`, error classes)
|
|
381
|
-
- Namespaced wildcards (`user.*`) — explicit non-goal; bring `eventemitter2`
|
|
382
|
-
if you need that
|
|
383
|
-
- Async / promise-returning handlers — explicit non-goal (handlers are
|
|
384
|
-
synchronous; resolve promises in user-land)
|
|
385
|
-
- Anything that pushes the core gzip past 1100 B
|
|
386
|
-
|
|
387
|
-
## Design principles
|
|
388
|
-
|
|
389
|
-
aieventjs follows the ai*js library-core priority order:
|
|
390
|
-
|
|
391
|
-
> Security > Correctness > Simplicity > YAGNI > Performance
|
|
392
|
-
|
|
393
|
-
Key invariants:
|
|
394
|
-
|
|
395
|
-
- `on()` returns a callable unsubscribe; calling it (or aborting
|
|
396
|
-
`opts.signal`) removes the handler in O(1).
|
|
397
|
-
- `emit()` snapshots the handler array before iterating — handlers added
|
|
398
|
-
during dispatch do NOT fire this round; handlers removed during dispatch
|
|
399
|
-
do NOT skip their successor.
|
|
400
|
-
- Wildcard `*` handlers fire AFTER type-matched handlers.
|
|
401
|
-
- `dispose()` is idempotent.
|
|
402
|
-
- All methods are destructurable: `const { on, emit } = bus` works.
|
|
403
|
-
|
|
404
|
-
## Commit & PR style
|
|
405
|
-
|
|
406
|
-
- Commit messages: imperative subject under 70 chars; body explains *why*.
|
|
407
|
-
- PRs: keep scope to one topic. Link the issue if any.
|
|
408
|
-
- Tests required for any behaviour change.
|
|
160
|
+
Run `pnpm lint` before PRs. If docs change, regenerate `llms-full.txt`.
|
|
409
161
|
|
|
410
|
-
##
|
|
162
|
+
## Rules
|
|
411
163
|
|
|
412
|
-
-
|
|
413
|
-
|
|
414
|
-
-
|
|
415
|
-
|
|
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.
|
|
416
168
|
|
|
417
169
|
## License
|
|
418
170
|
|
|
419
|
-
|
|
420
|
-
license that covers this project.
|
|
171
|
+
MIT
|
|
421
172
|
|
|
422
173
|
---
|