Add RelationRegistry API for custom relations#1186
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (10)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughCustom relation factory contracts, runtime registration, decorator creation, model resolution, instance-based many-relation handling, public exports, and integration tests were added. Existing factory relation construction now applies explicit casts. ChangesCustom relation support
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Decorator as createRelationDecorator
participant Model as BaseModelImpl
participant Registry as RelationRegistry
participant Factory as RelationFactory
Decorator->>Model: register relation with $addRelation
Model->>Registry: look up relation type
Registry-->>Model: return registered factory
Model->>Factory: create relation
Factory-->>Model: return relation contract
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/factories/factory_model.ts (1)
171-191: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winFail explicitly for unsupported custom factory relations.
A registered custom relation reaches this switch but matches no case, so
relation()returns with no relation registered. Add a default error (or a custom factory adapter contract) rather than silently dropping the requested relation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/factories/factory_model.ts` around lines 171 - 191, Update the relation-type switch in the factory relation registration method to add a default branch that throws an explicit error for unsupported custom relation types. Ensure unmatched types do not return silently or leave the requested relation unregistered, while preserving the existing built-in relation handling and hasManyThrough error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/orm/base_model/index.ts`:
- Around line 76-85: Update relation definition/instance handling and
$setRelated() so each relation retains its many-versus-one multiplicity when
created, rather than deriving it from the mutable RelationRegistry at assignment
time. Keep getManyRelations() for discovering registered custom relation types,
but ensure RelationRegistry.unregister(type) cannot change the behavior of
existing relations or cause valid arrays to be rejected.
In `@src/orm/relations/main.ts`:
- Around line 14-16: Update the BaseSubQueryBuilder re-export in the relations
entry point to use the emitted .js module specifier, matching the existing
BaseQueryBuilder and KeysExtractor exports; leave the exported symbol alias
unchanged.
In `@src/orm/relations/relation_registry.ts`:
- Around line 32-44: Update RelationRegistry.register to reject reserved
built-in relation type names such as hasOne and hasMany before checking for
duplicates or storing the factory. Reuse the existing built-in relation-name
definition if available, and throw a clear error for reserved names while
preserving normal registration for custom types.
---
Outside diff comments:
In `@src/factories/factory_model.ts`:
- Around line 171-191: Update the relation-type switch in the factory relation
registration method to add a default branch that throws an explicit error for
unsupported custom relation types. Ensure unmatched types do not return silently
or leave the requested relation unregistered, while preserving the existing
built-in relation handling and hasManyThrough error.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7912a62d-5cce-463a-bd4a-7fd05aacfa6e
📒 Files selected for processing (9)
src/factories/factory_model.tssrc/orm/base_model/index.tssrc/orm/decorators/create_relation_decorator.tssrc/orm/main.tssrc/orm/relations/main.tssrc/orm/relations/relation_registry.tssrc/types/model.tssrc/types/relations.tstest/orm/relation_registry.spec.ts
Relations now retain their isMany property on the relation instance itself rather than deriving it from mutable RelationRegistry state at assignment time. Previously, if a custom relation was unregistered after model boot, $setRelated would incorrectly reject arrays because it looked up isMany from the registry. Now each relation instance stores its multiplicity, making it independent of registry state changes. Changes: - Add readonly isMany property to BaseRelationContract - Add isMany = true/false to all built-in relation classes - Replace getManyRelations().includes() checks with relation.isMany - Remove isMany from RelationFactoryConfig (no longer needed) - Remove RelationRegistry.getManyRelationTypes() (no longer needed) - Add test verifying relations retain multiplicity after unregister This ensures RelationRegistry.unregister() cannot change the behavior of existing relation instances or cause valid arrays to be rejected.
RelationRegistry.register() now rejects reserved built-in relation type names (hasOne, hasMany, belongsTo, manyToMany, hasManyThrough). Attempting to register these names is ineffective because BaseModel.$addRelation() resolves built-ins first, so the registry entry would never be used. This prevents confusion and registry pollution. Changes: - Add BUILT_IN_TYPES constant in RelationRegistry.register() - Throw clear error when attempting to register reserved names - Add test verifying all built-in types are rejected
|
I've addressed all issues: 1. Relation multiplicity stored on instances (not registry lookup)Problem: Relations derived their many-vs-one multiplicity from Solution: Each relation instance now has a Benefits:
Changes:
2. Reject built-in relation type namesProblem: Registering Solution: Changes:
|
❓ Type of change
📚 Description
Add RelationRegistry API for custom relations
Summary
This PR introduces a new
RelationRegistryAPI that allows third-party packages to register custom relation types in Lucid ORM. This unlocks powerful extensibility while maintaining full type safety and seamless integration with existing Lucid features (preload, whereHas, withCount, serialization, etc.).Motivation
Currently, Lucid supports five built-in relation types (hasOne, hasMany, belongsTo, manyToMany, hasManyThrough). However, users frequently request specialized relations like:
Without an extension API, these features would require:
This PR solves the problem by providing an official, supported API for custom relations.
Changes Overview
1. Core API:
RelationRegistryClassNew file:
src/orm/relations/relation_registry.tsA centralized registry for managing custom relation types.
Key methods:
register(type, factory)- Register a new relation typeget(type)- Retrieve a relation factoryhas(type)- Check if a relation type existsgetManyRelationTypes()- Get all "many" relation types (for preloader)unregister(type)- Remove a relation (for testing)clear()- Clear all custom relations (for testing)Design decisions:
Why this approach?
2. Helper:
createRelationDecoratorFunctionNew file:
src/orm/decorators/create_relation_decorator.tsA utility to create type-safe relation decorators.
Usage:
Type constraints:
TRelationTypeis constrained toModelRelationTypes['__opaque_type']Why a helper?
3. Type System Integration
Modified file:
src/types/relations.tsAdded two new interfaces for module augmentation:
A.
KnownCustomRelationsPurpose: Register relation contracts (implementation classes)
Used in:
RelationshipsContractunion typeContains: Relation class types with methods like
boot(),eagerQuery(),setRelated()Example augmentation:
B.
KnownCustomOpaqueRelationsPurpose: Register relation opaque types (user-facing types)
Used in:
ModelRelationsunion typeContains: Opaque array types representing the relation data
Example augmentation:
Why Two Interfaces?
They serve different roles in the type system:
KnownCustomRelations$getRelation()KnownCustomOpaqueRelations4. Factory Types
Modified file:
src/types/relations.tsAdded two new interfaces for relation factories:
A.
RelationFactoryConfigPurpose: Configuration passed to
RelationRegistry.register()B.
RelationFactoryPurpose: Complete factory stored in registry
Note: Includes
typepropertyWhy separate?
5. Extensible Union Types
Modified file:
src/types/relations.tsMade union types extensible by including custom relations:
Before (closed unions):
❌ Cannot add custom relations
After (open unions):
Why this matters:
anytypes needed6. BaseModel Integration
Modified file:
src/orm/base_model/index.tsUpdated
$addRelation()to support custom relations:Changes:
Refactored built-in relation handling
Check registry for custom relations
Replaced static MANY_RELATIONS with getManyRelations() to support custom relations
Why these changes?
7. Type System Compatibility
Modified files:
src/types/model.ts- Updated$relationsDefinitionssignaturesrc/types/relations.ts- AddedsetRelated,pushRelated,setRelatedForManytoBaseRelationContractsrc/factories/factory_model.ts- Added type assertions for backward compatibilityWhy needed?
8. Exports
Modified file:
src/orm/main.tsAdded new exports:
Modified file:
src/orm/relations/main.ts9. Tests
New file:
test/orm/relation_registry.spec.tsComprehensive test coverage:
Breaking Changes
None. This PR is 100% backward compatible.
Performance Considerations
Type Safety
This PR maintains full type safety:
✅ Autocomplete for custom relation names in
$addRelation()✅ Type-checked relation options
✅ Inferred return types for relation queries
✅ Autocomplete in preload callbacks
✅ Type-safe access to related models
How?
Testing Checklist
Summary of Files Changed
New Files (6)
src/orm/relations/relation_registry.ts- Core registry implementationsrc/orm/decorators/create_relation_decorator.ts- Decorator helpertest/orm/relation_registry.spec.ts- Registry testsModified Files (6)
src/orm/base_model/index.ts- Custom relation support in $addRelationsrc/orm/main.ts- Export new APIssrc/types/relations.ts- Type augmentation interfaces, extensible unionssrc/types/model.ts- Updated signaturessrc/factories/factory_model.ts- Type compatibilitysrc/orm/relations/main.ts- (if any changes)Other notes
Summary by CodeRabbit