فهرست منبع

Initial commit

Billy Barrow 3 هفته پیش
کامیت
9fcf25eaba

+ 123 - 0
README.md

@@ -0,0 +1,123 @@
+# Inversion
+
+A dependency injection (IoC) container for Vala.
+
+## Features
+
+- **3 Lifecycles**: Transient (new instance each time), Scoped (one per scope), Singleton (one per container)
+- **Field injection**: Dependencies resolved via `inject<T>()` in field initializers
+- **Multi-registration**: Register multiple implementations for the same service type
+- **Scope-local registrations**: Add registrations that only exist within a scope
+- **Fluent API**: Chain methods for readable configuration
+
+## Dependencies
+
+- GLib 2.0
+- GObject 2.0
+- Invercargill-1
+
+## Building
+
+```bash
+meson setup builddir
+meson compile -C builddir
+```
+
+## Quick Start
+
+```vala
+using Inversion;
+
+// Define your service
+public interface Logger : Object {
+    public abstract void log(string message);
+}
+
+public class ConsoleLogger : Object, Logger {
+    public void log(string message) {
+        stdout.printf("[LOG] %s\n", message);
+    }
+}
+
+// Register and resolve
+var container = new Container();
+container.register_singleton<ConsoleLogger>((scope) => new ConsoleLogger())
+    .as_type(typeof(Logger));
+
+var scope = container.create_scope();
+var logger = scope.resolve<Logger>();
+logger.log("Hello!");
+```
+
+## Lifecycles
+
+| Lifecycle | Behavior |
+|-----------|----------|
+| `TRANSIENT` | New instance created every resolve |
+| `SCOPED` | One instance per scope |
+| `SINGLETON` | One instance per container |
+
+```vala
+container.register_transient<MyService>((s) => new MyService());
+container.register_scoped<MyService>((s) => new MyService());
+container.register_singleton<MyService>((s) => new MyService());
+```
+
+## Multiple Implementations
+
+```vala
+container.register_singleton<FileLogger>((s) => new FileLogger())
+    .as_type(typeof(Logger));
+container.register_singleton<ConsoleLogger>((s) => new ConsoleLogger())
+    .as_type(typeof(Logger));
+
+// Get all implementations
+var allLoggers = scope.resolve_all<Logger>();
+```
+
+## Field Injection
+
+Dependencies are injected using the `inject<T>()` function in field initializers:
+
+```vala
+public class UserService : Object {
+    private Logger logger = inject<Logger>();
+    
+    public void greet(string name) {
+        this.logger.log(@"Hello, $name!");
+    }
+}
+
+// Dependencies are resolved automatically when UserService is created
+var userService = scope.resolve<UserService>();
+```
+
+Use `inject_all<T>()` to inject all implementations of a service:
+
+```vala
+public class MultiLogger : Object {
+    private Lot<Logger> loggers = inject_all<Logger>();
+}
+```
+
+## Scopes
+
+Scopes manage scoped instance lifetime and can have their own local registrations:
+
+```vala
+var scope = container.create_scope();
+
+// Add a registration only available in this scope
+scope.register_singleton<RequestContext>((s) => new RequestContext())
+    .as_type(typeof(Context));
+```
+
+## Examples
+
+See the `examples/` directory for more detailed usage:
+
+- `BasicUsage.vala` - Registration and resolution basics
+- `LifecycleDemo.vala` - Transient vs Scoped vs Singleton
+- `InjectionDemo.vala` - Field injection with `inject<T>()`
+- `MultiRegistration.vala` - Multiple implementations per service
+- `ScopeRegistrationDemo.vala` - Scope-local registrations

+ 74 - 0
examples/BasicUsage.vala

@@ -0,0 +1,74 @@
+using Inversion;
+
+/**
+ * Basic Usage Example
+ * 
+ * This example demonstrates the fundamental features of the Inversion IoC container:
+ * - Registering types with different lifecycles
+ * - Resolving dependencies
+ * - Using the type-safe resolve methods
+ */
+public class BasicUsageExample : Object {
+
+    // Define a simple service interface
+    public interface ILogger : Object {
+        public abstract void log(string message);
+    }
+
+    // Console logger implementation
+    public class ConsoleLogger : Object, ILogger {
+        public void log(string message) {
+            stdout.printf("[LOG] %s\n", message);
+        }
+    }
+
+    // A service that depends on ILogger
+    public class UserService : Object {
+        private ILogger logger;
+
+        public UserService(ILogger logger) {
+            this.logger = logger;
+        }
+
+        public void greet(string name) {
+            this.logger.log(@"Hello, $name!");
+        }
+    }
+
+    public static int main(string[] args) {
+        stdout.printf("=== Inversion IoC Basic Usage Example ===\n\n");
+
+        // Create the container
+        var container = new Container();
+
+        // Register the logger as a singleton with a factory delegate
+        container.register_singleton<ConsoleLogger>((scope) => new ConsoleLogger())
+            .as_type(typeof(ILogger));
+
+        stdout.printf("Registered ConsoleLogger as ILogger (Singleton)\n\n");
+
+        // Create a scope
+        var scope = container.create_scope();
+
+        // Resolve the logger using type-safe method
+        try {
+            var logger = scope.resolve<ILogger>();
+            logger.log("Logger resolved successfully!");
+
+            // Resolve again to demonstrate singleton behavior
+            var logger2 = scope.resolve<ILogger>();
+            logger2.log("Second resolution - same instance!");
+
+            // Verify they are the same instance
+            if (logger == logger2) {
+                stdout.printf("\n✓ Singleton working: Both references point to the same instance\n");
+            }
+        } catch (Error e) {
+            stderr.printf("Error: %s\n", e.message);
+            return 1;
+        }
+
+        stdout.printf("\n=== Example Complete ===\n");
+        return 0;
+    }
+}

+ 132 - 0
examples/InjectionDemo.vala

