feat(cooler): Cache.Pull returns and removes a value atomically

- Cache.Pull(ctx, key) ([]byte, bool, error), Laravel's Cache::pull
- Redis driver runs GETDEL (Redis 6.2+); memory driver uses otter Compute
  with InvalidateOp under the per-key lock; expired keys report not found
- TestPullSmoke over the memory driver and miniredis, ExampleCache_Pull
- README and docs/services/caching.md ("One-time values")
This commit is contained in:
Jakub Zych
2026-10-20 22:58:44 +02:00
parent 26ebb6c98c
commit 5e0a04e4da
7 changed files with 164 additions and 4 deletions

View File

@@ -48,6 +48,36 @@ func ExampleFromApp() {
// Output: [Hello Summer] 1
}
func ExampleCache_Pull() {
// A one-time code: one request stores it with a short TTL, the request
// that redeems it pulls it, and a second redemption finds nothing.
cache, err := cooler.NewMemoryCache("acme:", cooler.DefaultMaxEntries)
if err != nil {
fmt.Println(err)
return
}
ctx := context.Background()
if err := cache.Set(ctx, "signin:4f9c", []byte(`{"user_id":7}`), 5*time.Minute); err != nil {
fmt.Println(err)
return
}
for range 2 {
payload, ok, err := cache.Pull(ctx, "signin:4f9c")
if err != nil {
fmt.Println(err)
return
}
if !ok {
fmt.Println("unknown, expired or already used")
continue
}
fmt.Println(string(payload))
}
// Output:
// {"user_id":7}
// unknown, expired or already used
}
// Article is a blog's article model.
type Article struct {
Slug string