typed_cache 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/README.md +25 -3
  4. data/examples.md +36 -2
  5. data/lib/typed_cache/backends/active_support.rb +50 -5
  6. data/lib/typed_cache/backends/memory.rb +5 -5
  7. data/lib/typed_cache/cache_key.rb +9 -1
  8. data/lib/typed_cache/cache_ref.rb +16 -16
  9. data/lib/typed_cache/decorator.rb +9 -1
  10. data/lib/typed_cache/decorators/instrumented.rb +21 -8
  11. data/lib/typed_cache/either.rb +22 -0
  12. data/lib/typed_cache/instrumenter.rb +6 -6
  13. data/lib/typed_cache/instrumenters/mixins/namespaced_singleton.rb +3 -0
  14. data/lib/typed_cache/instrumenters.rb +2 -1
  15. data/lib/typed_cache/maybe.rb +18 -0
  16. data/lib/typed_cache/namespace.rb +33 -6
  17. data/lib/typed_cache/railtie.rb +15 -0
  18. data/lib/typed_cache/snapshot.rb +18 -10
  19. data/lib/typed_cache/store.rb +47 -15
  20. data/lib/typed_cache/version.rb +1 -1
  21. data/lib/typed_cache.rb +4 -0
  22. data/rbi/typed_cache/backend.rbi +9 -0
  23. data/rbi/typed_cache/backends/active_support.rbi +13 -0
  24. data/rbi/typed_cache/backends/memory.rbi +13 -0
  25. data/rbi/typed_cache/backends.rbi +19 -0
  26. data/rbi/typed_cache/cache_builder.rbi +23 -0
  27. data/rbi/typed_cache/cache_key.rbi +16 -0
  28. data/rbi/typed_cache/cache_ref.rbi +56 -0
  29. data/rbi/typed_cache/decorator.rbi +67 -0
  30. data/rbi/typed_cache/decorators/instrumented.rbi +13 -0
  31. data/rbi/typed_cache/decorators.rbi +19 -0
  32. data/rbi/typed_cache/either.rbi +122 -0
  33. data/rbi/typed_cache/errors.rbi +20 -0
  34. data/rbi/typed_cache/instrumenter.rbi +45 -0
  35. data/rbi/typed_cache/instrumenters/mixins/namedspaced_singleton.rbi +33 -0
  36. data/rbi/typed_cache/instrumenters.rbi +19 -0
  37. data/rbi/typed_cache/maybe.rbi +108 -0
  38. data/rbi/typed_cache/namespace.rbi +30 -0
  39. data/rbi/typed_cache/snapshot.rbi +54 -0
  40. data/rbi/typed_cache/store.rbi +71 -0
  41. data/rbi/typed_cache/version.rbi +5 -0
  42. data/rbi/typed_cache.rbi +49 -0
  43. data/sig/generated/typed_cache/backends/active_support.rbs +14 -2
  44. data/sig/generated/typed_cache/backends/memory.rbs +2 -2
  45. data/sig/generated/typed_cache/cache_key.rbs +5 -0
  46. data/sig/generated/typed_cache/cache_ref.rbs +4 -4
  47. data/sig/generated/typed_cache/decorator.rbs +4 -0
  48. data/sig/generated/typed_cache/decorators/instrumented.rbs +4 -4
  49. data/sig/generated/typed_cache/either.rbs +24 -0
  50. data/sig/generated/typed_cache/instrumenter.rbs +5 -5
  51. data/sig/generated/typed_cache/instrumenters/mixins/namespaced_singleton.rbs +3 -0
  52. data/sig/generated/typed_cache/instrumenters.rbs +2 -0
  53. data/sig/generated/typed_cache/maybe.rbs +20 -0
  54. data/sig/generated/typed_cache/namespace.rbs +24 -3
  55. data/sig/generated/typed_cache/railtie.rbs +6 -0
  56. data/sig/generated/typed_cache/snapshot.rbs +12 -6
  57. data/sig/generated/typed_cache/store.rbs +23 -8
  58. data/sig/generated/typed_cache.rbs +2 -0
  59. data/typed_cache.gemspec +1 -1
  60. data.tar.gz.sig +0 -0
  61. metadata +26 -3
  62. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2cd862f92c635a4ee2fa15fa26186e56226d9dca6a132ad7de2f96fc9935ee31