@@ -0,0 +1,132 @@
+using Inversion;
+
+/**
+ * Injection Demo Example
+ * 
+ * This example demonstrates the inject<T>() method for property/field injection.
+ * The inject method allows dependencies to be resolved during object construction
+ * without requiring explicit constructor parameters.
+ * 
+ * Key concepts:
+ * - Using inject<T>() for field initialization
+ * - The injection context is automatically set up during container resolution
+ * - Multiple dependencies can be injected in the same class
+ */
+public class InjectionDemoExample : Object {
+
+    // Define service interfaces
+    public interface IHandler : Object {
+        public abstract void handle(string request);
+    }
+
+    public interface IRepository : Object {
+        public abstract string get_data();
+    }
+
+    // Console handler implementation
+    public class ConsoleHandler : Object, IHandler {
+        public void handle(string request) {
+            stdout.printf("[HANDLER] Processing: %s\n", request);
+        }
+    }
+
+    // Memory repository implementation
+    public class MemoryRepository : Object, IRepository {
+        public string get_data() {
+            return "Sample data from repository";
+        }
+    }
+
+    // A service that uses inject<T>() for dependency injection
+    // This class demonstrates field injection using the inject method
+    public class Controller : Object {
+        // Dependencies are injected during object construction
+        private IHandler handler = inject<IHandler>();
+        private IRepository repository = inject<IRepository>();
+
+        public void process_request(string request) {
+            var data = this.repository.get_data();
+            stdout.printf("[CONTROLLER] Retrieved data: %s\n", data);
+            this.handler.handle(request);
+        }
+    }
+
+    // Another service demonstrating nested injection
+    public class ApiService : Object {
+        private Controller controller = inject<Controller>();
+
+        public void execute(string request) {
+            stdout.printf("[API] Executing request: %s\n", request);
+            this.controller.process_request(request);
+        }
+    }
+
+    public static int main(string[] args) {
+        stdout.printf("=== Inversion IoC Injection Demo ===\n\n");
+
+        // Create the container
+        var container = new Container();
+
+        // Register all services with factory delegates
+        container.register_singleton<ConsoleHandler>((scope) => new ConsoleHandler())
+            .as_type(typeof(IHandler));
+
+        container.register_singleton<MemoryRepository>((scope) => new MemoryRepository())
+            .as_type(typeof(IRepository));
+
+        // Register Controller - it will use inject<T>() to resolve its dependencies
+        container.register_scoped<Controller>((scope) => new Controller());
+
+        // Register ApiService - demonstrates nested injection
+        container.register_transient<ApiService>((scope) => new ApiService());
+
+        stdout.printf("Registered services:\n");
+        stdout.printf("  - ConsoleHandler as IHandler (Singleton)\n");
+        stdout.printf("  - MemoryRepository as IRepository (Singleton)\n");
+        stdout.printf("  - Controller (Scoped) - uses inject<T>()\n");
+        stdout.printf("  - ApiService (Transient) - uses inject<T>()\n\n");
+
+        // Create a scope
+        var scope = container.create_scope();
+
+        try {
+            stdout.printf("--- Resolving ApiService (demonstrates nested injection) ---\n\n");
+            
+            // Resolve ApiService - this will trigger:
+            // 1. ApiService construction -> inject<Controller>()
+            // 2. Controller construction -> inject<IHandler>() and inject<IRepository>()
+            var api_service = scope.resolve<ApiService>();
+            api_service.execute("Test request #1");
+
+            stdout.printf("\n--- Resolving Controller directly ---\n\n");
+            
+            // Resolve Controller directly
+            var controller = scope.resolve<Controller>();
+            controller.process_request("Test request #2");
+
+            // Verify scoped behavior - same controller instance within scope
+            var controller2 = scope.resolve<Controller>();
+            if (controller == controller2) {
+                stdout.printf("\n✓ Scoped working: Same Controller instance within scope\n");
+            }
+
+            stdout.printf("\n--- Creating another scope ---\n\n");
+            
+            // Create another scope - should get a new Controller instance
+            var scope2 = container.create_scope();
+            var controller_in_scope2 = scope2.resolve<Controller>();
+            controller_in_scope2.process_request("Test request #3");
+
+            if (controller != controller_in_scope2) {
+                stdout.printf("\n✓ Scoped working: Different Controller instance in different scope\n");
+            }
+
+        } catch (Error e) {
+            stderr.printf("Error: %s\n", e.message);
+            return 1;
+        }
+
+        stdout.printf("\n=== Example Complete ===\n");
+        return 0;
+    }
+}

+ 91 - 0
examples/LifecycleDemo.vala

@@ -0,0 +1,91 @@
+using Inversion;
+using Invercargill;
+
+/**
+ * Lifecycle Demo Example
+ * 
+ * This example demonstrates the three different lifecycles in Inversion:
+ * - TRANSIENT: New instance each time
+ * - SCOPED: One instance per scope
+ * - SINGLETON: One instance for the entire container
+ */
+public class LifecycleDemo : Object {
+
+    // A counter service to track instance creation
+    public class Counter : Object {
+        private static int total_created = 0;
+        private int instance_id;
+
+        public Counter() {
+            total_created++;
+            this.instance_id = total_created;
+            stdout.printf("  Counter #%d created\n", this.instance_id);
+        }
+
+        public int id {
+            get { return this.instance_id; }
+        }
+    }
+
+    public static int main(string[] args) {
+        stdout.printf("=== Inversion IoC Lifecycle Demo ===\n\n");
+
+        var container = new Container();
+
+        // Register three counters with different lifecycles
+        container.register_transient<Counter>((scope) => new Counter());
+        stdout.printf("Registered Counter as TRANSIENT\n");
+
+        container.register_scoped<Counter>((scope) => new Counter());
+        stdout.printf("Registered Counter as SCOPED\n");
+
+        container.register_singleton<Counter>((scope) => new Counter());
+        stdout.printf("Registered Counter as SINGLETON\n\n");
+
+        // Note: In a real scenario, you'd register different types.
+        // For this demo, we'll show the lifecycle concepts.
+
+        // Demonstrate SINGLETON behavior
+        stdout.printf("--- SINGLETON Demo ---\n");
+        var scope1 = container.create_scope();
+        var scope2 = container.create_scope();
+
+        // Create first scope
+        stdout.printf("Creating scope 1:\n");
+
+        // Demonstrate TRANSIENT behavior
+        stdout.printf("\n--- TRANSIENT Demo ---\n");
+        stdout.printf("Resolving transient three times:\n");
+        
+        // Since all registrations are for Counter, resolve_all gets all of them
+        // Let's demonstrate by resolving multiple times
+        try {
+            var all_counters = scope1.resolve_all<Counter>();
+            stdout.printf("\nResolved all counters (3 different registrations):\n");
+            all_counters.iterate((obj) => {
+                var counter = (Counter) obj;
+                stdout.printf("  Got Counter #%d\n", counter.id);
+            });
+        } catch (Error e) {
+            stderr.printf("Error: %s\n", e.message);
+        }
+
+        stdout.printf("\n--- SCOPED Demo ---\n");
+        stdout.printf("Creating another scope and resolving:\n");
+        var scope3 = container.create_scope();
+        
+        try {
+            var counters = scope3.resolve_all<Counter>();
+            stdout.printf("Resolved in another scope:\n");
+            counters.iterate((obj) => {
+                var counter = (Counter) obj;
+                stdout.printf("  Got Counter #%d\n", counter.id);
+            });
+        } catch (Error e) {
+            stderr.printf("Error: %s\n", e.message);
+        }
+
+        stdout.printf("\n=== Lifecycle Demo Complete ===\n");
+        return 0;
+    }
+}

