add module loaded state

This commit is contained in:
Elijah Duffy
2025-05-30 16:40:31 -07:00
parent fc41a6ee5b
commit 891035cf93
2 changed files with 40 additions and 32 deletions

View File

@@ -46,11 +46,11 @@ func NewLifecycleL(defaultLogger *slog.Logger, modules ...*Module) *Lifecycle {
// Ensure modules are unique // Ensure modules are unique
unique := make(map[string]bool) unique := make(map[string]bool)
for _, sub := range modules { for _, mod := range modules {
if _, exists := unique[sub.name]; exists { if _, exists := unique[mod.name]; exists {
panic(fmt.Sprintf("duplicate module name: %s", sub.name)) panic(fmt.Sprintf("duplicate module name: %s", mod.name))
} }
unique[sub.name] = true unique[mod.name] = true
} }
return &Lifecycle{ return &Lifecycle{
@@ -81,8 +81,8 @@ func (app *Lifecycle) Logger() *slog.Logger {
// of the application lifecycle to ensure all resources are cleaned up properly. // of the application lifecycle to ensure all resources are cleaned up properly.
func (app *Lifecycle) Setup() error { func (app *Lifecycle) Setup() error {
setupCount := 0 setupCount := 0
for _, sub := range app.modules { for _, mod := range app.modules {
if ok, err := app.setupSingle(sub, app.logger); err != nil { if ok, err := app.setupSingle(mod, app.logger); err != nil {
return err return err
} else if ok { } else if ok {
setupCount++ setupCount++
@@ -127,23 +127,23 @@ func (app *Lifecycle) Require(modules ...*Module) error {
// This variation is useful when you need to set up modules with a non- // This variation is useful when you need to set up modules with a non-
// default logger. // default logger.
func (app *Lifecycle) RequireL(logger *slog.Logger, modules ...*Module) error { func (app *Lifecycle) RequireL(logger *slog.Logger, modules ...*Module) error {
for _, sub := range modules { for _, mod := range modules {
if sub == nil { if mod == nil {
return fmt.Errorf("module is nil") return fmt.Errorf("module is nil")
} }
// Check if the module is already set up // Check if the module is already set up
if _, ok := app.setupTracker[sub.name]; ok { if _, ok := app.setupTracker[mod.name]; ok {
app.logger.Warn("module already set up, ignoring", "module", sub.name) app.logger.Warn("module already set up, ignoring", "module", mod.name)
continue continue
} }
// Add the module to the lifecycle // Add the module to the lifecycle
app.modules = append(app.modules, sub) app.modules = append(app.modules, mod)
// Run the setup function for the module // Run the setup function for the module
if _, err := app.setupSingle(sub, logger); err != nil { if _, err := app.setupSingle(mod, logger); err != nil {
return fmt.Errorf("error setting up required module '%s': %w", sub.name, err) return fmt.Errorf("error setting up required module '%s': %w", mod.name, err)
} }
} }
@@ -153,19 +153,19 @@ func (app *Lifecycle) RequireL(logger *slog.Logger, modules ...*Module) error {
} }
// setupSingle is a helper function to set up a single module. // setupSingle is a helper function to set up a single module.
func (app *Lifecycle) setupSingle(sub *Module, logger *slog.Logger) (bool, error) { func (app *Lifecycle) setupSingle(mod *Module, logger *slog.Logger) (bool, error) {
if sub == nil { if mod == nil {
return false, fmt.Errorf("module is nil") return false, fmt.Errorf("module is nil")
} }
// Set the logger for the module // Set the logger for the module
sub.logger = logger mod.logger = logger
// Check if all dependencies are satisfied // Check if all dependencies are satisfied
for _, dep := range sub.depends { for _, dep := range mod.depends {
if _, ok := app.setupTracker[dep]; !ok { if _, ok := app.setupTracker[dep]; !ok {
if app.opts.DisableAutoload { if app.opts.DisableAutoload {
return false, fmt.Errorf("dependency '%s' not satisfied for '%s'", dep, sub.name) return false, fmt.Errorf("dependency '%s' not satisfied for '%s'", dep, mod.name)
} else { } else {
// Attempt to set up the dependency // Attempt to set up the dependency
mod, err := app.getModuleByName(dep) mod, err := app.getModuleByName(dep)
@@ -174,21 +174,22 @@ func (app *Lifecycle) setupSingle(sub *Module, logger *slog.Logger) (bool, error
} }
if _, err := app.setupSingle(mod, logger); err != nil { if _, err := app.setupSingle(mod, logger); err != nil {
return false, fmt.Errorf("error setting up dependency '%s' for '%s': %w", dep, sub.name, err) return false, fmt.Errorf("error setting up dependency '%s' for '%s': %w", dep, mod.name, err)
} }
} }
} }
} }
if sub.setup != nil { if mod.setup != nil {
// Run the setup function for the module // Run the setup function for the module
if err := sub.setup(); err != nil { if err := mod.setup(); err != nil {
return false, fmt.Errorf("error initializing '%s': %w", sub.name, err) return false, fmt.Errorf("error initializing '%s': %w", mod.name, err)
} }
// Mark this module as setup // Mark this module as setup
app.setupTracker[sub.name] = true app.setupTracker[mod.name] = true
mod.loaded = true
return true, nil return true, nil
} }
@@ -196,22 +197,23 @@ func (app *Lifecycle) setupSingle(sub *Module, logger *slog.Logger) (bool, error
} }
// singleTeardown is a helper function to tear down a single module. // singleTeardown is a helper function to tear down a single module.
func (app *Lifecycle) singleTeardown(sub *Module) (bool, error) { func (app *Lifecycle) singleTeardown(mod *Module) (bool, error) {
if sub == nil { if mod == nil {
return false, fmt.Errorf("module is nil") return false, fmt.Errorf("module is nil")
} }
// Check if the module was set up // Check if the module was set up
if _, ok := app.setupTracker[sub.name]; !ok { if _, ok := app.setupTracker[mod.name]; !ok {
return false, nil return false, nil
} }
// Run the teardown function for the module // Run the teardown function for the module
if sub.teardown != nil { if mod.teardown != nil {
sub.teardown() mod.teardown()
// Mark this module as torn down // Mark this module as torn down
app.teardownTracker[sub.name] = true app.teardownTracker[mod.name] = true
mod.loaded = false
return true, nil return true, nil
} }
@@ -220,9 +222,9 @@ func (app *Lifecycle) singleTeardown(sub *Module) (bool, error) {
// getModuleByName retrieves a module by its name from the lifecycle. // getModuleByName retrieves a module by its name from the lifecycle.
func (app *Lifecycle) getModuleByName(name string) (*Module, error) { func (app *Lifecycle) getModuleByName(name string) (*Module, error) {
for _, sub := range app.modules { for _, mod := range app.modules {
if sub.name == name { if mod.name == name {
return sub, nil return mod, nil
} }
} }
return nil, fmt.Errorf("module '%s' not found", name) return nil, fmt.Errorf("module '%s' not found", name)

View File

@@ -13,6 +13,7 @@ type Module struct {
setup setupFn setup setupFn
teardown teardownFn teardown teardownFn
depends []string depends []string
loaded bool // loaded indicates if the module has been set up
} }
// ModuleOpts contains user-exposed options when defining a module. // ModuleOpts contains user-exposed options when defining a module.
@@ -48,3 +49,8 @@ func (s *Module) Logger() *slog.Logger {
func (s *Module) Name() string { func (s *Module) Name() string {
return s.name return s.name
} }
// Loaded returns whether the module has been set up.
func (s *Module) Loaded() bool {
return s.loaded
}