safe_memoize 0.9.0 → 1.1.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,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "safe_memoize"
4
+
5
+ module SafeMemoize
6
+ module Stores
7
+ # Cache store adapter backed by Redis.
8
+ #
9
+ # Not auto-required. Add to your application:
10
+ # require "safe_memoize/stores/redis"
11
+ #
12
+ # Requires a Redis-compatible client that responds to +#get+, +#set+,
13
+ # +#del+, and +#scan_each+. Compatible with the +redis+ gem (v4+) and
14
+ # any drop-in replacement.
15
+ #
16
+ # Values and keys are serialized with +Marshal+ (Base64-encoded via
17
+ # +Array#pack("m0")+) so that any Ruby object, including +nil+ and
18
+ # +false+, can be stored and retrieved faithfully. TTL is forwarded to
19
+ # Redis as the +PX+ option (milliseconds, rounded up to the nearest
20
+ # millisecond; minimum 1 ms) to preserve sub-second precision.
21
+ #
22
+ # @example Basic setup
23
+ # require "redis"
24
+ # require "safe_memoize/stores/redis"
25
+ #
26
+ # REDIS_STORE = SafeMemoize::Stores::Redis.new(::Redis.new)
27
+ #
28
+ # class MyService
29
+ # prepend SafeMemoize
30
+ # def fetch(id) = http_get(id)
31
+ # memoize :fetch, store: REDIS_STORE, ttl: 300
32
+ # end
33
+ #
34
+ # @example With a custom namespace
35
+ # STORE = SafeMemoize::Stores::Redis.new(::Redis.new, namespace: "myapp:memo")
36
+ class Redis < Base
37
+ # @param client [Object] a Redis-compatible client responding to
38
+ # +#get+, +#set+, +#del+, and +#scan_each+
39
+ # @param namespace [String] key prefix used to scope all entries in Redis;
40
+ # defaults to +"safe_memoize"+
41
+ def initialize(client, namespace: "safe_memoize")
42
+ @client = client
43
+ @namespace = namespace
44
+ end
45
+
46
+ # @param key [Object] cache key (serialized with Marshal + Base64)
47
+ # @return [Object] the stored value, or {MISS} if absent
48
+ def read(key)
49
+ raw = @client.get(redis_key(key))
50
+ return MISS if raw.nil?
51
+
52
+ Marshal.load(raw) # rubocop:disable Security/MarshalLoad
53
+ rescue TypeError, ArgumentError
54
+ MISS
55
+ end
56
+
57
+ # @param key [Object] cache key
58
+ # @param value [Object] value to store (may be +nil+ or +false+)
59
+ # @param expires_in [Numeric, nil] TTL in seconds forwarded to Redis as +PX+
60
+ # (milliseconds, rounded up; minimum 1 ms); +nil+ means no expiry
61
+ # @return [void]
62
+ def write(key, value, expires_in: nil)
63
+ raw = Marshal.dump(value)
64
+ if expires_in
65
+ @client.set(redis_key(key), raw, px: [(expires_in * 1000).ceil, 1].max)
66
+ else
67
+ @client.set(redis_key(key), raw)
68
+ end
69
+ end
70
+
71
+ # @param key [Object]
72
+ # @return [void]
73
+ def delete(key)
74
+ @client.del(redis_key(key))
75
+ end
76
+
77
+ # Removes all entries written by this adapter (scoped to the namespace).
78
+ # Uses +SCAN+ internally to avoid blocking Redis.
79
+ # @return [void]
80
+ def clear
81
+ to_delete = []
82
+ @client.scan_each(match: "#{@namespace}:*") { |k| to_delete << k }
83
+ @client.del(*to_delete) unless to_delete.empty?
84
+ end
85
+
86
+ # Returns all live keys in the namespace, deserialized back to their
87
+ # original Ruby form. Entries that cannot be deserialized are silently
88
+ # skipped. Because Redis handles TTL natively, every key returned by
89
+ # +SCAN+ is live.
90
+ #
91
+ # @return [Array<Object>]
92
+ def keys
93
+ prefix = "#{@namespace}:"
94
+ result = []
95
+ @client.scan_each(match: "#{@namespace}:*") do |rk|
96
+ encoded = rk.delete_prefix(prefix)
97
+ result << Marshal.load(encoded.unpack1("m0")) # rubocop:disable Security/MarshalLoad
98
+ rescue ArgumentError, TypeError
99
+ # skip keys that cannot be deserialized (e.g. written by another serializer)
100
+ end
101
+ result
102
+ end
103
+
104
+ private
105
+
106
+ def redis_key(key)
107
+ "#{@namespace}:#{[Marshal.dump(key)].pack("m0")}"
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SafeMemoize
4
- VERSION = "0.9.0"
4
+ # The current gem version string.
5
+ VERSION = "1.1.0"
5
6
  end
