package module import ( "fmt" "sort" "sync" ) // Registry is a concurrency-safe in-memory index of loaded manifests. // It's held as a shared component by both Controller and Target. type Registry struct { mu sync.RWMutex modules map[string]*Manifest } // NewRegistry returns an empty registry. func NewRegistry() *Registry { return &Registry{modules: make(map[string]*Manifest)} } // LoadDir scans dir for modules and inserts them. Duplicate IDs are rejected. func (r *Registry) LoadDir(dir string) error { ms, err := LoadDir(dir) if err != nil { return err } r.mu.Lock() defer r.mu.Unlock() for _, m := range ms { if _, exists := r.modules[m.ID]; exists { return fmt.Errorf("duplicate module id %q in %s", m.ID, dir) } r.modules[m.ID] = m } return nil } // Put inserts or replaces a manifest. func (r *Registry) Put(m *Manifest) { r.mu.Lock() defer r.mu.Unlock() r.modules[m.ID] = m } // Get returns the manifest for id, or (nil, false) if unknown. func (r *Registry) Get(id string) (*Manifest, bool) { r.mu.RLock() defer r.mu.RUnlock() m, ok := r.modules[id] return m, ok } // List returns all manifests sorted by id. func (r *Registry) List() []*Manifest { r.mu.RLock() defer r.mu.RUnlock() out := make([]*Manifest, 0, len(r.modules)) for _, m := range r.modules { out = append(out, m) } sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID }) return out } // Len returns the number of modules currently registered. func (r *Registry) Len() int { r.mu.RLock() defer r.mu.RUnlock() return len(r.modules) }