typed_cache 0.3.2 → 0.4.0.pre.1

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 (53) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/typed_cache/backend.rb +80 -2
  4. data/lib/typed_cache/backends/active_support.rb +24 -98
  5. data/lib/typed_cache/backends/memory.rb +22 -73
  6. data/lib/typed_cache/backends.rb +2 -2
  7. data/lib/typed_cache/cache_builder.rb +131 -46
  8. data/lib/typed_cache/cache_key.rb +1 -1
  9. data/lib/typed_cache/cache_ref.rb +24 -55
  10. data/lib/typed_cache/decorator.rb +22 -12
  11. data/lib/typed_cache/decorators/instrumented.rb +12 -19
  12. data/lib/typed_cache/decorators.rb +7 -1
  13. data/lib/typed_cache/either.rb +28 -0
  14. data/lib/typed_cache/instrumenters.rb +6 -2
  15. data/lib/typed_cache/maybe.rb +28 -0
  16. data/lib/typed_cache/namespace.rb +1 -3
  17. data/lib/typed_cache/snapshot.rb +6 -0
  18. data/lib/typed_cache/store.rb +130 -78
  19. data/lib/typed_cache/version.rb +1 -1
  20. data/lib/typed_cache.rb +5 -2
  21. data/rbi/typed_cache/backend.rbi +44 -2
  22. data/rbi/typed_cache/backends/active_support.rbi +1 -1
  23. data/rbi/typed_cache/backends/memory.rbi +1 -1
  24. data/rbi/typed_cache/cache_builder.rbi +29 -10
  25. data/rbi/typed_cache/cache_key.rbi +14 -0
  26. data/rbi/typed_cache/cache_ref.rbi +6 -15
  27. data/rbi/typed_cache/decorator.rbi +23 -37
  28. data/rbi/typed_cache/decorators/instrumented.rbi +1 -1
  29. data/rbi/typed_cache/either.rbi +24 -0
  30. data/rbi/typed_cache/maybe.rbi +24 -0
  31. data/rbi/typed_cache/namespace.rbi +14 -0
  32. data/rbi/typed_cache/snapshot.rbi +6 -0
  33. data/rbi/typed_cache/store.rbi +26 -24
  34. data/rbi/typed_cache.rbi +1 -1
  35. data/sig/generated/typed_cache/backend.rbs +68 -2
  36. data/sig/generated/typed_cache/backends/active_support.rbs +13 -21
  37. data/sig/generated/typed_cache/backends/memory.rbs +10 -30
  38. data/sig/generated/typed_cache/backends.rbs +2 -2
  39. data/sig/generated/typed_cache/cache_builder.rbs +57 -16
  40. data/sig/generated/typed_cache/cache_ref.rbs +16 -37
  41. data/sig/generated/typed_cache/decorator.rbs +18 -6
  42. data/sig/generated/typed_cache/decorators/instrumented.rbs +3 -7
  43. data/sig/generated/typed_cache/decorators.rbs +9 -1
  44. data/sig/generated/typed_cache/either.rbs +24 -0
  45. data/sig/generated/typed_cache/instrumenters.rbs +4 -3
  46. data/sig/generated/typed_cache/maybe.rbs +24 -0
  47. data/sig/generated/typed_cache/namespace.rbs +0 -2
  48. data/sig/generated/typed_cache/snapshot.rbs +6 -0
  49. data/sig/generated/typed_cache/store.rbs +47 -40
  50. data/sig/generated/typed_cache.rbs +6 -2
  51. data.tar.gz.sig +0 -0
  52. metadata +3 -3
  53. metadata.gz.sig +0 -0
@@ -13,117 +13,202 @@ require 'dry/struct'
13
13
  require 'dry/types'
14
14
 
15
15
  module TypedCache
