mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 06:16:37 +08:00
225 lines
5.7 KiB
Go
225 lines
5.7 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Package core provides the micro-kernel service bus, generic IoC container, and runtime extensions.
|
|
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sync"
|
|
)
|
|
|
|
// Container manages service registration and resolution using Go reflection and generics.
|
|
type Container struct {
|
|
mu sync.RWMutex
|
|
parent *Container
|
|
services map[reflect.Type]any
|
|
listeners map[reflect.Type][]func(any)
|
|
}
|
|
|
|
// NewContainer creates a new IoC container instance with an optional parent container.
|
|
func NewContainer(parent *Container) *Container {
|
|
return &Container{
|
|
parent: parent,
|
|
services: make(map[reflect.Type]any),
|
|
listeners: make(map[reflect.Type][]func(any)),
|
|
}
|
|
}
|
|
|
|
func isNil(i any) bool {
|
|
if i == nil {
|
|
return true
|
|
}
|
|
v := reflect.ValueOf(i)
|
|
switch v.Kind() {
|
|
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
|
|
return v.IsNil()
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (c *Container) remove(targetType reflect.Type) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
delete(c.services, targetType)
|
|
}
|
|
|
|
// Provide registers a typed service implementation into the Context hierarchy's root IoC container.
|
|
func Provide[T any](ctx *Context, service T) {
|
|
if ctx == nil {
|
|
panic("core: nil context provided to Provide")
|
|
}
|
|
if isNil(service) {
|
|
panic("core: cannot provide nil service")
|
|
}
|
|
|
|
targetType := reflect.TypeFor[T]()
|
|
targetContainer := ctx.Root().Container()
|
|
targetContainer.provide(targetType, service)
|
|
|
|
ctx.OnDispose(func() error {
|
|
targetContainer.remove(targetType)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// ProvideScoped registers a typed service implementation strictly in the local Context container.
|
|
func ProvideScoped[T any](ctx *Context, service T) {
|
|
if ctx == nil {
|
|
panic("core: nil context provided to ProvideScoped")
|
|
}
|
|
if isNil(service) {
|
|
panic("core: cannot provide nil service")
|
|
}
|
|
|
|
targetType := reflect.TypeFor[T]()
|
|
targetContainer := ctx.Container()
|
|
targetContainer.provide(targetType, service)
|
|
|
|
ctx.OnDispose(func() error {
|
|
targetContainer.remove(targetType)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (c *Container) provide(targetType reflect.Type, service any) {
|
|
c.mu.Lock()
|
|
c.services[targetType] = service
|
|
|
|
// Collect any matching listeners to invoke outside the lock
|
|
var callbacks []func(any)
|
|
svcType := reflect.TypeOf(service)
|
|
for lType, cbs := range c.listeners {
|
|
if lType == targetType || (lType.Kind() == reflect.Interface && svcType.Implements(lType)) {
|
|
callbacks = append(callbacks, cbs...)
|
|
}
|
|
}
|
|
c.mu.Unlock()
|
|
|
|
for _, cb := range callbacks {
|
|
cb(service)
|
|
}
|
|
}
|
|
|
|
// Inject resolves a registered service of type T from the Context.
|
|
func Inject[T any](ctx *Context) (T, error) {
|
|
var zero T
|
|
if ctx == nil {
|
|
return zero, ErrNilContext
|
|
}
|
|
|
|
targetType := reflect.TypeFor[T]()
|
|
val, err := ctx.Container().resolve(targetType)
|
|
if err != nil {
|
|
return zero, err
|
|
}
|
|
|
|
typedVal, ok := val.(T)
|
|
if !ok {
|
|
return zero, fmt.Errorf("%w: cannot cast %T to %v", ErrServiceNotFound, val, targetType)
|
|
}
|
|
return typedVal, nil
|
|
}
|
|
|
|
func (c *Container) resolve(targetType reflect.Type) (any, error) {
|
|
c.mu.RLock()
|
|
// 1. Direct type match
|
|
if val, ok := c.services[targetType]; ok {
|
|
c.mu.RUnlock()
|
|
return val, nil
|
|
}
|
|
|
|
// 2. Interface assignment scan
|
|
if targetType.Kind() == reflect.Interface {
|
|
for _, val := range c.services {
|
|
if reflect.TypeOf(val).Implements(targetType) {
|
|
c.mu.RUnlock()
|
|
return val, nil
|
|
}
|
|
}
|
|
}
|
|
c.mu.RUnlock()
|
|
|
|
// 3. Fallback to parent container
|
|
if c.parent != nil {
|
|
return c.parent.resolve(targetType)
|
|
}
|
|
|
|
return nil, fmt.Errorf("%w: %v", ErrServiceNotFound, targetType)
|
|
}
|
|
|
|
// MustInject resolves a service of type T or panics if the service is not found.
|
|
func MustInject[T any](ctx *Context) T {
|
|
s, err := Inject[T](ctx)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("core: failed to inject service %v: %v", reflect.TypeFor[T](), err))
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Has returns true if a service of type T is registered and resolvable in the Context.
|
|
func Has[T any](ctx *Context) bool {
|
|
_, err := Inject[T](ctx)
|
|
return err == nil
|
|
}
|
|
|
|
// Using executes the given function synchronously if the required dependency is ready.
|
|
func Using[T1 any](ctx *Context, fn func(s1 T1)) error {
|
|
s1, err := Inject[T1](ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %w", ErrServiceNotReady, err)
|
|
}
|
|
fn(s1)
|
|
return nil
|
|
}
|
|
|
|
// Using2 executes the given function synchronously if both required dependencies are ready.
|
|
func Using2[T1, T2 any](ctx *Context, fn func(s1 T1, s2 T2)) error {
|
|
s1, err1 := Inject[T1](ctx)
|
|
s2, err2 := Inject[T2](ctx)
|
|
if err1 != nil || err2 != nil {
|
|
return fmt.Errorf("%w: (dep1: %v, dep2: %v)", ErrServiceNotReady, err1, err2)
|
|
}
|
|
fn(s1, s2)
|
|
return nil
|
|
}
|
|
|
|
// Using3 executes the given function synchronously if all 3 required dependencies are ready.
|
|
func Using3[T1, T2, T3 any](ctx *Context, fn func(s1 T1, s2 T2, s3 T3)) error {
|
|
s1, err1 := Inject[T1](ctx)
|
|
s2, err2 := Inject[T2](ctx)
|
|
s3, err3 := Inject[T3](ctx)
|
|
if err1 != nil || err2 != nil || err3 != nil {
|
|
return fmt.Errorf("%w: (dep1: %v, dep2: %v, dep3: %v)", ErrServiceNotReady, err1, err2, err3)
|
|
}
|
|
fn(s1, s2, s3)
|
|
return nil
|
|
}
|
|
|
|
// When registers a reactive hook that is called immediately if T is already provided,
|
|
// or called as soon as T is provided in the future.
|
|
func When[T any](ctx *Context, fn func(s T)) {
|
|
if ctx == nil {
|
|
panic("core: nil context provided to When")
|
|
}
|
|
|
|
targetType := reflect.TypeFor[T]()
|
|
c := ctx.Container()
|
|
|
|
// If already ready, execute immediately
|
|
if s, err := Inject[T](ctx); err == nil {
|
|
fn(s)
|
|
}
|
|
|
|
// Also register listener for future calls / updates
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.listeners[targetType] = append(c.listeners[targetType], func(val any) {
|
|
if typed, ok := val.(T); ok {
|
|
fn(typed)
|
|
}
|
|
})
|
|
}
|