safe_memoize 1.0.0 → 1.2.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,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SafeMemoize
4
+ module Stores
5
+ # Abstract base class for SafeMemoize cache store adapters.
6
+ #
7
+ # Subclass this and implement all abstract methods to plug in a custom backend
8
+ # (Redis, Memcached, Rails.cache, etc.). The {Stores::Memory} class is the
9
+ # built-in reference implementation.
10
+ #
11
+ # @abstract
12
+ #
13
+ # @example Minimal inline implementation
14
+ # class MyStore < SafeMemoize::Stores::Base
15
+ # def initialize = (@h = {})
16
+ # def read(key) = @h.fetch(key, MISS)
17
+ # def write(key, value, expires_in: nil) = (@h[key] = value)
18
+ # def delete(key) = @h.delete(key)
19
+ # def clear = @h.clear
20
+ # def keys = @h.keys
21
+ # end
22
+ class Base
23
+ # Sentinel returned by {#read} to signal a cache miss.
24
+ #
25
+ # Distinct from +nil+ and +false+, which are valid cached values.
26
+ MISS = Object.new.freeze
27
+
28
+ # Read a value from the store.
29
+ #
30
+ # @param key [Object] cache key
31
+ # @return [Object] the stored value, or {MISS} if absent or expired
32
+ # @abstract
33
+ def read(key)
34
+ raise NotImplementedError, "#{self.class}#read must be implemented"
35
+ end
36
+
37
+ # Write a value to the store.
38
+ #
39
+ # @param key [Object] cache key
40
+ # @param value [Object] value to cache (may be +nil+ or +false+)
41
+ # @param expires_in [Numeric, nil] seconds until expiry; +nil+ means no expiry
42
+ # @return [void]
43
+ # @abstract
44
+ def write(key, value, expires_in: nil)
45
+ raise NotImplementedError, "#{self.class}#write must be implemented"
46
+ end
47
+
48
+ # Delete a single entry.
49
+ #
50
+ # @param key [Object] cache key
51
+ # @return [void]
52
+ # @abstract
53
+ def delete(key)
54
+ raise NotImplementedError, "#{self.class}#delete must be implemented"
55
+ end
56
+
57
+ # Remove all entries from the store.
58
+ #
59
+ # @return [void]
60
+ # @abstract
61
+ def clear
62
+ raise NotImplementedError, "#{self.class}#clear must be implemented"
63
+ end
64
+
65
+ # Return all live (non-expired) keys.
66
+ #
67
+ # @return [Array<Object>]
68
+ # @abstract
69
+ def keys
70
+ raise NotImplementedError, "#{self.class}#keys must be implemented"
71
+ end
72
+
73
+ # Check whether a live entry exists for the given key.
74
+ #
75
+ # The default delegates to {#read}; subclasses may override for stores
76
+ # with a native existence check.
77
+ #
78
+ # @param key [Object]
79
+ # @return [Boolean]
80
+ def exist?(key)
81
+ read(key) != MISS
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SafeMemoize
4
+ module Stores
5
+ # Default in-process cache store backed by a plain +Hash+.
6
+ #
7
+ # Thread-safe via an internal +Mutex+. Supports per-entry TTL with lazy
8
+ # expiry: stale entries are not proactively removed but are treated as
9
+ # misses on read and excluded from {#keys}.
10
+ class Memory < Base
11
+ def initialize
12
+ @data = {}
13
+ @mutex = Mutex.new
14
+ end
15
+
16
+ # @param key [Object]
17
+ # @return [Object] stored value, or {MISS} if absent or expired
18
+ def read(key)
19
+ @mutex.synchronize do
20
+ entry = @data[key]
21
+ return MISS unless entry
22
+ return MISS if expired?(entry)
23
+
24
+ entry[:value]
25
+ end
26
+ end
27
+
28
+ # @param key [Object]
29
+ # @param value [Object]
30
+ # @param expires_in [Numeric, nil] seconds until expiry
31
+ # @return [void]
32
+ def write(key, value, expires_in: nil)
33
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
34
+ expires_at = expires_in ? now + expires_in.to_f : nil
35
+
36
+ @mutex.synchronize do
37
+ @data[key] = {value: value, expires_at: expires_at, cached_at: now}
38
+ end
39
+ end
40
+
41
+ # @param key [Object]
42
+ # @return [void]
43
+ def delete(key)
44
+ @mutex.synchronize { @data.delete(key) }
45
+ end
46
+
47
+ # Removes all entries.
48
+ # @return [void]
49
+ def clear
50
+ @mutex.synchronize { @data.clear }
51
+ end
52
+
53
+ # Returns all live (non-expired) keys.
54
+ # @return [Array<Object>]
55
+ def keys
56
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
57
+ @mutex.synchronize do
58
+ @data.filter_map { |k, entry| k unless entry[:expires_at] && entry[:expires_at] <= now }
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def expired?(entry)
65
+ expires_at = entry[:expires_at]
66
+ expires_at && expires_at <= Process.clock_gettime(Process::CLOCK_MONOTONIC)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "safe_memoize"
4
+
5
+ module SafeMemoize
6
+ module Stores
7
+ # Cache store adapter backed by any +ActiveSupport::Cache::Store+.
8
+ #
9
+ # Not auto-required. Add to your Rails initializer:
10
+ # require "safe_memoize/stores/rails_cache"
11
+ #
12
+ # Compatible with any +ActiveSupport::Cache::Store+ implementation
13
+ # (+MemoryStore+, +FileStore+, +MemCacheStore+, +RedisCacheStore+, etc.)
14
+ # and with +Rails.cache+ directly.
15
+ #
16
+ # Because +ActiveSupport::Cache+ returns +nil+ for both a cache miss and
17
+ # a cached +nil+ value, this adapter wraps every value in a two-element
18
+ # sentinel envelope before writing. The envelope is transparent to callers.
19
+ #
20
+ # TTL is forwarded as +expires_in:+ to the cache, so the underlying store
21
+ # manages expiry natively — there is no lazy-expiry overhead on read.
22
+ #
23
+ # {#clear} uses +delete_matched+ scoped to the adapter's namespace, so it
24
+ # never clears entries belonging to other parts of the application. The
25
+ # backend must respond to +delete_matched+ (all standard Rails cache stores
26
+ # do); a +NotImplementedError+ is raised if it does not.
27
+ #
28
+ # {#keys} returns an empty array — +ActiveSupport::Cache::Store+ does not
29
+ # expose a standard key enumeration API. Override the method if your
30
+ # backend supports it.
31
+ #
32
+ # @example Basic setup
33
+ # # config/initializers/safe_memoize.rb
34
+ # require "safe_memoize/stores/rails_cache"
35
+ #
36
+ # MEMO_STORE = SafeMemoize::Stores::RailsCache.new(Rails.cache)
37
+ #
38
+ # class MyService
39
+ # prepend SafeMemoize
40
+ # def fetch(id) = http_get(id)
41
+ # memoize :fetch, store: MEMO_STORE, ttl: 300
42
+ # end
43
+ #
44
+ # @example Dedicated cache store (recommended for production)
45
+ # MEMO_STORE = SafeMemoize::Stores::RailsCache.new(
46
+ # ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"]),
47
+ # namespace: "myapp:memo"
48
+ # )
49
+ class RailsCache < Base
50
+ # Tag prepended to every stored value so cached +nil+/+false+ are
51
+ # distinguishable from a cache miss.
52
+ VALUE_TAG = "safe_memoize:v1"
53
+
54
+ # @param cache [ActiveSupport::Cache::Store] the cache store to use;
55
+ # typically +Rails.cache+ or a dedicated store instance
56
+ # @param namespace [String] key prefix used to scope all entries;
57
+ # defaults to +"safe_memoize"+
58
+ def initialize(cache, namespace: "safe_memoize")
59
+ @cache = cache
60
+ @namespace = namespace
61
+ end
62
+
63
+ # @param key [Object] cache key (serialized with Marshal + Base64)
64
+ # @return [Object] the stored value, or {MISS} if absent or unrecognised
65
+ def read(key)
66
+ raw = @cache.read(cache_key(key))
67
+ return MISS unless raw.is_a?(Array) && raw.length == 2 && raw[0] == VALUE_TAG
68
+
69
+ raw[1]
70
+ end
71
+
72
+ # @param key [Object] cache key
73
+ # @param value [Object] value to store (may be +nil+ or +false+)
74
+ # @param expires_in [Numeric, nil] TTL in seconds forwarded to the cache
75
+ # as +expires_in:+; +nil+ means no expiry
76
+ # @return [void]
77
+ def write(key, value, expires_in: nil)
78
+ opts = expires_in ? {expires_in: expires_in} : {}
79
+ @cache.write(cache_key(key), [VALUE_TAG, value], **opts)
80
+ end
81
+
82
+ # @param key [Object]
83
+ # @return [void]
84
+ def delete(key)
85
+ @cache.delete(cache_key(key))
86
+ end
87
+
88
+ # Removes all entries written by this adapter (scoped to the namespace).
89
+ #
90
+ # Delegates to +delete_matched+ on the underlying store; raises
91
+ # +NotImplementedError+ if the store does not support it.
92
+ #
93
+ # @return [void]
94
+ # @raise [NotImplementedError] if the backing store does not respond to
95
+ # +delete_matched+
96
+ def clear
97
+ unless @cache.respond_to?(:delete_matched)
98
+ raise NotImplementedError,
99
+ "#{@cache.class} does not support delete_matched — " \
100
+ "implement clear manually or use a store that supports it (e.g. MemoryStore, RedisCacheStore)"
101
+ end
102
+ @cache.delete_matched(/\A#{Regexp.escape(@namespace)}:/)
103
+ end
104
+
105
+ # Returns an empty array.
106
+ #
107
+ # +ActiveSupport::Cache::Store+ does not expose a key enumeration API.
108
+ # Override this method if your backend supports key listing.
109
+ #
110
+ # @return [Array]
111
+ def keys
112
+ []
113
+ end
114
+
115
+ # @param key [Object]
116
+ # @return [Boolean]
117
+ def exist?(key)
118
+ @cache.exist?(cache_key(key))
119
+ end
120
+
121
+ private
122
+
123
+ def cache_key(key)
124
+ "#{@namespace}:#{[Marshal.dump(key)].pack("m0")}"
125
+ end
126
+ end
127
+ end
128
+ end
@@ -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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module SafeMemoize
4
4
  # The current gem version string.
5
- VERSION = "1.0.0"
5
+ VERSION = "1.2.0"
6
6
  end
data/lib/safe_memoize.rb CHANGED
@@ -2,8 +2,11 @@
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"
9
+ require_relative "safe_memoize/adapters/concurrent_ruby"
7
10
  require_relative "safe_memoize/class_methods"
8
11
  require_relative "safe_memoize/public_methods"
9
12
  require_relative "safe_memoize/cache_store_methods"
@@ -15,6 +18,8 @@ require_relative "safe_memoize/public_metrics_methods"
15
18
  require_relative "safe_memoize/custom_key_methods"
16
19
  require_relative "safe_memoize/public_custom_key_methods"
17
20
  require_relative "safe_memoize/lru_methods"
21
+ require_relative "safe_memoize/fiber_local_methods"
22
+ require_relative "safe_memoize/ractor_shared_methods"
18
23
  require_relative "safe_memoize/instance_methods"
19
24
 
20
25
  # Thread-safe memoization for Ruby that correctly handles +nil+ and +false+ values.
data/sig/safe_memoize.rbs CHANGED
@@ -32,13 +32,16 @@ module SafeMemoize
32
32
  attr_accessor active_support_notifications: bool
33
33
  attr_accessor statsd_client: untyped
34
34
  attr_accessor opentelemetry_tracer: untyped
35
+ attr_accessor default_store: Stores::Base?
35
36
 
36
37
  def initialize: () -> void
37
38
  end
38
39
 
39
40
  module ClassMethods
40
- 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_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
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?, ?fiber_local: bool, ?ractor_safe: bool) -> void
42
+ def safe_memoize_store: () -> Stores::Base?
43
+ def safe_memoize_store=: (Stores::Base?) -> Stores::Base?
44
+ 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)?, ?fiber_local: bool) -> void
42
45
  def reset_shared_memo: (Symbol | String method_name, *untyped args, **untyped kwargs) -> void