16
- class CacheBuilder
17
- # @rbs! type config = TypedCache::typed_cache_config
16
+ # @rbs!
17
+ # interface _CacheBuilder
18
+ # def build(namespace: Namespace) -> either[Error, Store[untyped]]
19
+ # def build!(namespace: Namespace) -> Store[untyped]
20
+ # def build_backend -> either[Error, Backend[untyped]]
21
+ # def build_backend! -> Backend[untyped]
22
+ # end
23
+
24
+ # @rbs!
25
+ # interface _CacheDefinition
26
+ # def with_backend(Symbol, *untyped, **untyped) -> (_CacheDefinition & _CacheBuilder)
27
+ # def with_decorator(Symbol, **untyped) -> self
28
+ # def with_instrumentation(Symbol) -> self
29
+ # end
30
+
31
+ class BackendConfig < Dry::Struct
32
+ attribute :name, Dry.Types::Symbol
33
+ attribute :args, Dry.Types::Array.of(Dry.Types::Any)
34
+ attribute :options, Dry.Types::Hash.map(Dry.Types::Symbol, Dry.Types::Any)
35
+ end
36
+
37
+ class DecoratorConfig < Dry::Struct
38
+ attribute :name, Dry.Types::Symbol
39
+ attribute :options, Dry.Types::Hash.map(Dry.Types::Symbol, Dry.Types::Any)
40
+ end
41
+
42
+ class CacheDefinition
43
+ # @rbs! include _CacheDefinition
44
+
18
45
  # @rbs! type instrumenter_source = :default | :dry | :rails | Instrumenter
19
46
 
20
- class BackendConfig < Dry::Struct
21
- attribute :name, Dry.Types::Symbol
22
- attribute :args, Dry.Types::Array.of(Dry.Types::Any)
23
- attribute :options, Dry.Types::Hash.map(Dry.Types::Symbol, Dry.Types::Any)
47
+ attr_reader :backend_config #: BackendConfig?
48
+ attr_reader :decorator_configs #: Array[DecoratorConfig]
49
+ attr_reader :instrumenter_source #: instrumenter_source
50
+
51
+ # @rbs (?BackendConfig?, ?Array[DecoratorConfig], ?instrumenter_source) -> void
52
+ def initialize(backend_config = nil, decorator_configs = [], instrumenter_source = nil)
53
+ @backend_config = backend_config
54
+ @decorator_configs = decorator_configs
55
+ @instrumenter_source = instrumenter_source
56
+ end
57
+
58
+ # @rbs override
59
+ # @rbs (Symbol, *untyped, **untyped) -> self
60
+ def with_backend(name, *args, **options)
61
+ self.class.new(BackendConfig.new(name:, args:, options:), @decorator_configs)
62
+ end
63
+
64
+ # @rbs override
65
+ # @rbs (Symbol, **untyped) -> self
66
+ def with_decorator(name, **options)
67
+ self.class.new(@backend_config, @decorator_configs + [DecoratorConfig.new(name:, options:)])
24
68
  end
25
69
 
26
- class DecoratorConfig < Dry::Struct
27
- attribute :name, Dry.Types::Symbol
28
- attribute :options, Dry.Types::Hash.map(Dry.Types::Symbol, Dry.Types::Any)
70
+ # @rbs override
71
+ # @rbs (instrumenter_source) -> self
72
+ def with_instrumentation(source = :default)
73
+ self.class.new(@backend_config, @decorator_configs, source)
29
74
  end
75
+ end
76
+
77
+ class CacheBuilder
78
+ # @rbs! include _CacheBuilder
79
+ # @rbs! include _CacheDefinition
30
80
 
31
- # @rbs (config, Registry[backend[untyped]], Registry[decorator[untyped]]) -> void
32
- def initialize(config, backend_registry = Backends, decorator_registry = Decorators)
33
- @config = config
81
+ # @rbs (?_CacheDefinition, ?Registry[backend[untyped]], ?Registry[decorator[untyped]]) -> void
82
+ def initialize(cache_definition = CacheDefinition.new, backend_registry = Backends, decorator_registry = Decorators)
34
83
  @backend_registry = backend_registry
35
84
  @decorator_registry = decorator_registry
36
85
 
