tasq/node_modules/@claude-flow/shared/dist/events/projections.js

421 lines
13 KiB
JavaScript

/**
* Event Projections for Read Models (ADR-007)
*
* Build read models from domain events using projections.
* Projections listen to events and maintain queryable state.
*
* Implemented Projections:
* - AgentStateProjection - Current state of all agents
* - TaskHistoryProjection - Complete task execution history
* - MemoryIndexProjection - Memory access patterns and index
*
* @module v3/shared/events/projections
*/
import { EventEmitter } from 'node:events';
// =============================================================================
// Projection Base Class
// =============================================================================
export class Projection extends EventEmitter {
eventStore;
initialized = false;
constructor(eventStore) {
super();
this.eventStore = eventStore;
}
/**
* Initialize the projection by replaying events
*/
async initialize() {
if (this.initialized)
return;
// Replay all events to build current state
for await (const event of this.eventStore.replay()) {
await this.handle(event);
}
this.initialized = true;
this.emit('initialized');
}
}
export class AgentStateProjection extends Projection {
agents = new Map();
/**
* Get state for a specific agent
*/
getAgent(agentId) {
return this.agents.get(agentId) || null;
}
/**
* Get all agents
*/
getAllAgents() {
return Array.from(this.agents.values());
}
/**
* Get agents by status
*/
getAgentsByStatus(status) {
return this.getAllAgents().filter((agent) => agent.status === status);
}
/**
* Get agents by domain
*/
getAgentsByDomain(domain) {
return this.getAllAgents().filter((agent) => agent.domain === domain);
}
/**
* Get active agent count
*/
getActiveAgentCount() {
return this.getAgentsByStatus('active').length;
}
async handle(event) {
switch (event.type) {
case 'agent:spawned':
this.handleAgentSpawned(event);
break;
case 'agent:started':
this.handleAgentStarted(event);
break;
case 'agent:stopped':
this.handleAgentStopped(event);
break;
case 'agent:failed':
this.handleAgentFailed(event);
break;
case 'agent:status-changed':
this.handleAgentStatusChanged(event);
break;
case 'agent:task-assigned':
this.handleAgentTaskAssigned(event);
break;
case 'agent:task-completed':
this.handleAgentTaskCompleted(event);
break;
}
}
reset() {
this.agents.clear();
this.emit('reset');
}
handleAgentSpawned(event) {
const { agentId, role, domain } = event.payload;
this.agents.set(agentId, {
id: agentId,
role: role,
domain: domain,
status: 'idle',
currentTask: null,
completedTasks: [],
failedTasks: [],
totalTaskDuration: 0,
taskCount: 0,
errorCount: 0,
spawnedAt: event.timestamp,
startedAt: null,
stoppedAt: null,
lastActivityAt: event.timestamp,
});
this.emit('agent:spawned', { agentId });
}
handleAgentStarted(event) {
const { agentId } = event.payload;
const agent = this.agents.get(agentId);
if (agent) {
agent.status = 'active';
agent.startedAt = event.timestamp;
agent.lastActivityAt = event.timestamp;
this.emit('agent:started', { agentId });
}
}
handleAgentStopped(event) {
const { agentId } = event.payload;
const agent = this.agents.get(agentId);
if (agent) {
agent.status = 'completed';
agent.stoppedAt = event.timestamp;
agent.lastActivityAt = event.timestamp;
this.emit('agent:stopped', { agentId });
}
}
handleAgentFailed(event) {
const { agentId } = event.payload;
const agent = this.agents.get(agentId);
if (agent) {
agent.status = 'error';
agent.errorCount++;
agent.lastActivityAt = event.timestamp;
this.emit('agent:failed', { agentId });
}
}
handleAgentStatusChanged(event) {
const { agentId, newStatus } = event.payload;
const agent = this.agents.get(agentId);
if (agent) {
agent.status = newStatus;
agent.lastActivityAt = event.timestamp;
this.emit('agent:status-changed', { agentId, status: newStatus });
}
}
handleAgentTaskAssigned(event) {
const { agentId, taskId } = event.payload;
const agent = this.agents.get(agentId);
if (agent) {
agent.currentTask = taskId;
agent.status = 'active';
agent.lastActivityAt = event.timestamp;
this.emit('agent:task-assigned', { agentId, taskId });
}
}
handleAgentTaskCompleted(event) {
const { agentId, taskId, duration } = event.payload;
const agent = this.agents.get(agentId);
if (agent) {
agent.completedTasks.push(taskId);
agent.currentTask = null;
agent.taskCount++;
agent.totalTaskDuration += duration || 0;
agent.status = 'idle';
agent.lastActivityAt = event.timestamp;
this.emit('agent:task-completed', { agentId, taskId });
}
}
}
export class TaskHistoryProjection extends Projection {
tasks = new Map();
/**
* Get task by ID
*/
getTask(taskId) {
return this.tasks.get(taskId) || null;
}
/**
* Get all tasks
*/
getAllTasks() {
return Array.from(this.tasks.values());
}
/**
* Get tasks by status
*/
getTasksByStatus(status) {
return this.getAllTasks().filter((task) => task.status === status);
}
/**
* Get tasks by agent
*/
getTasksByAgent(agentId) {
return this.getAllTasks().filter((task) => task.assignedAgent === agentId);
}
/**
* Get completed task count
*/
getCompletedTaskCount() {
return this.getTasksByStatus('completed').length;
}
/**
* Get average task duration
*/
getAverageTaskDuration() {
const completed = this.getTasksByStatus('completed').filter((t) => t.duration !== null);
if (completed.length === 0)
return 0;
const total = completed.reduce((sum, task) => sum + (task.duration || 0), 0);
return total / completed.length;
}
async handle(event) {
switch (event.type) {
case 'task:created':
this.handleTaskCreated(event);
break;
case 'task:queued':
this.handleTaskQueued(event);
break;
case 'task:started':
this.handleTaskStarted(event);
break;
case 'task:completed':
this.handleTaskCompleted(event);
break;
case 'task:failed':
this.handleTaskFailed(event);
break;
case 'task:blocked':
this.handleTaskBlocked(event);
break;
}
}
reset() {
this.tasks.clear();
this.emit('reset');
}
handleTaskCreated(event) {
const { taskId, taskType, title, priority, dependencies } = event.payload;
this.tasks.set(taskId, {
id: taskId,
type: taskType,
title: title,
status: 'pending',
priority: priority,
assignedAgent: null,
dependencies: dependencies || [],
blockedBy: [],
createdAt: event.timestamp,
startedAt: null,
completedAt: null,
failedAt: null,
duration: null,
result: null,
error: null,
retryCount: 0,
});
this.emit('task:created', { taskId });
}
handleTaskQueued(event) {
const { taskId } = event.payload;
const task = this.tasks.get(taskId);
if (task) {
task.status = 'queued';
this.emit('task:queued', { taskId });
}
}
handleTaskStarted(event) {
const { taskId, agentId } = event.payload;
const task = this.tasks.get(taskId);
if (task) {
task.status = 'in-progress';
task.assignedAgent = agentId;
task.startedAt = event.timestamp;
this.emit('task:started', { taskId, agentId });
}
}
handleTaskCompleted(event) {
const { taskId, result, duration } = event.payload;
const task = this.tasks.get(taskId);
if (task) {
task.status = 'completed';
task.completedAt = event.timestamp;
task.duration = duration || (task.startedAt ? event.timestamp - task.startedAt : null);
task.result = result;
this.emit('task:completed', { taskId });
}
}
handleTaskFailed(event) {
const { taskId, error, retryCount } = event.payload;
const task = this.tasks.get(taskId);
if (task) {
task.status = 'failed';
task.failedAt = event.timestamp;
task.error = error;
task.retryCount = retryCount;
this.emit('task:failed', { taskId });
}
}
handleTaskBlocked(event) {
const { taskId, blockedBy } = event.payload;
const task = this.tasks.get(taskId);
if (task) {
task.status = 'blocked';
task.blockedBy = blockedBy;
this.emit('task:blocked', { taskId, blockedBy });
}
}
}
export class MemoryIndexProjection extends Projection {
memories = new Map();
/**
* Get memory by ID
*/
getMemory(memoryId) {
return this.memories.get(memoryId) || null;
}
/**
* Get all active memories (not deleted)
*/
getActiveMemories() {
return Array.from(this.memories.values()).filter((m) => !m.isDeleted);
}
/**
* Get memories by namespace
*/
getMemoriesByNamespace(namespace) {
return this.getActiveMemories().filter((m) => m.namespace === namespace);
}
/**
* Get most accessed memories
*/
getMostAccessedMemories(limit = 10) {
return this.getActiveMemories()
.sort((a, b) => b.accessCount - a.accessCount)
.slice(0, limit);
}
/**
* Get total memory size by namespace
*/
getTotalSizeByNamespace(namespace) {
return this.getMemoriesByNamespace(namespace).reduce((sum, m) => sum + m.size, 0);
}
async handle(event) {
switch (event.type) {
case 'memory:stored':
this.handleMemoryStored(event);
break;
case 'memory:retrieved':
this.handleMemoryRetrieved(event);
break;
case 'memory:deleted':
this.handleMemoryDeleted(event);
break;
case 'memory:expired':
this.handleMemoryExpired(event);
break;
}
}
reset() {
this.memories.clear();
this.emit('reset');
}
handleMemoryStored(event) {
const { memoryId, namespace, key, memoryType, size } = event.payload;
this.memories.set(memoryId, {
id: memoryId,
namespace: namespace,
key: key,
type: memoryType,
size: size || 0,
accessCount: 0,
storedAt: event.timestamp,
lastAccessedAt: event.timestamp,
deletedAt: null,
isDeleted: false,
});
this.emit('memory:stored', { memoryId });
}
handleMemoryRetrieved(event) {
const { memoryId, accessCount } = event.payload;
const memory = this.memories.get(memoryId);
if (memory && !memory.isDeleted) {
memory.accessCount = accessCount;
memory.lastAccessedAt = event.timestamp;
this.emit('memory:retrieved', { memoryId });
}
}
handleMemoryDeleted(event) {
const { memoryId } = event.payload;
const memory = this.memories.get(memoryId);
if (memory) {
memory.isDeleted = true;
memory.deletedAt = event.timestamp;
this.emit('memory:deleted', { memoryId });
}
}
handleMemoryExpired(event) {
const { memoryId } = event.payload;
const memory = this.memories.get(memoryId);
if (memory) {
memory.isDeleted = true;
memory.deletedAt = event.timestamp;
this.emit('memory:expired', { memoryId });
}
}
}
//# sourceMappingURL=projections.js.map