data/lib/safe_memoize.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative "safe_memoize/version"
4
4
  require_relative "safe_memoize/configuration"
5
+ require_relative "safe_memoize/stores/base"
6
+ require_relative "safe_memoize/stores/memory"
5
7
  require_relative "safe_memoize/adapters/statsd"
6
8
  require_relative "safe_memoize/adapters/opentelemetry"
7
9
  require_relative "safe_memoize/class_methods"
@@ -17,27 +19,81 @@ require_relative "safe_memoize/public_custom_key_methods"
17
19
  require_relative "safe_memoize/lru_methods"
18
20
  require_relative "safe_memoize/instance_methods"
19
21
 
22
+ # Thread-safe memoization for Ruby that correctly handles +nil+ and +false+ values.
23
+ #
24
+ # Prepend this module into any class, then call {ClassMethods#memoize} to wrap
25
+ # instance methods with a per-instance cache backed by a +Mutex+.
26
+ #
27
+ # @example Basic usage
28
+ # class UserService
29
+ # prepend SafeMemoize
30
+ #
31
+ # def current_user
32
+ # User.find_by(session_id: session_id)
33
+ # end
34
+ # memoize :current_user
35
+ # end
36
+ #
37
+ # @example With TTL and LRU cap
38
+ # class ApiClient
39
+ # prepend SafeMemoize
40
+ #
41
+ # def fetch(id)
42
+ # http_get("/items/#{id}")
43
+ # end
44
+ # memoize :fetch, ttl: 60, max_size: 500
45
+ # end
46
+ #
47
+ # @see ClassMethods#memoize
48
+ # @see https://github.com/eclectic-coding/safe_memoize README
20
49
  module SafeMemoize
50
+ # Base class for all SafeMemoize-specific exceptions.
51
+ # Rescue this to catch any error raised by the library itself.
21
52
  class Error < StandardError; end
22
53
 
23
54
  include InstanceMethods
24
55
 
56
+ # @api private
25
57
  def self.prepended(base)
26
58
  base.extend(ClassMethods)
27
59
  end
28
60
 
61
+ # Yields the global {Configuration} object for mutation.
62
+ #
63
+ # @example
64
+ # SafeMemoize.configure do |c|
65
+ # c.default_ttl = 300
66
+ # end
67
+ #
68
+ # @yield [config] The current {Configuration} instance.
69
+ # @yieldparam config [Configuration]
70
+ # @return [void]
29
71
  def self.configure
30
72
  yield configuration
31
73
  end
32
74
 
75
+ # Returns the global {Configuration} instance, creating it on first access.
76
+ #
77
+ # @return [Configuration]
33
78
  def self.configuration
34
79
  @configuration ||= Configuration.new
35
80
  end
36
81
 
82
+ # Resets the global configuration to all defaults.
83
+ #
84
+ # Useful in test suites to prevent configuration leaking between examples.
85
+ #
86
+ # @return [Configuration] the new blank configuration
37
87
  def self.reset_configuration!
38
88
  @configuration = Configuration.new
39
89
  end
40
90
 
91
+ # Emits a structured deprecation warning through the configured handler.
92
+ #
93
+ # @param subject [String] short identifier of the deprecated symbol
94
+ # @param message [String] migration instructions
95
+ # @param horizon [String] version when the symbol will be removed (e.g. +"v2.0.0"+)
96
+ # @return [void]
41
97
  def self.deprecate(subject, message:, horizon:)
42
98
  text = "[SafeMemoize] #{subject} is deprecated and will be removed in #{horizon}. #{message}"
43
99
  handler = configuration.on_deprecation