37
- @backend_config = nil
38
- @decorator_configs = []
86
+ @cache_definition = cache_definition
39
87
  end
40
88
 
41
- # Builds the cache - the only method that can fail
42
- # @rbs (?Namespace) -> either[Error, Store[V]]
43
- def build(namespace = Namespace.at(@config.instrumentation.namespace))
89
+ # Builds the cache using the given namespace, defaulting to the root namespace
90
+ # @rbs (?Namespace) -> either[Error, Store[untyped]]
91
+ def build(namespace = Namespace.root)
44
92
  validate_and_build(namespace)
45
93
  end
46
94
 
95
+ # @rbs (Namespace) -> Store[untyped]
96
+ def build!(namespace = Namespace.root)
97
+ build(namespace).right_or_raise!
98
+ end
99
+
100
+ # Constructs only a typed backend from the registry without a Store wrapper
101
+ # @rbs () -> either[Error, Backend[untyped]]
102
+ def build_backend
103
+ create_backend
104
+ end
105
+
106
+ # Constructs only a typed backend from the registry without a Store wrapper,
107
+ # raises an error if the backend cannot be constructed
108
+ #
109
+ # @rbs () -> Backend[untyped]
110
+ def build_backend!
111
+ create_backend.right_or_raise!
112
+ end
113
+
47
114
  # Familiar Ruby fluent interface - always succeeds
48
115
  # Invalid configurations are caught during build()
49
116
  # @rbs (Symbol, *untyped, **untyped) -> self
50
117
  def with_backend(name, *args, **options)
51
- @backend_config = BackendConfig.new(name:, args:, options:)
52
- self
118
+ self.class.new(
119
+ @cache_definition.with_backend(name, *args, **options),
120
+ @backend_registry,
121
+ @decorator_registry,
122
+ )
53
123
  end
54
124
 
55
125
  # Adds an arbitrary decorator by registry key
56
126
  # @rbs (Symbol) -> self
57
127
  def with_decorator(name, **options)
58
- @decorator_configs << DecoratorConfig.new(name:, options:)
59
- self
128
+ self.class.new(
129
+ @cache_definition.with_decorator(name, **options),
130
+ @backend_registry,
131
+ @decorator_registry,
132
+ )
60
133
  end
61
134
 
62
135
  # Adds instrumentation using the specified strategy.
63
136
  # @rbs (instrumenter_source) -> either[Error, self]
64
137
  def with_instrumentation(source = :default)
65
- @instrumenter_source = source
66
- self
138
+ self.class.new(
139
+ @cache_definition.with_instrumentation(source),
140
+ @backend_registry,
141
+ @decorator_registry,
142
+ )
67
143
  end
68
144
 
69
145
  private
70
146
 
71
147
  # @rbs (Namespace) -> either[Error, Store[V]]
72
148
  def validate_and_build(namespace)
73
- create_store(namespace).bind do |store|
74
- apply_decorators(store).bind do |decorated_store|
75
- apply_instrumentation(decorated_store)
149
+ create_backend.bind do |backend|
150
+ apply_decorators(backend).bind do |decorated_backend|
151
+ apply_instrumentation(decorated_backend).bind do |instrumented_backend|
152
+ Either.right(Store.new(namespace, instrumented_backend))
153
+ end
76
154
  end
77
155
  end
78
156
  end
79
157
 
80
- # @rbs (Namespace) -> either[Error, Store[V]]
81
- def create_store(namespace)
82
- return Either.left(ArgumentError.new('Backend not configured')) unless @backend_config
158
+ # @rbs (Namespace) -> either[Error, Backend[untyped]]
159
+ def create_backend
160
+ backend_config = @cache_definition.backend_config
161
+
162
+ return Either.left(ArgumentError.new('Backend not configured')) unless backend_config
83
163
 
84
164
  # Prepend namespace to the arguments for the backend constructor
85
- @backend_registry.resolve(@backend_config.name, namespace, *@backend_config.args, **@backend_config.options)
165
+ @backend_registry.resolve(backend_config.name, *backend_config.args, **backend_config.options)
86
166
  end
