From 19232c55752e37263ae133d253646c7df2495ed5 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 09:46:02 +0000 Subject: [PATCH] feat(cache): add batch eviction support --- cache.go | 26 ++++++++++++++++++++++++++ cache_test.go | 32 ++++++++++++++++++++++++++++++++ docs/api-contract.md | 1 + docs/architecture.md | 2 ++ 4 files changed, 61 insertions(+) diff --git a/cache.go b/cache.go index 8457794..f5c366c 100644 --- a/cache.go +++ b/cache.go @@ -200,6 +200,32 @@ func (c *Cache) Delete(key string) error { return nil } +// DeleteMany removes several cached items in one call. +// +// err := c.DeleteMany("github/acme/repos", "github/acme/meta") +func (c *Cache) DeleteMany(keys ...string) error { + if err := c.ensureReady("cache.DeleteMany"); err != nil { + return err + } + + for _, key := range keys { + path, err := c.Path(key) + if err != nil { + return err + } + + err = c.medium.Delete(path) + if core.Is(err, fs.ErrNotExist) { + continue + } + if err != nil { + return core.E("cache.DeleteMany", "failed to delete cache file", err) + } + } + + return nil +} + // Clear removes all cached items under the cache base directory. // // err := c.Clear() diff --git a/cache_test.go b/cache_test.go index 4011543..f6b1922 100644 --- a/cache_test.go +++ b/cache_test.go @@ -235,6 +235,38 @@ func TestCache_Delete_Good(t *testing.T) { } } +func TestCache_DeleteMany_Good(t *testing.T) { + c, _ := newTestCache(t, "/tmp/cache-delete-many", time.Minute) + data := map[string]string{"foo": "bar"} + + if err := c.Set("key1", data); err != nil { + t.Fatalf("Set failed for key1: %v", err) + } + if err := c.Set("key2", data); err != nil { + t.Fatalf("Set failed for key2: %v", err) + } + if err := c.DeleteMany("key1", "missing", "key2"); err != nil { + t.Fatalf("DeleteMany failed: %v", err) + } + + var retrieved map[string]string + found, err := c.Get("key1", &retrieved) + if err != nil { + t.Fatalf("Get after DeleteMany returned an unexpected error: %v", err) + } + if found { + t.Error("expected key1 to be deleted") + } + + found, err = c.Get("key2", &retrieved) + if err != nil { + t.Fatalf("Get after DeleteMany returned an unexpected error: %v", err) + } + if found { + t.Error("expected key2 to be deleted") + } +} + func TestCache_Clear_Good(t *testing.T) { c, _ := newTestCache(t, "/tmp/cache-clear", time.Minute) data := map[string]string{"foo": "bar"} diff --git a/docs/api-contract.md b/docs/api-contract.md index 3410d48..e92dbc2 100644 --- a/docs/api-contract.md +++ b/docs/api-contract.md @@ -22,6 +22,7 @@ own usage example in a doc comment or Go example test. | `(*Cache).Get` | `func (c *Cache) Get(key string, dest any) (bool, error)` | `dappco.re/go/core/cache` | Retrieves a cached item if it exists and has not expired. | yes | no | | `(*Cache).Set` | `func (c *Cache) Set(key string, data any) error` | `dappco.re/go/core/cache` | Stores an item in the cache. | yes | no | | `(*Cache).Delete` | `func (c *Cache) Delete(key string) error` | `dappco.re/go/core/cache` | Removes an item from the cache. | yes | no | +| `(*Cache).DeleteMany` | `func (c *Cache) DeleteMany(keys ...string) error` | `dappco.re/go/core/cache` | Removes several items from the cache in one call. | yes | no | | `(*Cache).Clear` | `func (c *Cache) Clear() error` | `dappco.re/go/core/cache` | Removes all cached items. | yes | no | | `(*Cache).Age` | `func (c *Cache) Age(key string) time.Duration` | `dappco.re/go/core/cache` | Returns how old a cached item is, or `-1` if it is not cached. | yes | no | | `GitHubReposKey` | `func GitHubReposKey(org string) string` | `dappco.re/go/core/cache` | Returns the cache key for an organization's repo list. | yes | no | diff --git a/docs/architecture.md b/docs/architecture.md index 0f9f619..d445611 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -136,6 +136,8 @@ Key behaviours: - **`Delete(key)`** removes a single entry. If the file does not exist, the operation succeeds silently. +- **`DeleteMany(keys...)`** removes several entries in one call and ignores + missing files, using the same per-key path validation as `Delete()`. - **`Clear()`** calls `medium.DeleteAll(baseDir)`, removing the entire cache directory and all its contents.