├── .github └── workflows │ └── actions.yml ├── .gitignore ├── LICENSE ├── cache.go ├── cache_test.go ├── go.mod └── readme.md /.github/workflows/actions.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | name: test 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: actions/setup-go@v2 10 | with: 11 | go-version: '^1.18' 12 | - run: go test ./... 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Rodrigo Brito 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "time" 4 | 5 | type Item[T any] struct { 6 | TTL time.Time 7 | Value T 8 | } 9 | 10 | type Cache[T any] struct { 11 | data map[string]Item[T] 12 | clearInterval time.Duration 13 | } 14 | 15 | type Option[T any] func(c *Cache[T]) 16 | 17 | func New[T any](interval time.Duration) *Cache[T] { 18 | cache := &Cache[T]{ 19 | clearInterval: interval, 20 | data: make(map[string]Item[T]), 21 | } 22 | 23 | if cache.clearInterval > 0 { 24 | go cache.cleaner() 25 | } 26 | 27 | return cache 28 | } 29 | 30 | func (c *Cache[T]) cleaner() { 31 | for range time.NewTicker(c.clearInterval).C { 32 | for key, item := range c.data { 33 | if item.TTL.Before(time.Now()) { 34 | delete(c.data, key) 35 | } 36 | } 37 | } 38 | } 39 | 40 | func (c *Cache[T]) Get(key string) (value T, ok bool) { 41 | item, ok := c.data[key] 42 | if !ok { 43 | return 44 | } 45 | 46 | if item.TTL.Before(time.Now()) { 47 | delete(c.data, key) 48 | return value, false 49 | } 50 | 51 | return item.Value, true 52 | } 53 | 54 | func (c *Cache[T]) GetMulti(keys ...string) ([]T, bool) { 55 | result := make([]T, 0) 56 | for _, key := range keys { 57 | item, ok := c.data[key] 58 | if ok { 59 | if item.TTL.Before(time.Now()) { 60 | delete(c.data, key) 61 | continue 62 | } 63 | result = append(result, item.Value) 64 | } 65 | } 66 | return result, true 67 | } 68 | 69 | func (c *Cache[T]) Set(key string, value T, ttl time.Duration) { 70 | c.data[key] = Item[T]{ 71 | TTL: time.Now().Add(ttl), 72 | Value: value, 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func equal[T any](t *testing.T, value, expect T) { 10 | t.Helper() 11 | if !reflect.DeepEqual(value, expect) { 12 | t.Errorf("Expected %v, got %v", expect, value) 13 | } 14 | } 15 | 16 | func TestCache(t *testing.T) { 17 | cache := New[int](time.Millisecond) 18 | cache.Set("one", 1, time.Minute) 19 | cache.Set("two", 2, time.Minute) 20 | cache.Set("expired", 1, 0) 21 | 22 | t.Run("found", func(t *testing.T) { 23 | value, ok := cache.Get("one") 24 | equal(t, ok, true) 25 | equal(t, value, 1) 26 | }) 27 | 28 | t.Run("not found", func(t *testing.T) { 29 | value, ok := cache.Get("not found") 30 | equal(t, ok, false) 31 | equal(t, value, 0) 32 | }) 33 | 34 | t.Run("expired TTL", func(t *testing.T) { 35 | value, ok := cache.Get("expired") 36 | equal(t, ok, false) 37 | equal(t, value, 0) 38 | }) 39 | 40 | t.Run("get multi", func(t *testing.T) { 41 | values, ok := cache.GetMulti("one", "two") 42 | equal(t, ok, true) 43 | equal(t, values, []int{1, 2}) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rodrigo-brito/memory-cache 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Memory Cache 2 | 3 | A zero-dependency cache library for storing data in memory with **generics**. 4 | 5 | ## Requirements 6 | 7 | - Golang 1.18+ 8 | 9 | ## Installation 10 | 11 | ```bash 12 | go get -u github.com/rodrigo-brito/memory-cache 13 | ``` 14 | 15 | ## Examples of usage 16 | 17 | ```go 18 | type User struct { 19 | Name string 20 | } 21 | 22 | // Create a new cache for type `User` with a clean-up interval of 1 hour 23 | cache := cache.New[User](time.Hour) 24 | 25 | // Store a new user with key "user-1" and TTL of 1 minute. 26 | cache.Set("user-1", User{Name: "Rodrigo Brito"}, time.Minute) 27 | 28 | // Retrieve the user with key "user-1" 29 | user, ok := cache.Get("user-1") 30 | if ok { 31 | fmt.Println(user.Name) // output: "Rodrigo Brito" 32 | } 33 | ``` 34 | 35 | ## License 36 | 37 | This project is release under [MIT license](LICENSE). 38 | --------------------------------------------------------------------------------