events: modules log activity to the broker, any module reacts
The lighter sibling of provisioning — 1:many and broadcast, no credential,
just the broker's topic routing. A thin, audit-ready surface over the
broker's publish/subscribe:
- emit(type, body): publishes an Event carrying who emitted it (MESH_MODULE),
on which node (MESH_NODE) and when (ISO timestamp) — so a listener can
build a real audit trail.
- on(pattern, handler): react to events by topic pattern. The audit logger
is just on("#", ...).
Tested: a module emits; a targeted listener (module.umami.#) hears only its
events, the audit sink (#) hears every module's, and the metadata audit
needs is present.
The declared side — a manifest's emits/consumes, so the mesh knows the
event graph — and the audit-logger module are the next pieces.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
// Events — a module logs its activity onto the mesh broker, and any module reacts. The lighter
|
||||
// sibling of provisioning: provisioning is 1:1 and credentialed (a provider creates a resource for
|
||||
// one consumer); an event is 1:many and broadcast (a module emits, any number listen, no
|
||||
// credential — just the broker's topic routing). Declared as emits/consumes on the manifest so the
|
||||
// mesh knows the event graph.
|
||||
//
|
||||
// This is a thin, audit-ready surface over the broker's publish/subscribe: every event carries who
|
||||
// emitted it, on which node, and when — so a logger consuming `#` can write a real audit trail.
|
||||
|
||||
import { broker } from "../messaging/index.js";
|
||||
|
||||
/** An event on the mesh: a topic key, its source, and a body — with the metadata audit needs. */
|
||||
export interface Event<T = unknown> {
|
||||
/** The routing key, e.g. "module.umami.site.created". Dotted, so listeners can match by prefix. */
|
||||
readonly type: string;
|
||||
/** The emitting module. */
|
||||
readonly source: string;
|
||||
/** The node it was emitted from. */
|
||||
readonly node: string;
|
||||
/** ISO-8601 emit time. */
|
||||
readonly at: string;
|
||||
readonly body: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event. Source and node come from the environment the runtime set for the module
|
||||
* (MESH_MODULE, MESH_NODE), so a module names only the type and the body.
|
||||
*/
|
||||
export async function emit<T>(type: string, body: T): Promise<void> {
|
||||
const event: Event<T> = {
|
||||
type,
|
||||
source: process.env.MESH_MODULE ?? "unknown",
|
||||
node: process.env.MESH_NODE ?? "unknown",
|
||||
at: new Date().toISOString(),
|
||||
body,
|
||||
};
|
||||
await broker().publish<Event<T>>({ key: type, node: event.node, body: event });
|
||||
}
|
||||
|
||||
/**
|
||||
* React to events whose type matches a topic pattern (`*` one segment, `#` any). The audit logger
|
||||
* is just `on("#", …)`. The handler receives the whole event, metadata included.
|
||||
*/
|
||||
export async function on<T>(pattern: string, handler: (event: Event<T>) => Promise<void>): Promise<() => void> {
|
||||
return broker().subscribe<Event<T>>(pattern, async (envelope) => {
|
||||
await handler(envelope.body);
|
||||
});
|
||||
}
|
||||
@@ -8,4 +8,5 @@ export * from "./contracts/index.js";
|
||||
export * as tools from "./tools/index.js";
|
||||
export * as provisioner from "./provisioner/index.js";
|
||||
export * as messaging from "./messaging/index.js";
|
||||
export * as events from "./events/index.js";
|
||||
export * as primitives from "./primitives/index.js";
|
||||
|
||||
Reference in New Issue
Block a user