// Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 package core import ( "Wavelet/core/extpoints" "context" "errors" "fmt" "sync" "time" ) // Context is the central micro-kernel service bus and runtime lifecycle container. // It embeds Go standard context.Context compatibility, hierarchical scoping, // service resolution, and LIFO disposer teardown. type Context struct { goCtx context.Context cancel context.CancelFunc parent *Context container *Container events *EventBus router extpoints.RouterExtension migrations extpoints.MigrationExtension tasks extpoints.TaskExtension schedules extpoints.ScheduleExtension settings extpoints.SettingExtension config extpoints.ConfigExtension mu sync.RWMutex children []*Context disposers []Disposer drivers []Driver values map[any]any disposed bool } // NewContext creates a new root Context wrapping a standard Go context. // If base is nil, context.Background() is used by default. // //nolint:contextcheck func NewContext(base context.Context) *Context { if base == nil { base = context.Background() } ctx, cancel := context.WithCancel(base) return &Context{ goCtx: ctx, cancel: cancel, container: NewContainer(nil), events: NewEventBus(), router: extpoints.NewRouterRegistry(), migrations: extpoints.NewMigrationRegistry(), tasks: extpoints.NewTaskRegistry(), schedules: extpoints.NewScheduleRegistry(), settings: extpoints.NewSettingRegistry(), config: extpoints.NewConfigRegistry(nil), values: make(map[any]any), } } // Deadline returns the time when work done on behalf of this context should be canceled. func (c *Context) Deadline() (deadline time.Time, ok bool) { return c.goCtx.Deadline() } // Done returns a channel that's closed when work done on behalf of this context should be canceled. func (c *Context) Done() <-chan struct{} { return c.goCtx.Done() } // Err returns a non-nil error value after Done is closed. func (c *Context) Err() error { return c.goCtx.Err() } // Value returns the value associated with key, searching the local values map, // the underlying Go context, and fallback parent Contexts. func (c *Context) Value(key any) any { c.mu.RLock() if v, ok := c.values[key]; ok { c.mu.RUnlock() return v } c.mu.RUnlock() if v := c.goCtx.Value(key); v != nil { return v } if c.parent != nil { return c.parent.Value(key) } return nil } // GoContext returns the underlying standard Go context.Context. func (c *Context) GoContext() context.Context { return c.goCtx } // Set stores an arbitrary key-value pair in this Context's local storage. func (c *Context) Set(key, val any) { c.mu.Lock() defer c.mu.Unlock() if c.values == nil { c.values = make(map[any]any) } c.values[key] = val } // Get retrieves a key-value pair from this Context's local storage. func (c *Context) Get(key any) (any, bool) { c.mu.RLock() defer c.mu.RUnlock() if c.values == nil { return nil, false } v, ok := c.values[key] return v, ok } // Container returns the underlying IoC container for this Context. func (c *Context) Container() *Container { return c.container } // Parent returns the parent Context, or nil if this is a root Context. func (c *Context) Parent() *Context { return c.parent } // Root returns the root Context in the hierarchy. func (c *Context) Root() *Context { curr := c for curr.parent != nil { curr = curr.parent } return curr } // Fork creates a child Context with its own scoped IoC container and values, // linked to this Context for hierarchical fallback resolution and cascading teardown. func (c *Context) Fork() *Context { return c.ForkWithContext(c.goCtx) } // ForkWithContext creates a child Context using a specific standard Go context. // //nolint:contextcheck func (c *Context) ForkWithContext(base context.Context) *Context { if base == nil { base = c.goCtx } ctx, cancel := context.WithCancel(base) child := &Context{ goCtx: ctx, cancel: cancel, parent: c, container: NewContainer(c.container), events: c.events, router: c.router, migrations: c.migrations, tasks: c.tasks, schedules: c.schedules, settings: c.settings, config: c.config, values: make(map[any]any), } c.mu.Lock() c.children = append(c.children, child) c.mu.Unlock() return child } // Events returns the domain EventBus associated with this Context hierarchy. func (c *Context) Events() *EventBus { return c.events } // On registers an event listener on the EventBus and automatically attaches its Disposer // to this Context's teardown stack for automatic revocation when disposed. func (c *Context) On(topic string, handler any) Disposer { disposer := c.events.On(topic, handler) c.OnDispose(disposer) return disposer } // Effect registers a reversible side-effect cleanup callback on this Context. func (c *Context) Effect(fn any) { c.OnDispose(fn) } // Router returns the scoped RouterExtension registry with automatic disposer tracking. func (c *Context) Router() extpoints.RouterExtension { return newScopedRouterExtension(c, c.router) } // Migrations returns the MigrationExtension registry. func (c *Context) Migrations() extpoints.MigrationExtension { return c.migrations } // Tasks returns the scoped TaskExtension registry with automatic disposer tracking. func (c *Context) Tasks() extpoints.TaskExtension { return newScopedTaskExtension(c, c.tasks) } // Task is an alias for Tasks(). func (c *Context) Task() extpoints.TaskExtension { return c.Tasks() } // Schedules returns the scoped ScheduleExtension registry with automatic disposer tracking. func (c *Context) Schedules() extpoints.ScheduleExtension { return newScopedScheduleExtension(c, c.schedules) } // Schedule is an alias for Schedules(). func (c *Context) Schedule() extpoints.ScheduleExtension { return c.Schedules() } // Settings returns the scoped SettingExtension registry with automatic disposer tracking. func (c *Context) Settings() extpoints.SettingExtension { return newScopedSettingExtension(c, c.settings) } // Setting is an alias for Settings(). func (c *Context) Setting() extpoints.SettingExtension { return c.Settings() } // Config returns the process-level configuration extension point. The registry is // shared by every fork because configuration declarations are global facts, and it // carries no per-scope disposers: values are resolved once before Apply runs. func (c *Context) Config() extpoints.ConfigExtension { return c.config } // OnDispose registers a cleanup callback function to be executed when this Context is disposed. // It accepts func() error, func(), or Disposer. func (c *Context) OnDispose(fn any) { if fn == nil { return } var d Disposer switch f := fn.(type) { case Disposer: d = f case func() error: d = f case func(): d = func() error { f() return nil } default: panic(fmt.Sprintf("core: OnDispose expects func() error or func(), got %T", fn)) } c.mu.Lock() defer c.mu.Unlock() c.disposers = append(c.disposers, d) } // Dispose shuts down this Context and all child Contexts, running registered disposers in LIFO order. func (c *Context) Dispose() error { c.mu.Lock() if c.disposed { c.mu.Unlock() return nil } c.disposed = true // Copy children and disposers under lock children := make([]*Context, len(c.children)) copy(children, c.children) disposers := make([]Disposer, len(c.disposers)) copy(disposers, c.disposers) c.mu.Unlock() var errs []error // 1. Dispose all child contexts in reverse order for i := len(children) - 1; i >= 0; i-- { if err := children[i].Dispose(); err != nil { errs = append(errs, err) } } // 2. Run local disposers in LIFO order for i := len(disposers) - 1; i >= 0; i-- { if err := disposers[i](); err != nil { errs = append(errs, err) } } // 3. Cancel the Go context if c.cancel != nil { c.cancel() } // 4. Detach from parent if c.parent != nil { c.parent.removeChild(c) } return errors.Join(errs...) } func (c *Context) removeChild(target *Context) { c.mu.Lock() defer c.mu.Unlock() for i, child := range c.children { if child == target { c.children = append(c.children[:i], c.children[i+1:]...) break } } } // IsDisposed returns true if this Context has been disposed. func (c *Context) IsDisposed() bool { c.mu.RLock() defer c.mu.RUnlock() return c.disposed } // RegisterDriver registers a runtime driver engine on this Context hierarchy. func (c *Context) RegisterDriver(d Driver) error { if d == nil { return ErrNilService } root := c.Root() root.mu.Lock() root.drivers = append(root.drivers, d) root.mu.Unlock() c.OnDispose(func() error { root.mu.Lock() defer root.mu.Unlock() for i, drv := range root.drivers { if drv == d { root.drivers = append(root.drivers[:i], root.drivers[i+1:]...) break } } return nil }) return nil } // Drivers returns a copy of all drivers registered on this Context. func (c *Context) Drivers() []Driver { root := c.Root() root.mu.RLock() defer root.mu.RUnlock() result := make([]Driver, len(root.drivers)) copy(result, root.drivers) return result } // Driver looks up a registered driver by its driver type. func (c *Context) Driver(driverType DriverType) (Driver, bool) { root := c.Root() root.mu.RLock() defer root.mu.RUnlock() for _, d := range root.drivers { if d.Type() == driverType { return d, true } } return nil, false }