+ 103 - 0
examples/MultiRegistration.vala

@@ -0,0 +1,103 @@
+using Inversion;
+using Invercargill;
+
+/**
+ * Multi-Registration Example
+ * 
+ * This example demonstrates:
+ * - Registering multiple implementations for the same interface
+ * - Using resolve_all<T>() to get all implementations
+ * - Using custom factory delegates
+ */
+public class MultiRegistrationExample : Object {
+
+    // Define a handler interface
+    public interface IHandler : Object {
+        public abstract string name { get; }
+        public abstract void handle(string request);
+    }
+
+    // First handler implementation
+    public class AuthHandler : Object, IHandler {
+        public string name { get { return "Auth"; } }
+
+        public void handle(string request) {
+            stdout.printf("  [AuthHandler] Processing: %s\n", request);
+        }
+    }
+
+    // Second handler implementation
+    public class LoggingHandler : Object, IHandler {
+        public string name { get { return "Logging"; } }
+
+        public void handle(string request) {
+            stdout.printf("  [LoggingHandler] Processing: %s\n", request);
+        }
+    }
+
+    // Third handler implementation with custom configuration
+    public class CacheHandler : Object, IHandler {
+        private int ttl_seconds;
+
+        public CacheHandler(int ttl_seconds) {
+            this.ttl_seconds = ttl_seconds;
+        }
+
+        public string name { get { return "Cache"; } }
+
+        public void handle(string request) {
+            stdout.printf("  [CacheHandler] Processing: %s (TTL: %ds)\n", request, this.ttl_seconds);
+        }
+    }
+
+    public static int main(string[] args) {
+        stdout.printf("=== Inversion IoC Multi-Registration Example ===\n\n");
+
+        var container = new Container();
+
+        // Register multiple handlers for the same interface with factory delegates
+        container.register_singleton<AuthHandler>((scope) => new AuthHandler())
+            .as_type(typeof(IHandler));
+        stdout.printf("Registered AuthHandler as IHandler (Singleton)\n");
+
+        container.register_singleton<LoggingHandler>((scope) => new LoggingHandler())
+            .as_type(typeof(IHandler));
+        stdout.printf("Registered LoggingHandler as IHandler (Singleton)\n");
+
+        // Register with a custom factory delegate that requires parameters
+        container.register_singleton<CacheHandler>((scope) => {
+            return new CacheHandler(300); // 5 minute TTL
+        })
+            .as_type(typeof(IHandler));
+        stdout.printf("Registered CacheHandler as IHandler (with custom factory)\n\n");
+
+        // Create a scope
+        var scope = container.create_scope();
+
+        // Resolve a single handler (gets any one)
+        stdout.printf("--- Resolving Single Handler ---\n");
+        try {
+            var handler = scope.resolve<IHandler>();
+            stdout.printf("Got single handler: %s\n", handler.name);
+        } catch (Error e) {
+            stderr.printf("Error: %s\n", e.message);
+        }
+
+        // Resolve all handlers
+        stdout.printf("\n--- Resolving All Handlers ---\n");
+        var handlers = scope.resolve_all<IHandler>();
+        
+        stdout.printf("Processing request through all handlers:\n");
+        handlers.iterate((obj) => {
+            var handler = (IHandler) obj;
+            handler.handle("GET /api/users");
+        });
+
+        // Show count
+        var count = handlers.count();
+        stdout.printf("\nTotal handlers resolved: %u\n", count);
+
+        stdout.printf("\n=== Multi-Registration Example Complete ===\n");
+        return 0;
+    }
+}

+ 178 - 0
examples/ScopeRegistrationDemo.vala