87
167
 
88
- # @rbs (Store[V]) -> either[Error, Store[V]]
89
- def apply_decorators(store)
90
- return Either.right(store) if @decorator_configs.empty?
168
+ # @rbs (Backend[untyped]) -> either[Error, Decorator[untyped]]
169
+ def apply_decorators(backend)
170
+ decorator_configs = @cache_definition.decorator_configs
171
+
172
+ return Either.right(backend) if decorator_configs.empty?
91
173
 
92
- names = @decorator_configs.map(&:name)
174
+ names = decorator_configs.map(&:name)
93
175
 
94
176
  name_counts = names.tally
95
177
  duplicates = name_counts.keys.select { |name| name_counts[name] > 1 }
96
178
  return Either.left(ArgumentError.new("Duplicate decorator: #{duplicates.join(", ")}")) if duplicates.any?
97
179
 
98
- @decorator_configs.reduce(Either.right(store)) do |result, decorator_config|
99
- result.bind do |current_store|
100
- @decorator_registry.resolve(decorator_config.name, current_store, **decorator_config.options)
180
+ decorator_configs.reduce(Either.right(backend)) do |result, decorator_config|
181
+ result.bind do |current_backend|
182
+ @decorator_registry.resolve(decorator_config.name, current_backend, **decorator_config.options)
101
183
  end
102
184
  end
103
185
  rescue => e
104
186
  Either.left(StoreError.new(:decorator_application, 'decorator', "Failed to apply decorator: #{e.message}", e))
105
187
  end
106
188
 
107
- def apply_instrumentation(store)
108
- return Either.right(store) unless @instrumenter_source
189
+ # @rbs (Backend[untyped]) -> either[Error, Backend[untyped]]
190
+ def apply_instrumentation(backend)
191
+ instrumenter_source = @cache_definition.instrumenter_source
192
+
193
+ return Either.right(backend) unless instrumenter_source
109
194
 
110
195
  instrumenter =
111
- case @instrumenter_source
196
+ case instrumenter_source
112
197
  when Symbol
113
- Instrumenters.resolve(@instrumenter_source, namespace: @config.default_namespace)
198
+ Instrumenters.resolve(instrumenter_source)
114
199
  when Instrumenter
115
- Either.right(@instrumenter_source)
200
+ Either.right(instrumenter_source)
116
201
  else
117
202
  Either.left(TypedCache::TypeError.new(
118
203
  ':default | :dry | :rails | Instrumenter',
119
- @instrumenter_source.class.name,
120
- @instrumenter_source,
121
- "Invalid instrumenter source: #{@instrumenter_source.inspect}",
204
+ instrumenter_source.class.name,
205
+ instrumenter_source,
206
+ "Invalid instrumenter source: #{instrumenter_source.inspect}",
122
207
  ))
123
208
  end
124
209
 
125
210
  instrumenter.bind do |i|
126
- @decorator_registry.resolve(:instrumented, store, instrumenter: i)
211
+ @decorator_registry.resolve(:instrumented, backend, instrumenter: i)
127
212
  end
128
213
  end
129
214
  end
@@ -30,7 +30,7 @@ module TypedCache
30
30
 
31
31
  # @rbs () -> String
32
32
  def inspect
33
- "#<#{self.class} namespace=#{@namespace} key=#{@key}>"
33
+ "CacheKey(#{@namespace}:#{@key})"
34
34
  end
35
35
 
36
36
  # @rbs () -> Integer
@@ -21,7 +21,7 @@ module TypedCache
21
21
  end
22
22
 
23
23
  # Gets a value from the cache as a snapshot
24
- #: -> either[Error, Snapshot[V]]
24
+ #: -> either[Error, Snapshot[maybe[V]]]
25
25
  def read
26
26
  store.read(key)
27
27
  end
@@ -33,14 +33,14 @@ module TypedCache
33
33
  end
34
34
 
