Skip to content

Development Guidelines

Code Standards & Best Practices

TypeScript Guidelines

Type Safety First

All code must be written with strict TypeScript configuration. No any types allowed except for well-documented edge cases.

// ✅ Good: Explicit typing
interface ServicePlanRequest {
  templateId: string;
  customerId: string;
  metadata?: Record<string, unknown>;
}

// ❌ Bad: Using 'any'
function processRequest(request: any): any {
  return request.data;
}

// ✅ Good: Generic types when appropriate
function processRequest<T extends ServicePlanRequest>(request: T): ProcessResult<T> {
  return {
    success: true,
    data: request,
    timestamp: new Date().toISOString()
  };
}

Interface Conventions

  • Use PascalCase for interfaces and types
  • Prefix interfaces with descriptive nouns
  • Use composition over inheritance
// Core entity interfaces
interface ServicePlan {
  id: string;
  templateId: string;
  status: ServicePlanStatus;
  createdAt: Date;
  updatedAt: Date;
}

// Request/Response interfaces
interface CreateServicePlanRequest {
  templateId: string;
  customerId: string;
  initialData?: ServicePlanInitialData;
}

interface CreateServicePlanResponse {
  servicePlan: ServicePlan;
  success: boolean;
  errors?: ValidationError[];
}

// Agent-specific interfaces
interface AgentExecutionContext {
  servicePlan: ServicePlan;
  request: AgentRequest;
  utilities: AgentUtilities;
  state: AgentState;
}

Naming Conventions

File Naming

kebab-case.ts          # Regular modules
PascalCase.ts          # Classes and interfaces
camelCase.test.ts      # Test files
index.ts               # Barrel exports

Variable and Function Naming

// Variables: camelCase
const servicePlanId = 'bss-plan-001';
const isPaymentCurrent = true;

// Functions: camelCase with verb prefixes
function calculateQuotaUsage(plan: ServicePlan): QuotaUsage { }
function validateServiceAccess(request: AccessRequest): ValidationResult { }
function createServicePlan(data: CreateServicePlanRequest): ServicePlan { }

// Constants: SCREAMING_SNAKE_CASE
const MAX_RETRY_ATTEMPTS = 3;
const DEFAULT_TIMEOUT_MS = 5000;
const FSM_STATE_TRANSITIONS = {
  INITIAL: ['DEPOSIT_DUE'],
  DEPOSIT_DUE: ['CURRENT', 'CANCELLED']
} as const;

Class and Interface Naming

// Classes: PascalCase with descriptive nouns
class ServicePlanService { }
class FSMEngine { }
class AgentRegistry { }

// Interfaces: PascalCase
interface ServicePlan { }
interface AgentFunction { }
interface FSMTransition { }

// Enums: PascalCase with plural nouns when appropriate
enum ServicePlanStatus {
  ACTIVE = 'ACTIVE',
  SUSPENDED = 'SUSPENDED',
  COMPLETED = 'COMPLETED',
  CANCELLED = 'CANCELLED'
}

Code Organization

Module Structure

// Standard module structure
export interface ModuleTypes {
  // Type definitions first
}

export class ModuleClass {
  // Public methods first, private methods last

  public async publicMethod(): Promise<Result> {
    return this.privateHelper();
  }

  private privateHelper(): Result {
    // Implementation
  }
}

export const moduleUtilities = {
  // Utility functions
};

// Default export last (if applicable)
export default ModuleClass;

Import Organization

// 1. Node.js built-in modules
import { promisify } from 'util';
import path from 'path';

// 2. Third-party libraries
import express from 'express';
import { GraphQLSchema } from 'graphql';

// 3. Internal modules (absolute imports)
import { ServicePlanService } from '../services/service-plan-service';
import { FSMEngine } from '../fsm/engine';

// 4. Relative imports
import { AgentFunction } from './types';
import { calculateQuota } from './utilities';

// 5. Type-only imports (separate from value imports)
import type { ServicePlan, AgentContext } from '../types';

Architecture Patterns

Agent Development Pattern

Functional Agent Design

All agents must follow the functional pattern with pure functions:

// Agent function signature
export type AgentFunction = (
  request: AgentRequest,
  servicePlan: ServicePlan,
  context: AgentContext
) => Promise<AgentResult>;

// Example agent implementation
export const bssServiceAgent: AgentFunction = async (
  request,
  servicePlan,
  context
) => {
  // 1. Validate inputs
  const validationResult = validateRequest(request, servicePlan);
  if (!validationResult.isValid) {
    return createErrorResult(validationResult.errors);
  }

  // 2. Process business logic
  const businessResult = await processBusinessLogic(request, servicePlan, context);

  // 3. Update state
  const updatedState = updateAgentState(context.state, businessResult);

  // 4. Generate outputs
  const outputs = generateOutputs(businessResult);

  return {
    success: true,
    updatedState,
    outputs,
    fsmSignals: businessResult.fsmSignals
  };
};

Agent State Management

// Agent state interface (confined to agent module)
interface BSSAgentState {
  quotaUsage: QuotaUsage;
  paymentStatus: PaymentStatus;
  assetAssignments: AssetAssignment[];
  lastActivity: Date;
}