@@ -0,0 +1,178 @@
+using Inversion;
+
+/**
+ * Example demonstrating scope-local registrations.
+ * 
+ * This shows how to register items directly on a scope,
+ * which is useful for per-request context in web applications.
+ */
+public class RequestContext : Object, IRequestContext {
+    public string request_id { get; set; }
+    public string path { get; set; }
+    
+    public RequestContext(string request_id, string path) {
+        this.request_id = request_id;
+        this.path = path;
+    }
+}
+
+public interface IRequestContext : Object {
+    public abstract string request_id { get; set; }
+    public abstract string path { get; set; }
+}
+
+public class RequestLogger : Object {
+    private RequestContext _context;
+    
+    public RequestLogger(RequestContext context) {
+        this._context = context;
+    }
+    
+    public void log(string message) {
+        stdout.printf("[%s] %s\n", this._context.request_id, message);
+    }
+}
+
+public class RequestHandler : Object {
+    private RequestContext _context;
+    private RequestLogger _logger;
+    
+    public RequestHandler(RequestContext context, RequestLogger logger) {
+        this._context = context;
+        this._logger = logger;
+    }
+    
+    public void handle() {
+        this._logger.log(@"Handling request for path: $(this._context.path)");
+        this._logger.log("Request completed successfully");
+    }
+}
+
+public class ApplicationService : Object {
+    public void do_something() {
+        stdout.printf("ApplicationService doing something...\n");
+    }
+}
+
+void main() {
+    // Create the main container with application-wide services
+    var container = new Container();
+    
+    // Register application-wide singleton service
+    container.register_singleton<ApplicationService>((scope) => {
+        return new ApplicationService();
+    });
+    
+    // Register handler as transient - it will receive scope-local dependencies
+    container.register_transient<RequestHandler>((scope) => {
+        var context = scope.resolve<RequestContext>();
+        var logger = scope.resolve<RequestLogger>();
+        return new RequestHandler(context, logger);
+    });
+    
+    // Register logger as transient - it will receive scope-local context
+    container.register_transient<RequestLogger>((scope) => {
+        var context = scope.resolve<RequestContext>();
+        return new RequestLogger(context);
+    });
+    
+    stdout.printf("=== Simulating Web Request 1 ===\n\n");
+    
+    // Create a scope for the first request
+    var scope1 = container.create_scope();
+    
+    // Register request-specific context in the scope using register_local_scoped
+    var context1 = new RequestContext("req-001", "/api/users");
+    scope1.register_local_scoped<RequestContext>((s) => context1);
+    
+    // Resolve and use the handler - it gets the request-specific context
+    var handler1 = scope1.resolve<RequestHandler>();
+    handler1.handle();
+    
+    // Also resolve the application service - comes from container
+    var app_service1 = scope1.resolve<ApplicationService>();
+    app_service1.do_something();
+    
+    stdout.printf("\n=== Simulating Web Request 2 ===\n\n");
+    
+    // Create a separate scope for the second request
+    var scope2 = container.create_scope();
+    
+    // Register a different context for this request
+    var context2 = new RequestContext("req-002", "/api/products");
+    scope2.register_local_scoped<RequestContext>((s) => context2);
+    
+    // This handler gets the second request's context
+    var handler2 = scope2.resolve<RequestHandler>();
+    handler2.handle();
+    
+    stdout.printf("\n=== Demonstrating Fluent API with as_type ===\n\n");
+    
+    // Create a scope with an instance registered against an interface
+    var scope3 = container.create_scope();
+    var context3 = new RequestContext("req-003", "/api/fluent");
+    
+    // Use fluent API to register against multiple types
+    scope3.register_local_scoped<RequestContext>((s) => context3)
+        .as_type(typeof(IRequestContext));  // Also register as interface
+    
+    // Can now resolve by either the concrete type or interface
+    var by_concrete = scope3.resolve<RequestContext>();
+    var by_interface = scope3.resolve<IRequestContext>();
+    stdout.printf("Resolved by concrete type: %s\n", by_concrete.request_id);
+    stdout.printf("Resolved by interface: %s\n", by_interface.request_id);
+    // Verify they're the same underlying object
+    stdout.printf("Same instance: %s\n", ((Object)by_concrete) == ((Object)by_interface) ? "yes" : "no");
+    
+    stdout.printf("\n=== Demonstrating Scope Isolation ===\n\n");
+    
+    // Create a new scope - it does NOT inherit the other scope's local registrations
+    var isolated_scope = container.create_scope();
+    
+    // This would throw because RequestContext is not registered in isolated scope
+    try {
+        var handler_isolated = isolated_scope.resolve<RequestHandler>();
+    } catch (Error e) {
+        stdout.printf("Expected error in isolated scope: %s\n", e.message);
+    }
+    
+    stdout.printf("\n=== Demonstrating resolve_all with Both Sources ===\n\n");
+    
+    // Register an additional RequestContext in scope2 using transient
+    scope2.register_local_transient<RequestContext>((s) => {
+        return new RequestContext("req-002-secondary", "/api/products/secondary");
+    });
+    
+    // resolve_all returns both scope-local and container registrations
+    var all_contexts = scope2.resolve_all<RequestContext>();
+    stdout.printf("All RequestContext instances:\n");
+    foreach (var obj in all_contexts.to_immutable_buffer()) {
+        if (obj != null) {
+            var ctx = (RequestContext) obj;
+            stdout.printf("  - %s: %s\n", ctx.request_id, ctx.path);
+        }
+    }
+    
+    stdout.printf("\n=== Scope-Local Scoped Factory Demo ===\n\n");
+    
+    var scope4 = container.create_scope();
+    
+    // Register a scoped factory - same instance returned within the scope
+    scope4.register_local_scoped<RequestContext>((s) => {
+        stdout.printf("Creating scoped RequestContext...\n");
+        return new RequestContext("req-004", "/api/scoped");
+    });
+    
+    // First resolution creates the instance
+    var ctx4a = scope4.resolve<RequestContext>();
+    stdout.printf("First resolution: %s\n", ctx4a.request_id);
+    
+    // Second resolution returns the cached instance
+    var ctx4b = scope4.resolve<RequestContext>();
+    stdout.printf("Second resolution: %s\n", ctx4b.request_id);
+    
+    // Verify they're the same instance
+    stdout.printf("Same instance: %s\n", ctx4a == ctx4b ? "yes" : "no");
+    
+    stdout.printf("\nDone!\n");
+}

+ 36 - 0
examples/meson.build

