import { Injectable, OnModuleDestroy, Logger } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { ValkeyClient, ValkeyClientConfig, EventErrorHandler } from "./valkey.client"; import type { TaskState, AgentState, TaskStatus, AgentStatus, OrchestratorEvent, EventHandler, TaskContext, } from "./types"; /** * NestJS service for Valkey state management and pub/sub */ @Injectable() export class ValkeyService implements OnModuleDestroy { private readonly client: ValkeyClient; private readonly logger = new Logger(ValkeyService.name); constructor(private readonly configService: ConfigService) { const config: ValkeyClientConfig = { host: this.configService.get("orchestrator.valkey.host", "localhost"), port: this.configService.get("orchestrator.valkey.port", 6379), connectTimeout: this.configService.get("orchestrator.valkey.connectTimeout", 5000), commandTimeout: this.configService.get("orchestrator.valkey.commandTimeout", 3000), logger: { error: (message: string, error?: unknown) => { this.logger.error(message, error instanceof Error ? error.stack : String(error)); }, }, }; const password = this.configService.get("orchestrator.valkey.password"); if (password) { config.password = password; } else { // SEC-ORCH-15: Warn when Valkey password is not configured const nodeEnv = this.configService.get("NODE_ENV", "development"); const isProduction = nodeEnv === "production"; if (isProduction) { this.logger.warn( "SECURITY WARNING: VALKEY_PASSWORD is not configured in production environment. " + "Valkey connections without authentication are insecure. " + "Set VALKEY_PASSWORD environment variable to secure your Valkey instance." ); } else { this.logger.warn( "VALKEY_PASSWORD is not configured. " + "Consider setting VALKEY_PASSWORD for secure Valkey connections." ); } } this.client = new ValkeyClient(config); } async onModuleDestroy(): Promise { await this.client.disconnect(); } /** * Task State Management */ async getTaskState(taskId: string): Promise { return this.client.getTaskState(taskId); } async setTaskState(state: TaskState): Promise { return this.client.setTaskState(state); } async deleteTaskState(taskId: string): Promise { return this.client.deleteTaskState(taskId); } async updateTaskStatus( taskId: string, status: TaskStatus, agentId?: string, error?: string ): Promise { return this.client.updateTaskStatus(taskId, status, agentId, error); } async listTasks(): Promise { return this.client.listTasks(); } /** * Agent State Management */ async getAgentState(agentId: string): Promise { return this.client.getAgentState(agentId); } async setAgentState(state: AgentState): Promise { return this.client.setAgentState(state); } async deleteAgentState(agentId: string): Promise { return this.client.deleteAgentState(agentId); } async updateAgentStatus( agentId: string, status: AgentStatus, error?: string ): Promise { return this.client.updateAgentStatus(agentId, status, error); } async listAgents(): Promise { return this.client.listAgents(); } /** * Event Pub/Sub */ async publishEvent(event: OrchestratorEvent): Promise { return this.client.publishEvent(event); } async subscribeToEvents(handler: EventHandler, errorHandler?: EventErrorHandler): Promise { return this.client.subscribeToEvents(handler, errorHandler); } /** * Convenience methods */ async createTask(taskId: string, context: TaskContext): Promise { const now = new Date().toISOString(); const state: TaskState = { taskId, status: "pending", context, createdAt: now, updatedAt: now, }; await this.setTaskState(state); } async createAgent(agentId: string, taskId: string): Promise { const state: AgentState = { agentId, status: "spawning", taskId, }; await this.setAgentState(state); } /** * Check Valkey connectivity * @returns true if connection is healthy, false otherwise */ async ping(): Promise { return this.client.ping(); } }