BaseService.ts

Abstract base class for micro-block services with comprehensive metadata, operation framework, and validation. Includes all service interfaces and operation parameter validation.

BaseService.tsv1.0.012.9 KB
BaseService.ts(typescript)
1/**
2 * Validation rules for operation parameters
3 */
4export interface ParameterValidation {
5  min?: number;                    // Minimum value (numbers) or length (strings/arrays)
6  max?: number;                    // Maximum value (numbers) or length (strings/arrays)
7  options?: string[];              // Valid options for dropdown/select
8  pattern?: string;                // Regex pattern for string validation
9  step?: number;                   // Step increment for numbers
10}
11
12/**
13 * Definition of a parameter for a service operation
14 */
15export interface OperationParameter {
16  name: string;                    // Parameter name (matches method signature)
17  type: 'string' | 'number' | 'boolean' | 'object' | 'array';
18  required: boolean;               // Whether parameter is required
19  defaultValue?: any;              // Default value to use in UI
20  description?: string;            // Human-readable description
21  displayName?: string;            // UI-friendly name (defaults to name)
22  validation?: ParameterValidation; // Validation rules
23  group?: string;                  // Group parameters in UI (e.g., 'Performance', 'Security')
24  sensitive?: boolean;             // Mark as sensitive (password field, etc.)
25}
26
27/**
28 * Definition of a service operation that can be executed via admin interface
29 */
30export interface ServiceOperation {
31  name: string;                    // Method name to call (e.g., 'start', 'stop')
32  displayName: string;             // Human-friendly name for UI
33  description: string;             // What this operation does
34  parameters: OperationParameter[]; // Parameters this operation accepts
35  category?: string;               // Group operations (e.g., 'control', 'configuration', 'maintenance')
36  permissions?: string[];          // Required permissions to execute
37  dangerous?: boolean;             // Requires confirmation dialog
38  confirmationMessage?: string;    // Custom confirmation message for dangerous operations
39  icon?: string;                   // UI icon name/class
40  buttonStyle?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger'; // UI button styling
41}
42
43/**
44 * Result of executing an operation
45 */
46export interface OperationResult<T = any> {
47  success: boolean;
48  data?: T;
49  message?: string;
50  error?: string;
51  metadata?: Record<string, any>;
52}
53
54/**
55 * Service metadata interface for comprehensive service discovery and selection
56 */
57export interface ServiceMetadata {
58  // === Identity & Basic Info ===
59  name: string;                    // Unique service identifier
60  displayName: string;             // Human-friendly name  
61  description: string;             // What this service does
62  contract: string;                // Interface it implements (e.g., 'ICacheService')
63  implementation: string;          // Implementation type (e.g., 'InMemory', 'Redis')
64  version: string;                 // Service implementation version
65  contractVersion: string;         // Interface contract version
66  
67  // === Capabilities & Features ===
68  features: string[];              // Key features this implementation provides
69  limitations?: string[];          // Known limitations or constraints
70  
71  // === Dependencies (Portable Service Pattern) ===
72  dependencies: {
73    services?: string[];           // Required service interfaces (e.g., ['IDatabaseService'])
74  };
75  
76  // === Requirements ===
77  requirements: {
78    services?: string[];           // Other service dependencies (e.g., ['IDatabaseService'])
79    runtime?: string[];            // External dependencies (e.g., ['Redis 6.0+', 'Node.js 18+'])
80    configuration: {
81      required: string[];          // Required config keys (e.g., ['REDIS_URL'])
82      optional?: string[];         // Optional config keys (e.g., ['REDIS_POOL_SIZE'])
83    };
84  };
85  
86  // === Use Case Recommendations ===
87  recommendations: {
88    idealFor: string[];            // Perfect use cases
89    acceptableFor?: string[];      // Works but not optimal
90    notRecommendedFor?: string[];  // Poor fit scenarios
91  };
92  
93  // === Configuration Schema (optional) ===
94  configurationSchema?: {
95    [key: string]: {
96      type: 'string' | 'number' | 'boolean' | 'array' | 'object';
97      description: string;
98      default?: any;
99      validation?: string;         // Validation rule or regex
100      example?: any;
101    };
102  };
103}
104
105/**
106 * Abstract base class for all services in the micro-block architecture.
107 * Enforces metadata requirements for service discovery and selection.
108 */
109export abstract class BaseService {
110  /**
111   * Static metadata that describes this service implementation.
112   * Must be accessible before instantiation for discovery purposes.
113   */
114  static readonly metadata: ServiceMetadata;
115  
116  /**
117   * Get the metadata for this service.
118   * Returns the static metadata property.
119   */
120  abstract getMetadata(): ServiceMetadata;
121  
122  /**
123   * Initialize the service. Configuration should be provided via constructor.
124   * Override this method to implement service-specific initialization.
125   */
126  abstract initialize(): Promise<void> | void;
127  
128  /**
129   * Health check for the service.
130   * Override this method to implement service-specific health checks.
131   */
132  abstract isHealthy(): Promise<boolean> | boolean;
133  
134  /**
135   * Graceful shutdown of the service.
136   * Override this method to implement service-specific cleanup.
137   */
138  abstract destroy(): Promise<void> | void;
139  
140  /**
141   * Get service-specific status information.
142   * Override this method to provide custom status details.
143   */
144  getStatus(): Record<string, any> {
145    return {
146      healthy: this.isHealthy(),
147      metadata: this.getMetadata()
148    };
149  }
150  
151  /**
152   * Get available operations for this service.
153   * Override this method to expose service-specific operations for admin interface.
154   * Can be async to allow services to determine available operations based on current state.
155   */
156  getOperations(): ServiceOperation[] | Promise<ServiceOperation[]> {
157    return [];
158  }
159
160  /**
161   * Execute a service-specific operation with enhanced error handling and result formatting.
162   * This provides a generic way for services to expose custom operations
163   * while maintaining type safety through service-specific interfaces.
164   */
165  async executeOperation<T = any>(
166    operation: string, 
167    params?: Record<string, any>
168  ): Promise<OperationResult<T>> {
169    try {
170      // Validate that the operation exists - handle both sync and async getOperations
171      const operationsResult = this.getOperations();
172      const availableOperations = await Promise.resolve(operationsResult);
173      const operationDef = availableOperations.find((op: ServiceOperation) => op.name === operation);
174      
175      if (!operationDef) {
176        return {
177          success: false,
178          error: `Operation '${operation}' is not supported by service '${this.getMetadata().name}'`,
179          metadata: { availableOperations: availableOperations.map((op: ServiceOperation) => op.name) }
180        };
181      }
182
183      // Validate parameters
184      const validationResult = this.validateOperationParameters(operationDef, params || {});
185      if (!validationResult.valid) {
186        return {
187          success: false,
188          error: `Parameter validation failed: ${validationResult.errors.join(', ')}`,
189          metadata: { validationErrors: validationResult.errors }
190        };
191      }
192
193      // Execute the operation
194      const result = await this.executeOperationInternal(operation, params || {});
195      
196      return {
197        success: true,
198        data: result,
199        message: `Operation '${operation}' completed successfully`,
200        metadata: { operation: operationDef.name, executedAt: new Date().toISOString() }
201      };
202
203    } catch (error) {
204      return {
205        success: false,
206        error: error instanceof Error ? error.message : String(error),
207        metadata: { operation, executedAt: new Date().toISOString() }
208      };
209    }
210  }
211
212  /**
213   * Internal operation execution - override this in derived classes.
214   * This method should handle the actual operation logic.
215   */
216  protected async executeOperationInternal<T = any>(
217    operation: string, 
218    params: Record<string, any>
219  ): Promise<T> {
220    throw new Error(
221      `Operation '${operation}' is not implemented by service '${this.getMetadata().name}'`
222    );
223  }
224  
225  /**
226   * Validate operation parameters against their definitions.
227   */
228  protected validateOperationParameters(
229    operation: ServiceOperation, 
230    params: Record<string, any>
231  ): { valid: boolean; errors: string[] } {
232    const errors: string[] = [];
233
234    // Check required parameters
235    for (const param of operation.parameters) {
236      if (param.required && (params[param.name] === undefined || params[param.name] === null)) {
237        errors.push(`Required parameter '${param.name}' is missing`);
238        continue;
239      }
240
241      const value = params[param.name];
242      if (value !== undefined && value !== null) {
243        // Type validation
244        const actualType = Array.isArray(value) ? 'array' : typeof value;
245        if (actualType !== param.type) {
246          errors.push(`Parameter '${param.name}' must be of type '${param.type}', got '${actualType}'`);
247          continue;
248        }
249
250        // Validation rules
251        if (param.validation) {
252          const validationErrors = this.validateParameterValue(param.name, value, param.validation);
253          errors.push(...validationErrors);
254        }
255      }
256    }
257
258    // Check for unknown parameters
259    const allowedParams = new Set(operation.parameters.map(p => p.name));
260    for (const paramName of Object.keys(params)) {
261      if (!allowedParams.has(paramName)) {
262        errors.push(`Unknown parameter '${paramName}'`);
263      }
264    }
265
266    return { valid: errors.length === 0, errors };
267  }
268
269  /**
270   * Validate a single parameter value against validation rules.
271   */
272  private validateParameterValue(name: string, value: any, validation: ParameterValidation): string[] {
273    const errors: string[] = [];
274
275    // Min/Max validation
276    if (typeof value === 'number') {
277      if (validation.min !== undefined && value < validation.min) {
278        errors.push(`Parameter '${name}' must be at least ${validation.min}`);
279      }
280      if (validation.max !== undefined && value > validation.max) {
281        errors.push(`Parameter '${name}' must be at most ${validation.max}`);
282      }
283    }
284
285    // String/Array length validation
286    if (typeof value === 'string' || Array.isArray(value)) {
287      if (validation.min !== undefined && value.length < validation.min) {
288        errors.push(`Parameter '${name}' must have at least ${validation.min} characters/items`);
289      }
290      if (validation.max !== undefined && value.length > validation.max) {
291        errors.push(`Parameter '${name}' must have at most ${validation.max} characters/items`);
292      }
293    }
294
295    // Options validation (enum-like)
296    if (validation.options && !validation.options.includes(String(value))) {
297      errors.push(`Parameter '${name}' must be one of: ${validation.options.join(', ')}`);
298    }
299
300    // Pattern validation (regex)
301    if (validation.pattern && typeof value === 'string') {
302      const regex = new RegExp(validation.pattern);
303      if (!regex.test(value)) {
304        errors.push(`Parameter '${name}' does not match required pattern`);
305      }
306    }
307
308    return errors;
309  }
310
311  /**
312   * Validate configuration against the service's requirements.
313   * Uses the metadata to check required and optional configuration keys.
314   */
315  protected validateConfiguration(config: Record<string, any>): void {
316    const metadata = this.getMetadata();
317    const required = metadata.requirements.configuration.required || [];
318    
319    // Check required configuration
320    for (const key of required) {
321      if (config[key] === undefined || config[key] === null) {
322        throw new Error(
323          `Required configuration key '${key}' is missing for service '${metadata.name}'`
324        );
325      }
326    }
327    
328    // Validate configuration against schema if provided
329    if (metadata.configurationSchema) {
330      for (const [key, value] of Object.entries(config)) {
331        const schema = metadata.configurationSchema[key];
332        if (schema && !this.validateConfigValue(value, schema)) {
333          throw new Error(
334            `Configuration key '${key}' is invalid for service '${metadata.name}': ${schema.validation || 'Type mismatch'}`
335          );
336        }
337      }
338    }
339  }
340  
341  /**
342   * Validate a single configuration value against its schema.
343   */
344  private validateConfigValue(value: any, schema: NonNullable<ServiceMetadata['configurationSchema']>[string]): boolean {
345    // Basic type checking
346    const actualType = Array.isArray(value) ? 'array' : typeof value;
347    if (actualType !== schema.type) {
348      return false;
349    }
350    
351    // Additional validation rules can be added here
352    if (schema.validation) {
353      // For now, just basic validation - could be extended with regex, range checks, etc.
354      if (schema.type === 'string' && schema.validation.includes('Must be')) {
355        // Could implement more sophisticated validation
356        return value.length > 0;
357      }
358    }
359    
360    return true;
361  }
362}

Metadata

Path
utaba/main/services/micro-block/BaseService.ts
Namespace
utaba/main/services/micro-block
Author
utaba
Category
services
Technology
typescript
Contract Version
1.0.0
MIME Type
application/typescript
Published
18-Jul-2025
Last Updated
18-Jul-2025