// registry.go — multi-index manager. Mirrors the catalogd Registry // shape: a thread-safe map[name]*Index with Create / Get / Delete. // Per-index operations (Add, Search) go through each Index's own // RWMutex so registry-wide locking only fires on lifecycle events. package vectord import ( "errors" "fmt" "sort" "sync" ) // ErrIndexNotFound is returned by Get / Delete when the requested // name has no registered index. var ErrIndexNotFound = errors.New("vectord: index not found") // ErrIndexAlreadyExists is returned by Create when the name is // taken. Callers can treat this as a 409 Conflict — paralleling // catalogd's ADR-020 idempotency contract, but stricter (no // "same params reuses index" semantics yet). var ErrIndexAlreadyExists = errors.New("vectord: index already exists") // Registry holds the live indexes by name. type Registry struct { mu sync.RWMutex indexes map[string]*Index } func NewRegistry() *Registry { return &Registry{indexes: make(map[string]*Index)} } // Create builds a new Index from params and registers it under // params.Name. Returns ErrIndexAlreadyExists if the name is taken. func (r *Registry) Create(p IndexParams) (*Index, error) { idx, err := NewIndex(p) if err != nil { return nil, err } r.mu.Lock() defer r.mu.Unlock() if _, exists := r.indexes[p.Name]; exists { return nil, fmt.Errorf("%w: %q", ErrIndexAlreadyExists, p.Name) } r.indexes[p.Name] = idx return idx, nil } // Get returns the index for name, or ErrIndexNotFound. func (r *Registry) Get(name string) (*Index, error) { r.mu.RLock() defer r.mu.RUnlock() idx, ok := r.indexes[name] if !ok { return nil, fmt.Errorf("%w: %q", ErrIndexNotFound, name) } return idx, nil } // Delete removes the index for name. Returns ErrIndexNotFound if // not present (so callers see explicit no-op vs success on the // idempotent path). func (r *Registry) Delete(name string) error { r.mu.Lock() defer r.mu.Unlock() if _, ok := r.indexes[name]; !ok { return fmt.Errorf("%w: %q", ErrIndexNotFound, name) } delete(r.indexes, name) return nil } // Names returns the registered index names sorted ascending — // stable enumeration for /v1/vectors GET listings. func (r *Registry) Names() []string { r.mu.RLock() defer r.mu.RUnlock() out := make([]string, 0, len(r.indexes)) for name := range r.indexes { out = append(out, name) } sort.Strings(out) return out }