4.9 KiB
Library Standards and Patterns
This document defines the standardized patterns for all libraries in the @stock-bot ecosystem.
Export Patterns
Standard: Named Exports Only
All libraries should use named exports only. Default exports have been removed for consistency and better tree-shaking.
Example:
// ✅ Good - Named exports
export { createCache } from './cache';
export type { CacheOptions } from './types';
// ❌ Bad - Default export
export default createCache;
Initialization Patterns
Libraries follow different initialization patterns based on their purpose:
1. Singleton with Global State
Use for: Global services that should have only one instance (config, logger)
Example: Config library
let configInstance: ConfigManager | null = null;
export function initializeConfig(): AppConfig {
if (!configInstance) {
configInstance = new ConfigManager();
}
return configInstance.initialize();
}
export function getConfig(): AppConfig {
if (!configInstance) {
throw new Error('Config not initialized');
}
return configInstance.get();
}
2. Factory with Registry
Use for: Services that need instance reuse based on configuration (cache, logger instances)
Example: Cache library
const cacheInstances = new Map<string, CacheProvider>();
export function createCache(options: CacheOptions): CacheProvider {
if (options.shared) {
const key = generateKey(options);
if (cacheInstances.has(key)) {
return cacheInstances.get(key)!;
}
const cache = new RedisCache(options);
cacheInstances.set(key, cache);
return cache;
}
return new RedisCache(options);
}
3. Pure Factory Functions
Use for: Services that need creation logic beyond simple instantiation
Example: Event bus with configuration processing
export function createEventBus(config: EventBusConfig): EventBus {
// Process config, set defaults, etc.
const processedConfig = { ...defaultConfig, ...config };
return new EventBus(processedConfig);
}
Note: Simple instantiation doesn't need factories - use direct class instantiation or DI container.
4. Direct Class Exports
Use for: Simple utilities or services managed by DI container
Example: MongoDB library
export { MongoDBClient } from './client';
// No factory function - let DI container handle instantiation
5. Singleton Classes
Use for: Manager classes that coordinate multiple instances
Example: QueueManager
export class QueueManager {
private static instance: QueueManager | null = null;
static initialize(config: QueueConfig): QueueManager {
if (!QueueManager.instance) {
QueueManager.instance = new QueueManager(config);
}
return QueueManager.instance;
}
static getInstance(): QueueManager {
if (!QueueManager.instance) {
throw new Error('QueueManager not initialized');
}
return QueueManager.instance;
}
}
Pattern Selection Guide
Choose the initialization pattern based on these criteria:
| Pattern | When to Use | Examples |
|---|---|---|
| Singleton with Global State | - One instance per process - Stateful configuration - Process-wide settings |
config, logger setup |
| Factory with Registry | - Multiple instances with same config should share - Connection pooling - Resource optimization |
cache, logger instances |
| Pure Factory | - Complex initialization logic - Configuration processing needed - Defaults to apply |
event bus (if needed) |
| Direct Class Export | - DI container manages lifecycle - Simple initialization - No special setup needed |
database clients (MongoDB, PostgreSQL, QuestDB), utilities |
| Singleton Class | - Coordinates multiple resources - Central management point - Graceful shutdown needed |
QueueManager, ConnectionManager |
Additional Standards
Error Handling
- All libraries should throw descriptive errors
- Consider creating custom error classes for domain-specific errors
- Always include context in error messages
Configuration
- Accept configuration through constructor/factory parameters
- Validate configuration using Zod schemas
- Provide sensible defaults where appropriate
Testing
- All libraries must have unit tests
- Use consistent test file naming:
*.test.ts - Mock external dependencies
Documentation
- Every library must have a README.md
- Include usage examples
- Document all public APIs with JSDoc
TypeScript
- Export all public types
- Use strict TypeScript settings
- Avoid
anytypes
Dependencies
- Minimize external dependencies
- Use exact versions for critical dependencies
- Document peer dependencies clearly