@@ -0,0 +1,36 @@
+# Inversion IoC Examples
+
+# Basic Usage Example - demonstrates fundamental IoC concepts
+executable('basic-usage',
+    'BasicUsage.vala',
+    dependencies: [inversion_dep, invercargill_dep],
+    install: false
+)
+
+# Lifecycle Demo - shows TRANSIENT, SCOPED, and SINGLETON behaviors
+executable('lifecycle-demo',
+    'LifecycleDemo.vala',
+    dependencies: [inversion_dep, invercargill_dep],
+    install: false
+)
+
+# Multi-Registration Example - multiple implementations of the same interface
+executable('multi-registration',
+    'MultiRegistration.vala',
+    dependencies: [inversion_dep, invercargill_dep],
+    install: false
+)
+
+# Injection Demo - demonstrates inject<T>() method for field injection
+executable('injection-demo',
+    'InjectionDemo.vala',
+    dependencies: [inversion_dep, invercargill_dep],
+    install: false
+)
+
+# Scope Registration Demo - demonstrates scope-local registrations
+executable('scope-registration-demo',
+    'ScopeRegistrationDemo.vala',
+    dependencies: [inversion_dep, invercargill_dep],
+    install: false
+)

+ 14 - 0
meson.build

@@ -0,0 +1,14 @@
+project('inversion', ['c', 'vala'],
+  version: '0.1.0',
+)
+
+# Dependencies
+glib_dep = dependency('glib-2.0')
+gobject_dep = dependency('gobject-2.0')
+invercargill_dep = dependency('invercargill-1')
+
+# VAPI Directory
+add_project_arguments(['--vapidir', join_paths(meson.current_source_dir(), 'vapi')], language: 'vala')
+
+subdir('src')
+subdir('examples')

+ 241 - 0
plans/scope-local-registrations.md

@@ -0,0 +1,241 @@
+# 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 {
+        <<interface>>
+        +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<Type, Registration> local_registrations;
+
+// Scope-local factories keyed by registration
+private Dictionary<Registration, Factory> 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<Object> resolve_all_type(Type service_type) {
+    var results = new List<Object>();
+    
+    // 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<RequestContext>();
+        return new RequestLogger(ctx.request_id);
+    });
+    
+    // Resolve handlers - they will receive the request_context
+    var handler = scope.resolve<RequestHandler>();
+    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.

+ 164 - 0
src/Container.vala

@@ -0,0 +1,164 @@
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Inversion {
+
+    /**
+     * Error domain for container-related errors.
+     */
+    public errordomain ContainerError {
+        /**
+         * The requested type is not registered in the container.
+         */
+        NOT_REGISTERED,
+        /**
+         * Cycle detected in injection
+         */
+        CYCLE_DETECTED,
+        /**
+         * Resolved to a registration with an invalid lifecycle for the lifecycle of the scope
+         */
+        ILLEGAL_LIFECYCLE_COMBINATION
+    }
+
+    /**
+     * The inversion of control container that manages component registrations and lifecycles.
+     * 
+     * The container is responsible for:
+     * - Storing component registrations
+     * - Managing singleton instances
+     * - Creating scopes for scoped instance management
+     * - Resolving dependencies
+     * 
+     * Multiple registrations can be mapped to a single service type. When resolving,
+     * you can get a single instance (any one) or all instances.
+     * 
+     * Example usage:
+     * {{{
+     * var container = new Container();
+     * 
+     * // Register a type with a factory delegate
+     * container.register(typeof(MyService), (scope) => new MyService())
+     *     .with_lifecycle(Lifecycle.SINGLETON);
+     * 
+     * // Register a type as multiple interfaces
+     * container.register(typeof(MyImplementation), (scope) => new MyImplementation())
+     *     .as_type(typeof(IServiceA))
+     *     .as_type(typeof(IServiceB))
+     *     .with_lifecycle(Lifecycle.SCOPED)
+     *     .build();
+     * 
+     * // Register with a custom Factory instance
+     * var factory = new MyCustomFactory();
+     * container.register_with_factory_instance(typeof(MyService), factory)
+     *     .as_type(typeof(IService))
+     *     .build();
+     * }}}
+     */
+    public class Container : Object {
+
+        private Catalogue<Type, Registration> registrations;
+        private Dictionary<Registration, Object> singletons;
+
+        public Container() {
+            this.registrations = new Catalogue<Type, Registration>();
+            this.singletons = new Dictionary<Registration, Object>();
+            register_singleton<Container>(s => this);
+        }
+
+
+        public Registration register_factory_type(Type implementation_type, Factory factory, Lifecycle lifecycle) {
+            var registration = new Registration(implementation_type, factory, lifecycle);
+            this.registrations.add(implementation_type, registration);
+
+            // Add subsequent aliases added to this registration
+            registration.alias_added.connect(t => {
+                this.registrations.add(t, registration);
+            });
+            
+            return registration;
+        }
+
+        public Registration register_factory<T>(Factory factory, Lifecycle lifecycle) {
+            return register_factory_type(typeof(T), factory, lifecycle);
+        }
+
+        public Registration register_type(Type implementation_type, owned FactoryDelegate factory_func, Lifecycle lifecycle) {
+            var delegate_factory = new DelegateFactory((owned)factory_func);
+            return register_factory_type(implementation_type, delegate_factory, lifecycle);
+        }
+
+        public Registration register<T>(owned FactoryDelegate factory_func, Lifecycle lifecycle) {
+            return register_type(typeof(T), (owned)factory_func, lifecycle);
+        }
+
+        public Registration register_transient<T>(owned FactoryDelegate factory_func) {
+            return register<T>((owned)factory_func, Lifecycle.TRANSIENT);
+        }
+
+        public Registration register_scoped<T>(owned FactoryDelegate factory_func) {
+            return register<T>((owned)factory_func, Lifecycle.SCOPED);
+        }
+
+        public Registration register_singleton<T>(owned FactoryDelegate factory_func) {
+            return register<T>((owned)factory_func, Lifecycle.SINGLETON);
+        }
+
+        public bool is_registered(Type type) {
+            return this.registrations.has(type);
+        }
+
+        public Registration get_registration(Type service_type) throws ContainerError {
+            try {
+                return this.registrations.get_any(service_type);
+            } catch (Error e) {
+                throw new ContainerError.NOT_REGISTERED(
+                    "Type %s is not registered in the container: %s".printf(service_type.name(), e.message)
+                );
+            }
+        }
+
+        /**
+         * Gets all registrations for a service type.
+         * 
+         * @param service_type The type to look up
+         * @return An enumerable of all registrations for the type (may be empty)
+         */
+        public Enumerable<Registration> get_registrations(Type service_type) {
+            return this.registrations.get_or_empty(service_type);
+        }
+
+        /**
+         * Gets or creates a singleton instance for a registration.
+         * 
+         * @param registration The registration to get the singleton for
+         * @param scope The current scope for dependency resolution
+         * @return The singleton instance
+         * @throws Error if instance creation fails
+         */
+        internal Object get_or_create_singleton(Registration registration, Type requested_type) throws Error {
+            Object instance;
+            if (!this.singletons.try_get(registration, out instance)) {
+                var singleton_scope = new Scope(this, Lifecycle.SINGLETON);
+                instance = create_with_injection_context(singleton_scope, requested_type, registration);
+                this.singletons.set(registration, instance);
+            }
+            return instance;
+        }
+
+        /**
+         * Creates a new scope for resolving scoped instances.
+         * 
+         * @return A new Scope tied to this container
+         */
+        public Scope create_scope() {
+            return new Scope(this, Lifecycle.SCOPED);
+        }
+
+        public Scope create_transient_scope() {
+            return new Scope(this, Lifecycle.TRANSIENT);
+        }
+
+    }
+
+}