35
35
  # Deletes the value from the cache and returns the deleted value as a snapshot
36
- #: -> either[Error, Snapshot[V]]
36
+ #: -> either[Error, maybe[V]]
37
37
  def delete
38
38
  store.delete(key)
39
39
  end
40
40
 
41
41
  # Fetches a value from cache, computing and storing it if not found
42
42
  # The snapshot indicates whether the value came from cache or was computed
43
- #: () { -> V } -> either[Error, Snapshot[V]]
43
+ #: () { -> V? } -> either[Error, Snapshot[maybe[V]]]
44
44
  def fetch(&block)
45
45
  store.fetch(key, &block)
46
46
  end
@@ -60,24 +60,26 @@ module TypedCache
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
- read.map { |snapshot| snapshot.map(&block) }
63
+ read.map { |snapshot| snapshot.map { |mb| mb.map(&block) } }
64
64
  end
65
65
 
66
- # Binds over the cached value, allowing for monadic composition with snapshots
67
- #: [R] () { (V) -> either[Error, R] } -> either[Error, Snapshot[R]]
68
- def bind(&block)
69
- read.bind { |snapshot| snapshot.bind(&block) }
70
- end
71
-
72
- alias flat_map bind
73
-
74
66
  # Updates the cached value using the provided block
75
67
  # Returns the updated value as a snapshot with source=:updated
76
- #: () { (V) -> V } -> either[Error, Snapshot[V]]
68
+ #: () { (V) -> V? } -> either[Error, Snapshot[maybe[V]]]
77
69
  def update(&block)
78
70
  read.bind do |snapshot|
79
- new_value = yield(snapshot.value)
80
- write(new_value)
71
+ new_value = snapshot.value.bind do |value|
72
+ new_value = yield(value)
73
+ Maybe.wrap(new_value)
74
+ end
75
+
76
+ if new_value.some?
77
+ write(new_value.value).map do |snapshot|
78
+ snapshot.map { new_value }
79
+ end
80
+ else
81
+ delete.map { snapshot }
82
+ end
81
83
  rescue => e