43
46
  def reset_all_shared_memos: () -> void
44
47
  def shared_memoized?: (Symbol | String method_name, *untyped args, **untyped kwargs) -> bool
@@ -186,6 +189,37 @@ module SafeMemoize
186
189
  def lru_clear_all: () -> void
187
190
  end
188
191
 
192
+ module FiberLocalMethods
193
+ FIBER_STORE_KEY: Symbol
194
+
195
+ def fiber_local_memoized?: (Symbol | String method_name, *untyped args, **untyped kwargs) ?{ () -> untyped } -> bool
196
+ def reset_fiber_memo: (Symbol | String method_name, *untyped args, **untyped kwargs) -> void
197
+ def reset_all_fiber_memos: () -> void
198
+
199
+ private
200
+
201
+ def fiber_root_store!: () -> Hash[untyped, untyped]
202
+ def fiber_memo_store!: () -> { cache: Hash[memo_key, memo_record], lru: Hash[Symbol, Hash[memo_key, true]] }
203
+ def fiber_memo_cache!: () -> Hash[memo_key, memo_record]
204
+ def fiber_memo_lru!: () -> Hash[Symbol, Hash[memo_key, true]]
205
+ def fiber_root_store_or_nil: () -> Hash[untyped, untyped]?
206
+ def fiber_memo_store_or_nil: () -> { cache: Hash[memo_key, memo_record], lru: Hash[Symbol, Hash[memo_key, true]] }?
207
+ def fiber_memo_cache_or_nil: () -> Hash[memo_key, memo_record]?
208
+ def fiber_memo_lru_or_nil: () -> Hash[Symbol, Hash[memo_key, true]]?
209
+ end
210
+
211
+ module RactorSharedMethods
212
+ def reset_ractor_memo: (Symbol | String method_name, *untyped args, **untyped kwargs) -> void
213
+ def reset_all_ractor_memos: () -> void
214
+ def ractor_memoized?: (Symbol | String method_name, *untyped args, **untyped kwargs) -> bool
215
+ def ractor_memo_count: (?(Symbol | String) method_name) -> Integer
216
+
217
+ private
218
+
219
+ def __ractor_cache_send__: (untyped supervisor, Symbol op, *untyped args) -> untyped
220
+ def __safe_memo_ractor_supervisor__: () -> untyped
221
+ end
222
+
189
223
  module InstanceMethods