4
- data.tar.gz: a862c34b4e86e838e4a30bfda8149357581782d4e2c64dd6a37882724930955d
3
+ metadata.gz: 52137d39af663110c085558e5d6885fbe22d02b4a185f7a1ef3f4aa2dbe6a619
4
+ data.tar.gz: 030bf378a6effe782ab28fdb876c64c73d8997e5a3a9ec543735abe55973e421
5
5
  SHA512:
6
- metadata.gz: 2df948e5a9e9808c13547e940547484ad9cff0ce75085f3ee512a8ef866459dd791c7d1c85147b65dbb6ccbacea973cc412b44d3be12f7cb564c94afee452f2a
7
- data.tar.gz: 57e1c99cb2c11a82e1b9add65357a8abacdc0260ea1ca66c6cd3616ac13db73439b24c25643eed5425a1a23fb62984bf2feb4ce854c1a54f1bcbd790248e0f2d
6
+ metadata.gz: 38641da2ef43033f286833d84492b02f09dcfca291e7eab53c93bf6c9f737de5f077984a4f742de06c62a0c90db19d00bbc03cb032485143e9a542576fb5f90c
7
+ data.tar.gz: 2fb8cd40d84bb43a3f9905ccc7c0d30d853cdf0b030cf192a6e17517545ac94f256a3371f85bcd068e4f08d12c7a32a937f8eb3a87622b8b3a16429e772bd8bb
checksums.yaml.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  TypedCache is a lightweight, type-safe façade around your favourite Ruby cache
10
10
  stores. It adds three things on top of the raw back-end implementation:
11
11
 
12
- 1. **Namespacing** – hierarchical `Namespace` helpers prevent key collisions.
12
+ 1. **Namespacing** – hierarchical `Namespace` helpers prevent key collisions. You can create nested namespaces easily, like `Namespace.at("users", "profiles", "avatars")`.
13
13
  2. **Stronger types** – RBS signatures as well as monadic types like `Either`, `Maybe`, and `Snapshot` wrap cache results so you always know whether you have a value, an error, or a cache-miss.
14
14
  3. **Composable decorators** – behaviours like instrumentation can be layered
15
15
  on without touching the underlying store.
@@ -125,9 +125,9 @@ result.fold(
125
125
  )
126
126
  ```
127
127
 
128
- ## The `CacheRef` API
128
+ ## The `CacheRef` and `Store` APIs
129
129
 
130
- While you can call `get`, `set`, and `fetch` directly on the `store`, the more powerful way to work with TypedCache is via the `CacheRef` object. It provides a rich, monadic API for a single cache key.
130
+ While you can call `get`, `set`, and `fetch` directly on the `store`, the more powerful way to work with TypedCache is via the `CacheRef` object. It provides a rich, monadic API for a single cache key. The `Store` also provides `fetch_all` for batch operations.
131
131
 
132
132
  You get a `CacheRef` by calling `store.ref(key)`:
133
133
 
@@ -154,6 +154,28 @@ name_either = user_ref.map { |user| user[:name] }
154
154
  puts "User name is: #{name_either.value.value}" # unwrap Either, then Snapshot
155
155
  ```
156
156
 
