package cache import ( "context" "sync" "time" ) type memoryEntry struct { data []byte expiresAt time.Time } type memoryCache struct { mu sync.RWMutex entries map[string]memoryEntry } func newMemoryCache() Cache { c := &memoryCache{entries: make(map[string]memoryEntry)} go c.evictLoop() return c } func (c *memoryCache) Get(_ context.Context, key string) ([]byte, error) { c.mu.RLock() entry, ok := c.entries[key] c.mu.RUnlock() if !ok || time.Now().After(entry.expiresAt) { if ok { c.mu.Lock() delete(c.entries, key) c.mu.Unlock() } return nil, ErrNotFound } dst := make([]byte, len(entry.data)) copy(dst, entry.data) return dst, nil } func (c *memoryCache) GetMulti(ctx context.Context, keys []string) (map[string][]byte, error) { if len(keys) == 0 { return map[string][]byte{}, nil } result := make(map[string][]byte, len(keys)) for _, key := range keys { value, err := c.Get(ctx, key) if err != nil { if err == ErrNotFound { continue } return nil, err } result[key] = value } return result, nil } func (c *memoryCache) Set(_ context.Context, key string, value []byte, ttl time.Duration) error { dst := make([]byte, len(value)) copy(dst, value) c.mu.Lock() c.entries[key] = memoryEntry{data: dst, expiresAt: time.Now().Add(ttl)} c.mu.Unlock() return nil } func (c *memoryCache) SetMulti(ctx context.Context, items []Item) error { for _, item := range items { if err := c.Set(ctx, item.Key, item.Value, item.TTL); err != nil { return err } } return nil } func (c *memoryCache) Delete(_ context.Context, key string) error { c.mu.Lock() delete(c.entries, key) c.mu.Unlock() return nil } func (c *memoryCache) DeleteMany(ctx context.Context, keys []string) error { for _, key := range keys { if err := c.Delete(ctx, key); err != nil { return err } } return nil } func (c *memoryCache) DeletePrefix(_ context.Context, prefix string) error { if prefix == "" { return nil } c.mu.Lock() for key := range c.entries { if len(key) >= len(prefix) && key[:len(prefix)] == prefix { delete(c.entries, key) } } c.mu.Unlock() return nil } func (c *memoryCache) evictLoop() { ticker := time.NewTicker(60 * time.Second) defer ticker.Stop() for range ticker.C { now := time.Now() c.mu.Lock() for k, v := range c.entries { if now.After(v.expiresAt) { delete(c.entries, k) } } c.mu.Unlock() } }