Code Style Guide
This guide covers coding conventions used in Nexgent. Following these guidelines ensures consistency across the codebase.
General Principles
- Readability - Code should be self-documenting
- Consistency - Follow existing patterns
- Simplicity - Prefer simple solutions
- Type Safety - Use TypeScript strictly
TypeScript
Strict Mode
TypeScript strict mode is enabled:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}Type Annotations
// Good: Explicit types for function signatures
function calculateStopLoss(price: number, percentage: number): number {
return price * (1 + percentage / 100);
}
// Good: Interface for objects
interface Agent {
id: string;
name: string;
tradingMode: 'live' | 'simulation';
}
// Avoid: any type
function processData(data: any) { } // ❌
function processData(data: unknown) { } // ✓Type vs Interface
// Use interface for object shapes
interface Agent {
id: string;
name: string;
}
// Use type for unions, primitives, tuples
type TradingMode = 'live' | 'simulation';
type Result<T> = { success: true; data: T } | { success: false; error: string };Naming Conventions
Variables and Functions
// camelCase for variables and functions
const agentBalance = 10.5;
function getAgentById(id: string) { }
// Descriptive names
const a = 10; // ❌ unclear
const solBalance = 10; // ✓ clearClasses and Interfaces
// PascalCase for classes and interfaces
class AgentService { }
interface TradingConfig { }
// Prefix interfaces with 'I' for repository contracts
interface IAgentRepository { }Constants
// SCREAMING_SNAKE_CASE for constants
const MAX_RETRY_ATTEMPTS = 3;
const DEFAULT_TIMEOUT_MS = 5000;
// Group related constants in objects
const REDIS_KEYS = {
AGENT_CONFIG: (id: string) => `agent:${id}:config`,
BALANCE: (wallet: string) => `balance:${wallet}`,
};Files and Directories
// kebab-case for files
agent-service.ts
trading-config.ts
price-update-manager.ts
// Feature-based directory names
domain/
agents/
agent-service.ts
agent.repository.ts
trading/
position-service.tsCode Organization
File Structure
// 1. Imports (external, then internal, then relative)
import express from 'express';
import { prisma } from '@/infrastructure/database/client.js';
import { calculateStopLoss } from './utils.js';
// 2. Types and interfaces
interface ServiceConfig {
timeout: number;
}
// 3. Constants
const DEFAULT_TIMEOUT = 5000;
// 4. Main class/function
export class MyService {
// ...
}
// 5. Helper functions (private)
function helperFunction() {
// ...
}Import Order
// 1. Node built-ins
import { randomUUID } from 'crypto';
// 2. External packages
import express from 'express';
import { z } from 'zod';
// 3. Internal packages
import { DEFAULT_TRADING_CONFIG } from 'nexgent-open-source-trading-engine/shared';
// 4. Absolute imports (path aliases)
import { prisma } from '@/infrastructure/database/client.js';
import { AgentService } from '@/domain/agents/agent-service.js';
// 5. Relative imports
import { helper } from './utils.js';Functions
Function Length
Keep functions short and focused:
// Good: Single responsibility
async function getAgent(id: string): Promise<Agent | null> {
return prisma.agent.findUnique({ where: { id } });
}
async function validateAgentOwnership(agentId: string, userId: string): Promise<boolean> {
const agent = await getAgent(agentId);
return agent?.userId === userId;
}
// Avoid: Functions that do too much
async function getAndValidateAndUpdateAgent() { } // ❌Parameter Count
Limit parameters; use objects for many options:
// Good: Object parameter for many options
interface CreateAgentOptions {
name: string;
userId: string;
tradingMode: TradingMode;
walletAddress?: string;
}
async function createAgent(options: CreateAgentOptions): Promise<Agent> {
// ...
}
// Avoid: Many positional parameters
async function createAgent(
name: string,
userId: string,
mode: string,
wallet: string,
config: object
) { } // ❌Return Early
// Good: Early returns reduce nesting
function validateAgent(agent: Agent | null): Agent {
if (!agent) {
throw new Error('Agent not found');
}
if (agent.status !== 'active') {
throw new Error('Agent not active');
}
return agent;
}
// Avoid: Deep nesting
function validateAgent(agent: Agent | null): Agent {
if (agent) {
if (agent.status === 'active') {
return agent;
} else {
throw new Error('Agent not active');
}
} else {
throw new Error('Agent not found');
}
} // ❌Error Handling
Custom Error Classes
// Domain-specific errors
export class AgentServiceError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly details?: Record<string, unknown>
) {
super(message);
this.name = 'AgentServiceError';
}
}
// Usage
throw new AgentServiceError(
'Agent not found',
'AGENT_NOT_FOUND',
{ agentId }
);Try-Catch Patterns
// Good: Specific error handling
try {
await executeSwap(params);
} catch (error) {
if (error instanceof SwapServiceError) {
// Handle swap-specific errors
logger.error({ error }, 'Swap failed');
throw error;
}
// Re-throw unexpected errors
throw new Error(`Unexpected error: ${error}`);
}Async/Await
Always Use Async/Await
// Good: async/await
async function fetchData(): Promise<Data> {
const response = await fetch(url);
const data = await response.json();
return data;
}
// Avoid: Promise chains (unless necessary)
function fetchData(): Promise<Data> {
return fetch(url)
.then(res => res.json())
.then(data => data);
} // ❌ harder to readParallel Execution
// Good: Run independent operations in parallel
const [agent, balance, positions] = await Promise.all([
getAgent(agentId),
getBalance(walletAddress),
getPositions(agentId),
]);
// Avoid: Sequential when parallel is possible
const agent = await getAgent(agentId);
const balance = await getBalance(walletAddress);
const positions = await getPositions(agentId); // ❌ slowerComments
When to Comment
// Good: Explain WHY, not WHAT
// Use fixed stepper mode because it's more predictable for most users
const stopLossMode = 'fixed';
// Good: Document complex algorithms
/**
* Calculate stop loss percentage using exponential decay.
*
* Formula: keepPercent = 100 - (baseDropPercent * e^(-k * change))
* This creates a curve that's loose at low gains and tighter at high gains.
*/
function calculateExponentialStopLoss(change: number): number {
// ...
}
// Avoid: Obvious comments
// Get the agent by ID
const agent = await getAgentById(id); // ❌ obviousJSDoc for Public APIs
/**
* Create a new trading agent
*
* @param options - Agent creation options
* @returns The created agent
* @throws AgentServiceError if validation fails
*
* @example
* const agent = await agentService.create({
* name: 'My Agent',
* userId: 'user-123',
* tradingMode: 'simulation',
* });
*/
async function create(options: CreateAgentOptions): Promise<Agent> {
// ...
}Frontend Specific
React Components
// Good: Functional components with TypeScript
interface AgentCardProps {
agent: Agent;
onSelect: (id: string) => void;
}
export function AgentCard({ agent, onSelect }: AgentCardProps) {
return (
<div onClick={() => onSelect(agent.id)}>
{agent.name}
</div>
);
}Hooks
// Custom hooks prefixed with 'use'
function useAgents() {
return useQuery({
queryKey: ['agents'],
queryFn: agentsService.list,
});
}
function useAgentSelection() {
const [selectedId, setSelectedId] = useState<string | null>(null);
// ...
}Styling
// Use Tailwind CSS classes
<div className="flex items-center gap-4 p-4 bg-card rounded-lg">
// Use cn() for conditional classes
<button className={cn(
"px-4 py-2 rounded",
isActive && "bg-primary text-white",
isDisabled && "opacity-50 cursor-not-allowed"
)}>