82
84
  Either.left(StoreError.new(
83
85
  :update,
@@ -88,28 +90,9 @@ module TypedCache
88
90
  end
89
91
  end
90
92
 
91
- # Returns the cached value or a default if the cache is empty/errored
92
- #: (V) -> V
93
- def value_or(default)
94
- read.fold(
95
- ->(_error) { default },
96
- ->(snapshot) { snapshot.value },
97
- )
98
- end
99
-
100
- # Returns a Maybe containing the cached value, or None if not present
101
- # This provides a more functional approach than value_or
102
- #: -> maybe[V]
103
- def value_maybe
104
- read.fold(
105
- ->(_error) { Maybe.none },
106
- ->(snapshot) { Maybe.some(snapshot.value) },
107
- )
108
- end
109
-
110
93
  # Computes and caches a value if the cache is currently empty
111
94
  # Returns existing snapshot if present, computed snapshot if cache miss, error otherwise
112
- #: () { -> V } -> either[Error, Snapshot[V]]
95
+ #: () { -> V? } -> either[Error, Snapshot[maybe[V]]]
113
96
  def compute_if_absent(&block)
114
97
  fetch(&block).fold(
115
98
  ->(error) {
@@ -124,26 +107,6 @@ module TypedCache
124
107
  )
125
108
  end
126
109
 
127
- # Creates a new CacheRef with the same store but different key
128
- #: [R] (String) -> CacheRef[R]
129
- def with_key(new_key)
130
- CacheRef.new(store, store.namespace.key(new_key))
131
- end
132
-
133
- # Creates a scoped CacheRef by appending to the current key path
134
- #: [R] (String) -> CacheRef[R]
135
- def scope(scope_key)
136
- new_namespace = store.namespace.nested(key.key)
137
- new_store = store.with_namespace(new_namespace)
138
- CacheRef.new(new_store, new_namespace.key(scope_key))
139
- end
140
-
141
- # Pattern matching support for Either[Error, Snapshot[V]] results
142
- #: [R] (^(Error) -> R, ^(Snapshot[V]) -> R) -> R
143
- def fold(left_fn, right_fn)
144
- read.fold(left_fn, right_fn)
145
- end
146
-
147
110
  # Convenience method to work with the snapshot directly
148
111
  #: [R] () { (Snapshot[V]) -> R } -> either[Error, R]
149
112
  def with_snapshot(&block)
@@ -155,5 +118,11 @@ module TypedCache
155
118
  def with(&block)
156
119
  read.map { |snapshot| yield(snapshot.value) }
157
120
  end
121
+
122
+ # @rbs () -> String
123
+ def to_s = "CacheRef(#{key} from #{store.namespace})"
124
+
125
+ # @rbs () -> String
126
+ def inspect = "CacheRef(#{key}, #{store.inspect})"
158
127
  end
159
128
  end
@@ -9,21 +9,21 @@ module TypedCache
9
9
  module Decorator
10
10
  extend Forwardable
11
11
 
12
- include Store #[V]
13
- # @rbs! include Store::_Store[V]
14
- # @rbs! include Store::_Decorator[V]
15
-
16
12
  # @rbs!
17
- # def store: -> Store[V]
13
+ # interface _Decorator[V]
14
+ # def initialize: (Backend[V]) -> void
15
+ # end
18
16
 
19
- Store.instance_methods(false).each do |method_name|
20
- def_delegator :store, method_name
21
- end
17
+ include Backend #[V]
18
+ # @rbs! include Backend::_Backend[V]
19
+ # @rbs! include _Decorator[V]
22
20
 
23
21
  # @rbs override
24
- #: (cache_key) -> either[Error, CacheRef[V]]
25
- def ref(key)
26
- CacheRef.new(self, namespaced_key(key))
22
+ # @rbs () -> Backend[V]
23
+ def backend = raise NotImplementedError, "#{self.class} must implement #backend"
24
+
25
+ Backend.instance_methods(false).each do |method_name|
26
+ def_delegator :backend, method_name
27
27
  end
28
28
 
29
29
  # @rbs override
@@ -31,7 +31,17 @@ module TypedCache
31
31
  def initialize_copy(other)
32
32
  super
33
33
 
34
- @store = other.store.clone
34
+ @backend = other.backend.clone
35
35
  end
36
+
37
+ # @rbs override
38
+ # @rbs () -> String
39
+ def to_s = "#{self.class.name}(#{backend})"
40
+
41
+ # @rbs override
42
+ # @rbs () -> String
43
+ def inspect = "Decorator(#{self.class.name}, #{backend.inspect})"
36
44
  end
45
+
46
+ # @rbs! type decorator[V] = Decorator::_Decorator[V] & Backend[V]
37
47
  end
@@ -9,7 +9,7 @@ module TypedCache
9
9
 
10
10
  extend Forwardable
11
11
 
12
- attr_reader :store #: TypedCache::Store[V]
12
+ attr_reader :backend #: Backend[V]
13
13
  attr_reader :instrumenter #: Instrumenter
14
14
 
15
15
  class << self
@@ -27,7 +27,7 @@ module TypedCache
27
27
  return #{alias_prefix}_without_instrumentation(...) if @in_instrumentation
28
28
 
29
29
  key = #{alias_prefix}_instrumentation_key(...)
30
- instrumenter.instrument(:"#{operation}", key, store_type: store_type) do
30
+ instrumenter.instrument(:"#{operation}", key) do
31
31
  @in_instrumentation = true
32
32
 
33
33
  #{alias_prefix}_without_instrumentation(...)
@@ -42,9 +42,9 @@ module TypedCache
42
42
  end
43
43
  end
44
44
 
45
- #: (TypedCache::Store[V], instrumenter: Instrumenter) -> void
46
- def initialize(store, instrumenter:)
47
- @store = store
45
+ #: (Backend[V], instrumenter: Instrumenter) -> void
46
+ def initialize(backend, instrumenter:)
47
+ @backend = backend
48
48
  @instrumenter = instrumenter
49
49
 
50
50
  # Avoid instrumenting the cache calls themselves, fetch_all may call fetch for example
@@ -58,21 +58,14 @@ module TypedCache
58
58
  @instrumenter = other.instrumenter
59
59
  end
60
60
 
61
- # @rbs override
62
- #: -> String
63
- def store_type
64
- # Use polymorphism - delegate to the wrapped store
65
- "instrumented(#{store.store_type})"
66
- end
67
-
68
61
  # Additional methods that might exist on the wrapped store
69
62
  def respond_to_missing?(method_name, include_private = false)
70
- store.respond_to?(method_name, include_private) || super
63
+ backend.respond_to?(method_name, include_private) || super
71
64
  end
72
65
 
73
66
  def method_missing(method_name, *args, &block)
74
- if store.respond_to?(method_name)
75
- store.send(method_name, *args, &block)
67
+ if backend.respond_to?(method_name)
68
+ backend.send(method_name, *args, &block)
76
69
  else
77
70
  super
78
71
  end
@@ -80,12 +73,12 @@ module TypedCache
80
73
 
81
74
  # Instrument core operations with proper key extraction
82
75
  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('_') }
76
+ instrument(:read_multi) { |keys, *_| keys.map(&:to_s).join('_') }
77
+ instrument(:write) { |key, *_| key }
78
+ instrument(:write_multi) { |values, *_| values.map { |key, _| key.to_s }.join('_') }
86
79
  instrument(:delete) { |key, *_| key }
87
80
  instrument(:fetch) { |key, *_| key }
88
- instrument(:fetch_all) { |keys, *_| keys.map(&:to_s).join('_') }
81
+ instrument(:fetch_multi) { |keys, *_| keys.map(&:to_s).join('_') }
89
82
  instrument(:key?) { |key, *_| key }
90
83
  instrument(:clear) { 'all' }
91
84
  end
@@ -23,7 +23,7 @@ module TypedCache
23
23
  # lets end-users register their own via `Decorators.register`.
24
24
  REGISTRY = Registry.new('decorator', {
25
25
  instrumented: Instrumented,
26
- }).freeze
26
+ }).freeze #: Registry[Decorator]
27
27
 