+ 39 - 0
src/DelegateFactory.vala

@@ -0,0 +1,39 @@
+namespace Inversion {
+
+    /**
+     * A factory that uses a delegate function to create instances.
+     * 
+     * This factory is used when custom instantiation logic is required,
+     * such as when dependencies need to be manually resolved or when
+     * construction requires complex setup.
+     */
+    public class DelegateFactory : Object, Factory {
+        /**
+         * The delegate used to create instances.
+         */
+        private FactoryDelegate _delegate;
+
+        /**
+         * Creates a new delegate factory.
+         * 
+         * @param registration The registration to create instances for
+         * @param delegate The delegate to use for instance creation
+         */
+        public DelegateFactory (owned FactoryDelegate delegate) {
+            this._delegate = (owned)delegate;
+        }
+
+        /**
+         * Creates a new instance using the delegate.
+         * 
+         * @param scope The current scope for dependency resolution
+         * @return A new instance created by the delegate
+         * @throws Error if the delegate throws an error
+         */
+        public Object create(Scope scope) throws Error {
+            return this._delegate(scope);
+        }
+
+    }
+
+}

+ 23 - 0
src/Factory.vala

@@ -0,0 +1,23 @@
+namespace Inversion {
+
+    /**
+     * Interface for factories that create component instances.
+     * 
+     * Factories are responsible for creating instances of registered components.
+     * Different factory implementations can provide different creation strategies
+     * (reflection-based, delegate-based, etc.).
+     */
+    public interface Factory : Object {
+
+        /**
+         * Creates a new instance of the component.
+         * 
+         * @param scope The current scope for dependency resolution
+         * @return A new instance of the component
+         * @throws Error if instance creation fails
+         */
+        public abstract Object create(Scope scope) throws Error;
+
+    }
+
+}

+ 156 - 0
src/Injection.vala

@@ -0,0 +1,156 @@
+using Invercargill.DataStructures;
+using Invercargill;
+
+namespace Inversion {
+
+    private Dictionary<Thread, Lifo<InjectionContext>> injection_contexts_by_thread;
+    private Mutex global_injection_context_lock;
+
+    public class InjectionContext {
+        public Registration registration { get; set; }
+        public Type requested_type { get; set; }
+        public Scope scope { get; set; }
+        public Error? error { get; set;}
+        public HashSet<Registration> stack_registrations { get; set; }
+        public InjectionContext? previous_context { get; set; }
+
+        public InjectionContext(Scope scope, Type requested_type, Registration registration, InjectionContext? previous = null) {
+            this.scope = scope;
+            this.registration = registration;
+            this.previous_context = previous;
+            this.requested_type = requested_type;
+            stack_registrations = new HashSet<Registration>();
+            stack_registrations.add(registration);
+            if(previous != null) {
+                stack_registrations.union_with(previous.stack_registrations);
+            }
+        }
+    }
+
+    public InjectionContext enter_new_injection_context(Scope scope, Type requested_type, Registration registration) throws Error {
+        if(injection_contexts_by_thread == null) {
+            global_injection_context_lock.lock();
+            if(injection_contexts_by_thread == null) {
+                injection_contexts_by_thread = new Dictionary<Thread, Lifo<InjectionContext>>();
+            }
+            global_injection_context_lock.unlock();
+        }
+
+        var thread = Thread.self<int>();
+        Lifo<InjectionContext> stack;
+        if(!injection_contexts_by_thread.try_get(thread, out stack)) {
+            stack = new Lifo<InjectionContext>();
+            stack.unblock();
+            injection_contexts_by_thread[thread] = stack;
+        }
+
+        InjectionContext current = null;
+        if(stack.try_peek(out current)) {
+            if(current.error != null) {
+                throw current.error;
+            }
+            if(current.stack_registrations.has(registration)) {
+                // Cycle detected
+                var cycle = registration.implementation_type.name();
+                if(requested_type != registration.implementation_type) {
+                    cycle += @" (as $(requested_type.name()))";
+                }
+
+                var ctx = current;
+                while(ctx != null && ctx.stack_registrations.has(registration)) {
+                    var label = ctx.registration.implementation_type.name();
+                    if(ctx.requested_type != ctx.registration.implementation_type) {
+                        label += @" (as $(ctx.requested_type.name()))";
+                    }
+                    cycle = @"$label => $cycle";
+                    ctx = ctx.previous_context;
+                }
+
+                throw new ContainerError.CYCLE_DETECTED(@"Cycle detected during dependency injection: $cycle ad infinitum.");
+            }
+        }
+
+        var context  = new InjectionContext(scope, requested_type, registration, current);
+        stack.push(context);
+        return context;
+    }
+
+    public void exit_injection_context() throws Error {
+        var thread = Thread.self<int>();
+        var stack = injection_contexts_by_thread[thread];
+        var result = stack.pop();
+        if(stack.peek_count() == 0) {
+            injection_contexts_by_thread.remove(thread);
+        }
+        if(result.error != null) {
+            throw result.error;
+        }
+    }
+
+    public Object create_with_injection_context(Scope scope, Type requested_type, Registration registration) throws Error {
+        enter_new_injection_context(scope, requested_type, registration);
+        Error? error = null;
+        Object object = null;
+        try {
+            object = registration.factory.create(scope);
+        }
+        catch(Error e) {
+            error = e;
+        }
+        exit_injection_context();
+        if(error != null) {
+            throw error;
+        }
+        return object;
+    }
+
+    public InjectionContext get_injection_context() {
+        if(injection_contexts_by_thread == null) {
+            error("Injection context has not been initialised");
+        }
+        var thread = Thread.self<int>();
+        var result = injection_contexts_by_thread.get_or_default(thread);
+        if(result == null) {
+            error("Injection context has not been initialised for this thread");
+        }
+
+        try {
+            return result.peek();
+        }
+        catch {
+            error("Empty injection context stack");
+        }
+    }
+
+    public T inject<T>() {
+        var context = get_injection_context();
+        if(context.error != null) {
+            return null;
+        }
+
+        try {
+            return context.scope.resolve<T>();
+        }
+        catch(Error e) {
+            context.error = e;
+            return null;
+        }
+    }
+
+    public Lot<T> inject_all<T>() {
+        var context = get_injection_context();
+        if(context.error != null) {
+            return Iterate.nothing<T>().to_immutable_buffer();
+        }
+
+        try {
+            return context.scope.resolve_all<T>()
+                .to_immutable_buffer();
+        }
+        catch(Error e) {
+            context.error = e;
+            return Iterate.nothing<T>().to_immutable_buffer();
+        }
+    }
+
+}

