Architectural Decision: Schema Registry Factory Pattern and TypeScript Declaration Merging
Resolved 💬 4 comments Opened Aug 25, 2025 by chris-schra Closed Aug 25, 2025
Context
Based on commit 52f8c3d, significant architectural decisions were made regarding the schema registry system and testing patterns. These decisions need to be documented for the team.
Key Architectural Changes
1. Removal of Singleton registerSchema Function
- Previous: Individual schemas called
registerSchema(schema)to register themselves - New: Schemas are included via factory configuration instead
- Impact: Eliminates singleton pattern, improves testability and configuration flexibility
2. Factory Pattern for Schema Registry
- Decision:
compileSchemaRegistryis now a proper factory function accepting configuration - Previous: Function returned hardcoded objects
- New: Function compiles registry based on input configuration
- Benefits: Enables custom schemas, tier-based features, and test-specific configurations
3. TypeScript Declaration Merging for Testing
- Decision: Tests must use TypeScript declaration merging when adding custom schemas
- Pattern: Tests declare schemas in the
SchemaRegistrymodule interface - Critical: This is required for TypeScript magic to work correctly
Architectural Decisions
Option A: Singleton Schema Registration (REJECTED)
// REJECTED approach
registerSchema(CustomerSchema);
registerSchema(SessionSchema);
const registry = compileSchemaRegistry(); // Gets all registered schemas
Option B: Factory Configuration (ACCEPTED)
// ACCEPTED approach
const registry = compileSchemaRegistry({
schemas: [CustomerSchema, SessionSchema]
});
Rationale: Factory pattern provides better control, testability, and eliminates global state.
Option C: Automatic Schema Discovery (DEFERRED)
// Future consideration - automatic import scanning
const registry = compileSchemaRegistry({
autoDiscoverSchemas: true,
scanPaths: ['./schemas/**/*.ts']
});
Testing Pattern Requirements
When implementing tests with custom schemas, developers MUST use TypeScript declaration merging:
// REQUIRED: Custom schema definition
const CustomSchema = {
kind: 'Custom',
primaryKey: { name: 'id', type: 'string' },
// ... schema definition
} as const satisfies BaseSchema;
// REQUIRED: Declaration merging for TypeScript magic
declare module '@edgora-hq/signals-entities/schemas/SchemaRegistry' {
interface SchemaRegistry {
Custom: typeof CustomSchema;
}
}
// REQUIRED: Include schema in factory configuration
const registry = compileSchemaRegistry({
schemas: [CustomSchema]
});
Missing Documentation
This pattern needs to be documented in:
- Testing guides: How to properly add custom schemas in tests
- Schema development docs: Factory pattern usage examples
- Architecture docs: Decision rationale and migration path
- TypeScript magic docs: Declaration merging requirements
Decisions to Track
- [ ] Factory vs Singleton: Use factory pattern for schema registry (Decided)
- [ ] Declaration Merging: Required for custom schemas in tests (Decided)
- [ ] Schema Configuration: Pass schemas via config object (Decided)
- [ ] Migration Strategy: How to migrate existing singleton usage (Needs Decision)
- [ ] Documentation Strategy: Where to document the testing pattern (Needs Decision)
Implementation Priority
- HIGH: Document declaration merging pattern in testing guides
- HIGH: Update schema development documentation with factory examples
- MEDIUM: Create migration guide for existing singleton usage
- LOW: Consider automatic schema discovery for future enhancement
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