// State manager for centralized operations
export class BSSAgentStateManager {
  static create(initialData: Partial<BSSAgentState>): BSSAgentState {
    return {
      quotaUsage: { used: 0, available: 0 },
      paymentStatus: 'PENDING',
      assetAssignments: [],
      lastActivity: new Date(),
      ...initialData
    };
  }

  static validate(state: unknown): state is BSSAgentState {
    // Validation logic
    return true;
  }

  static update(
    currentState: BSSAgentState, 
    updates: Partial<BSSAgentState>
  ): BSSAgentState {
    return { ...currentState, ...updates };
  }
}

FSM Development Pattern

FSM Definition Structure

{
  "name": "Service Lifecycle FSM",
  "description": "Manages service delivery lifecycle",
  "states": [
    "INITIAL",
    "WAIT_BATTERY_ISSUE", 
    "BATTERY_ISSUED",
    "BATTERY_RETURNED",
    "COMPLETE"
  ],
  "inputs": [
    "DEPOSIT_CONFIRMED",
    "BATTERY_ISSUED", 
    "BATTERY_RETURNED",
    "SERVICE_CANCELLED"
  ],
  "transitions": {
    "INITIAL": {
      "DEPOSIT_CONFIRMED": {
        "target_state": "WAIT_BATTERY_ISSUE",
        "outputs": ["SERVICE_READY"]
      }
    }
  }
}

FSM Processing Pattern

// FSM engine usage
export class ServiceFSMProcessor {
  constructor(private fsmEngine: FSMEngine) {}

  async processTransition(
    servicePlan: ServicePlan,
    signal: FSMSignal,
    context: ProcessingContext
  ): Promise<FSMResult> {
    // 1. Load current state
    const currentState = servicePlan.service_cycle_state;

    // 2. Process transition
    const result = await this.fsmEngine.processTransition(
      currentState,
      signal,
      context
    );

    // 3. Validate result
    if (!result.success) {
      throw new FSMTransitionError(result.error);
    }

    // 4. Return structured result
    return {
      newState: result.newState,
      outputs: result.outputs,
      sideEffects: result.sideEffects
    };
  }
}

Service Layer Pattern

Service Class Structure

export class ServicePlanService {
  constructor(
    private repository: ServicePlanRepository,
    private fsmEngine: FSMEngine,
    private agentRegistry: AgentRegistry
  ) {}

  // Public API methods
  async createServicePlan(request: CreateServicePlanRequest): Promise<ServicePlan> {
    // Implementation
  }

  async executeCommand(
    planId: string, 
    command: ServiceCommand
  ): Promise<CommandResult> {
    // 1. Load service plan
    const servicePlan = await this.repository.findById(planId);

    // 2. Execute agent
    const agentResult = await this.executeAgent(servicePlan, command);

    // 3. Process FSM transitions
    const fsmResults = await this.processFSMSignals(
      servicePlan, 
      agentResult.fsmSignals
    );

    // 4. Persist changes
    await this.persistChanges(servicePlan, agentResult, fsmResults);

    return {
      success: true,
      servicePlan: servicePlan,
      agentResult,
      fsmResults
    };
  }

  // Private helper methods
  private async executeAgent(
    servicePlan: ServicePlan, 
    command: ServiceCommand
  ): Promise<AgentResult> {
    // Implementation
  }
}

Error Handling

Error Types and Hierarchy

// Base error class
export abstract class ABSError extends Error {
  abstract readonly code: string;
  abstract readonly statusCode: number;

  constructor(message: string, public readonly context?: Record<string, unknown>) {
    super(message);
    this.name = this.constructor.name;
  }
}

// Specific error types
export class ValidationError extends ABSError {
  readonly code = 'VALIDATION_ERROR';
  readonly statusCode = 400;
}

export class FSMTransitionError extends ABSError {
  readonly code = 'FSM_TRANSITION_ERROR';
  readonly statusCode = 422;
}

export class AgentExecutionError extends ABSError {
  readonly code = 'AGENT_EXECUTION_ERROR';  
  readonly statusCode = 500;
}

Error Handling Pattern

// Service method with comprehensive error handling
export class ServicePlanService {
  async executeCommand(planId: string, command: ServiceCommand): Promise<CommandResult> {
    try {
      // Validate inputs
      const validationResult = this.validateCommand(command);
      if (!validationResult.isValid) {
        throw new ValidationError(
          'Invalid command parameters',
          { errors: validationResult.errors, command }
        );
      }

      // Execute business logic
      const result = await this.processCommand(planId, command);
      return result;

    } catch (error) {
      // Log error with context
      this.logger.error('Command execution failed', {
        planId,
        command: command.type,
        error: error.message,
        stack: error.stack
      });

      // Re-throw with additional context if needed
      if (error instanceof ABSError) {
        throw error;
      }

      // Wrap unexpected errors
      throw new AgentExecutionError(
        'Unexpected error during command execution',
        { planId, command, originalError: error.message }
      );
    }
  }
}

Testing Guidelines

Test Organization

