113 lines
2.9 KiB
TypeScript
113 lines
2.9 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import type { JsonObject } from "./types.js";
|
|
|
|
export type PlanningDomainEventType = "requirements_defined" | "tasks_planned";
|
|
export type ExecutionDomainEventType = "code_committed" | "task_blocked";
|
|
export type ValidationDomainEventType = "validation_passed" | "validation_failed";
|
|
export type IntegrationDomainEventType = "branch_merged";
|
|
|
|
export type DomainEventType =
|
|
| PlanningDomainEventType
|
|
| ExecutionDomainEventType
|
|
| ValidationDomainEventType
|
|
| IntegrationDomainEventType;
|
|
|
|
export type DomainEventSource = "pipeline" | "actor";
|
|
|
|
export type DomainEventPayload = {
|
|
summary?: string;
|
|
details?: JsonObject;
|
|
errorCode?: string;
|
|
artifactPointer?: string;
|
|
};
|
|
|
|
export type DomainEvent<TType extends DomainEventType = DomainEventType> = {
|
|
id: string;
|
|
type: TType;
|
|
source: DomainEventSource;
|
|
sessionId: string;
|
|
nodeId: string;
|
|
attempt: number;
|
|
timestamp: string;
|
|
payload: DomainEventPayload;
|
|
};
|
|
|
|
export type DomainEventEmission<TType extends DomainEventType = DomainEventType> = {
|
|
type: TType;
|
|
payload?: DomainEventPayload;
|
|
};
|
|
|
|
export type DomainEventHandler<TType extends DomainEventType = DomainEventType> = (
|
|
event: DomainEvent<TType>,
|
|
) => Promise<void> | void;
|
|
|
|
const DOMAIN_EVENT_TYPES = new Set<DomainEventType>([
|
|
"requirements_defined",
|
|
"tasks_planned",
|
|
"code_committed",
|
|
"task_blocked",
|
|
"validation_passed",
|
|
"validation_failed",
|
|
"branch_merged",
|
|
]);
|
|
|
|
export function isDomainEventType(value: string): value is DomainEventType {
|
|
return DOMAIN_EVENT_TYPES.has(value as DomainEventType);
|
|
}
|
|
|
|
export function createDomainEvent(input: {
|
|
type: DomainEventType;
|
|
source: DomainEventSource;
|
|
sessionId: string;
|
|
nodeId: string;
|
|
attempt: number;
|
|
payload?: DomainEventPayload;
|
|
}): DomainEvent {
|
|
return {
|
|
id: randomUUID(),
|
|
type: input.type,
|
|
source: input.source,
|
|
sessionId: input.sessionId,
|
|
nodeId: input.nodeId,
|
|
attempt: input.attempt,
|
|
timestamp: new Date().toISOString(),
|
|
payload: input.payload ?? {},
|
|
};
|
|
}
|
|
|
|
export class DomainEventBus {
|
|
private readonly handlers = new Map<DomainEventType, Set<DomainEventHandler>>();
|
|
|
|
subscribe<TType extends DomainEventType>(
|
|
type: TType,
|
|
handler: DomainEventHandler<TType>,
|
|
): () => void {
|
|
const typedHandler = handler as DomainEventHandler;
|
|
const set = this.handlers.get(type);
|
|
if (set) {
|
|
set.add(typedHandler);
|
|
} else {
|
|
this.handlers.set(type, new Set([typedHandler]));
|
|
}
|
|
|
|
return () => {
|
|
const current = this.handlers.get(type);
|
|
current?.delete(typedHandler);
|
|
if (current && current.size === 0) {
|
|
this.handlers.delete(type);
|
|
}
|
|
};
|
|
}
|
|
|
|
async publish<TType extends DomainEventType>(event: DomainEvent<TType>): Promise<void> {
|
|
const set = this.handlers.get(event.type);
|
|
if (!set || set.size === 0) {
|
|
return;
|
|
}
|
|
|
|
for (const handler of set) {
|
|
await handler(event);
|
|
}
|
|
}
|
|
}
|