Registry Pattern Refactoring
Overview
Extracted common registry and builder patterns from ToolRegistry and TypeMapperContext into reusable base classes in src/lib/common/registry/.
Motivation
Both ToolRegistry and TypeMapperContext implemented similar patterns:
- Registry pattern: Map-based storage with type-safe lookups
- Builder pattern: Fluent API for constructing immutable instances
- Dispatcher pattern: Main handler + specialized handlers by pattern
- Validation: Common validation interfaces and utilities
New Abstractions
Base Classes
Registry<K, V>
Location: src/lib/common/registry/base-registry.ts
Abstract base class for immutable registries:
- Readonly Map storage
- Type-safe get/getOrThrow methods
- Common query methods (keys, values, entries, has)
abstract class Registry<K, V> {
protected readonly items: ReadonlyMap<K, V>;
get(key: K): V | undefined
getOrThrow(key: K): V
has(key: K): boolean
keys(): K[]
values(): V[]
// ... more
}
BuildableRegistry<K, V>
Location: src/lib/common/registry/buildable-registry.ts
Base class for registries built with builder pattern:
- Extends
Registry<K, V> - Provides
BaseBuilderclass that can be extended - Mutable during construction, immutable after build()
abstract class BuildableRegistry<K, V> extends Registry<K, V> {
protected static BaseBuilder = class<K, V> {
protected items = new Map<K, V>();
register(key: K, value: V): this
has(key: K): boolean
protected getItems(): Map<K, V>
}
}
Dispatcher<T> & DispatcherBuilder<T>
Location: src/lib/common/registry/dispatcher.ts
Pattern for main handler + specialized handlers:
- Immutable Dispatcher with main + specialized Map
- Builder accumulates handlers then builds immutable instance
class Dispatcher<T> {
readonly main: T;
readonly specialized: ReadonlyMap<string, T>;
getSpecialized(pattern: string): T | undefined
}
class DispatcherBuilder<T> {
setMain(handler: T): this
register(pattern: string, handler: T): this
build(): Dispatcher<T>
}
Validation Utilities
Location: src/lib/common/registry/validation.ts
Common validation patterns:
ValidatableinterfaceValidationResulttypevalidateAll()helper functionvalidateSubset()helpercheckRequired()helper
interface Validatable {
validate(): Promise<ValidationResult> | ValidationResult
}
interface ValidationResult {
valid: boolean
errors?: string[]
warnings?: string[]
}
Refactored Components
ToolRegistry
File: src/lib/common/tool.ts
Before: Mutable registry with direct Map manipulation
class ToolRegistry {
private tools = new Map<QualifiedName, Tool>();
register(tool: Tool) { this.tools.set(...) }
}
export const toolRegistry = new ToolRegistry();
After: Immutable registry with builder pattern
class ToolRegistry extends BuildableRegistry<QualifiedName, Tool> {
static Builder = class { ... }
static builder(): Builder { ... }
private constructor(tools: Map<...>) { super(tools); }
}
export const toolRegistry = ToolRegistry.builder().build();
Benefits:
- ✅ Immutable after construction
- ✅ Builder pattern for clean initialization
- ✅ Inherits common query methods from base class
- ✅ Implements
Validatableinterface
TypeMapperContext
File: src/lib/codegen/builders/ast/mappings/context.ts
Before: Custom dispatcher implementation
class TypeMapperContext {
private readonly registries: {
zod: { main: T, specialized: Map<string, T> }
typeNode: { main: T, specialized: Map<string, T> }
}
}
After: Uses Dispatcher pattern
type ConverterDispatchers = {
zod: Dispatcher<ZodASTConverter>
typeNode: Dispatcher<TypeNodeConverter>
}
class TypeMapperContext {
private readonly dispatchers: ConverterDispatchers;
static Builder = class {
private builders = {
zod: new DispatcherBuilder<ZodASTConverter>(),
typeNode: new DispatcherBuilder<TypeNodeConverter>(),
}
}
}
Benefits:
- ✅ Reuses Dispatcher abstraction
- ✅ Cleaner builder implementation
- ✅ Explicit dispatcher semantics
- ✅ Reduced code duplication
File Structure
src/lib/common/
├── registry/
│ ├── index.ts # Re-exports all abstractions
│ ├── base-registry.ts # Registry<K, V> base class
│ ├── buildable-registry.ts # BuildableRegistry<K, V>
│ ├── dispatcher.ts # Dispatcher<T> + DispatcherBuilder<T>
│ └── validation.ts # Validatable, ValidationResult, helpers
└── tool.ts # ToolRegistry uses BuildableRegistry
Usage Examples
Creating a New Registry
import { BuildableRegistry } from '@/lib/common/registry';
class MyRegistry extends BuildableRegistry<string, MyItem> {
static Builder = class {
private items = new Map<string, MyItem>();
register(item: MyItem): this {
this.items.set(item.id, item);
return this;
}
build(): MyRegistry {
return new MyRegistry(this.items);
}
};
static builder() {
return new MyRegistry.Builder();
}
private constructor(items: Map<string, MyItem>) {
super(items);
}
}
// Usage
const registry = MyRegistry.builder()
.register(item1)
.register(item2)
.build();
Using Dispatcher Pattern
import { DispatcherBuilder } from '@/lib/common/registry';
type Handler = (input: string) => string;
const dispatcher = new DispatcherBuilder<Handler>()
.setMain((input) => `main: ${input}`)
.register('special', (input) => `special: ${input}`)
.build();
dispatcher.main('test'); // "main: test"
dispatcher.getSpecialized('special')?.('x'); // "special: x"
Breaking Changes
ToolRegistry
Old API (mutable):
toolRegistry.register(tool); // Direct mutation
New API (immutable):
// At initialization
const registry = ToolRegistry.builder()
.register(tool1)
.register(tool2)
.build();
// Usage (unchanged)
const tool = registry.get(name);
TypeMapperContext
No breaking changes - internal refactoring only. Public API remains the same.
Benefits
- DRY - Eliminates duplicate registry/builder logic
- Type Safety - Generic base classes ensure consistency
- Immutability - Enforced through readonly Maps and builder pattern
- Testability - Base classes can be unit tested once
- Reusability - Other parts of codebase can adopt these patterns
- Documentation - Patterns are now explicitly documented
Future Enhancements
- Add more registry types (e.g.,
CacheRegistry,ServiceRegistry) - Add registry composition utilities
- Add serialization/deserialization support
- Add registry merging capabilities
- Extract more patterns as needed
Related Files
src/lib/common/tool.ts- Tool and ToolRegistrysrc/lib/codegen/builders/ast/mappings/context.ts- TypeMapperContextsrc/lib/common/registry/- All registry abstractions