-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
Overview
This epic captures opportunities to implement advanced TypeScript patterns including decorators, dependency injection, and type system enhancements that would make ElizaOS a world-class TypeScript codebase.
Analysis performed by: 6 parallel senior TypeScript engineer agents analyzing the entire monorepo
Executive Summary
| Category | Pattern | Impact | Effort | Breaking Changes |
|---|---|---|---|---|
| Types | Branded UUIDs | 🟢 High | Low | Gradual migration |
| Core | Service Decorators | 🟢 High | High | Medium |
| Plugin | @Action/@Provider Decorators | 🟢 High | Medium | Low (opt-in) |
| Server | Shared API Contracts | 🟢 High | Medium | Low |
| CLI | @command Decorators | 🟡 Medium | Medium | Low |
| Database | @transactional Decorator | 🟡 Medium | Low | None |
| Core | DI Container | 🟡 Medium | High | High |
1. Branded UUID Types (Priority: Critical)
Problem: All UUIDs are interchangeable - agentId, roomId, entityId can be accidentally swapped at runtime.
Solution:
// Branded types prevent mixing at compile-time
declare const __brand: unique symbol;
type Brand<T, B> = T & { readonly [__brand]: B };
type AgentId = Brand<UUID, 'AgentId'>;
type RoomId = Brand<UUID, 'RoomId'>;
type EntityId = Brand<UUID, 'EntityId'>;
// Compile-time error:
function getAgent(agentId: AgentId): Agent;
getAgent(someRoomId); // ❌ Error: RoomId not assignable to AgentIdFiles: packages/core/src/types/primitives.ts
2. Component Decorators (@action, @Provider, @service)
Current - Manual plugin assembly:
export const bootstrapPlugin: Plugin = {
name: 'bootstrap',
actions: [action1, action2, action3, ...], // 15+ manual entries
providers: [provider1, provider2, ...],
services: [Service1, Service2],
};Proposed - Auto-discovered via decorators:
@Action({ name: 'REPLY', similes: ['GREET', 'RESPOND'] })
export class ReplyAction implements IAction {
@Inject() private runtime!: IAgentRuntime;
async validate(): Promise<boolean> { return true; }
async handler(message: Memory): Promise<ActionResult> {
// Runtime auto-injected, cleaner signatures
}
}
@Provider({ name: 'TIME', position: 1 })
export class TimeProvider implements IProvider {
async get(): Promise<ProviderResult> { /* ... */ }
}
@Plugin({ name: 'bootstrap' })
export class BootstrapPlugin {} // Components auto-registered via decorator metadataFiles: Create packages/core/src/decorators/
3. Shared API Contracts Package
Problem: Server and client have duplicated/divergent type definitions
Solution:
// packages/api-contracts/src/index.ts - Single source of truth
import { z } from 'zod';
export const CreateAgentSchema = z.object({
characterPath: z.string().optional(),
characterJson: z.record(z.unknown()).optional(),
});
export type CreateAgentRequest = z.infer<typeof CreateAgentSchema>;
export const AgentSchema = z.object({
id: z.string().uuid(),
name: z.string(),
status: z.enum(['active', 'inactive']),
});
export type Agent = z.infer<typeof AgentSchema>;
// Route contracts for type-safe client
export const API_ROUTES = {
agents: {
list: { method: 'GET', path: '/api/agents', response: z.array(AgentSchema) },
create: { method: 'POST', path: '/api/agents', body: CreateAgentSchema, response: AgentSchema },
},
} as const;Files: Create packages/api-contracts/
4. CLI Command Decorators
Current:
command('agent')
.option('-j, --json', 'output as JSON')
.option('-r, --remote-url <url>', 'URL of remote runtime')
.action(handler);Proposed:
@CommandModule({ name: 'agent', description: 'Manage ElizaOS agents' })
export class AgentCommand {
constructor(@Inject(AgentsService) private agentsService: AgentsService) {}
@SubCommand({ name: 'list', alias: 'ls' })
async list(
@Option({ flags: '-j, --json' }) json: boolean,
@Option({ flags: '-r, --remote-url <url>' }) remoteUrl: string,
@Option({ flags: '-p, --port <port>' }) @Validate(IsPort()) port: number,
): Promise<void> {
const agents = await this.agentsService.listAgents();
// ...
}
}Files: Create packages/cli/src/decorators/
5. Database Decorators
@transactional Decorator:
@Transactional({ isolation: 'serializable' })
async transferData(sourceId: AgentId, targetId: AgentId): Promise<void> {
// All operations automatically wrapped in transaction with rollback on error
}Repository Pattern:
@Repository({ entity: AgentEntity })
export class AgentRepository extends BaseRepository<Agent, AgentId> {
// Automatic CRUD: findById, findAll, create, update, delete
async findByName(name: string): Promise<Agent | null> {
// Custom queries
}
}Files: Create packages/plugin-sql/src/decorators/
6. Type-Safe Event System
Current: String event names with manual typing
Proposed: Template literal types
type PlatformEventType = `${PlatformPrefix}:${EventType}`;
// Generates: 'DISCORD:MESSAGE_RECEIVED' | 'TELEGRAM:MESSAGE_RECEIVED' | ...
runtime.registerEvent('DISCORD:INVALID', handler); // ❌ Compile error
runtime.registerEvent('DISCORD:MESSAGE_RECEIVED', handler); // ✅ Type-safeFiles: packages/core/src/types/events.ts
7. Dependency Injection Container (Future)
@Injectable()
export class AgentService {
constructor(
@Inject(DI_TOKENS.RUNTIME) private runtime: IAgentRuntime,
@Inject(DI_TOKENS.DATABASE) private db: IDatabaseAdapter,
) {}
}
// Container resolves dependencies automatically
const agentService = container.resolve(AgentService);Recommendation: Consider for v2.0 due to architectural impact
Implementation Roadmap
Phase 1: Quick Wins (1-2 weeks)
- Branded UUID types in
primitives.ts - Type predicates file (
types/guards.ts) - Eliminate
anytypes inelizaos.ts - Template literal event types
Phase 2: Decorator Foundation (2-4 weeks)
- Create decorator infrastructure in
packages/core/src/decorators/ - Implement @action, @Provider, @service decorators
- Add @transactional to database layer
- Create shared API contracts package
Phase 3: CLI & Server Enhancement (2-3 weeks)
- @CommandModule decorators for CLI
- Controller decorators for server
- Zod validation middleware
- OpenAPI documentation decorators
Phase 4: Advanced Patterns (4-6 weeks)
- Full DI container implementation
- Module system with imports/exports
- CQRS pattern for complex operations
Files to Create
| File | Purpose |
|---|---|
packages/core/src/types/guards.ts |
Type predicates and guards |
packages/core/src/decorators/index.ts |
Decorator exports |
packages/core/src/decorators/action.ts |
@action decorator |
packages/core/src/decorators/provider.ts |
@Provider decorator |
packages/core/src/decorators/service.ts |
@service decorator |
packages/core/src/decorators/inject.ts |
@Inject decorator |
packages/core/src/di/container.ts |
DI container |
packages/api-contracts/src/index.ts |
Shared API schemas |
packages/cli/src/decorators/command.ts |
@command decorator |
packages/plugin-sql/src/decorators/transactional.ts |
@transactional |
Files to Modify
| File | Changes |
|---|---|
packages/core/src/types/primitives.ts |
Add branded UUID types |
packages/core/src/types/events.ts |
Template literal types |
packages/core/src/types/database.ts |
Discriminated unions for logs |
packages/core/src/types/elizaos.ts |
Replace any with specific types |
packages/core/src/types/service.ts |
Enhanced ServiceClassMap |
packages/core/src/runtime.ts |
Integrate decorator registry |
packages/plugin-bootstrap/src/index.ts |
Optional module pattern |
packages/server/src/middleware/validation.ts |
Zod integration |
Benefits
| Benefit | Description |
|---|---|
| Type Safety | Compile-time prevention of ID mixing, event typos, service mismatches |
| Reduced Boilerplate | 40-60% less manual wiring code in plugins |
| Better DX | IDE autocomplete, inline documentation, better error messages |
| Testability | DI enables easy mocking and isolation |
| Maintainability | Self-documenting decorators, single source of truth |
| Scalability | Module boundaries, lazy loading, tree-shaking |
References
- TypeScript Decorators
- reflect-metadata
- TSyringe - Lightweight DI container
- NestJS Architecture - Decorator patterns
- Zod - Runtime validation with TypeScript inference
🤖 Generated with Claude Code