summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/bus/pure.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-04 22:40:46 +0900
committerAdam Malczewski <[email protected]>2026-06-04 22:40:56 +0900
commit669c269d972f7bfcd9036017ff5e668d2eeafdd8 (patch)
tree9dfd465feb78c1c30c75d09528476b0531689bcd /packages/kernel/src/bus/pure.ts
parent0af8b1de733a199dbf5dcf779cc45f495048b1c3 (diff)
downloaddispatch-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.ts83
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;
+}