package skillpack import ( "context" "sort" "sync" ) // Memory is a zero-dependency in-process Store — a light host or a test gets // subscription persistence with no DB. Returned values are copies, so callers // can mutate them without corrupting the store. type Memory struct { mu sync.RWMutex subs map[string]*Subscription // by ID } // NewMemory returns an empty in-memory Store. func NewMemory() *Memory { return &Memory{subs: map[string]*Subscription{}} } var _ Store = (*Memory)(nil) func (m *Memory) Initialize(context.Context) error { return nil } func (m *Memory) Save(_ context.Context, s *Subscription) error { m.mu.Lock() defer m.mu.Unlock() cp := *s m.subs[s.ID] = &cp return nil } func (m *Memory) Get(_ context.Context, id string) (*Subscription, error) { m.mu.RLock() defer m.mu.RUnlock() s, ok := m.subs[id] if !ok { return nil, ErrNotFound } cp := *s return &cp, nil } func (m *Memory) GetByName(_ context.Context, name string) (*Subscription, error) { m.mu.RLock() defer m.mu.RUnlock() for _, s := range m.subs { if s.Name == name { cp := *s return &cp, nil } } return nil, ErrNotFound } func (m *Memory) List(context.Context) ([]Subscription, error) { m.mu.RLock() defer m.mu.RUnlock() out := make([]Subscription, 0, len(m.subs)) for _, s := range m.subs { out = append(out, *s) } sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name }) return out, nil } func (m *Memory) Delete(_ context.Context, id string) error { m.mu.Lock() defer m.mu.Unlock() delete(m.subs, id) return nil } // MemoryPackCache is a zero-dependency in-process PackCache. Trees are copied on // the way in and out so a cached pin is immutable in practice. type MemoryPackCache struct { mu sync.RWMutex trees map[string]Tree } // NewMemoryPackCache returns an empty in-memory PackCache. func NewMemoryPackCache() *MemoryPackCache { return &MemoryPackCache{trees: map[string]Tree{}} } var _ PackCache = (*MemoryPackCache)(nil) func (c *MemoryPackCache) Put(_ context.Context, digest string, t Tree) error { c.mu.Lock() defer c.mu.Unlock() c.trees[digest] = cloneTree(t) return nil } func (c *MemoryPackCache) Get(_ context.Context, digest string) (Tree, error) { c.mu.RLock() defer c.mu.RUnlock() t, ok := c.trees[digest] if !ok { return nil, ErrNotFound } return cloneTree(t), nil } func cloneTree(t Tree) Tree { cp := make(Tree, len(t)) for k, v := range t { b := make([]byte, len(v)) copy(b, v) cp[k] = b } return cp }