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
PascalCasefor 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.