safe_memoize 0.1.2 → 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.
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SafeMemoize
4
+ module LruMethods
5
+ private
6
+
7
+ # Per-method LRU order: { method_name => { cache_key => true, ... } }
8
+ # Ruby Hash insertion order gives LRU for free: oldest key first, newest last.
9
+ def lru_order_store
10
+ @__safe_memo_lru_order__ ||= {}
11
+ end
12
+
13
+ # Mark +cache_key+ as most recently used for +method_name+.
14
+ def lru_touch(method_name, cache_key)
15
+ method_store = lru_order_store[method_name] ||= {}
16
+ method_store.delete(cache_key)
17
+ method_store[cache_key] = true
18
+ end
19
+
20
+ # Evict the least-recently-used entry for +method_name+ when at +max_size+.
21
+ # Must be called while holding the mutex.
22
+ def lru_evict_if_over_limit(method_name, max_size)
23
+ method_store = lru_order_store[method_name]
24
+ return unless method_store && !method_store.empty?
25
+
26
+ cache = @__safe_memo_cache__
27
+
28
+ # Prune stale LRU references left behind by reset_memo calls.
29
+ method_store.delete_if { |key, _| !cache&.key?(key) }
30
+
31
+ return if method_store.size < max_size
32
+
33
+ lru_cache_key = method_store.keys.first
34
+ return unless lru_cache_key
35
+
36
+ method_store.delete(lru_cache_key)
37
+ record = cache&.delete(lru_cache_key)
38
+ call_memo_hooks(:on_evict, lru_cache_key, record) if record
39
+ end
40
+
41
+ # Clear all LRU tracking state. Called by reset_all_memos.
42
+ def lru_clear_all
43
+ @__safe_memo_lru_order__ = {}
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SafeMemoize
4
+ module PublicCustomKeyMethods
5
+ def memoize_with_custom_key(method_name, &key_generator)
6
+ raise ArgumentError, "block required for key generation" unless key_generator
7
+
8
+ register_custom_key(method_name, &key_generator)
9
+ end
10
+
11
+ def clear_custom_keys(method_name = nil)
12
+ if method_name
13
+ with_memo_lock do
14
+ custom_key_store.delete(method_name.to_sym)
15
+ end
16
+ else
17
+ with_memo_lock do
18
+ _clear_custom_keys
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SafeMemoize
4
+ module PublicMethods
5
+ def memoized?(method_name, *args, **kwargs, &block)
6
+ return false if block
7
+
8
+ cache_key = safe_memo_cache_key(method_name, args, kwargs)
9
+
10
+ with_memo_lock do
11
+ memo_cache_hit?(cache_key)
12
+ end
13
+ end
14
+
15
+ def memo_count(*method_name)
16
+ scoped_method = safe_memo_scoped_method(method_name)
17
+
18
+ with_memo_lock do
19
+ safe_memo_count_for(scoped_method)
20
+ end
21
+ end
22
+
23
+ def memo_keys(*method_name)
24
+ scoped_method = safe_memo_scoped_method(method_name)
25
+
26
+ with_memo_lock do
27
+ safe_memo_keys_for(scoped_method)
28
+ end
29
+ end
30
+
31
+ def memo_values(*method_name)
32
+ scoped_method = safe_memo_scoped_method(method_name)
33
+
34
+ with_memo_lock do
35
+ safe_memo_values_for(scoped_method)
36
+ end
37
+ end
38
+
39
+ def on_memo_expire(&block)
40
+ raise ArgumentError, "block required" unless block
41
+
42
+ register_memo_hook(:on_expire, &block)
43
+ end
44
+
45
+ def on_memo_evict(&block)
46
+ raise ArgumentError, "block required" unless block
47
+
48
+ register_memo_hook(:on_evict, &block)
49
+ end
50
+
51
+ def on_memo_hit(&block)
52
+ raise ArgumentError, "block required" unless block
53
+
54
+ register_memo_hook(:on_hit, &block)
55
+ end
56
+
57
+ def clear_memo_hooks(hook_type = nil)
58
+ with_memo_lock do
59
+ _clear_memo_hooks(hook_type)
60
+ end
61
+ end
62
+
63
+ def reset_memo(method_name, *args, **kwargs)
64
+ method_name = method_name.to_sym
65
+
66
+ matcher = memo_matcher_for(method_name, args, kwargs)
67
+
68
+ with_memo_lock do
69
+ with_memo_cache do |cache|
70
+ cache.delete_if do |key, record|
71
+ if matcher.call(key)
72
+ call_memo_hooks(:on_evict, key, record)
73
+ true
74
+ else
75
+ false
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def reset_all_memos
83
+ with_memo_lock do
84
+ if defined?(@__safe_memo_cache__) && @__safe_memo_cache__
85
+ @__safe_memo_cache__.each do |key, record|
86
+ call_memo_hooks(:on_evict, key, record)
87
+ end
88
+ end
89
+ @__safe_memo_cache__ = {}
90
+ lru_clear_all
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SafeMemoize
4
+ module PublicMetricsMethods
5
+ def cache_stats
6
+ with_memo_lock do
7
+ metrics = memo_metrics_store
8
+
9
+ if metrics.empty?
10
+ return {
11
+ total_hits: 0,
12
+ total_misses: 0,
13
+ hit_rate: 0.0,
14
+ miss_rate: 0.0,
15
+ average_computation_time: 0.0,
16
+ entries: []
17
+ }
18
+ end
19
+
20
+ total_hits = metrics.values.sum { |m| m[:hits] }
21
+ total_misses = metrics.values.sum { |m| m[:misses] }
22
+ total_time = metrics.values.sum { |m| m[:total_time] }
23
+ total_calls = total_hits + total_misses
24
+
25
+ hit_rate = total_calls.zero? ? 0.0 : (total_hits.to_f / total_calls * 100).round(2)
26
+ miss_rate = total_calls.zero? ? 0.0 : (total_misses.to_f / total_calls * 100).round(2)
27
+ avg_time = total_misses.zero? ? 0.0 : (total_time / total_misses).round(6)
28
+
29
+ entries = metrics.map do |cache_key, stats|
30
+ method_name, args, _kwargs = cache_key
31
+ entry_hit_rate = if (stats[:hits] + stats[:misses]).zero?
32
+ 0.0
33
+ else
34
+ (stats[:hits].to_f / (stats[:hits] + stats[:misses]) * 100).round(2)
35
+ end
36
+
37
+ {
38
+ method: method_name,
39
+ args: args,
40
+ hits: stats[:hits],
41
+ misses: stats[:misses],
42
+ hit_rate: entry_hit_rate,
43
+ computation_time: stats[:total_time].round(6)
44
+ }
45
+ end
46
+
47
+ {
48
+ total_hits: total_hits,
49
+ total_misses: total_misses,
50
+ hit_rate: hit_rate,
51
+ miss_rate: miss_rate,
52
+ average_computation_time: avg_time,
53
+ entries: entries
54
+ }
55
+ end
56
+ end
57
+
58
+ def cache_stats_for(method_name)
59
+ method_name = method_name.to_sym
60
+
61
+ with_memo_lock do
62
+ metrics = memo_metrics_store
63
+ method_metrics = metrics.select { |key, _| key[0] == method_name }
64
+
65
+ if method_metrics.empty?
66
+ return {
67
+ method: method_name,
68
+ total_hits: 0,
69
+ total_misses: 0,
70
+ hit_rate: 0.0,
71
+ miss_rate: 0.0,
72
+ average_computation_time: 0.0,
73
+ entries: []
74
+ }
75
+ end
76
+
77
+ total_hits = method_metrics.values.sum { |m| m[:hits] }
78
+ total_misses = method_metrics.values.sum { |m| m[:misses] }
79
+ total_time = method_metrics.values.sum { |m| m[:total_time] }
80
+ total_calls = total_hits + total_misses
81
+
82
+ hit_rate = total_calls.zero? ? 0.0 : (total_hits.to_f / total_calls * 100).round(2)
83
+ miss_rate = total_calls.zero? ? 0.0 : (total_misses.to_f / total_calls * 100).round(2)
84
+ avg_time = total_misses.zero? ? 0.0 : (total_time / total_misses).round(6)
85
+
86
+ entries = method_metrics.map do |cache_key, stats|
87
+ _method, args, _kwargs = cache_key
88
+ entry_hit_rate = if (stats[:hits] + stats[:misses]).zero?
89
+ 0.0
90
+ else
91
+ (stats[:hits].to_f / (stats[:hits] + stats[:misses]) * 100).round(2)
92
+ end
93
+
94
+ {
95
+ args: args,
96
+ hits: stats[:hits],
97
+ misses: stats[:misses],
98
+ hit_rate: entry_hit_rate,
99
+ computation_time: stats[:total_time].round(6)
100
+ }
101
+ end
102
+
103
+ {
104
+ method: method_name,
105
+ total_hits: total_hits,
106
+ total_misses: total_misses,
107
+ hit_rate: hit_rate,
108
+ miss_rate: miss_rate,
109
+ average_computation_time: avg_time,
110
+ entries: entries
111
+ }
112
+ end
113
+ end
114
+
115
+ def cache_hit_rate
116
+ stats = cache_stats
117
+ stats[:hit_rate]
118
+ end
119
+
120
+ def cache_miss_rate
121
+ stats = cache_stats
122
+ stats[:miss_rate]
123
+ end
124
+
125
+ def cache_metrics_reset
126
+ with_memo_lock do
127
+ _reset_cache_metrics
128
+ end
129
+ end
130
+ end
131
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SafeMemoize
4
- VERSION = "0.1.2"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/safe_memoize.rb CHANGED
@@ -1,206 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "safe_memoize/version"
4
+ require_relative "safe_memoize/class_methods"
5
+ require_relative "safe_memoize/public_methods"
6
+ require_relative "safe_memoize/cache_store_methods"
7
+ require_relative "safe_memoize/cache_record_methods"
8
+ require_relative "safe_memoize/inspection_methods"
9
+ require_relative "safe_memoize/hooks_methods"
10
+ require_relative "safe_memoize/cache_metrics_methods"
11
+ require_relative "safe_memoize/public_metrics_methods"
12
+ require_relative "safe_memoize/custom_key_methods"
13
+ require_relative "safe_memoize/public_custom_key_methods"
14
+ require_relative "safe_memoize/lru_methods"
15
+ require_relative "safe_memoize/instance_methods"
4
16
 
