feat(daemon): Reorganize and refactor core into client/daemon/shared modules; add IPC protocol and tests
This commit is contained in:
152
ts/shared/common/utils.errorhandler.ts
Normal file
152
ts/shared/common/utils.errorhandler.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
ts/shared/protocol/error.codes.ts
Normal file
26
ts/shared/protocol/error.codes.ts
Normal 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',
|
||||
}
|
198
ts/shared/protocol/ipc.types.ts
Normal file
198
ts/shared/protocol/ipc.types.ts
Normal 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'];
|
5
ts/shared/protocol/protocol.version.ts
Normal file
5
ts/shared/protocol/protocol.version.ts
Normal 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';
|
Reference in New Issue
Block a user