# Plan: Scope-Local Registrations ## Overview Add the ability to register items directly on a Scope, enabling per-scope dependency injection patterns such as injecting a `RequestContext` in web request handling scenarios. ## Requirements Summary Based on user clarification: 1. **Child scope isolation**: Child scopes created via `create_child_scope()` should NOT see parent scope's local registrations 2. **Registration API**: - `register_scoped(typeof(RequestContext), request_context)` - register an existing instance - `register_transient(typeof(RequestContext), () => new RequestContext())` - register with factory delegate 3. **Resolution priority**: Scope-local registrations take precedence over container registrations for `resolve`, but `resolve_all` returns both ## Current Architecture ```mermaid classDiagram class Container { -Catalogue~Type,Registration~ registrations -Dictionary~Registration,Factory~ factories -Dictionary~Registration,Object~ singletons +register Type, FactoryDelegate +create_scope Scope } class Scope { -Container container -Dictionary~Registration,Object~ scoped_instances +resolve_type Type +resolve_all_type Type +create_child_scope Scope } class Registration { +Type implementation_type +Lifecycle lifecycle +as_type Type +with_lifecycle Lifecycle +build } class Factory { <> +create Scope Object } Container --> Scope : creates Scope --> Container : references Container --> Registration : stores Container --> Factory : stores Registration --> Lifecycle ``` ## Proposed Changes ### 1. Add Scope-Local Storage Add fields to [`Scope.vala`](src/Scope.vala) to store scope-local registrations and factories: ```vala // Scope-local registrations keyed by service type private Catalogue local_registrations; // Scope-local factories keyed by registration private Dictionary local_factories; // Counter for generating unique registration IDs private int local_registration_counter; ``` ### 2. Create Scope-Local Registration Class Create a lightweight registration approach for scope-local items. Since these are simpler than container registrations, we can use a minimal approach: ```vala private class LocalRegistration : Registration { public LocalRegistration(Type service_type, Scope scope) { // Simple registration that only serves one type } } ``` Alternatively, reuse the existing [`Registration`](src/Registration.vala) class but manage it separately within the Scope. ### 3. Add Registration Methods to Scope #### `register_instance` - Register an existing instance ```vala /** * Registers an existing instance in this scope. * * The instance will be returned when the specified service type is resolved. * This registration is only visible within this scope, not in child scopes. * * @param service_type The type to register the instance as * @param instance The instance to register */ public void register_instance(Type service_type, Object instance); ``` Implementation approach: - Create a simple Registration for the service type - Create a Factory that always returns the provided instance - Store in `local_registrations` and `local_factories` - Also store in `scoped_instances` for immediate availability #### `register_transient` - Register with factory delegate ```vala /** * Registers a transient factory in this scope. * * A new instance will be created each time the service type is resolved. * This registration is only visible within this scope, not in child scopes. * * @param service_type The type to register * @param factory_func The factory delegate to create instances */ public void register_transient(Type service_type, owned FactoryDelegate factory_func); ``` ### 4. Modify Resolution Logic #### Update `resolve_type` and `resolve_registration` Current flow: 1. Get registration from container 2. Resolve based on lifecycle New flow: 1. Check scope-local registrations first 2. If found, use scope-local factory 3. If not found, fall back to container registrations ```vala public Object resolve_type(Type service_type) throws Error { // Check scope-local first if (this.local_registrations.has(service_type)) { var registration = this.local_registrations.get_any(service_type); return this.resolve_local_registration(registration, service_type); } // Fall back to container var registration = this.container.get_registration(service_type); return this.resolve_registration(registration, service_type); } ``` #### Update `resolve_all_type` Merge results from both scope-local and container registrations: ```vala public Enumerable resolve_all_type(Type service_type) { var results = new List(); // Add scope-local instances first foreach (var registration in this.local_registrations.get_or_empty(service_type)) { results.append(resolve_local_registration(registration, service_type)); } // Add container instances foreach (var registration in this.container.get_registrations(service_type)) { results.append(resolve_registration(registration, service_type)); } return Iterate.over(results); } ``` ### 5. Child Scope Behavior No changes needed to `create_child_scope()` - it already creates a fresh Scope with just a reference to the container. The new scope-local registration storage will be empty for child scopes, satisfying the isolation requirement. ## Resolution Flow Diagram ```mermaid flowchart TD A[resolve_type called] --> B{Scope has local registration?} B -->|Yes| C[Use scope-local factory] C --> D{Lifecycle?} D -->|Instance| E[Return cached instance] D -->|Transient| F[Create new via factory] B -->|No| G{Container has registration?} G -->|Yes| H[Use container factory] H --> I{Lifecycle?} I -->|Singleton| J[Return container singleton] I -->|Scoped| K[Return/create scoped instance] I -->|Transient| L[Create new via factory] G -->|No| M[Throw NOT_REGISTERED] ``` ## Usage Example ```vala // In a web server handler void handle_request(Request request) { // Create a scope for this request var scope = container.create_scope(); // Register request-specific context var request_context = new RequestContext(request); scope.register_instance(typeof(RequestContext), request_context); // Register a transient factory for request-specific logger scope.register_transient(typeof(RequestLogger), (s) => { var ctx = s.resolve(); return new RequestLogger(ctx.request_id); }); // Resolve handlers - they will receive the request_context var handler = scope.resolve(); handler.handle(); } ``` ## Implementation Checklist - [ ] Add `local_registrations` Catalogue to Scope - [ ] Add `local_factories` Dictionary to Scope - [ ] Implement `register_instance(Type, Object)` on Scope - [ ] Implement `register_transient(Type, owned FactoryDelegate)` on Scope - [ ] Modify `resolve_type` to check scope-local first - [ ] Modify `resolve_all_type` to merge both sources - [ ] Add helper method `resolve_local_registration` if needed - [ ] Create example file demonstrating the feature - [ ] Add unit tests for scope-local registration behavior ## Files to Modify | File | Changes | |------|---------| | [`src/Scope.vala`](src/Scope.vala) | Add storage fields, registration methods, update resolution logic | | [`examples/ScopeRegistrationDemo.vala`](examples/ScopeRegistrationDemo.vala) | New example file demonstrating usage | ## Open Questions None - requirements have been clarified.