28
28
  class << self
29
29
  extend Forwardable
@@ -31,6 +31,12 @@ module TypedCache
31
31
  # Delegate common registry helpers
32
32
  delegate [:resolve, :register, :available, :registered?] => :registry
33
33
 
34
+ # @rbs!
35
+ # def resolve: (Symbol, *untyped, **untyped) -> either[Error, Decorator[untyped]]
36
+ # def available: -> Array[Symbol]
37
+ # def register: (Symbol, Class) -> either[Error, void]
38
+ # def registered?: (Symbol) -> bool
39
+
34
40
  # @api private
35
41
  # @rbs () -> Registry[Store[untyped]]
36
42
  def registry = REGISTRY
@@ -86,6 +86,20 @@ module TypedCache
86
86
  def deconstruct_keys(keys)
87
87
  { error: }
88
88
  end
89
+
90
+ # @rbs (other: Object) -> bool
91
+ def ==(other)
92
+ other.is_a?(Left) && other.error == error
93
+ end
94
+
95
+ # @rbs () -> Integer
96
+ def hash = [Left, error].hash
97
+
98
+ # @rbs () -> String
99
+ def to_s = "Left(#{error})"
100
+
101
+ # @rbs () -> String
102
+ def inspect = "Left(#{error.inspect})"
89
103
  end
90
104
 
91
105
  # @rbs generic out R
@@ -139,5 +153,19 @@ module TypedCache
139
153
  def deconstruct_keys(keys)
140
154
  { value: }
141
155
  end