157
+ ### Batch Operations with `fetch_all`
158
+
159
+ For retrieving multiple keys at once, the `Store` provides a `fetch_all` method. This is more efficient than fetching keys one by one, especially with remote back-ends like Redis.
160
+
161
+ It takes a list of keys and a block to compute the values for any missing keys.
162
+
163
+ ```ruby
164
+ user_refs = store.fetch_all("users:123", "users:456") do |missing_key|
165
+ # This block is called for each cache miss
166
+ user_id = missing_key.split(":").last
167
+ puts "Cache miss for #{missing_key}! Computing..."
168
+ { id: user_id, name: "Fetched User #{user_id}" }
169
+ end
170
+
171
+ user_refs.each do |key, snapshot_either|
172
+ snapshot_either.fold(
173
+ ->(err) { warn "Error for #{key}: #{err.message}" },
174
+ ->(snapshot) { puts "Got value for #{key}: #{snapshot.value}" }
175
+ )
176
+ end
177
+ ```
178
+
157
179
  The `CacheRef` API encourages a functional style and makes composing cache operations safe and predictable.
158
180
 
159
181
  ## Instrumentation
data/examples.md CHANGED
@@ -70,9 +70,26 @@ posts_store = base_builder.build(TypedCache::Namespace.at("posts")).value
70
70
  comments_store = base_builder.build(TypedCache::Namespace.at("comments")).value
71
71
  ```
72
72
 
73
- ## CacheRef API
73
+ ## Advanced Namespacing
74
74
 
75
- The `CacheRef` is the most powerful way to interact with a cache key.
75
+ You can create nested namespaces using variadic arguments to `Namespace.at` or by chaining `join`.
76
+
77
+ ```ruby
78
+ # These are equivalent
79
+ ns1 = TypedCache::Namespace.at("app", "v1", "users")
80
+ ns2 = TypedCache::Namespace.at("app").join("v1").join("users")
81
+
82
+ puts ns1.to_s # => "app:v1:users"
83
+ puts ns2.to_s # => "app:v1:users"
84
+
85
+ # You can then build a store with this complex namespace
86
+ user_store = base_builder.build(ns1).value
87
+ user_store.ref("123").set({ name: "Deeply Nested" })
88
+ ```
89
+
90
+ ## CacheRef and Store APIs
91
+
92
+ The `CacheRef` is the most powerful way to interact with a single cache key, while the `Store` provides batch operations.
76
93
 
77
94
  ```ruby
78
95
  ref = store.ref("some-key") # => CacheRef
@@ -90,6 +107,23 @@ result.fold(
90
107
  )
