feat(daemon): Reorganize and refactor core into client/daemon/shared modules; add IPC protocol and tests

This commit is contained in:
2025-08-28 18:17:41 +00:00
parent 1516185c4d
commit ece16b75e2
23 changed files with 83 additions and 32 deletions

View File

@@ -0,0 +1,152 @@
/**
* Centralized error handling utility for TSPM
*/
// Define error types
export enum ErrorType {
CONFIG = 'ConfigError',
PROCESS = 'ProcessError',
RUNTIME = 'RuntimeError',
VALIDATION = 'ValidationError',
UNKNOWN = 'UnknownError',
}
// Base error class with type and code support
export class TspmError extends Error {
type: ErrorType;
code: string;
details?: Record<string, any>;
constructor(
message: string,
type: ErrorType = ErrorType.UNKNOWN,
code: string = 'ERR_UNKNOWN',
details?: Record<string, any>,
) {
super(message);
this.name = type;
this.type = type;
this.code = code;
this.details = details;
// Preserve proper stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
toString(): string {
return `[${this.type}:${this.code}] ${this.message}`;
}
}
// Specific error classes
export class ConfigError extends TspmError {
constructor(
message: string,
code: string = 'ERR_CONFIG',
details?: Record<string, any>,
) {
super(message, ErrorType.CONFIG, code, details);
}
}
export class ProcessError extends TspmError {
constructor(
message: string,
code: string = 'ERR_PROCESS',
details?: Record<string, any>,
) {
super(message, ErrorType.PROCESS, code, details);
}
}
export class ValidationError extends TspmError {
constructor(
message: string,
code: string = 'ERR_VALIDATION',
details?: Record<string, any>,
) {
super(message, ErrorType.VALIDATION, code, details);
}
}
// Utility for handling any error type
export const handleError = (error: Error | unknown): TspmError => {
if (error instanceof TspmError) {
return error;
}
if (error instanceof Error) {
return new TspmError(error.message, ErrorType.UNKNOWN, 'ERR_UNKNOWN', {
originalError: error,
});
}
return new TspmError(String(error), ErrorType.UNKNOWN, 'ERR_UNKNOWN');
};
// Logger with different log levels
export enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
NONE = 4,
}
export class Logger {
private static instance: Logger;
private level: LogLevel = LogLevel.INFO;
private componentName: string;
constructor(componentName: string) {
this.componentName = componentName;
}
static getInstance(componentName: string): Logger {
if (!Logger.instance) {
Logger.instance = new Logger(componentName);
}
return Logger.instance;
}
setLevel(level: LogLevel): void {
this.level = level;
}
private formatMessage(message: string): string {
const timestamp = new Date().toISOString();
return `[${timestamp}] [${this.componentName}] ${message}`;
}
debug(message: string): void {
if (this.level <= LogLevel.DEBUG) {
console.log(this.formatMessage(`DEBUG: ${message}`));
}
}
info(message: string): void {
if (this.level <= LogLevel.INFO) {
console.log(this.formatMessage(message));
}
}
warn(message: string): void {
if (this.level <= LogLevel.WARN) {
console.warn(this.formatMessage(`WARNING: ${message}`));
}
}
error(error: Error | unknown): void {
if (this.level <= LogLevel.ERROR) {
const tspmError = handleError(error);
console.error(this.formatMessage(`ERROR: ${tspmError.toString()}`));
// In debug mode, also log stack trace
if (this.level === LogLevel.DEBUG && tspmError.stack) {
console.error(tspmError.stack);
}
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* Standardized error codes for IPC communication
* These are used instead of string messages for better error handling
*/
export enum ErrorCode {
// General errors
UNKNOWN_ERROR = 'UNKNOWN_ERROR',
INVALID_REQUEST = 'INVALID_REQUEST',
// Process errors
PROCESS_NOT_FOUND = 'PROCESS_NOT_FOUND',
PROCESS_ALREADY_EXISTS = 'PROCESS_ALREADY_EXISTS',
PROCESS_START_FAILED = 'PROCESS_START_FAILED',
PROCESS_STOP_FAILED = 'PROCESS_STOP_FAILED',
// Daemon errors
DAEMON_NOT_RUNNING = 'DAEMON_NOT_RUNNING',
DAEMON_ALREADY_RUNNING = 'DAEMON_ALREADY_RUNNING',
// Memory errors
MEMORY_LIMIT_EXCEEDED = 'MEMORY_LIMIT_EXCEEDED',
// Config errors
CONFIG_INVALID = 'CONFIG_INVALID',
CONFIG_SAVE_FAILED = 'CONFIG_SAVE_FAILED',
}

View File

@@ -0,0 +1,198 @@
import type { IProcessConfig, IProcessInfo } from './classes.tspm.js';
import type { IProcessLog } from './classes.processwrapper.js';
// Base message types
export interface IpcRequest<T = any> {
id: string;
method: string;
params: T;
}
export interface IpcResponse<T = any> {
id: string;
success: boolean;
result?: T;
error?: {
code: number;
message: string;
data?: any;
};
}
// Request/Response pairs for each operation
// Start command
export interface StartRequest {
config: IProcessConfig;
}
export interface StartResponse {
processId: string;
pid?: number;
status: 'online' | 'stopped' | 'errored';
}
// Stop command
export interface StopRequest {
id: string;
}
export interface StopResponse {
success: boolean;
message?: string;
}
// Restart command
export interface RestartRequest {
id: string;
}
export interface RestartResponse {
processId: string;
pid?: number;
status: 'online' | 'stopped' | 'errored';
}
// Delete command
export interface DeleteRequest {
id: string;
}
export interface DeleteResponse {
success: boolean;
message?: string;
}
// List command
export interface ListRequest {
// No parameters needed
}
export interface ListResponse {
processes: IProcessInfo[];
}
// Describe command
export interface DescribeRequest {
id: string;
}
export interface DescribeResponse {
processInfo: IProcessInfo;
config: IProcessConfig;
}
// Get logs command
export interface GetLogsRequest {
id: string;
lines?: number;
}
export interface GetLogsResponse {
logs: IProcessLog[];
}
// Start all command
export interface StartAllRequest {
// No parameters needed
}
export interface StartAllResponse {
started: string[];
failed: Array<{
id: string;
error: string;
}>;
}
// Stop all command
export interface StopAllRequest {
// No parameters needed
}
export interface StopAllResponse {
stopped: string[];
failed: Array<{
id: string;
error: string;
}>;
}
// Restart all command
export interface RestartAllRequest {
// No parameters needed
}
export interface RestartAllResponse {
restarted: string[];
failed: Array<{
id: string;
error: string;
}>;
}
// Daemon status command
export interface DaemonStatusRequest {
// No parameters needed
}
export interface DaemonStatusResponse {
status: 'running' | 'stopped';
pid?: number;
uptime?: number;
processCount: number;
memoryUsage?: number;
cpuUsage?: number;
}
// Daemon shutdown command
export interface DaemonShutdownRequest {
graceful?: boolean;
timeout?: number; // milliseconds
}
export interface DaemonShutdownResponse {
success: boolean;
message?: string;
}
// Heartbeat command
export interface HeartbeatRequest {
// No parameters needed
}
export interface HeartbeatResponse {
timestamp: number;
status: 'healthy' | 'degraded';
}
// Type mappings for methods
export type IpcMethodMap = {
start: { request: StartRequest; response: StartResponse };
stop: { request: StopRequest; response: StopResponse };
restart: { request: RestartRequest; response: RestartResponse };
delete: { request: DeleteRequest; response: DeleteResponse };
list: { request: ListRequest; response: ListResponse };
describe: { request: DescribeRequest; response: DescribeResponse };
getLogs: { request: GetLogsRequest; response: GetLogsResponse };
startAll: { request: StartAllRequest; response: StartAllResponse };
stopAll: { request: StopAllRequest; response: StopAllResponse };
restartAll: { request: RestartAllRequest; response: RestartAllResponse };
'daemon:status': {
request: DaemonStatusRequest;
response: DaemonStatusResponse;
};
'daemon:shutdown': {
request: DaemonShutdownRequest;
response: DaemonShutdownResponse;
};
heartbeat: { request: HeartbeatRequest; response: HeartbeatResponse };
};
// Helper type to extract request type for a method
export type RequestForMethod<M extends keyof IpcMethodMap> =
IpcMethodMap[M]['request'];
// Helper type to extract response type for a method
export type ResponseForMethod<M extends keyof IpcMethodMap> =
IpcMethodMap[M]['response'];

View File

@@ -0,0 +1,5 @@
/**
* Protocol version for client-daemon communication
* This allows for version compatibility checks between client and daemon
*/
export const PROTOCOL_VERSION = '1.0.0';