+ 44 - 0
src/InstanceFactory.vala

@@ -0,0 +1,44 @@
+namespace Inversion {
+
+    /**
+     * A factory that always returns a pre-existing instance.
+     * 
+     * This factory is used for scope-local instance registrations where
+     * an existing object should be returned for all resolution requests.
+     */
+    public class InstanceFactory : Object, Factory {
+
+        /**
+         * The registration this factory is associated with.
+         */
+        public Registration registration { get; construct set; }
+
+        /**
+         * The instance to return for all create calls.
+         */
+        private Object _instance;
+
+        /**
+         * Creates a new instance factory.
+         * 
+         * @param registration The registration to associate with
+         * @param instance The instance to always return
+         */
+        public InstanceFactory(Registration registration, Object instance) {
+            Object(registration: registration);
+            this._instance = instance;
+        }
+
+        /**
+         * Returns the pre-existing instance.
+         * 
+         * @param scope The current scope (unused)
+         * @return The instance provided at construction
+         */
+        public Object create(Scope scope) throws Error {
+            return this._instance;
+        }
+
+    }
+
+}

+ 26 - 0
src/Lifecycle.vala

@@ -0,0 +1,26 @@
+namespace Inversion {
+
+    /**
+     * Defines the lifecycle behavior for registered components.
+     */
+    public enum Lifecycle {
+
+        /**
+         * A new instance is created every time the component is requested.
+         */
+        TRANSIENT,
+
+        /**
+         * A single instance is created per scope. Within the same scope,
+         * the same instance is returned for each request.
+         */
+        SCOPED,
+
+        /**
+         * A single instance is created for the lifetime of the container.
+         * The same instance is returned for all requests across all scopes.
+         */
+        SINGLETON
+    }
+
+}

+ 73 - 0
src/Registration.vala

@@ -0,0 +1,73 @@
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Inversion {
+
+    /**
+     * Represents a component registration in the IoC container.
+     * 
+     * A registration maps one or more service types (typically interfaces or base classes)
+     * to an implementation type, along with lifecycle and factory configuration.
+     * 
+     * Example usage:
+     * {{{
+     * // Register a type as multiple interfaces
+     * container.register(typeof(MyImplementation))
+     *     .as_type(typeof(IServiceA))
+     *     .as_type(typeof(IServiceB))
+     *     .with_lifecycle(Lifecycle.SCOPED)
+     *     .build();
+     * }}}
+     */
+    public class Registration : Object {
+
+        public Type implementation_type { get; construct set; }
+
+        public Lifecycle lifecycle { get; construct set; default = Lifecycle.TRANSIENT; }
+
+        public Factory factory { get; construct set; }
+
+        private HashSet<Type> types;
+
+        public ImmutableLot<Type> type_aliases { get; private set; }
+
+        public signal void alias_added(Type alias);
+
+        /**
+         * Creates a new registration for the specified implementation type.
+         * 
+         * @param implementation_type The concrete type to register
+         * @param container The container this registration belongs to, or null for scope-local registrations
+         */
+        public Registration(Type implementation_type, Factory factory, Lifecycle lifecycle) {
+            this.implementation_type = implementation_type;
+            this.factory = factory;
+            this.lifecycle = lifecycle;
+
+            this.types = new HashSet<Type>();
+            this.types.add(implementation_type);
+            this.type_aliases = types.to_immutable_buffer();
+        }
+
+        public Registration as_type(Type type) {
+            this.types.add(type);
+            this.type_aliases = types.to_immutable_buffer();
+            alias_added(type);
+            return this;
+        }
+
+        public bool provides(Type type) {
+            return this.types.contains(type);
+        }
+
+    }
+
+    /**
+     * Delegate for custom factory methods that create instances.
+     * 
+     * @param scope The current scope (for resolving dependencies)
+     * @return A new instance of the requested type
+     */
+    public delegate Object FactoryDelegate(Scope scope) throws Error;
+
+}

+ 146 - 0
src/Scope.vala