5
17
  module SafeMemoize
6
18
  class Error < StandardError; end
7
19
 
20
+ include InstanceMethods
21
+
8
22
  def self.prepended(base)
9
23
  base.extend(ClassMethods)
10
24
  end
11
-
12
- module ClassMethods
13
- def memoize(method_name)
14
- method_name = method_name.to_sym
15
- visibility = memoized_method_visibility(method_name)
16
-
17
- mod = Module.new do
18
- define_method(method_name) do |*args, **kwargs, &block|
19
- # Blocks bypass cache entirely — they aren't comparable
20
- return super(*args, **kwargs, &block) if block
21
-
22
- cache_key = safe_memo_cache_key(method_name, args, kwargs)
23
-
24
- # Fast path: check without lock
25
- return memo_cache_read(cache_key) if memo_cache_hit?(cache_key)
26
-
27
- memo_fetch_or_store(cache_key) { super(*args, **kwargs) }
28
- end
29
-
30
- send(visibility, method_name)
31
- end
32
-
33
- prepend mod
34
- end
35
-
36
- private
37
-
38
- def memoized_method_visibility(method_name)
39
- return :private if private_method_defined?(method_name)
40
- return :protected if protected_method_defined?(method_name)
41
-
42
- :public
43
- end
44
- end
45
-
46
- def memoized?(method_name, *args, **kwargs, &block)
47
- return false if block
48
-
49
- cache_key = safe_memo_cache_key(method_name, args, kwargs)
50
-
51
- with_memo_lock do
52
- with_memo_cache { |cache| cache.key?(cache_key) } || false
53
- end
54
- end
55
-
56
- def memo_count(*method_name)
57
- scoped_method = safe_memo_scoped_method(method_name)
58
-
59
- with_memo_lock do
60
- safe_memo_count_for(scoped_method)
61
- end
62
- end
63
-
64
- def memo_keys(*method_name)
65
- scoped_method = safe_memo_scoped_method(method_name)
66
-
67
- with_memo_lock do
68
- safe_memo_keys_for(scoped_method)
69
- end
70
- end
71
-
72
- def memo_values(*method_name)
73
- scoped_method = safe_memo_scoped_method(method_name)
74
-
75
- with_memo_lock do
76
- safe_memo_values_for(scoped_method)
77
- end
78
- end
79
-
80
- def reset_memo(method_name, *args, **kwargs)
81
- method_name = method_name.to_sym
82
-
83
- matcher = memo_matcher_for(method_name, args, kwargs)
84
-
85
- with_memo_lock do
86
- with_memo_cache do |cache|
87
- cache.delete_if { |key, _| matcher.call(key) }
88
- end
89
- end
90
- end
91
-
92
- def reset_all_memos
93
- with_memo_lock do
94
- @__safe_memo_cache__ = {}
95
- end
96
- end
97
-
98
- private
99
-
100
- def safe_memo_scoped_method(method_name)
101
- raise ArgumentError, "expected 0 or 1 arguments" if method_name.length > 1
102
-
103
- method_name.first&.to_sym
104
- end
105
-
106
- def with_memo_lock
107
- if defined?(@__safe_memo_mutex__) && @__safe_memo_mutex__
108
- @__safe_memo_mutex__.synchronize { yield }
109
- else
110
- yield
111
- end
112
- end
113
-
114
- def memo_cache_or_nil
115
- return nil unless defined?(@__safe_memo_cache__)
116
-
117
- @__safe_memo_cache__
118
- end
119
-
120
- def memo_cache_hit?(cache_key)
121
- cache = memo_cache_or_nil
122
- cache&.key?(cache_key)
123
- end
124
-
125
- def memo_cache_read(cache_key)
126
- cache = memo_cache_or_nil
127
- cache && cache[cache_key]
128
- end
129
-
130
- def memo_fetch_or_store(cache_key)
131
- memo_mutex!.synchronize do
132
- @__safe_memo_cache__ ||= {}
133
-
134
- if @__safe_memo_cache__.key?(cache_key)
135
- @__safe_memo_cache__[cache_key]
136
- else
137
- @__safe_memo_cache__[cache_key] = yield
138
- end
139
- end
140
- end
141
-
142
- def memo_mutex!
143
- @__safe_memo_mutex__ ||= Mutex.new
144
- end
145
-
146
- def with_memo_cache
147
- cache = memo_cache_or_nil
148
- return nil unless cache
149
-
150
- yield cache
151
- end
152
-
153
- def memo_matcher_for(method_name, args, kwargs)
154
- if args.empty? && kwargs.empty?
155
- ->(key) { key[0] == method_name }
156
- else
157
- cache_key = safe_memo_cache_key(method_name, args, kwargs)
158
- ->(key) { key == cache_key }
159
- end
160
- end
161
-
162
- def memo_entries_for(method_name)
163
- cache = memo_cache_or_nil
164
- return [] unless cache
165
-
166
- entries = cache.to_a
167
- return entries unless method_name
168
-
169
- entries.select { |(cache_key, _)| cache_key[0] == method_name }
170
- end
171
-
172
- def safe_memo_count_for(method_name)
173
- memo_entries_for(method_name).length
174
- end
175
-
176
- def safe_memo_keys_for(method_name)
177
- entries = memo_entries_for(method_name)
178
- include_method = method_name.nil?
179
-
180
- entries.map do |(cache_key, value)|
181
- memo_projection(cache_key, value, include_method: include_method, include_value: false)
182
- end
183
- end
184
-
185
- def safe_memo_values_for(method_name)
186
- entries = memo_entries_for(method_name)
187
- include_method = method_name.nil?
188
-
189
- entries.map do |(cache_key, value)|
190
- memo_projection(cache_key, value, include_method: include_method, include_value: true)
191
- end
192
- end
193
-
194
- def memo_projection(cache_key, value, include_method:, include_value:)
195
- method_name, args, kwargs = cache_key
196
-
197
- payload = {args: args, kwargs: kwargs}
198
- payload[:method] = method_name if include_method
199
- payload[:value] = value if include_value
200
- payload
201
- end
202
-
203
- def safe_memo_cache_key(method_name, args, kwargs)
204
- [method_name.to_sym, args, kwargs]
205
- end
206
25
  end