@@ -0,0 +1,245 @@
1
+ # typed: true
2
+
3
+ # Sorbet type stubs for the safe_memoize gem.
4
+ #
5
+ # These stubs cover every symbol listed in the public API guarantee
6
+ # (see README "Public API and versioning guarantee"). Opt-in extensions
7
+ # (SafeMemoize::Rails, SafeMemoize::Adapters::*) are included as well,
8
+ # but they are not part of the v1.0.0 semver guarantee.
9
+ #
10
+ # Usage: add `require "safe_memoize"` to your Sorbet ignore list if you
11
+ # use tapioca, or copy this file into sorbet/rbi/shims/ for manual setups.
12
+
13
+ module SafeMemoize
14
+ VERSION = T.let(T.unsafe(nil), String)
15
+
16
+ class Error < StandardError; end
17
+
18
+ sig { params(base: T::Class[T.anything]).void }
19
+ def self.prepended(base); end
20
+
21
+ sig { params(blk: T.proc.params(config: SafeMemoize::Configuration).void).void }
22
+ def self.configure(&blk); end
23
+
24
+ sig { returns(SafeMemoize::Configuration) }
25
+ def self.configuration; end
26
+
27
+ sig { returns(SafeMemoize::Configuration) }
28
+ def self.reset_configuration!; end
29
+
30
+ sig { params(subject: String, message: String, horizon: String).void }
31
+ def self.deprecate(subject, message:, horizon:); end
32
+
33
+ class Configuration
34
+ sig { returns(T.nilable(Numeric)) }
35
+ attr_accessor :default_ttl
36
+
37
+ sig { returns(T.nilable(Integer)) }
38
+ attr_accessor :default_max_size
39
+
40
+ sig { returns(T.nilable(T.proc.params(message: String).void)) }
41
+ attr_accessor :on_deprecation
42
+
43
+ sig { returns(T.nilable(T.proc.params(error: Exception, hook_type: Symbol, cache_key: T.untyped).void)) }
44
+ attr_accessor :on_hook_error
45
+
46
+ sig { returns(T::Boolean) }
47
+ attr_accessor :active_support_notifications
48
+
49
+ sig { returns(T.untyped) }
50
+ attr_accessor :statsd_client
51
+
52
+ sig { returns(T.untyped) }
53
+ attr_accessor :opentelemetry_tracer
54
+
55
+ sig { void }
56
+ def initialize; end
57
+ end
58
+
59
+ module ClassMethods
60
+ sig do
61
+ params(
62
+ method_name: T.any(Symbol, String),
63
+ ttl: T.nilable(Numeric),
64
+ max_size: T.nilable(Integer),
65
+ ttl_refresh: T::Boolean,
66
+ if: T.nilable(T.proc.params(result: T.untyped).returns(T.untyped)),
67
+ unless: T.nilable(T.proc.params(result: T.untyped).returns(T.untyped)),
68
+ shared: T::Boolean,
69
+ key: T.nilable(T.proc.params(args: T.untyped).returns(T.untyped))
70
+ ).void
71
+ end
72
+ def memoize(method_name, ttl: nil, max_size: nil, ttl_refresh: false, if: nil, unless: nil, shared: false, key: nil); end
73
+
74
+ sig do
75
+ params(
76
+ except: T::Array[T.any(Symbol, String)],
77
+ only: T::Array[T.any(Symbol, String)],
78
+ include_protected: T::Boolean,
79
+ include_private: T::Boolean,
80
+ ttl: T.nilable(Numeric),
81
+ max_size: T.nilable(Integer),
82
+ ttl_refresh: T::Boolean,
83
+ if: T.nilable(T.proc.params(result: T.untyped).returns(T.untyped)),
84
+ unless: T.nilable(T.proc.params(result: T.untyped).returns(T.untyped)),
85
+ shared: T::Boolean,
86
+ key: T.nilable(T.proc.params(args: T.untyped).returns(T.untyped))
87
+ ).void
88
+ end
89
+ def memoize_all(except: [], only: [], include_protected: false, include_private: false, ttl: nil, max_size: nil, ttl_refresh: false, if: nil, unless: nil, shared: false, key: nil); end
90
+
91
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).void }
92
+ def reset_shared_memo(method_name, *args, **kwargs); end
93
+
94
+ sig { void }
95
+ def reset_all_shared_memos; end
96
+
97
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T::Boolean) }
98
+ def shared_memoized?(method_name, *args, **kwargs); end
99
+
100
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(Integer) }
101
+ def shared_memo_count(method_name = nil); end
102
+
103
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T.nilable(Float)) }
104
+ def shared_memo_age(method_name, *args, **kwargs); end
105
+
106
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T::Boolean) }
107
+ def shared_memo_stale?(method_name, *args, **kwargs); end
108
+ end
109
+
110
+ module PublicMethods
111
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped, blk: T.nilable(T.proc.returns(T.untyped))).returns(T::Boolean) }
112
+ def memoized?(method_name, *args, **kwargs, &blk); end
113
+
114
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T.nilable(T.any(Float, Integer))) }
115
+ def memo_ttl_remaining(method_name, *args, **kwargs); end
116
+
117
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(Integer) }
118
+ def memo_count(method_name = nil); end
119
+
120
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(T::Array[T.untyped]) }
121
+ def memo_keys(method_name = nil); end
122
+
123
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(T::Array[T.untyped]) }
124
+ def memo_values(method_name = nil); end
125
+
126
+ sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
127
+ def on_memo_expire(&blk); end
128
+
129
+ sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
130
+ def on_memo_evict(&blk); end
131
+
132
+ sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
133
+ def on_memo_hit(&blk); end
134
+
135
+ sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
136
+ def on_memo_miss(&blk); end
137
+
138
+ sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
139
+ def on_memo_store(&blk); end
140
+
141
+ sig { params(hook_type: T.nilable(Symbol)).void }
142
+ def clear_memo_hooks(hook_type = nil); end
143
+
144
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, ttl: T.nilable(Numeric), kwargs: T.untyped, blk: T.proc.returns(T.untyped)).returns(T.untyped) }
145
+ def warm_memo(method_name, *args, ttl: nil, **kwargs, &blk); end
146
+
147
+ sig { params(method_name: T.any(Symbol, String), arg_sets: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
148
+ def memo_preload(method_name, *arg_sets); end
149
+
150
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(T::Hash[T.untyped, T.untyped]) }
151
+ def dump_memo(method_name = nil); end
152
+
153
+ sig { params(snapshot: T::Hash[T.untyped, T.untyped]).void }
154
+ def load_memo(snapshot); end
155
+
156
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, ttl: T.nilable(Numeric), kwargs: T.untyped).returns(T::Boolean) }
157
+ def memo_touch(method_name, *args, ttl: nil, **kwargs); end
158
+
159
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T.untyped) }
160
+ def memo_refresh(method_name, *args, **kwargs); end
161
+
162
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T.nilable(Float)) }
163
+ def memo_age(method_name, *args, **kwargs); end
164
+
165
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T::Boolean) }
166
+ def memo_stale?(method_name, *args, **kwargs); end
167
+
168
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).void }
169
+ def reset_memo(method_name, *args, **kwargs); end
170
+
171
+ sig { void }
172
+ def reset_all_memos; end
173
+
174
+ sig do
175
+ params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped)
176
+ .returns(T.nilable(T::Hash[Symbol, T.untyped]))
177
+ end
178
+ def memo_inspect(method_name, *args, **kwargs); end
179
+ end
180
+
181
+ module PublicMetricsMethods
182
+ sig { returns(T::Hash[Symbol, T.untyped]) }
183
+ def cache_stats; end
184
+
185
+ sig { params(method_name: T.any(Symbol, String)).returns(T::Hash[Symbol, T.untyped]) }
186
+ def cache_stats_for(method_name); end
187
+
188
+ sig { returns(Float) }
189
+ def cache_hit_rate; end
190
+
191
+ sig { returns(Float) }
192
+ def cache_miss_rate; end
193
+
194
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).void }
195
+ def cache_metrics_reset(method_name = nil); end
196
+ end
197
+
198
+ module PublicCustomKeyMethods
199
+ sig { params(method_name: T.any(Symbol, String), blk: T.proc.params(args: T.untyped).returns(T.untyped)).void }
200
+ def memoize_with_custom_key(method_name, &blk); end
201
+
202
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).void }
203
+ def clear_custom_keys(method_name = nil); end
204
+ end
205
+
206
+ module Adapters
207
+ module StatsD
208
+ METRIC_NAMES = T.let(T.unsafe(nil), T::Hash[Symbol, String])
209
+
210
+ sig { params(client: T.untyped, hook_type: Symbol, cache_key: T.untyped, class_name: T.nilable(String)).void }
211
+ def self.dispatch(client, hook_type, cache_key, class_name); end
212
+ end
213
+
214
+ module OpenTelemetry
215
+ SPAN_NAME = T.let(T.unsafe(nil), String)
216
+
217
+ sig { params(tracer: T.untyped, method_name: T.any(Symbol, String), class_name: T.nilable(String), blk: T.proc.returns(T.untyped)).returns(T.untyped) }
218
+ def self.trace(tracer, method_name, class_name, &blk); end
219
+ end
220
+ end
221
+
222
+ module Rails
223
+ sig { params(instance: T.untyped).void }
224
+ def self.track(instance); end
225
+
226
+ sig { void }
227
+ def self.reset_tracked!; end
228
+
229
+ module RequestScoped
230
+ sig { params(base: Module).void }
231
+ def self.included(base); end
232
+
233
+ sig { void }
234
+ def reset_request_memos; end
235
+ end
236
+
237
+ class Middleware
238
+ sig { params(app: T.untyped).void }
239
+ def initialize(app); end
240
+
241
+ sig { params(env: T.untyped).returns(T.untyped) }
242
+ def call(env); end
243
+ end
244
+ end
245
+ end
data/sig/safe_memoize.rbs CHANGED
@@ -1,5 +1,9 @@
1
1
  module SafeMemoize
