typed_cache 0.1.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 (52) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE +201 -0
  4. data/README.md +168 -0
  5. data/examples.md +190 -0
  6. data/lib/typed_cache/backend.rb +16 -0
  7. data/lib/typed_cache/backends/active_support.rb +113 -0
  8. data/lib/typed_cache/backends/memory.rb +166 -0
  9. data/lib/typed_cache/backends.rb +34 -0
  10. data/lib/typed_cache/cache_builder.rb +77 -0
  11. data/lib/typed_cache/cache_key.rb +45 -0
  12. data/lib/typed_cache/cache_ref.rb +155 -0
  13. data/lib/typed_cache/clock.rb +23 -0
  14. data/lib/typed_cache/decorator.rb +12 -0
  15. data/lib/typed_cache/decorators.rb +35 -0
  16. data/lib/typed_cache/either.rb +121 -0
  17. data/lib/typed_cache/errors.rb +64 -0
  18. data/lib/typed_cache/instrumentation.rb +112 -0
  19. data/lib/typed_cache/maybe.rb +92 -0
  20. data/lib/typed_cache/namespace.rb +162 -0
  21. data/lib/typed_cache/registry.rb +55 -0
  22. data/lib/typed_cache/snapshot.rb +72 -0
  23. data/lib/typed_cache/store/instrumented.rb +83 -0
  24. data/lib/typed_cache/store.rb +152 -0
  25. data/lib/typed_cache/version.rb +5 -0
  26. data/lib/typed_cache.rb +58 -0
  27. data/sig/generated/typed_cache/backend.rbs +17 -0
  28. data/sig/generated/typed_cache/backends/active_support.rbs +56 -0
  29. data/sig/generated/typed_cache/backends/memory.rbs +95 -0
  30. data/sig/generated/typed_cache/backends.rbs +21 -0
  31. data/sig/generated/typed_cache/cache_builder.rbs +37 -0
  32. data/sig/generated/typed_cache/cache_key.rbs +33 -0
  33. data/sig/generated/typed_cache/cache_ref.rbs +91 -0
  34. data/sig/generated/typed_cache/clock.rbs +15 -0
  35. data/sig/generated/typed_cache/decorator.rbs +14 -0
  36. data/sig/generated/typed_cache/decorators.rbs +25 -0
  37. data/sig/generated/typed_cache/either.rbs +106 -0
  38. data/sig/generated/typed_cache/errors.rbs +51 -0
  39. data/sig/generated/typed_cache/instrumentation.rbs +30 -0
  40. data/sig/generated/typed_cache/maybe.rbs +85 -0
  41. data/sig/generated/typed_cache/namespace.rbs +130 -0
  42. data/sig/generated/typed_cache/registry.rbs +25 -0
  43. data/sig/generated/typed_cache/snapshot.rbs +50 -0
  44. data/sig/generated/typed_cache/store/instrumented.rbs +37 -0
  45. data/sig/generated/typed_cache/store.rbs +104 -0
  46. data/sig/generated/typed_cache/version.rbs +5 -0
  47. data/sig/generated/typed_cache.rbs +34 -0
  48. data/sig/handwritten/gems/zeitwerk/2.7/zeitwerk.rbs +9 -0
  49. data/typed_cache.gemspec +42 -0
  50. data.tar.gz.sig +0 -0
  51. metadata +228 -0
  52. metadata.gz.sig +0 -0
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedCache
4
+ # Base error class for TypedCache operations
5
+ class Error < StandardError; end
6
+
7
+ # Store operation errors (network, I/O, etc.)
8
+ class StoreError < Error
9
+ attr_reader :operation, :key, :original_error
10
+
11
+ # @rbs (Symbol, String, String, Exception?) -> void
12
+ def initialize(operation, key, message, original_error = nil)
13
+ super(message)
14
+ @operation = operation
15
+ @key = key
16
+ @original_error = original_error
17
+ end
18
+
19
+ # @rbs () -> String
20
+ def detailed_message
21
+ base = "#{operation.upcase} operation failed for key '#{key}': #{message}"
22
+ original_error ? "#{base} (#{original_error.class}: #{original_error.message})" : base
23
+ end
24
+
25
+ # @rbs () -> bool
26
+ def has_cause?
27
+ !@original_error.nil?
28
+ end
29
+ end
30
+
31
+ # Type safety violations
32
+ class TypeError < Error
33
+ attr_reader :expected_type, :actual_type, :value
34
+
35
+ # @rbs (String, String, untyped, String) -> void
36
+ def initialize(expected_type, actual_type, value, message)
37
+ super(message)
38
+ @expected_type = expected_type
39
+ @actual_type = actual_type
40
+ @value = value
41
+ end
42
+
43
+ # @rbs () -> String
44
+ def type_mismatch_message
45
+ "Expected #{expected_type}, got #{actual_type}"
46
+ end
47
+ end
48
+
49
+ # Cache miss (when expecting a value to exist)
50
+ class CacheMissError < Error
51
+ attr_reader :key
52
+
53
+ # @rbs (CacheKey) -> void
54
+ def initialize(key)
55
+ super("Cache miss for key: #{key}")
56
+ @key = key
57
+ end
58
+
59
+ # @rbs () -> bool
60
+ def cache_miss?
61
+ true
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/configurable'
4
+
5
+ module TypedCache
6
+ # Instrumentation hooks for ActiveSupport::Notifications integration
7
+ # All instrumentation is explicit and opt-in - no automatic behavior
8
+ module Instrumentation
9
+ class << self
10
+ # @rbs! type config = TypedCache::_TypedCacheInstrumentationConfig
11
+
12
+ # @rbs () -> config
13
+ def config
14
+ TypedCache.config.instrumentation
15
+ end
16
+
17
+ # Check if ActiveSupport::Notifications is available
18
+ # @rbs () -> bool
19
+ def notifications_available?
20
+ defined?(ActiveSupport::Notifications)
21
+ end
22
+
23
+ # Main instrumentation method
24
+ #: [T] (String, String, String, Hash[Symbol, untyped]) { -> T } -> T
25
+ def instrument(operation, namespace, key, payload = {})
26
+ return yield unless config.enabled && notifications_available?
27
+
28
+ event_name = "#{operation}.#{config.namespace}"
29
+ start_time = current_time
30
+
31
+ begin
32
+ result = yield
33
+
34
+ # Determine success and extract metadata
35
+ success, snapshot_data = extract_result_metadata(result)
36
+
37
+ final_payload = base_payload(namespace, key, start_time).merge(payload).merge({
38
+ success: success,
39
+ **snapshot_data,
40
+ })
41
+
42
+ ActiveSupport::Notifications.instrument(event_name, final_payload) do
43
+ # This block is called by subscribers who want the result
44
+ result
45
+ end
46
+
47
+ result
48
+ rescue => error
49
+ error_payload = base_payload(namespace, key, start_time).merge(payload).merge({
50
+ success: false,
51
+ error: error.class.name,
52
+ error_message: error.message,
53
+ })
54
+
55
+ ActiveSupport::Notifications.instrument(event_name, error_payload)
56
+ raise
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ # Cross-platform current time helper (uses Time.current when available)
63
+ #: -> Time
64
+ def current_time
65
+ if Time.respond_to?(:current)
66
+ Time.current
67
+ else
68
+ Time.now
69
+ end
70
+ end
71
+
72
+ # @rbs (String, String, Time) -> Hash[Symbol, untyped]
73
+ def base_payload(namespace, key, start_time)
74
+ {
75
+ namespace: namespace,
76
+ key: key,
77
+ duration: (current_time - start_time) * 1000.0, # milliseconds
78
+ store_type: nil, # Will be set by caller if available
79
+ }
80
+ end
81
+
82
+ # @rbs (Either[StandardError, Snapshot]) -> [bool, Hash[Symbol, untyped]]
83
+ def extract_result_metadata(result)
84
+ case result
85
+ when Either
86
+ if result.right?
87
+ snapshot = result.value
88
+ if snapshot.is_a?(Snapshot)
89
+ [true, {
90
+ cache_hit: snapshot.from_cache?,
91
+ cache_miss: !snapshot.from_cache?,
92
+ source: snapshot.source,
93
+ snapshot_age: snapshot.age,
94
+ },]
95
+ else
96
+ [true, { cache_hit: false, cache_miss: true }]
97
+ end
98
+ else
99
+ error_data = {
100
+ cache_hit: false,
101
+ cache_miss: true,
102
+ error_type: result.error.class.name,
103
+ }
104
+ [false, error_data]
105
+ end
106
+ else
107
+ [true, {}]
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedCache
4
+ # @rbs!
5
+ # type maybe[T] = (Nothing | Some[T]) & _Maybe[T]
6
+
7
+ module Maybe
8
+ include Kernel
9
+
10
+ class << self
11
+ #: [V](V) -> maybe[V]
12
+ def some(value) = Some.new(value)
13
+ #: -> maybe[bot]
14
+ def none = Nothing.new
15
+
16
+ #: [V](V? | maybe[V]) -> maybe[V]
17
+ def wrap(value)
18
+ case value
19
+ when Nothing, Some then value
20
+ when NilClass then Nothing.new
21
+ else
22
+ Some.new(value)
23
+ end
24
+ end
25
+
26
+ alias [] some
27
+ end
28
+ end
29
+
30
+ # @rbs!
31
+ # interface _Maybe[out V]
32
+ # def some?: -> bool
33
+ # def nothing?: -> bool
34
+ # def map: [T] () { (V) -> T } -> maybe[T]
35
+ # def bind: [T] () { (V) -> maybe[T] } -> maybe[T]
36
+ # alias flat_map bind
37
+ # end
38
+
39
+ # @rbs generic out V
40
+ class Some
41
+ # @rbs! include _Maybe[V]
42
+
43
+ attr_reader :value #: V
44
+
45
+ #: (V) -> void
46
+ def initialize(value)
47
+ @value = value
48
+ end
49
+
50
+ # @rbs override
51
+ #: -> TrueClass
52
+ def some? = true
53
+ # @rbs override
54
+ #: -> FalseClass
55
+ def nothing? = false
56
+
57
+ # @rbs override
58
+ #: [T] () { (V) -> T } -> maybe[T]
59
+ def map(&) = Some.new(yield(value))
60
+
61
+ # @rbs override
62
+ #: [T] () { (V) -> maybe[T] } -> maybe[T]
63
+ def bind(&) = yield(value)
64
+
65
+ alias flat_map bind
66
+
67
+ #: (Array[top]) -> ({ value: V })
68
+ def deconstruct_keys(keys)
69
+ { value: }
70
+ end
71
+ end
72
+
73
+ class Nothing
74
+ # @rbs! include _Maybe[bot]
75
+
76
+ # @rbs override
77
+ #: -> FalseClass
78
+ def some? = false
79
+ # @rbs override
80
+ #: -> TrueClass
81
+ def nothing? = true
82
+
83
+ # @rbs override
84
+ #: [T] () { (V) -> T } -> maybe[T]
85
+ def map(&) = self
86
+
87
+ # @rbs override
88
+ #: [T] () { (V) -> maybe[T] } -> maybe[T]
89
+ def bind(&) = self
90
+ alias flat_map bind
91
+ end
92
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedCache
4
+ # Provides a type-safe, composable namespace abstraction for cache keys.
5
+ #
6
+ # The Namespace class allows you to create hierarchical namespaces for cache keys,
7
+ # ensuring that keys are properly scoped and collisions are avoided. Each Namespace
8
+ # instance can generate cache keys (via #key), create nested namespaces (via #nested),
9
+ # and traverse to parent namespaces (via #parent_namespace).
10
+ #
11
+ # Example:
12
+ # ns = TypedCache::Namespace.at("users")
13
+ # ns.key("123") # => #<TypedCache::CacheKey namespace=users key=123>
14
+ # ns2 = ns.nested("sessions")
15
+ # ns2.key("abc") # => #<TypedCache::CacheKey namespace=users:sessions key=abc>
16
+ #
17
+ # Namespaces are composable and immutable. The key factory can be customized for advanced use cases.
18
+ class Namespace
19
+ class << self
20
+ # Returns a new Namespace instance rooted at the given namespace string.
21
+ #
22
+ # @param namespace [String] the root namespace
23
+ # @return [Namespace] a new Namespace instance at the given root
24
+ #
25
+ # Example:
26
+ # TypedCache::Namespace.at("users") # => #<TypedCache::Namespace namespace=users>
27
+ #
28
+ # The returned Namespace can be further nested or used to generate cache keys.
29
+ #
30
+ # @rbs (String) -> Namespace
31
+ def at(namespace)
32
+ root.nested(namespace)
33
+ end
34
+
35
+ # Returns the root Namespace instance (with an empty namespace).
36
+ #
37
+ # @return [Namespace] the root Namespace
38
+ #
39
+ # Example:
40
+ # TypedCache::Namespace.root # => #<TypedCache::Namespace namespace=>
41
+ #
42
+ # The root namespace is useful as a starting point for building nested namespaces.
43
+ #
44
+ # @rbs () -> Namespace
45
+ def root
46
+ new(TypedCache.config.default_namespace) { |ns, key| CacheKey.new(ns, key) }
47
+ end
48
+ end
49
+
50
+ # Initializes a new Namespace instance with the given namespace string and key factory.
51
+ #
52
+ # @param namespace [String] the namespace string for this instance
53
+ # @param key_factory [Proc] a block that creates CacheKey instances from key strings
54
+ # @yield [key] the key string to create a CacheKey from
55
+ # @yieldreturn [CacheKey] the created cache key
56
+ #
57
+ # Example:
58
+ # Namespace.new("users") { |key| CacheKey.new("users", key) }
59
+ #
60
+ # @rbs (String) { (Namespace, String) -> CacheKey } -> void
61
+ def initialize(namespace, &key_factory)
62
+ @namespace = namespace
63
+ @key_factory = key_factory
64
+ end
65
+
66
+ # Creates a nested namespace by appending the given namespace to the current one.
67
+ #
68
+ # @param namespace [String] the namespace to append
69
+ # @param key_factory [Proc, nil] optional custom key factory for the nested namespace
70
+ # @return [Namespace] a new Namespace instance with the combined namespace
71
+ #
72
+ # Example:
73
+ # ns = Namespace.at("users")
74
+ # ns.nested("sessions") # => #<TypedCache::Namespace namespace=users:sessions>
75
+ #
76
+ # If no key_factory is provided, the parent's key factory is inherited.
77
+ #
78
+ # @rbs (String) ?{ (Namespace, String) -> CacheKey } -> Namespace
79
+ def nested(namespace, &key_factory)
80
+ key_factory ||= @key_factory
81
+
82
+ self.class.new("#{@namespace}:#{namespace}", &key_factory)
83
+ end
84
+
85
+ # Returns the parent namespace by removing the last namespace segment.
86
+ #
87
+ # @return [Namespace] the parent namespace, or self if already at root
88
+ #
89
+ # Example:
90
+ # ns = Namespace.at("users:sessions")
91
+ # ns.parent_namespace # => #<TypedCache::Namespace namespace=users>
92
+ #
93
+ # For root namespaces (empty string), returns self.
94
+ #
95
+ # @rbs () -> Namespace
96
+ def parent_namespace
97
+ return self if @namespace.empty?
98
+
99
+ case pathsep_idx = @namespace.rindex(':')
100
+ when nil
101
+ self.class.root
102
+ else
103
+ self.class.new(@namespace[...pathsep_idx])
104
+ end
105
+ end
106
+
107
+ # Creates a cache key using the namespace's key factory.
108
+ #
109
+ # @param key [String] the key string to create a cache key from
110
+ # @return [CacheKey] the created cache key
111
+ #
112
+ # Example:
113
+ # ns = Namespace.at("users")
114
+ # ns.key("123") # => #<TypedCache::CacheKey namespace=users key=123>
115
+ #
116
+ # @rbs (String) -> CacheKey
117
+ def key(key)
118
+ @key_factory.call(self, key)
119
+ end
120
+
121
+ # @rbs () -> bool
122
+ def root? = @namespace.empty?
123
+
124
+ # Returns the namespace string representation.
125
+ #
126
+ # @return [String] the namespace string
127
+ #
128
+ # Example:
129
+ # ns = Namespace.at("users:sessions")
130
+ # ns.to_s # => "users:sessions"
131
+ #
132
+ # @rbs () -> String
133
+ def to_s
134
+ @namespace
135
+ end
136
+
137
+ # Returns a string representation of the Namespace instance for debugging.
138
+ #
139
+ # @return [String] a debug-friendly string representation
140
+ #
141
+ # Example:
142
+ # ns = Namespace.at("users")
143
+ # ns.inspect # => "#<TypedCache::Namespace namespace=users>"
144
+ #
145
+ # @rbs () -> String
146
+ def inspect
147
+ "#<#{self.class} #{@namespace}>"
148
+ end
149
+
150
+ # @rbs () -> Integer
151
+ def hash
152
+ [@namespace].hash
153
+ end
154
+
155
+ # @rbs (Object) -> bool
156
+ def ==(other)
157
+ other.is_a?(self.class) && other.to_s == to_s
158
+ end
159
+
160
+ alias eql? ==
161
+ end
162
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedCache
4
+ # Generic registry for managing class-based factories
5
+ # @rbs generic T
6
+ class Registry
7
+ # @rbs (String, Hash[Symbol, Class[T]]) -> void
8
+ def initialize(name, defaults = {})
9
+ @name = name
10
+ @registry = defaults.dup
11
+ end
12
+
13
+ # @rbs (Symbol, *untyped, **untyped) -> either[Error, T]
14
+ def resolve(key, *, **, &)
15
+ klass = @registry[key]
16
+ return Either.left(ArgumentError.new("Unknown #{@name}: #{key}")) unless klass
17
+
18
+ Either.right(klass.new(*, **))
19
+ rescue => e
20
+ Either.left(StoreError.new(
21
+ :"#{@name}_creation",
22
+ key.to_s,
23
+ "Failed to create #{@name} '#{key}': #{e.message}",
24
+ e,
25
+ ))
26
+ end
27
+
28
+ # @rbs (Symbol) -> maybe[Class[T]]
29
+ def find(key)
30
+ klass = @registry[key]
31
+ return Maybe.none unless klass
32
+
33
+ Maybe.some(klass)
34
+ end
35
+
36
+ # @rbs (Symbol, Class[T]) -> either[Error, void]
37
+ def register(key, klass)
38
+ return Either.left(ArgumentError.new("#{@name.capitalize} name cannot be nil")) if key.nil?
39
+ return Either.left(ArgumentError.new("#{@name.capitalize} class cannot be nil")) if klass.nil?
40
+
41
+ @registry[key] = klass
42
+ Either.right(nil)
43
+ end
44
+
45
+ # @rbs () -> Array[Symbol]
46
+ def available
47
+ @registry.keys
48
+ end
49
+
50
+ # @rbs (Symbol) -> bool
51
+ def registered?(key)
52
+ @registry.key?(key)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedCache
4
+ # Immutable snapshot of a cached value with metadata about its source and age
5
+ # @rbs generic V
6
+ class Snapshot
7
+ attr_reader :value #: V
8
+ attr_reader :retrieved_at #: Time
9
+ attr_reader :source #: Symbol
10
+
11
+ #: (V, source: Symbol, retrieved_at: Time) -> void
12
+ def initialize(value, source:, retrieved_at: Time.now)
13
+ @value = value
14
+ @retrieved_at = retrieved_at
15
+ @source = source
16
+ end
17
+
18
+ # Age of the snapshot in seconds
19
+ #: -> Float
20
+ def age
21
+ Time.now - retrieved_at
22
+ end
23
+
24
+ # Whether this value came from cache
25
+ #: -> bool
26
+ def from_cache?
27
+ source == :cache
28
+ end
29
+
30
+ # Whether this value was freshly computed
31
+ #: -> bool
32
+ def computed?
33
+ source == :computed
34
+ end
35
+
36
+ # Whether this value was updated
37
+ #: -> bool
38
+ def updated?
39
+ source == :updated
40
+ end
41
+
42
+ # Map over the value while preserving snapshot metadata
43
+ #: [R] () { (V) -> R } -> Snapshot[R]
44
+ def map(&block)
45
+ new_value = yield(value)
46
+ Snapshot.new(new_value, source: source, retrieved_at: retrieved_at)
47
+ end
48
+
49
+ # Bind over the value with Either error handling
50
+ #: [R] () { (V) -> either[Error, R] } -> either[Error, Snapshot[R]]
51
+ def bind(&block)
52
+ result = yield(value)
53
+ result.map { |new_value| Snapshot.new(new_value, source: source, retrieved_at: retrieved_at) }
54
+ end
55
+
56
+ alias flat_map bind
57
+
58
+ class << self
59
+ # Creates a snapshot for a computed value
60
+ #: [V] (V) -> Snapshot[V]
61
+ def computed(value)
62
+ new(value, source: :computed)
63
+ end
64
+
65
+ # Creates a snapshot for an updated value
66
+ #: [V] (V) -> Snapshot[V]
67
+ def updated(value)
68
+ new(value, source: :updated)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedCache
4
+ # Decorator that adds instrumentation to any Store implementation
5
+ # This decorator can wrap any store to add ActiveSupport::Notifications
6
+ # @rbs generic V
7
+ class Store::Instrumented # rubocop:disable Style/ClassAndModuleChildren
8
+ include Decorator #[V]
9
+
10
+ extend Forwardable
11
+
12
+ attr_reader :store #: TypedCache::Store[V]
13
+
14
+ class << self
15
+ private
16
+
17
+ # @rbs (Symbol, ?operation: String) ?{ (*untyped, **untyped) -> String } -> void
18
+ def instrument(method_name, operation: method_name.to_s, &key_selector)
19
+ define_method(:"#{method_name}_with_instrumentation") do |*args, **kwargs, &block|
20
+ key = key_selector.call(*args, **kwargs) if key_selector # rubocop:disable Performance/RedundantBlockCall
21
+
22
+ Instrumentation.instrument(operation, namespace, key || 'n/a', store_type: store_type) do
23
+ send(:"#{method_name}_without_instrumentation", *args, **kwargs, &block)
24
+ end
25
+ end
26
+
27
+ alias_method(:"#{method_name}_without_instrumentation", method_name)
28
+ alias_method(method_name, :"#{method_name}_with_instrumentation")
29
+ end
30
+ end
31
+
32
+ #: (TypedCache::Store[V]) -> void
33
+ def initialize(store)
34
+ @store = store
35
+ end
36
+
37
+ # @rbs override
38
+ #: -> String
39
+ def namespace
40
+ store.namespace
41
+ end
42
+
43
+ # @rbs override
44
+ #: -> String
45
+ def store_type
46
+ # Use polymorphism - delegate to the wrapped store
47
+ "instrumented(#{store.store_type})"
48
+ end
49
+
50
+ # @rbs override
51
+ #: (cache_key) -> either[Error, CacheRef[V]]
52
+ def ref(key)
53
+ CacheRef.new(self, key)
54
+ end
55
+
56
+ # Additional methods that might exist on the wrapped store
57
+ def respond_to_missing?(method_name, include_private = false)
58
+ store.respond_to?(method_name, include_private) || super
59
+ end
60
+
61
+ def method_missing(method_name, *args, &block)
62
+ if store.respond_to?(method_name)
63
+ store.send(method_name, *args, &block)
64
+ else
65
+ super
66
+ end
67
+ end
68
+
69
+ Store.instance_methods(false).each do |method_name|
70
+ next if instance_methods(false).include?(method_name)
71
+
72
+ def_delegator :store, method_name
73
+ end
74
+
75
+ # Instrument core operations with proper key extraction
76
+ instrument(:get) { |key, *_| key }
77
+ instrument(:set) { |key, *_| key }
78
+ instrument(:delete) { |key, *_| key }
79
+ instrument(:fetch) { |key, *_| key }
80
+ instrument(:key?) { |key, *_| key }
81
+ instrument(:clear) { 'all' }
82
+ end
83
+ end