diff options
| author | Adam Malczewski <[email protected]> | 2026-06-04 22:40:46 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-04 22:40:56 +0900 |
| commit | 669c269d972f7bfcd9036017ff5e668d2eeafdd8 (patch) | |
| tree | 9dfd465feb78c1c30c75d09528476b0531689bcd /packages/kernel/src/bus/pure.ts | |
| parent | 0af8b1de733a199dbf5dcf779cc45f495048b1c3 (diff) | |
| download | dispatch-669c269d972f7bfcd9036017ff5e668d2eeafdd8.tar.gz dispatch-669c269d972f7bfcd9036017ff5e668d2eeafdd8.zip | |
feat(kernel): event/hook bus (events, filters, services) — pure dispatch + stateful shell
Diffstat (limited to 'packages/kernel/src/bus/pure.ts')
| -rw-r--r-- | packages/kernel/src/bus/pure.ts | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/packages/kernel/src/bus/pure.ts b/packages/kernel/src/bus/pure.ts new file mode 100644 index 0000000..7cd9143 --- /dev/null +++ b/packages/kernel/src/bus/pure.ts @@ -0,0 +1,83 @@ +import type { Logger } from "../contracts/extension.js"; +import type { EventHandler, FilterHandler } from "../contracts/hooks.js"; + +export function dispatchEventSync<T>( + handlers: ReadonlyArray<EventHandler<T>>, + payload: T, + logger: Logger, + hookId: string, +): void { + for (const handler of handlers) { + try { + const result = handler(payload); + if (result instanceof Promise) { + result.catch((err: unknown) => { + logger.error(`Event hook "${hookId}" handler rejected`, err); + }); + } + } catch (err) { + logger.error(`Event hook "${hookId}" handler threw`, err); + } + } +} + +export async function dispatchEventAsync<T>( + handlers: ReadonlyArray<EventHandler<T>>, + payload: T, + logger: Logger, + hookId: string, + timeoutMs?: number, +): Promise<void> { + const promises = handlers.map(async (handler) => { + try { + await handler(payload); + } catch (err) { + logger.error(`Event hook "${hookId}" handler threw`, err); + } + }); + + if (timeoutMs !== undefined) { + await Promise.race([ + Promise.all(promises), + new Promise<void>((resolve) => { + setTimeout(resolve, timeoutMs); + }), + ]); + } else { + await Promise.all(promises); + } +} + +export interface FilterEntry<T> { + readonly fn: FilterHandler<T>; + readonly priority: number; + readonly order: number; +} + +export function sortFilters<T>( + entries: ReadonlyArray<FilterEntry<T>>, +): ReadonlyArray<FilterEntry<T>> { + return [...entries].sort((a, b) => { + if (a.priority !== b.priority) return a.priority - b.priority; + return a.order - b.order; + }); +} + +export async function applyFilterChain<T>( + filters: ReadonlyArray<FilterHandler<T>>, + value: T, + logger: Logger, + hookId: string, + failClosed: boolean, +): Promise<T> { + let current = value; + for (const fn of filters) { + try { + current = await fn(current); + } catch (err) { + if (failClosed) throw err; + logger.error(`Filter "${hookId}" handler threw (fail-open, passing through)`, err); + } + } + return current; +} |