190
224
  include PublicMethods
191
225
  include CacheStoreMethods
@@ -197,6 +231,33 @@ module SafeMemoize
197
231
  include CustomKeyMethods
198
232
  include PublicCustomKeyMethods
199
233
  include LruMethods
234
+ include FiberLocalMethods
235
+ end
236
+
237
+ module Stores
238
+ class Base
239
+ MISS: Object
240
+
241
+ def read: (untyped key) -> untyped
242
+ def write: (untyped key, untyped value, ?expires_in: Numeric?) -> void
243
+ def delete: (untyped key) -> void
244
+ def clear: () -> void
245
+ def keys: () -> Array[untyped]
246
+ def exist?: (untyped key) -> bool
247
+ end
248
+
249
+ class Memory < Base
250
+ def initialize: () -> void
251
+ def read: (untyped key) -> untyped
252
+ def write: (untyped key, untyped value, ?expires_in: Numeric?) -> void
253
+ def delete: (untyped key) -> void
254
+ def clear: () -> void
255
+ def keys: () -> Array[untyped]
256
+
257
+ private
258
+
259
+ def expired?: ({ expires_at: Float?, value: untyped, cached_at: Float }) -> bool
260
+ end
200
261
  end
201
262
 
202
263
  module Adapters
@@ -210,6 +271,17 @@ module SafeMemoize
210
271
  SPAN_NAME: String
