Class: TypedCache::Store
- Inherits:
-
Object
- Object
- TypedCache::Store
- Defined in:
- lib/typed_cache/store.rb
Overview
Generic interface for type-safe cache storage implementations All stores are assumed to handle namespacing internally
This interface follows the Command-Query Separation principle:
-
Commands (set, delete, clear) perform actions and may return results
-
Queries (get, key?, fetch) ask questions without side effects
Instance Attribute Summary collapse
-
#backend ⇒ Object
readonly
: Backend.
-
#namespace ⇒ Object
readonly
@rbs! interface _Store def read: (cache_key) -> either[Error, Snapshot[maybe]] def read_all: (Array) -> either[Error, Hash[CacheKey, Snapshot]] def ref: (cache_key) -> CacheRef def write: (cache_key, V) -> either[Error, Snapshot] def write_all: (Hash[cache_key, V]) -> either[Error, Hash[CacheKey, Snapshot]] def delete: (cache_key) -> either[Error, maybe] def key?: (cache_key) -> bool def clear: () -> void def fetch: (cache_key) { (CacheKey) -> V? } -> either[Error, Snapshot[maybe]] def fetch_all: (Array) { (CacheKey) -> V? } -> either[Error, Array[Snapshot]] def fetch_or_compute_all: (Array) { (Array) -> Hash[CacheKey, V] } -> either[Error, Hash[CacheKey, Snapshot]] def namespace: () -> Namespace def with_namespace: (Namespace) -> Store def at_namespace: (Namespace) -> Store def cache_for: [T] (Class, at: (Namespace | String | Array)) -> Store def backend: () -> Backend end include _Store.
Instance Method Summary collapse
- #cache_for(klass, at: nil) ⇒ Object
-
#clear ⇒ Object
Clears all values from the cache namespace (command operation).
-
#delete(key) ⇒ Object
Removes a value from the cache, returning the removed value.
-
#fetch(key, **kwargs, &block) ⇒ Object
Fetches a value from cache, computing and storing it if not found This is an atomic operation that combines read and write.
- #fetch_all(keys, **kwargs, &block) ⇒ Object
- #fetch_or_compute_all(keys, **kwargs, &block) ⇒ Object
-
#initialize(namespace, backend) ⇒ Store
constructor
A new instance of Store.
- #initialize_copy(other) ⇒ Object
- #inspect ⇒ Object
- #instrumenter ⇒ Object
-
#key?(key) ⇒ Boolean
Checks if a key exists in the cache (query operation).
-
#read(key, **kwargs) ⇒ Object
Retrieves a value from the cache.
- #read_all(keys, **kwargs) ⇒ Object
-
#ref(key) ⇒ Object
Retrieves a cache reference for a key.
- #to_s ⇒ Object
-
#with_namespace(ns) ⇒ Object
(also: #at_namespace)
Accepts a String segment or a fully-formed Namespace and returns a cloned store scoped to that namespace.
-
#write(key, value, **kwargs) ⇒ Object
Stores a value in the cache.
- #write_all(values, **kwargs) ⇒ Object
Constructor Details
#initialize(namespace, backend) ⇒ Store
Returns a new instance of Store.
40 41 42 43 |
# File 'lib/typed_cache/store.rb', line 40 def initialize(namespace, backend) @namespace = namespace @backend = backend end |
Instance Attribute Details
#backend ⇒ Object (readonly)
: Backend
37 38 39 |
# File 'lib/typed_cache/store.rb', line 37 def backend @backend end |
#namespace ⇒ Object
@rbs!
interface _Store[V]
def read: (cache_key) -> either[Error, Snapshot[maybe[V]]]
def read_all: (Array[cache_key]) -> either[Error, Hash[CacheKey, Snapshot[V]]]
def ref: (cache_key) -> CacheRef[V]
def write: (cache_key, V) -> either[Error, Snapshot[V]]
def write_all: (Hash[cache_key, V]) -> either[Error, Hash[CacheKey, Snapshot[V]]]
def delete: (cache_key) -> either[Error, maybe[V]]
def key?: (cache_key) -> bool
def clear: () -> void
def fetch: (cache_key) { (CacheKey) -> V? } -> either[Error, Snapshot[maybe[V]]]
def fetch_all: (Array[cache_key]) { (CacheKey) -> V? } -> either[Error, Array[Snapshot[V]]]
def fetch_or_compute_all: (Array[cache_key]) { (Array[CacheKey]) -> Hash[CacheKey, V] } -> either[Error, Hash[CacheKey, Snapshot[V]]]
def namespace: () -> Namespace
def with_namespace: (Namespace) -> Store[V]
def at_namespace: (Namespace) -> Store[V]
def cache_for: [T] (Class[T], at: (Namespace | String | Array[String])) -> Store[T]
def backend: () -> Backend[V]
end
include _Store[V]
36 37 38 |
# File 'lib/typed_cache/store.rb', line 36 def namespace @namespace end |
Instance Method Details
#cache_for(klass, at: nil) ⇒ Object
206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/typed_cache/store.rb', line 206 def cache_for(klass, at: nil) new_namespace = case at when Namespace then at when Array then namespace.join(*at) else namespace.nested(at.to_s) end clone.tap { |store| store.namespace = new_namespace } end |
#clear ⇒ Object
Clears all values from the cache namespace (command operation)
127 128 129 |
# File 'lib/typed_cache/store.rb', line 127 def clear backend.clear end |
#delete(key) ⇒ Object
Removes a value from the cache, returning the removed value
109 110 111 112 113 114 115 116 |
# File 'lib/typed_cache/store.rb', line 109 def delete(key) key = namespaced_key(key) deleted_value = backend.delete(key) Either.right(Maybe.wrap(deleted_value)) rescue => e Either.left(StoreError.new(:delete, key, "Failed to delete from cache: #{e.message}", e)) end |
#fetch(key, **kwargs, &block) ⇒ Object
Fetches a value from cache, computing and storing it if not found This is an atomic operation that combines read and write
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/typed_cache/store.rb', line 134 def fetch(key, **kwargs, &block) key = namespaced_key(key) computed = false result = backend.fetch(key, **kwargs) do computed = true yield(key) end if result.nil? Either.right(Snapshot.cached(key, Maybe.none)) else snapshot = computed ? Snapshot.computed(key, result) : Snapshot.cached(key, result) Either.right(snapshot.map { Maybe.wrap(_1) }) end rescue => e Either.left(StoreError.new(:fetch, key, "Failed to fetch from cache: #{e.message}", e)) end |
#fetch_all(keys, **kwargs, &block) ⇒ Object
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/typed_cache/store.rb', line 153 def fetch_all(keys, **kwargs, &block) keys = keys.map { |key| namespaced_key(key) } computed_keys = Set.new fetched_values = backend.fetch_multi(keys, **kwargs) do |key| computed_keys << key yield(key) end Either.right(keys.to_h do |key| snapshot = computed_keys.include?(key) ? Snapshot.computed(key, fetched_values[key]) : Snapshot.cached(key, fetched_values[key]) [key, snapshot] end) rescue => e Either.left(StoreError.new(:fetch_all, keys, "Failed to fetch from cache: #{e.message}", e)) end |
#fetch_or_compute_all(keys, **kwargs, &block) ⇒ Object
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/typed_cache/store.rb', line 170 def fetch_or_compute_all(keys, **kwargs, &block) keys = keys.map { |key| namespaced_key(key) } cached_values = backend.read_multi(keys, **kwargs) missing_keys = keys - cached_values.keys if missing_keys.any? computed_values = yield(missing_keys) backend.write_multi(computed_values, **kwargs) end cached_values.transform_values! { |value| Snapshot.cached(key, value) } computed_values.transform_values! { |value| Snapshot.computed(key, value) } cached_values.merge(computed_values) end |
#initialize_copy(other) ⇒ Object
46 47 48 49 |
# File 'lib/typed_cache/store.rb', line 46 def initialize_copy(other) super @namespace = other.namespace end |
#inspect ⇒ Object
224 |
# File 'lib/typed_cache/store.rb', line 224 def inspect = "Store(#{namespace}, #{backend.inspect})" |
#instrumenter ⇒ Object
187 |
# File 'lib/typed_cache/store.rb', line 187 def instrumenter = @instrumenter ||= Instrumenters::Null.new(namespace:) |
#key?(key) ⇒ Boolean
Checks if a key exists in the cache (query operation)
120 121 122 123 |
# File 'lib/typed_cache/store.rb', line 120 def key?(key) key = namespaced_key(key) backend.key?(key) end |
#read(key, **kwargs) ⇒ Object
Retrieves a value from the cache
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/typed_cache/store.rb', line 53 def read(key, **kwargs) key = namespaced_key(key) value = backend.read(key, **kwargs) Either.right( if value.nil? Snapshot.cached(key, Maybe.none) else Snapshot.cached(key, Maybe.some(value)) end, ) rescue => e Either.left(StoreError.new(:read, key, "Failed to read from cache: #{e.message}", e)) end |
#read_all(keys, **kwargs) ⇒ Object
70 71 72 73 74 75 76 77 78 79 |
# File 'lib/typed_cache/store.rb', line 70 def read_all(keys, **kwargs) keys = keys.map { |key| namespaced_key(key) } cached_values = backend.read_multi(keys, **kwargs) keys.filter_map do |key| next unless cached_values.key?(key.to_s) [key, Snapshot.cached(key, cached_values[key.to_s])] end.to_h end |
#ref(key) ⇒ Object
Retrieves a cache reference for a key
83 84 85 |
# File 'lib/typed_cache/store.rb', line 83 def ref(key) CacheRef.new(self, namespaced_key(key)) end |
#to_s ⇒ Object
221 |
# File 'lib/typed_cache/store.rb', line 221 def to_s = "Store(#{namespace})" |
#with_namespace(ns) ⇒ Object Also known as: at_namespace
192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/typed_cache/store.rb', line 192 def with_namespace(ns) new_namespace = case ns when Namespace then ns when Array then namespace.join(*ns) else # treat as nested segment under the current namespace namespace.nested(ns.to_s) end clone.tap { |store| store.namespace = new_namespace } end |
#write(key, value, **kwargs) ⇒ Object
Stores a value in the cache
89 90 91 92 93 94 95 96 |
# File 'lib/typed_cache/store.rb', line 89 def write(key, value, **kwargs) key = namespaced_key(key) backend.write(key, value, **kwargs) Either.right(Snapshot.cached(key, value)) rescue => e Either.left(StoreError.new(:write, key, "Failed to write to cache: #{e.message}", e)) end |
#write_all(values, **kwargs) ⇒ Object
100 101 102 103 104 105 |
# File 'lib/typed_cache/store.rb', line 100 def write_all(values, **kwargs) values.transform_keys! { |key| namespaced_key(key) } written_values = backend.write_multi(values, **kwargs) written_values.transform_values { |value| Snapshot.cached(key, value) } end |