1package hostenv23import (4 "fmt"5 "sort"6 "sync"7)89// MemorySessions keeps session records for as long as the process lives and10// no longer. It is the store a host uses when it has nowhere to write --11// `atlas serve` without a data directory, a test -- and it is here rather than12// in a test file because "state that does not survive the process" is a real13// host decision, not a mock.14//15// It is safe for concurrent use.16type MemorySessions struct {17 mu sync.Mutex18 records map[string][]byte19}2021// NewMemorySessions returns an empty in-memory session store.22func NewMemorySessions() *MemorySessions {23 return &MemorySessions{records: map[string][]byte{}}24}2526// Load reads a record, or reports [ErrNoSession].27func (m *MemorySessions) Load(name string) ([]byte, error) {28 if err := ValidName(name); err != nil {29 return nil, fmt.Errorf("session %q: %w", name, err)30 }31 m.mu.Lock()32 defer m.mu.Unlock()33 held, ok := m.records[name]34 if !ok {35 return nil, ErrNoSession36 }37 out := make([]byte, len(held))38 copy(out, held)39 return out, nil40}4142// Save writes a record whole.43func (m *MemorySessions) Save(name string, data []byte) error {44 if err := ValidName(name); err != nil {45 return fmt.Errorf("session %q: %w", name, err)46 }47 held := make([]byte, len(data))48 copy(held, data)49 m.mu.Lock()50 defer m.mu.Unlock()51 m.records[name] = held52 return nil53}5455// Delete forgets a record. One that was never held is already forgotten, so56// there is nothing here to report.57func (m *MemorySessions) Delete(name string) error {58 if err := ValidName(name); err != nil {59 return fmt.Errorf("session %q: %w", name, err)60 }61 m.mu.Lock()62 defer m.mu.Unlock()63 delete(m.records, name)64 return nil65}6667// Names lists the records held, sorted.68func (m *MemorySessions) Names() ([]string, error) {69 m.mu.Lock()70 defer m.mu.Unlock()71 names := make([]string, 0, len(m.records))72 for name := range m.records {73 names = append(names, name)74 }75 sort.Strings(names)76 return names, nil77}