91
108
  ```
92
109
 
110
+ ### Fetch all (get or compute)
111
+
112
+ The `fetch_all` method on the `store` is used for bulk-retrieving items. It gets all existing values from the cache and for the ones that are missing, it runs the block, stores the result, and returns it.
113
+
114
+ ```ruby
115
+ results = store.fetch_all("user:1", "user:2") do |missing_key|
116
+ # logic to compute the value for a missing key
117
+ "computed-#{missing_key}"
118
+ end
119
+
120
+ results.each do |key, snapshot_either|
121
+ snapshot_either.map do |snapshot|
122
+ puts "#{key} -> #{snapshot.value} (from_cache?=#{snapshot.from_cache?})"
123
+ end
124
+ end
125
+ ```
126
+
93
127
  ### Fetch (get or compute)
94
128
 
95
129
  The `fetch` method is the most common operation. It gets a value from the cache, but if it's missing, it runs the block, stores the result, and returns it.
@@ -20,24 +20,24 @@ module TypedCache
20
20
 
21
21
  # @rbs override
22
22
  #: (cache_key) -> either[Error, Snapshot[V]]
23
- def get(key)
23
+ def read(key)
24
24
  cache_key_str = namespaced_key(key).to_s
25
25
  raw_value = cache_store.read(cache_key_str, default_options)
26
26
  return Either.left(CacheMissError.new(key)) if raw_value.nil?
27
27
 
28
- Either.right(Snapshot.new(raw_value, source: :cache))
28
+ Either.right(Snapshot.cached(key, raw_value))
29
29
  rescue => e
30
30
  Either.left(StoreError.new(:get, key, "Failed to read from cache: #{e.message}", e))
31
31
  end
32
32
 
33
33
  # @rbs override
34
34
  #: (cache_key, V) -> either[Error, Snapshot[V]]
35
- def set(key, value)
35
+ def write(key, value)
36
36
  cache_key_str = namespaced_key(key).to_s
37
37
  success = cache_store.write(cache_key_str, value, default_options)
38
38
 
39
39
  if success
40
- Either.right(Snapshot.new(value, source: :cache))
40
+ Either.right(Snapshot.cached(key, value))
41
41
  else
42
42
  Either.left(StoreError.new(:set, key, 'Failed to write to cache', nil))
43
43
  end
@@ -45,10 +45,19 @@ module TypedCache
45
45
  Either.left(StoreError.new(:set, key, "Failed to write to cache: #{e.message}", e))
46
46
  end
47
47
 
48
+ # @rbs override
49
+ #: (Hash[cache_key, V]) -> either[Error, Array[Snapshot[V]]]
50
+ def write_all(values)
51
+ results = cache_store.write_multi(values.map { |key, value| [namespaced_key(key).to_s, value] }.to_h, default_options)
52
+ Either.right(results.map { |key, value| Snapshot.cached(key, value) })
53
+ rescue => e
54
+ Either.left(StoreError.new(:set_all, values, "Failed to write to cache: #{e.message}", e))
55
+ end
56
+
48
57
  # @rbs override
49
58
  #: (cache_key) -> either[Error, Snapshot[V]]
50
59
  def delete(key)
51
- get(key).fold(
60
+ read(key).fold(
52
61
  ->(error) { Either.left(error) },
53
62
  ->(snapshot) {
54
63
  cache_key_str = namespaced_key(key).to_s
@@ -60,6 +69,42 @@ module TypedCache
60
69
  Either.left(StoreError.new(:delete, key, "Failed to delete from cache: #{e.message}", e))
61
70
  end
62
71
 
72
+ # @rbs override
73
+ #: (Array[cache_key]) -> either[Error, Array[Snapshot[V]]]
74
+ def read_all(keys)
75
+ results = cache_store.read_multi(*keys.map { |key| namespaced_key(key).to_s }, default_options)
76
+ Either.right(results.map { |key, value| [key, Snapshot.cached(key, value)] }.to_h)
77
+ end
78
+
79
+ # @rbs override
80
+ #: (Array[cache_key]) { (CacheKey) -> V? } -> either[Error, Array[Snapshot[V]]]
81
+ def fetch_all(keys, &block)
82
+ cache_keys = keys.map { |key| namespaced_key(key) }
83
+ key_map = cache_keys.index_by(&:to_s)
84
+
85
+ computed_keys = Set.new #: Set[String]
86
+ results = cache_store.fetch_multi(*key_map.keys, default_options) do |key|
87
+ computed_keys << key
88
+ yield(key_map[key])
89
+ end
90
+
91
+ snapshots = [] #: Array[Snapshot[V]]
92
+
93
+ results.each do |key, value|
94
+ maybe_value = Maybe.wrap(value)
95
+ snapshots <<
96
+ if computed_keys.include?(key)
97
+ Snapshot.computed(key, maybe_value)
98
+ else
99
+ Snapshot.cached(key, maybe_value)
100
+ end
101
+ end
102
+
103
+ Either.right(snapshots)
104
+ rescue StandardError => e
105
+ Either.left(StoreError.new(:fetch_all, keys, "Failed to fetch from cache: #{e.message}", e))
106
+ end
107
+
63
108
  # @rbs override
64
109
  #: (cache_key) -> bool
65
110
  def key?(key)
@@ -69,7 +69,7 @@ module TypedCache
69
69
 
70
70
  # @rbs override
71
71
  #: (cache_key) -> either[Error, Snapshot[V]]
72
- def get(key)
72
+ def read(key)
73
73
  key = namespaced_key(key)
74
74
  return Either.left(CacheMissError.new(key)) unless backing_store.key?(key)
75
75
 
@@ -80,17 +80,17 @@ module TypedCache
80
80
  return Either.left(CacheMissError.new(key))
81
81
  end
82
82
 
83
- Either.right(Snapshot.new(entry.value, source: :cache))
83
+ Either.right(Snapshot.cached(key, entry.value))
84
84
  end
85
85
 
86
86
  # @rbs override
87
87
  #: (cache_key, V) -> either[Error, Snapshot[V]]
88
- def set(key, value)
88
+ def write(key, value)
89
89
  key = namespaced_key(key)
90
90
  expires_at = Clock.now + @ttl
91
91
  entry = Entry.new(value: value, expires_at: expires_at)
92
92
  backing_store[key] = entry
93
- Either.right(Snapshot.new(value, source: :cache))
93
+ Either.right(Snapshot.cached(key, value))
94
94
  rescue => e
95
95
  Either.left(StoreError.new(
96
96
  :set,
@@ -108,7 +108,7 @@ module TypedCache
108
108
  if entry.nil?
109
109
  Either.left(CacheMissError.new(key))
110
110
  else
111
- Either.right(Snapshot.new(entry.value, source: :cache))
111
+ Either.right(Snapshot.cached(key, entry.value))
112
112
  end
113
113
  end
114
114
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'forwardable'
3
4
  require_relative 'namespace'
4
5
 
5
6
  module TypedCache
@@ -22,7 +23,7 @@ module TypedCache
22
23
 
23
24
  # @rbs () -> String
24
25
  def to_s
25
- "#{@namespace}:#{@key}"
26
+ [@namespace.to_s, @key].join(delimiter)
26
27
  end
27
28
 
28
29
  alias cache_key to_s
@@ -43,5 +44,12 @@ module TypedCache
43
44
  end
44
45
 
45
46
  alias eql? ==
47
+
48
+ private
49
+
50
+ # @rbs (String) -> String
51
+ def delimiter
52
+ TypedCache.config.cache_delimiter
53
+ end
46
54
  end
47
55
  end
@@ -22,14 +22,14 @@ module TypedCache
22
22
 
23
23
  # Gets a value from the cache as a snapshot
24
24
  #: -> either[Error, Snapshot[V]]
25
- def get
26
- store.get(key)
25
+ def read
26
+ store.read(key)
27
27
  end
28
28
 
29
29
  # Sets a value in the cache and returns it as an updated snapshot
30
30
  #: (V) -> either[Error, Snapshot[V]]
31
- def set(value)
32
- store.set(key, value)
31
+ def write(value)
32
+ store.write(key, value)
33
33
  end
34
34
 
35
35
  # Deletes the value from the cache and returns the deleted value as a snapshot
@@ -48,25 +48,25 @@ module TypedCache
48
48
  # Checks if the cache contains a value for this key
49
49
  #: -> bool
50
50
  def present?
51
- store.get(key).right?
51
+ store.read(key).right?
52
52
  end
53
53
 
54
54
  # Checks if the cache is empty for this key
55
55
  #: -> bool
56
56
  def empty?
57
- store.get(key).left?
57
+ store.read(key).left?
58
58
  end
59
59
 
60
60
  # Maps over the cached value if it exists, preserving snapshot metadata
61
61
  #: [R] () { (V) -> R } -> either[Error, Snapshot[R]]
62
62
  def map(&block)
63
- get.map { |snapshot| snapshot.map(&block) }
63
+ read.map { |snapshot| snapshot.map(&block) }
64
64
  end
65
65
 
66
66
  # Binds over the cached value, allowing for monadic composition with snapshots
67
67
  #: [R] () { (V) -> either[Error, R] } -> either[Error, Snapshot[R]]
68
68
  def bind(&block)
69
- get.bind { |snapshot| snapshot.bind(&block) }
69
+ read.bind { |snapshot| snapshot.bind(&block) }
70
70
  end
71
71
 
72
72
  alias flat_map bind
@@ -75,9 +75,9 @@ module TypedCache
75
75
  # Returns the updated value as a snapshot with source=:updated
76
76
  #: () { (V) -> V } -> either[Error, Snapshot[V]]
77
77
  def update(&block)
78
- get.bind do |snapshot|
78
+ read.bind do |snapshot|
79
79
  new_value = yield(snapshot.value)
80
- set(new_value)
80
+ write(new_value)
81
81
  rescue => e
82
82
  Either.left(StoreError.new(
83
83
  :update,
@@ -91,7 +91,7 @@ module TypedCache
91
91
  # Returns the cached value or a default if the cache is empty/errored
92
92
  #: (V) -> V
93
93
  def value_or(default)
94
- get.fold(
94
+ read.fold(
95
95
  ->(_error) { default },
96
96
  ->(snapshot) { snapshot.value },
97
97
  )
@@ -101,7 +101,7 @@ module TypedCache
101
101
  # This provides a more functional approach than value_or
102
102
  #: -> maybe[V]
103
103
  def value_maybe
104
- get.fold(
104
+ read.fold(
105
105
  ->(_error) { Maybe.none },
106
106
  ->(snapshot) { Maybe.some(snapshot.value) },
107
107
  )
@@ -139,21 +139,21 @@ module TypedCache
139
139
  end
140
140
 
141
141
  # Pattern matching support for Either[Error, Snapshot[V]] results
142
- #: [R] () { (Error) -> R } () { (Snapshot[V]) -> R } -> R
142
+ #: [R] (^(Error) -> R, ^(Snapshot[V]) -> R) -> R
143
143
  def fold(left_fn, right_fn)
144
- get.fold(left_fn, right_fn)
144
+ read.fold(left_fn, right_fn)
145
145
  end
146
146
 
147
147
  # Convenience method to work with the snapshot directly
148
148
  #: [R] () { (Snapshot[V]) -> R } -> either[Error, R]
149
149
  def with_snapshot(&block)
150
- get.map(&block)
150
+ read.map(&block)
151
151
  end
152
152
 
153
153
  # Convenience method to work with just the value (losing snapshot context)
154
154
  #: [R] () { (V) -> R } -> either[Error, R]
155
155
  def with(&block)
156
- get.map { |snapshot| yield(snapshot.value) }
156
+ read.map { |snapshot| yield(snapshot.value) }
157
157
  end
158
158
  end
159
159
  end
@@ -23,7 +23,15 @@ module TypedCache
23
23
  # @rbs override
24
24
  #: (cache_key) -> either[Error, CacheRef[V]]
25
25
  def ref(key)
26
- CacheRef.new(self, key)
26
+ CacheRef.new(self, namespaced_key(key))
27
+ end
28
+
29
+ # @rbs override
30
+ #: (self) -> void
31
+ def initialize_copy(other)
32
+ super
33
+
34
+ @store = other.store.clone
27
35
  end
28
36
  end
29
37
  end
@@ -24,9 +24,15 @@ module TypedCache
24
24
 
25
25
  class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
26
26
  def #{alias_prefix}_with_instrumentation(...)
27
+ return #{alias_prefix}_without_instrumentation(...) if @in_instrumentation
28
+
27
29
  key = #{alias_prefix}_instrumentation_key(...)
28
30
  instrumenter.instrument(:"#{operation}", key, store_type: store_type) do
31
+ @in_instrumentation = true
32
+
29
33
  #{alias_prefix}_without_instrumentation(...)
34
+ ensure
35
+ @in_instrumentation = false
30
36
  end
31
37
  end
32
38
  RUBY
@@ -40,6 +46,16 @@ module TypedCache
40
46
  def initialize(store, instrumenter:)
41
47
  @store = store
42
48
  @instrumenter = instrumenter
49
+
50
+ # Avoid instrumenting the cache calls themselves, fetch_all may call fetch for example
51
+ @in_instrumentation = false
52
+ end
53
+
54
+ # @rbs override
55
+ #: (self) -> self
56
+ def initialize_copy(other)
57
+ super
58
+ @instrumenter = other.instrumenter
43
59
  end
44
60
 
45
61
  # @rbs override
@@ -49,12 +65,6 @@ module TypedCache
49
65
  "instrumented(#{store.store_type})"
50
66
  end
51
67
 
52
- # @rbs override
53
- # @rbs (key) -> CacheRef[V]
54
- def ref(key)
55
- CacheRef.new(self, key)
56
- end
57
-
58
68
  # Additional methods that might exist on the wrapped store
59
69
  def respond_to_missing?(method_name, include_private = false)
60
70
  store.respond_to?(method_name, include_private) || super
@@ -69,10 +79,13 @@ module TypedCache
69
79
  end
70
80
 
71
81
  # Instrument core operations with proper key extraction
72
- instrument(:get) { |key, *_| key }
73
- instrument(:set) { |key, *_| key }
82
+ instrument(:read) { |key, *_| key }
83
+ instrument(:read_all) { |keys, *_| keys.map(&:to_s).join('_') }
84
+ instrument(:write) { |key, *_| key }
85
+ instrument(:write_all) { |values, *_| values.map { |key, _| key.to_s }.join('_') }
74
86
  instrument(:delete) { |key, *_| key }
75
87
  instrument(:fetch) { |key, *_| key }
88
+ instrument(:fetch_all) { |keys, *_| keys.map(&:to_s).join('_') }
76
89
  instrument(:key?) { |key, *_| key }
77
90
  instrument(:clear) { 'all' }
78
91
  end
@@ -27,6 +27,8 @@ module TypedCache
27
27
  # @rbs! interface _Either[out E, out R]
28
28
  # def left?: -> bool
29
29
  # def right?: -> bool
30
+ # def right_or_else: (^(E) -> void) -> R
31
+ # def right_or_raise!: -> R
30
32
  # def map: [T] () { (R) -> T } -> either[E, T]
31
33
  # def bind: [E2, R2] () { (R) -> either[E2, R2] } -> either[E | E2, R2]
32
34
  # def map_left: [F] () { (E) -> F } -> either[F, R]
@@ -39,6 +41,8 @@ module TypedCache
39
41
 
40
42
  attr_reader :error #: E
41
43
 
44
+ alias value error
45
+
42
46
  #: (E) -> void
43
47
  def initialize(error)
44
48
  @error = error
@@ -52,6 +56,14 @@ module TypedCache
52
56
  #: -> false
53
57
  def right? = false
54
58
 
59
+ # @rbs override
60
+ #: (^(E) -> void) -> bot
61
+ def right_or_else(&) = yield(error)
62
+
63
+ # @rbs override
64
+ #: -> bot
65
+ def right_or_raise! = raise(error)
66
+
55
67
  # @rbs override
56
68
  #: [T] () { (R) -> T } -> either[E, T]
57
69
  def map(&) = self
@@ -82,6 +94,8 @@ module TypedCache
82
94
 
83
95
  attr_reader :value #: R
84
96
 
97
+ alias result value
98
+
85
99
  #: (R) -> void
86
100
  def initialize(value)
87
101
  @value = value
@@ -95,6 +109,14 @@ module TypedCache
95
109
  #: -> true
96
110
  def right? = true
97
111
 
112
+ # @rbs override
113
+ #: (^(E) -> void) -> R
114
+ def right_or_else(&) = value
115
+
116
+ # @rbs override
117
+ #: -> R
118
+ def right_or_raise! = value
119
+
98
120
  # @rbs override
99
121
  #: [T] () { (R) -> T } -> either[E, T]
100
122
  def map(&) = Right.new(yield(value))
@@ -6,7 +6,7 @@ module TypedCache
6
6
  # @rbs! type event = Dry::Events::Event | ActiveSupport::Notifications::Event
7
7
 
8
8
  # @rbs [R](String, String, **untyped) { -> R } -> R
9
- def instrument(event_name, key, **payload)
9
+ def instrument(event_name, key, **payload, &)
10
10
  raise NotImplementedError, "#{self.class} must implement #instrument"
11
11
  end
12
12
 
@@ -20,21 +20,21 @@ module TypedCache
20
20
  config.namespace
21
21
  end
22
22
 
23
+ # @rbs () -> bool
24
+ def enabled? = config.enabled
25
+
26
+ private
27
+
23
28
  # @rbs (String, String, **untyped) -> Hash[Symbol, untyped]
24
29
  def build_payload(operation, key, **payload)
25
30
  { namespace:, key:, operation: }.merge(payload)
26
31
  end
27
32
 
28
- # @rbs () -> bool
29
- def enabled? = config.enabled
30
-
31
33
  # @rbs (String) -> String
32
34
  def event_name(operation)
33
35
  "#{namespace}.#{operation}"
34
36
  end
35
37
 
36
- private
37
-
38
38
  # @rbs () -> TypedCache::_TypedCacheInstrumentationConfig
39
39
  def config
40
40
  TypedCache.config.instrumentation
@@ -43,6 +43,9 @@ module TypedCache
43
43
  namespace_cache.get(namespace.to_s)
44
44
  end
45
45
 
46
+ # @rbs () -> void
47
+ def clear_namespace_cache = namespace_cache.clear
48
+
46
49
  # @rbs () -> Concurrent::Map[String, Class[Instrumenter & NamespacedSingleton]]
47
50
  def namespace_cache = @namespace_cache ||= Concurrent::Map.new # rubocop:disable ThreadSafety
48
51
  end
@@ -28,11 +28,12 @@ module TypedCache
28
28
  # @rbs () -> Registry[Symbol, Class[Instrumenter]]
29
29
  def registry = REGISTRY
30
30
 
31
+ # @rbs! def register: (Symbol, Class[Instrumenter]) -> void
31
32
  # @rbs! def resolve: (Symbol, **untyped) -> either[Error, Instrumenter]
32
33
  # @rbs! def available: () -> Array[Symbol]
33
34
  # @rbs! def registered?: (Symbol) -> Boolean
34
35
 
35
- def_delegators :registry, :resolve, :available, :registered?
36
+ def_delegators :registry, :resolve, :available, :registered?, :register
36
37
  end
37
38
  end
38
39
  end
@@ -33,6 +33,8 @@ module TypedCache
33
33
  # def nothing?: -> bool
34
34
  # def map: [T] () { (V) -> T } -> maybe[T]
35
35
  # def bind: [T] () { (V) -> maybe[T] } -> maybe[T]
36
+ # def value_or: [T] (T) -> T
37
+ # def value_or_raise!: -> V
36
38
  # alias flat_map bind
37
39
  # end
38
40
 
@@ -62,6 +64,14 @@ module TypedCache
62
64
  #: [T] () { (V) -> maybe[T] } -> maybe[T]
63
65
  def bind(&) = yield(value)
64
66
 
67
+ # @rbs override
68
+ #: [T] (T) -> T
69
+ def value_or(default) = value
70
+
71
+ # @rbs override
72
+ #: -> V
73
+ def value_or_raise! = value
74
+
65
75
  alias flat_map bind
66
76
 
67
77
  #: (Array[top]) -> ({ value: V })
@@ -88,5 +98,13 @@ module TypedCache
88
98
  #: [T] () { (V) -> maybe[T] } -> maybe[T]
89
99
  def bind(&) = self
90
100
  alias flat_map bind
101
+
102
+ # @rbs override
103
+ #: [T] (T) -> T
104
+ def value_or(default) = default
105
+
106
+ # @rbs override
107
+ #: -> V
108
+ def value_or_raise! = raise TypedCache::TypeError, 'Nothing has no value'
91
109
  end
92
110
  end