data/sig/safe_memoize.rbs CHANGED
@@ -1,47 +1,146 @@
1
1
  module SafeMemoize
2
2
  VERSION: String
3
+ include InstanceMethods
3
4
 
4
- type memo_key = [Symbol, Array[untyped], Hash[Symbol, untyped]]
5
- type memo_entry = [memo_key, untyped]
5
+ type default_memo_key = [Symbol, Array[untyped], Hash[Symbol, untyped]]
6
+ type custom_memo_key = [Symbol, untyped]
7
+ type memo_key = default_memo_key | custom_memo_key
8
+ type memo_record = { value: untyped, expires_at: Float? }
6
9
 
7
- @__safe_memo_cache__: Hash[memo_key, untyped]?
10
+ @__safe_memo_cache__: Hash[memo_key, memo_record]?
8
11
  @__safe_memo_mutex__: Mutex?
9
12
 
10
13
  def self.prepended: (Class base) -> void
11
14
 
12
- def memoized?: (Symbol | String method_name, *untyped args, **untyped kwargs) -> bool
13
- def memo_count: () -> Integer
14
- def memo_count: (Symbol | String method_name) -> Integer
15
- def memo_keys: () -> Array[untyped]
16
- def memo_keys: (Symbol | String method_name) -> Array[untyped]
17
- def memo_values: () -> Array[untyped]
18
- def memo_values: (Symbol | String method_name) -> Array[untyped]
19
- def reset_memo: (Symbol | String method_name, *untyped args, **untyped kwargs) -> void
20
- def reset_all_memos: () -> void
21
-
22
- private
23
-
24
- def safe_memo_scoped_method: (Array[untyped] method_name) -> Symbol?
25
- def with_memo_lock: { () -> untyped } -> untyped
26
- def memo_cache_or_nil: () -> Hash[memo_key, untyped]?
27
- def memo_cache_hit?: (memo_key cache_key) -> bool
28
- def memo_cache_read: (memo_key cache_key) -> untyped?
29
- def memo_fetch_or_store: (memo_key cache_key) { () -> untyped } -> untyped
30
- def memo_mutex!: () -> Mutex
31
- def with_memo_cache: { (Hash[memo_key, untyped] cache) -> untyped } -> untyped?
32
- def memo_matcher_for: (Symbol method_name, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> ((memo_key) -> bool)
33
- def memo_entries_for: (Symbol? method_name) -> Array[memo_entry]
34
- def safe_memo_count_for: (Symbol? method_name) -> Integer
35
- def safe_memo_keys_for: (Symbol? method_name) -> Array[untyped]
36
- def safe_memo_values_for: (Symbol? method_name) -> Array[untyped]
37
- def memo_projection: (memo_key cache_key, untyped value, include_method: bool, include_value: bool) -> Hash[Symbol, untyped]
38
- def safe_memo_cache_key: (Symbol | String method_name, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> memo_key
39
-
40
15
  module ClassMethods
41
- def memoize: (Symbol | String method_name) -> void
16
+ def memoize: (Symbol | String method_name, ?ttl: Numeric?, ?max_size: Integer?, ?if: (^(untyped result) -> boolish)?, ?unless: (^(untyped result) -> boolish)?) -> void
17
+
18
+ private
19
+
20
+ def memoized_method_visibility: (Symbol method_name) -> Symbol
21
+ end
22
+
23
+ module PublicMethods
24
+ def memoized?: (Symbol | String method_name, *untyped args, **untyped kwargs) ?{ () -> untyped } -> bool
25
+ def memo_count: (*untyped method_name) -> Integer
26
+ def memo_keys: (*untyped method_name) -> Array[untyped]
27
+ def memo_values: (*untyped method_name) -> Array[untyped]
28
+ def on_memo_expire: { (memo_key cache_key, memo_record record) -> untyped } -> void
29
+ def on_memo_evict: { (memo_key cache_key, memo_record record) -> untyped } -> void
30
+ def on_memo_hit: { (memo_key cache_key, memo_record record) -> untyped } -> void
31
+ def clear_memo_hooks: (Symbol? hook_type) -> void
32
+ def reset_memo: (Symbol | String method_name, *untyped args, **untyped kwargs) -> void
33
+ def reset_all_memos: () -> void
34
+ end
35
+
36
+ module CacheStoreMethods
37
+ @__safe_memo_cache__: Hash[memo_key, memo_record]?
38
+ @__safe_memo_mutex__: Mutex?
39
+
40
+ private
41
+
42
+ def with_memo_lock: { () -> untyped } -> untyped
43
+ def memo_cache_or_nil: () -> Hash[memo_key, memo_record]?
44
+ def memo_cache_hit?: (memo_key cache_key) -> bool
45
+ def memo_cache_record: (memo_key cache_key) -> memo_record?
46
+ def memo_cache_read: (memo_key cache_key) -> untyped?
47
+ def memo_fetch_or_store: (memo_key cache_key) { () -> untyped } -> untyped
48
+ def memo_mutex!: () -> Mutex
49
+ def with_memo_cache: { (Hash[memo_key, memo_record] cache) -> untyped } -> untyped?
50
+ end
51
+
52
+ module CacheRecordMethods
53
+ private
42
54
 
55
+ def memo_ttl: (Numeric? ttl) -> Float?
56
+ def memo_expires_at: (Float? ttl) -> Float?
57
+ def memo_record: (untyped value, expires_at: Float?) -> memo_record
58
+ def memo_record_value: (memo_record record) -> untyped
59
+ def memo_record_live?: (memo_record? record) -> bool
60
+ def memo_prune_expired_entries!: (Hash[memo_key, memo_record] cache) -> void
61
+ end
62
+
63
+ module InspectionMethods
43
64
  private
44
65
 
45
- def memoized_method_visibility: (Symbol method_name) -> (:private | :protected | :public)
66
+ def safe_memo_scoped_method: (Array[untyped] method_name) -> Symbol?
67
+ def memo_matcher_for: (Symbol method_name, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> untyped
68
+ def memo_entries_for: (Symbol? method_name) -> Array[untyped]
69
+ def safe_memo_count_for: (Symbol? method_name) -> Integer
70
+ def safe_memo_keys_for: (Symbol? method_name) -> Array[untyped]
71
+ def safe_memo_values_for: (Symbol? method_name) -> Array[untyped]
72
+ def memo_projection: (memo_key cache_key, memo_record value, include_method: bool, include_value: bool) -> Hash[Symbol, untyped]
73
+ def safe_memo_cache_key: (Symbol | String method_name, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> default_memo_key
74
+ end
75
+
76
+ module HooksMethods
77
+ @__safe_memo_hooks__: { on_expire: Array[Proc], on_evict: Array[Proc], on_hit: Array[Proc] }?
78
+
79
+ private
80
+
81
+ def memo_hook_store: () -> { on_expire: Array[Proc], on_evict: Array[Proc], on_hit: Array[Proc] }
82
+ def register_memo_hook: (Symbol hook_type) { (memo_key cache_key, memo_record record) -> untyped } -> void
83
+ def call_memo_hooks: (Symbol hook_type, memo_key cache_key, memo_record record) -> void
84
+ def _clear_memo_hooks: (Symbol? hook_type) -> void
85
+ end
86
+
87
+ module CacheMetricsMethods
88
+ @__safe_memo_metrics__: Hash[memo_key, { hits: Integer, misses: Integer, total_time: Float }]?
89
+
90
+ private
91
+
92
+ def memo_metrics_store: () -> Hash[memo_key, { hits: Integer, misses: Integer, total_time: Float }]
93
+ def record_cache_hit: (Symbol method_name, Array[untyped] args) -> void
94
+ def record_cache_miss: (Symbol method_name, Array[untyped] args, Float computation_time) -> void
95
+ def _reset_cache_metrics: () -> void
96
+ end
97
+
98
+ module PublicMetricsMethods
99
+ def cache_stats: () -> Hash[Symbol, untyped]
100
+ def cache_stats_for: (Symbol | String method_name) -> Hash[Symbol, untyped]
101
+ def cache_hit_rate: () -> Float
102
+ def cache_miss_rate: () -> Float
103
+ def cache_metrics_reset: () -> void
104
+ end
105
+
106
+ module CustomKeyMethods
107
+ @__safe_memo_custom_keys__: Hash[Symbol, Proc]?
108
+
109
+ private
110
+
111
+ def custom_key_store: () -> Hash[Symbol, Proc]
112
+ def register_custom_key: (Symbol | String method_name) { (*untyped args, **untyped kwargs) -> untyped } -> void
113
+ def compute_cache_key: (Symbol | String method_name, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> memo_key
114
+ def _clear_custom_keys: () -> void
115
+ end
116
+
117
+ module PublicCustomKeyMethods
118
+ def memoize_with_custom_key: (Symbol | String method_name) { (*untyped args, **untyped kwargs) -> untyped } -> void
119
+ def clear_custom_keys: (Symbol | String? method_name) -> void
120
+ end
121
+
122
+ module LruMethods
123
+ @__safe_memo_lru_order__: Hash[Symbol, Hash[memo_key, true]]?
124
+
125
+ private
126
+
127
+ def lru_order_store: () -> Hash[Symbol, Hash[memo_key, true]]
128
+ def lru_touch: (Symbol method_name, memo_key cache_key) -> void
129
+ def lru_evict_if_over_limit: (Symbol method_name, Integer max_size) -> void
130
+ def lru_clear_all: () -> void
131
+ end
132
+
133
+ module InstanceMethods
134
+ include PublicMethods
135
+ include CacheStoreMethods
136
+ include CacheRecordMethods
137
+ include InspectionMethods
138
+ include HooksMethods
139
+ include CacheMetricsMethods
140
+ include PublicMetricsMethods
141
+ include CustomKeyMethods
142
+ include PublicCustomKeyMethods
143
+ include LruMethods
46
144
  end
47
145
  end
146
+