@@ -0,0 +1,146 @@
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Inversion {
+
+    /**
+     * Represents a scope for dependency resolution.
+     * 
+     * A scope manages the lifecycle of scoped components - instances that
+     * should be shared within the scope but not across different scopes.
+     * This is useful for scenarios like per-request lifecycles in web applications.
+     * 
+     * Scopes are created from a Container and inherit all registrations from it.
+     * Each scope maintains its own cache of scoped instances.
+     * 
+     * Scopes can also have their own local registrations that are only visible
+     * within that scope (not inherited by child scopes). This is useful for
+     * registering request-specific context objects in web applications.
+     */
+    public class Scope : Object {
+
+        public Container container { get; construct set; }
+        private Dictionary<Registration, Object> scoped_instances;
+        private Catalogue<Type, Registration> local_registrations;
+        public Lifecycle scope_lifecycle { get; construct set; }
+
+        /**
+         * Creates a new scope for the given container.
+         * 
+         * @param container The container this scope belongs to
+         */
+        internal Scope(Container container, Lifecycle lifecycle) {
+            Object(container: container);
+            this.scoped_instances = new Dictionary<Registration, Object>();
+            this.local_registrations = new Catalogue<Type, Registration>();
+            this.scope_lifecycle = lifecycle;
+
+            if(lifecycle == Lifecycle.SCOPED) {
+                register_local_scoped<Scope>(s => this);
+            }
+            else {
+                register_local_transient<Scope>(s => this);
+            }
+        }
+
+
+        /**
+         * Checks if this scope has a local registration for the specified type.
+         * 
+         * @param service_type The type to check
+         * @return true if a local registration exists
+         */
+        public bool is_locally_registered(Type type) {
+            return this.local_registrations.has(type);
+        }
+
+        public bool is_registered(Type type) {
+            return is_locally_registered(type) || container.is_registered(type);
+        }
+
+        public Registration register_local_factory_type(Type implementation_type, Factory factory, Lifecycle lifecycle) requires (lifecycle != Lifecycle.SINGLETON) {
+            var registration = new Registration(implementation_type, factory, lifecycle);
+            local_registrations.add(implementation_type, registration);
+
+            // Add subsequent aliases added to this registration
+            registration.alias_added.connect(t => {
+                local_registrations.add(t, registration);
+            });
+            
+            return registration;
+        }
+
+        public Registration register_local_factory<T>(Factory factory, Lifecycle lifecycle) requires (lifecycle != Lifecycle.SINGLETON) {
+            return register_local_factory_type(typeof(T), factory, lifecycle);
+        }
+
+        public Registration register_local_type(Type implementation_type, owned FactoryDelegate factory_func, Lifecycle lifecycle) requires (lifecycle != Lifecycle.SINGLETON) {
+            var delegate_factory = new DelegateFactory((owned)factory_func);
+            return register_local_factory_type(implementation_type, delegate_factory, lifecycle);
+        }
+
+        public Registration register_local<T>(owned FactoryDelegate factory_func, Lifecycle lifecycle) requires (lifecycle != Lifecycle.SINGLETON) {
+            return register_local_type(typeof(T), (owned)factory_func, lifecycle);
+        }
+
+        public Registration register_local_transient<T>(owned FactoryDelegate factory_func) {
+            return register_local<T>((owned)factory_func, Lifecycle.TRANSIENT);
+        }
+
+        public Registration register_local_scoped<T>(owned FactoryDelegate factory_func) {
+            return register_local<T>((owned)factory_func, Lifecycle.SCOPED);
+        }
+
+        public Enumerable<Registration> get_local_registrations(Type service_type) {
+            return this.local_registrations.get_or_empty(service_type);
+        }
+
+        public Object resolve_type(Type type) throws Error {
+            Registration registration;
+            if (!local_registrations.try_get_any(type, out registration)) {
+                registration = this.container.get_registration(type);
+            }
+
+            if(scope_lifecycle == Lifecycle.SINGLETON && registration.lifecycle == Lifecycle.SCOPED) {
+                throw new ContainerError.ILLEGAL_LIFECYCLE_COMBINATION("Cannot resolve scoped type from a singleton scope");
+            }
+            return this.resolve_registration(registration, type);
+        }
+
+        public T resolve<T>() throws Error {
+            return (T) this.resolve_type(typeof(T));
+        }
+
+        public Enumerable<Object> resolve_all_type(Type type) throws Error {
+            return local_registrations
+                .get_or_empty(type)
+                .concat(container.get_registrations(type))
+                .where(r => scope_lifecycle != Lifecycle.SINGLETON || r.lifecycle != Lifecycle.SCOPED)
+                .attempt_select<Object>(r => resolve_registration(r, type))
+                .to_immutable_buffer();
+        }
+
+        public Enumerable<Object> resolve_all<T>() throws Error {
+            return resolve_all_type(typeof(T));
+        }
+
+        private Object resolve_registration(Registration registration, Type requested_type) throws Error {
+            switch (registration.lifecycle) {
+                case Lifecycle.SINGLETON:
+                    return container.get_or_create_singleton(registration, requested_type);
+                    
+                case Lifecycle.SCOPED:
+                case Lifecycle.TRANSIENT:
+                default:
+                    Object instance;
+                    if (!this.scoped_instances.try_get(registration, out instance)) {
+                        instance = create_with_injection_context(this, requested_type, registration);
+                        this.scoped_instances.set(registration, instance);
+                    }
+                    return instance;
+            }
+        }
+
+    }
+
+}

+ 22 - 0
src/meson.build

@@ -0,0 +1,22 @@
+sources = files(
+    'Container.vala',
+    'DelegateFactory.vala',
+    'Factory.vala',
+    'InstanceFactory.vala',
+    'Lifecycle.vala',
+    'Registration.vala',
+    'Scope.vala',
+    'Injection.vala'
+)
+
+libinversion = shared_library('inversion',
+    sources,
+    dependencies: [glib_dep, gobject_dep, invercargill_dep],
+    install: true
+)
+
+inversion_dep = declare_dependency(
+    link_with: libinversion,
+    include_directories: include_directories('.'),
+    dependencies: [glib_dep, gobject_dep, invercargill_dep]
+)