2
2
  VERSION: String
3
+
4
+ class Error < StandardError
5
+ end
6
+
3
7
  include InstanceMethods
4
8
 
5
9
  type default_memo_key = [Symbol, Array[untyped], Hash[Symbol, untyped]]
@@ -28,12 +32,13 @@ module SafeMemoize
28
32
  attr_accessor active_support_notifications: bool
29
33
  attr_accessor statsd_client: untyped
30
34
  attr_accessor opentelemetry_tracer: untyped
35
+ attr_accessor default_store: Stores::Base?
31
36
 
32
37
  def initialize: () -> void
33
38
  end
34
39
 
35
40
  module ClassMethods
36
- def memoize: (Symbol | String method_name, ?ttl: Numeric?, ?max_size: Integer?, ?ttl_refresh: bool, ?if: (^(untyped result) -> boolish)?, ?unless: (^(untyped result) -> boolish)?, ?shared: bool, ?key: (^(*untyped args, **untyped kwargs) -> untyped)?) -> void
41
+ def memoize: (Symbol | String method_name, ?ttl: Numeric?, ?max_size: Integer?, ?ttl_refresh: bool, ?if: (^(untyped result) -> boolish)?, ?unless: (^(untyped result) -> boolish)?, ?shared: bool, ?key: (^(*untyped args, **untyped kwargs) -> untyped)?, ?store: Stores::Base?) -> void
37
42
  def memoize_all: (?except: Array[Symbol | String], ?only: Array[Symbol | String], ?include_protected: bool, ?include_private: bool, ?ttl: Numeric?, ?max_size: Integer?, ?if: (^(untyped result) -> boolish)?, ?unless: (^(untyped result) -> boolish)?, ?shared: bool, ?key: (^(*untyped args, **untyped kwargs) -> untyped)?) -> void