156
+
157
+ # @rbs (other: Object) -> bool
158
+ def ==(other)
159
+ other.is_a?(Right) && other.value == value
160
+ end
161
+
162
+ # @rbs () -> Integer
163
+ def hash = [Right, value].hash
164
+
165
+ # @rbs () -> String
166
+ def to_s = "Right(#{value})"
167
+
168
+ # @rbs () -> String
169
+ def inspect = "Right(#{value.inspect})"
142
170
  end
143
171
  end
@@ -19,7 +19,7 @@ module TypedCache
19
19
  rails: ActiveSupport,
20
20
  default: Null,
21
21
  null: Null,
22
- }) #: Registry[Symbol, Class[Instrumenter]]
22
+ }) #: Registry[Instrumenter]
23
23
 
24
24
  class << self
25
25
  extend Forwardable
@@ -28,8 +28,12 @@ module TypedCache
28
28
  # @rbs () -> Registry[Symbol, Class[Instrumenter]]
29
29
  def registry = REGISTRY
30
30
 
31
+ # @rbs (Symbol, ?namespace: String, **untyped) -> either[Error, Instrumenter]
32
+ def resolve(name, namespace: TypedCache.config.instrumentation.namespace, **options)
33
+ registry.resolve(name, namespace: namespace, **options)
34
+ end
35
+
31
36
  # @rbs! def register: (Symbol, Class[Instrumenter]) -> void
32
- # @rbs! def resolve: (Symbol, **untyped) -> either[Error, Instrumenter]
33
37
  # @rbs! def available: () -> Array[Symbol]
34
38
  # @rbs! def registered?: (Symbol) -> Boolean
35
39
 
@@ -78,6 +78,20 @@ module TypedCache
78
78
  def deconstruct_keys(keys)
79
79
  { value: }
80
80
  end
81
+
82
+ # @rbs (other: Object) -> bool
83
+ def ==(other)
84
+ other.is_a?(Some) && other.value == value
85
+ end
86
+
87
+ # @rbs () -> Integer
88
+ def hash = [Some, value].hash
89
+
90
+ # @rbs () -> String
91
+ def to_s = "Some(#{value})"
92
+
93
+ # @rbs () -> String
94
+ def inspect = "Some(#{value.inspect})"
81
95
  end
82
96
 
83
97
  class Nothing
@@ -106,5 +120,19 @@ module TypedCache
106
120
  # @rbs override
107
121
  #: -> V
108
122
  def value_or_raise! = raise TypedCache::TypeError, 'Nothing has no value'
123
+
124
+ # @rbs (other: Object) -> bool
125
+ def ==(other)
126
+ other.is_a?(Nothing)
127
+ end
128
+
129
+ # @rbs () -> Integer
130
+ def hash = [Nothing].hash
131
+
132
+ # @rbs () -> String
133
+ def to_s = 'Nothing'
134
+
135
+ # @rbs () -> String
136
+ def inspect = to_s
109
137
  end
110
138
  end
@@ -164,7 +164,7 @@ module TypedCache
164
164
  #
165
165
  # @rbs () -> String
166
166
  def inspect
167
- "#<#{self.class} #{@namespace}>"
167
+ "Namespace(#{@namespace})"
168
168
  end
169
169
 
170
170
  # @rbs () -> Integer
@@ -177,8 +177,6 @@ module TypedCache
177
177
  other.is_a?(self.class) && other.to_s == to_s
178
178
  end
179
179
 
180
- alias eql? ==
181
-
182
180
  private
183
181
 
184
182
  # @rbs (String) -> String
@@ -57,6 +57,12 @@ module TypedCache
57
57
 
58
58
  alias flat_map bind
59
59
 
60
+ # @rbs () -> String
61
+ def to_s = "Snapshot(#{key} retrieved at #{retrieved_at} from #{source} with value #{value})"
62
+
63
+ # @rbs () -> String
64
+ def inspect = "Snapshot(#{key}, #{value.inspect}, #{source}, #{retrieved_at})"
65
+
60
66
  class << self
61
67
  # Creates a snapshot for a cached value
62
68
  #: [V] (CacheKey, V) -> Snapshot[V]