211
272
  def self.trace: (untyped tracer, Symbol | String method_name, String? class_name) { () -> untyped } -> untyped
212
273
  end
274
+
275
+ class ConcurrentRuby < Stores::Base
276
+ def initialize: () -> void
277
+ def read: (untyped key) -> untyped
278
+ def write: (untyped key, untyped value, ?expires_in: Numeric?) -> void
279
+ def delete: (untyped key) -> void
280
+ def clear: () -> void
281
+ def keys: () -> Array[untyped]
282
+ private
283
+ def expired?: ({ expires_at: Float?, value: untyped, cached_at: Float }) -> bool
284
+ end
213
285
  end
214
286
 
215
287
  module Rails
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: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -52,6 +52,7 @@ files:
52
52
  - benchmarks/benchmark.rb
53
53
  - codecov.yml
54
54
  - lib/safe_memoize.rb
55
+ - lib/safe_memoize/adapters/concurrent_ruby.rb
55
56
  - lib/safe_memoize/adapters/opentelemetry.rb
56
57
  - lib/safe_memoize/adapters/statsd.rb
57
58
  - lib/safe_memoize/cache_metrics_methods.rb
@@ -60,6 +61,7 @@ files:
60
61
  - lib/safe_memoize/class_methods.rb
61
62
  - lib/safe_memoize/configuration.rb
62
63
  - lib/safe_memoize/custom_key_methods.rb
64
+ - lib/safe_memoize/fiber_local_methods.rb
63
65
  - lib/safe_memoize/hooks_methods.rb
64
66
  - lib/safe_memoize/inspection_methods.rb
65
67
  - lib/safe_memoize/instance_methods.rb
@@ -67,10 +69,15 @@ files:
67
69
  - lib/safe_memoize/public_custom_key_methods.rb
68
70
  - lib/safe_memoize/public_methods.rb
69
71
  - lib/safe_memoize/public_metrics_methods.rb
72
+ - lib/safe_memoize/ractor_shared_methods.rb
70
73
  - lib/safe_memoize/rails.rb
71
74
  - lib/safe_memoize/rails/middleware.rb
72
75
  - lib/safe_memoize/rails/request_scoped.rb
73
76
  - lib/safe_memoize/release_tooling.rb
77
+ - lib/safe_memoize/stores/base.rb
78
+ - lib/safe_memoize/stores/memory.rb
79
+ - lib/safe_memoize/stores/rails_cache.rb
80
+ - lib/safe_memoize/stores/redis.rb
74
81
  - lib/safe_memoize/version.rb
75
82
  - rbi/safe_memoize.rbi
76
83
  - sig/safe_memoize.rbs