38
43
  def reset_shared_memo: (Symbol | String method_name, *untyped args, **untyped kwargs) -> void
39
44
  def reset_all_shared_memos: () -> void
@@ -56,15 +61,15 @@ module SafeMemoize
56
61
 
57
62
  def memoized?: (Symbol | String method_name, *untyped args, **untyped kwargs) ?{ () -> untyped } -> bool
58
63
  def memo_ttl_remaining: (Symbol | String method_name, *untyped args, **untyped kwargs) -> (Float | Integer | nil)
59
- def memo_count: (*untyped method_name) -> Integer
60
- def memo_keys: (*untyped method_name) -> Array[untyped]
61
- def memo_values: (*untyped method_name) -> Array[untyped]
64
+ def memo_count: (?(Symbol | String) method_name) -> Integer
65
+ def memo_keys: (?(Symbol | String) method_name) -> Array[untyped]
66
+ def memo_values: (?(Symbol | String) method_name) -> Array[untyped]
62
67
  def on_memo_expire: { (memo_key cache_key, memo_record record) -> untyped } -> void
63
68
  def on_memo_evict: { (memo_key cache_key, memo_record record) -> untyped } -> void
64
69
  def on_memo_hit: { (memo_key cache_key, memo_record record) -> untyped } -> void
65
70
  def on_memo_miss: { (memo_key cache_key, memo_record record) -> untyped } -> void