// Test file structure
describe('ServicePlanService', () => {
  describe('createServicePlan', () => {
    it('should create service plan with valid template', async () => {
      // Arrange
      const template = createMockTemplate();
      const request = createMockRequest();

      // Act  
      const result = await service.createServicePlan(request);

      // Assert
      expect(result).toBeDefined();
      expect(result.status).toBe('ACTIVE');
    });

    it('should throw validation error for invalid template', async () => {
      // Arrange
      const invalidRequest = createInvalidRequest();

      // Act & Assert
      await expect(service.createServicePlan(invalidRequest))
        .rejects.toThrow(ValidationError);
    });
  });

  describe('executeCommand', () => {
    // Command-specific tests
  });
});

Mock Creation Patterns

// Mock factory functions
export const createMockServicePlan = (overrides: Partial<ServicePlan> = {}): ServicePlan => ({
  id: 'plan-001',
  templateId: 'template-001',
  status: 'ACTIVE',
  service_cycle_state: 'WAIT_BATTERY_ISSUE',
  payment_cycle_state: 'CURRENT',
  createdAt: new Date('2024-01-01'),
  updatedAt: new Date('2024-01-01'),
  ...overrides
});

// Mock service dependencies
export const createMockDependencies = () => ({
  repository: {
    findById: jest.fn(),
    save: jest.fn(),
    delete: jest.fn()
  } as jest.Mocked<ServicePlanRepository>,

  fsmEngine: {
    processTransition: jest.fn()
  } as jest.Mocked<FSMEngine>,

  agentRegistry: {
    getAgent: jest.fn(),
    executeAgent: jest.fn()
  } as jest.Mocked<AgentRegistry>
});

Documentation Standards

Code Documentation

/**
 * Executes a service command for a specific service plan.
 * 
 * This method coordinates agent execution and FSM state transitions
 * to process customer service requests. It ensures atomic operations
 * and consistent state management across the platform.
 * 
 * @param planId - Unique identifier for the service plan
 * @param command - Service command to execute
 * @returns Promise containing command execution result
 * 
 * @throws {ValidationError} When command parameters are invalid
 * @throws {FSMTransitionError} When state transition is not allowed
 * @throws {AgentExecutionError} When agent processing fails
 * 
 * @example
 * ```typescript
 * const result = await service.executeCommand('plan-001', {
 *   type: 'PROCESS_SERVICE_REQUEST',
 *   data: { customerId: 'customer-001', locationId: 'station-001' }
 * });
 * ```
 */
export async executeCommand(
  planId: string, 
  command: ServiceCommand
): Promise<CommandResult> {
  // Implementation
}

README Standards

Each module should include a comprehensive README with: - Purpose and overview - Installation and setup instructions
- API documentation with examples - Configuration options - Testing instructions - Contributing guidelines

Performance Guidelines

Database Query Optimization

// ✅ Good: Use indexes and specific selects
const servicePlans = await repository
  .createQueryBuilder('sp')
  .select(['sp.id', 'sp.status', 'sp.service_cycle_state'])
  .where('sp.customerId = :customerId', { customerId })
  .andWhere('sp.status IN (:...statuses)', { statuses: ['ACTIVE', 'SUSPENDED'] })
  .getMany();

// ❌ Bad: Select all columns and filter in memory
const allPlans = await repository.find();
const filteredPlans = allPlans.filter(plan => 
  plan.customerId === customerId && 
  ['ACTIVE', 'SUSPENDED'].includes(plan.status)
);

Caching Strategies

// Cache frequently accessed data
export class ServicePlanService {
  private templateCache = new Map<string, ServicePlanTemplate>();

  async getTemplate(templateId: string): Promise<ServicePlanTemplate> {
    // Check cache first
    if (this.templateCache.has(templateId)) {
      return this.templateCache.get(templateId)!;
    }

    // Load from database
    const template = await this.templateRepository.findById(templateId);

    // Cache for future use
    this.templateCache.set(templateId, template);

    return template;
  }
}

Security Guidelines

Input Validation

// Always validate and sanitize inputs
export const validateServicePlanRequest = (request: unknown): ValidationResult<CreateServicePlanRequest> => {
  const schema = z.object({
    templateId: z.string().uuid(),
    customerId: z.string().uuid(),
    metadata: z.record(z.unknown()).optional()
  });

  try {
    const validated = schema.parse(request);
    return { isValid: true, data: validated, errors: [] };
  } catch (error) {
    return { 
      isValid: false, 
      data: null, 
      errors: error.errors.map(e => e.message) 
    };
  }
};

Authentication Context

// Always check authentication in resolvers
export const createServicePlanResolver = async (
  parent: any,
  args: { input: CreateServicePlanInput },
  context: GraphQLContext
) => {
  // Verify authentication
  if (!context.user) {
    throw new AuthenticationError('Authentication required');
  }

  // Check authorization
  if (!context.user.permissions.includes('CREATE_SERVICE_PLAN')) {
    throw new AuthorizationError('Insufficient permissions');
  }

  // Process request
  return await context.services.servicePlanService.createServicePlan(args.input);
};

Following these guidelines ensures consistent, maintainable, and scalable code across the ABS Platform codebase.