66
71
  def on_memo_store: { (memo_key cache_key, memo_record record) -> untyped } -> void
67
- def clear_memo_hooks: (Symbol? hook_type) -> void
72
+ def clear_memo_hooks: (?Symbol hook_type) -> void
68
73
  def warm_memo: (Symbol | String method_name, *untyped args, ?ttl: Numeric?, **untyped kwargs) { () -> untyped } -> untyped
69
74
  def memo_preload: (Symbol | String method_name, *Array[untyped] arg_sets) -> Array[untyped]
70
75
  def dump_memo: (?Symbol | String method_name) -> Hash[memo_key, untyped]
@@ -167,7 +172,7 @@ module SafeMemoize
167
172
 
168
173
  module PublicCustomKeyMethods
169
174
  def memoize_with_custom_key: (Symbol | String method_name) { (*untyped args, **untyped kwargs) -> untyped } -> void
170
- def clear_custom_keys: (Symbol | String? method_name) -> void
175
+ def clear_custom_keys: (?(Symbol | String) method_name) -> void
171
176
  end
172
177
 
173
178
  module LruMethods
@@ -195,7 +200,39 @@ module SafeMemoize
195
200
  include LruMethods
196
201
  end
197
202
 
203
+ module Stores
204
+ class Base
205
+ MISS: Object
206
+
207
+ def read: (untyped key) -> untyped
208
+ def write: (untyped key, untyped value, ?expires_in: Numeric?) -> void
209
+ def delete: (untyped key) -> void
210
+ def clear: () -> void
211
+ def keys: () -> Array[untyped]
212
+ def exist?: (untyped key) -> bool
213
+ end
214
+
215
+ class Memory < Base
216
+ def initialize: () -> void
217
+ def read: (untyped key) -> untyped
218
+ def write: (untyped key, untyped value, ?expires_in: Numeric?) -> void
219
+ def delete: (untyped key) -> void
220
+ def clear: () -> void
221
+ def keys: () -> Array[untyped]
222
+
223
+ private
224
+
225
+ def expired?: ({ expires_at: Float?, value: untyped, cached_at: Float }) -> bool
226
+ end
227
+ end
228
+
198
229
  module Adapters
230
+ module StatsD
231
+ METRIC_NAMES: Hash[Symbol, String]
232
+
233
+ def self.dispatch: (untyped client, Symbol hook_type, memo_key cache_key, String? class_name) -> void
234
+ end
235
+
199
236
  module OpenTelemetry
200
237
  SPAN_NAME: String
201
238
  def self.trace: (untyped tracer, Symbol | String method_name, String? class_name) { () -> untyped } -> untyped
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: safe_memoize
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -41,11 +41,13 @@ extra_rdoc_files: []
41
41
  files:
42
42
  - ".github/workflows/ci.yml"
43
43
  - ".github/workflows/release.yml"
44
+ - ".yardopts"
44
45
  - CHANGELOG.md
45
46
  - LICENSE.txt
46
47
  - README.md
47
48
  - ROADMAP.md
48
49
  - Rakefile
50
+ - UPGRADING.md
49
51
  - benchmarks/README.md
50
52
  - benchmarks/benchmark.rb
51
53
  - codecov.yml
@@ -69,7 +71,12 @@ files:
69
71
  - lib/safe_memoize/rails/middleware.rb
70
72
  - lib/safe_memoize/rails/request_scoped.rb
71
73
  - lib/safe_memoize/release_tooling.rb
74
+ - lib/safe_memoize/stores/base.rb
75
+ - lib/safe_memoize/stores/memory.rb
76
+ - lib/safe_memoize/stores/rails_cache.rb
77
+ - lib/safe_memoize/stores/redis.rb
72
78
  - lib/safe_memoize/version.rb
79
+ - rbi/safe_memoize.rbi
73
80
  - sig/safe_memoize.rbs
74
81
  homepage: https://github.com/eclectic-coding/safe_memoize
75
82
  licenses: