typed_cache 0.3.2 → 0.4.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/typed_cache/backend.rb +72 -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 +41 -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 +34 -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 -40
  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 +60 -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 +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3dadf4193084b314da94b9144bbef5766f8898a443404ef10f66202685394622
4
- data.tar.gz: faaa5d4542bbe2ca5716a309f4361c885217633a02b68109d6b402741516494f
3
+ metadata.gz: f4ce9735b042027d3a74d3d5b624c916dade1673855d906503c843e214ab08f7
4
+ data.tar.gz: 1e448a580e62f8889edd6520066d3ef212bf3ad8892e58ce79ba1d3eaec94fbf
5
5
  SHA512:
6
- metadata.gz: c82b0271eadd1415e9064f90d7093cff83d38e27c611f634efdd4abf4b96f8b05cd878102d98320acbb2c93dd6b3932ff15bdd841e66b8a9bbe0097460cf9a2b
7
- data.tar.gz: 895344cc495dbf7aacc384e8bcb3a89f39fc928472b7ab013421f5c3ab35565c1c84c7beb054ec09528d6a61bc7ade3d3b1c30fd948adb47177ec9c909ad18ed
6
+ metadata.gz: 7173225ff902a77ac9083483a005238ac7b54301943a75c4179e52ccaaaaff12923f17a0d427f6f7a07da56d183e588c4fe8922af64b4a0c7077e1042f786805
7
+ data.tar.gz: 5b9e1fefd5b4e82e5cb24f126336a69d92c91e2b2d0312b19e1ad82e3af5967721224d47bd1960dfcf2637dc50c92da1d290fa9783a3b6a2f320d9ee7b9e4acd
checksums.yaml.gz.sig CHANGED
Binary file
@@ -10,7 +10,77 @@ module TypedCache
10
10
  # that actually persist data.
11
11
  # @rbs generic V
12
12
  module Backend
13
- include Store #[V]
14
- # @rbs! include Store::_Store[V]
13
+ # @rbs! type cache_key = String | CacheKey
14
+
15
+ # @rbs!
16
+ # interface _Backend[V]
17
+ # def read: (cache_key, **top) -> V?
18
+ # def read_multi: (Array[cache_key], **top) -> Hash[cache_key, V]
19
+ # def write: (cache_key, V, **top) -> V
20
+ # def write_multi: (Hash[cache_key, V], **top) -> Hash[cache_key, V]
21
+ # def delete: (cache_key, **top) -> V?
22
+ # def key?: (cache_key) -> bool
23
+ # def clear: () -> void
24
+ # def fetch: (cache_key, **top) { () -> V? } -> V?
25
+ # def fetch_multi: (Array[cache_key], **top) { (cache_key) -> V? } -> Hash[cache_key, V]
26
+ # end
27
+
28
+ # @rbs! include _Backend[V]
29
+
30
+ # @rbs override
31
+ # @rbs (cache_key, **top) -> V?
32
+ def read(key, **opts)
33
+ raise NotImplementedError, "#{self.class} must implement #read"
34
+ end
35
+
36
+ # @rbs override
37
+ # @rbs (Array[cache_key], **top) -> Hash[cache_key, V]
38
+ def read_multi(keys, **opts)
39
+ keys.to_h { |key| [key, read(key, **opts)] }
40
+ end
41
+
42
+ # @rbs override
43
+ # @rbs (cache_key, V, **top) -> V
44
+ def write(key, value, **opts)
45
+ raise NotImplementedError, "#{self.class} must implement #write"
46
+ end
47
+
48
+ # @rbs override
49
+ # @rbs (Hash[cache_key, V], **top) -> Hash[cache_key, V]
50
+ def write_multi(values, **opts)
51
+ values.transform_values { |value| write(value, **opts) }
52
+ end
53
+
54
+ # @rbs override
55
+ # @rbs (cache_key, **top) -> V?
56
+ def delete(key, **opts)
57
+ raise NotImplementedError, "#{self.class} must implement #delete"
58
+ end
59
+
60
+ # @rbs override
61
+ # @rbs (cache_key) -> bool
62
+ def key?(key)
63
+ raise NotImplementedError, "#{self.class} must implement #key?"
64
+ end
65
+
66
+ # @rbs override
67
+ # @rbs (**top) -> void
68
+ def clear(**opts)
69
+ raise NotImplementedError, "#{self.class} must implement #clear"
70
+ end
71
+
72
+ # @rbs override
73
+ # @rbs (cache_key, **top) { () -> V? } -> V?
74
+ def fetch(key, **opts, &block)
75
+ raise NotImplementedError, "#{self.class} must implement #fetch"
76
+ end
77
+
78
+ # @rbs override
79
+ # @rbs (Array[cache_key], **top) { (cache_key) -> V? } -> Hash[cache_key, V]
80
+ def fetch_multi(keys, **opts, &block)
81
+ keys.to_h { |key| [key, fetch(key, **opts, &block)] }
82
+ end
15
83
  end
84
+
85
+ # @rbs! type backend[V] = Backend::_Backend[V]
16
86
  end
@@ -7,152 +7,78 @@ module TypedCache
7
7
  class ActiveSupport
8
8
  include Backend #[V]
9
9
 
10
- attr_reader :namespace #: Namespace
11
10
  attr_reader :cache_store #: ::ActiveSupport::Cache::Store
12
11
  attr_reader :default_options #: Hash[Symbol, top]
13
12
 
14
- #: (Namespace, ::ActiveSupport::Cache::Store, ?Hash[Symbol, top]) -> void
15
- def initialize(namespace, cache_store, default_options = {})
16
- @namespace = namespace
13
+ #: (::ActiveSupport::Cache::Store, ?Hash[Symbol, top]) -> void
14
+ def initialize(cache_store, default_options = {})
17
15
  @cache_store = cache_store
18
16
  @default_options = default_options
19
17
  end
20
18
 
21
19
  # @rbs override
22
- #: (cache_key, **top) -> either[Error, Snapshot[V]]
20
+ #: (cache_key, **top) -> V?
23
21
  def read(key, **kwargs)
24
- cache_key_str = namespaced_key(key).to_s
25
- raw_value = cache_store.read(cache_key_str, default_options.merge(**kwargs))
26
- return Either.left(CacheMissError.new(key)) if raw_value.nil?
27
-
28
- Either.right(Snapshot.cached(key, raw_value))
29
- rescue => e
30
- Either.left(StoreError.new(:get, key, "Failed to read from cache: #{e.message}", e))
22
+ cache_store.read(key, default_options.merge(kwargs))
31
23
  end
32
24
 
33
25
  # @rbs override
34
- #: (cache_key, V, **top) -> either[Error, Snapshot[V]]
26
+ #: (cache_key, V, **top) -> V
35
27
  def write(key, value, **kwargs)
36
- cache_key_str = namespaced_key(key).to_s
37
- success = cache_store.write(cache_key_str, value, default_options.merge(**kwargs))
38
-
39
- if success
40
- Either.right(Snapshot.cached(key, value))
41
- else
42
- Either.left(StoreError.new(:set, key, 'Failed to write to cache', nil))
43
- end
44
- rescue => e
45
- Either.left(StoreError.new(:set, key, "Failed to write to cache: #{e.message}", e))
28
+ cache_store.write(key, value, default_options.merge(kwargs))
46
29
  end
47
30
 
48
31
  # @rbs override
49
- #: (Hash[cache_key, V], **top) -> either[Error, Array[Snapshot[V]]]
32
+ #: (Hash[cache_key, V], **top) -> Array[V]
50
33
  def write_all(values, **kwargs)
51
- results = cache_store.write_multi(values.map { |key, value| [namespaced_key(key).to_s, value] }.to_h, default_options.merge(**kwargs))
52
- Either.right(results.map { |key, value| Snapshot.cached(key, value) })
53
- rescue => e
54
- Either.left(StoreError.new(:set_all, values, "Failed to write to cache: #{e.message}", e))
34
+ cache_store.write_multi(values, default_options.merge(kwargs))
55
35
  end
56
36
 
57
37
  # @rbs override
58
- #: (cache_key) -> either[Error, Snapshot[V]]
38
+ #: (cache_key) -> V?
59
39
  def delete(key)
60
- read(key).fold(
61
- ->(error) { Either.left(error) },
62
- ->(snapshot) {
63
- cache_key_str = namespaced_key(key).to_s
64
- cache_store.delete(cache_key_str, default_options)
65
- Either.right(snapshot)
66
- },
67
- )
68
- rescue => e
69
- Either.left(StoreError.new(:delete, key, "Failed to delete from cache: #{e.message}", e))
40
+ cache_store.delete(key, default_options)
70
41
  end
71
42
 
72
43
  # @rbs override
73
- #: (Array[cache_key], **top) -> either[Error, Array[Snapshot[V]]]
44
+ #: (Array[cache_key], **top) -> Hash[cache_key, V]
74
45
  def read_all(keys, **kwargs)
75
- results = cache_store.read_multi(*keys.map { |key| namespaced_key(key).to_s }, default_options.merge(**kwargs))
76
- Either.right(results.map { |key, value| [key, Snapshot.cached(key, value)] }.to_h)
46
+ cache_store.read_multi(*keys, default_options.merge(kwargs))
77
47
  end
78
48
 
79
49
  # @rbs override
80
- #: (Array[cache_key], **top) { (CacheKey) -> V? } -> either[Error, Array[Snapshot[V]]]
81
- def fetch_all(keys, **kwargs, &block)
82
- cache_keys = keys.map { |key| namespaced_key(key) }
83
- key_map = cache_keys.index_by(&:to_s)
84
-
85
- computed_keys = Set.new #: Set[String]
86
- results = cache_store.fetch_multi(*key_map.keys, default_options.merge(**kwargs)) do |key|
87
- computed_keys << key
88
- yield(key_map[key])
89
- end
90
-
91
- snapshots = [] #: Array[Snapshot[V]]
92
-
93
- results.each do |key, value|
94
- maybe_value = Maybe.wrap(value)
95
- snapshots <<
96
- if computed_keys.include?(key)
97
- Snapshot.computed(key, maybe_value)
98
- else
99
- Snapshot.cached(key, maybe_value)
100
- end
101
- end
50
+ #: (cache_key, **top) { () -> V? } -> V?
51
+ def fetch(key, **kwargs, &block)
52
+ cache_store.fetch(key, default_options.merge(kwargs), &block)
53
+ end
102
54
 
103
- Either.right(snapshots)
104
- rescue StandardError => e
105
- Either.left(StoreError.new(:fetch_all, keys, "Failed to fetch from cache: #{e.message}", e))
55
+ # @rbs override
56
+ #: (Array[cache_key], **top) { (CacheKey) -> V? } -> Hash[cache_key, V]
57
+ def fetch_all(keys, **kwargs, &block)
58
+ cache_store.fetch_multi(*keys, default_options.merge(kwargs), &block)
106
59
  end
107
60
 
108
61
  # @rbs override
109
62
  #: (cache_key) -> bool
110
63
  def key?(key)
111
- cache_store.exist?(namespaced_key(key).to_s, default_options)
112
- rescue => _e
113
- false
64
+ cache_store.exist?(key, default_options)
114
65
  end
115
66
 
116
67
  # @rbs override
117
- #: -> maybe[Error]
68
+ #: -> void
118
69
  def clear
119
- if cache_store.respond_to?(:delete_matched)
120
- namespace_prefix_patterns.each do |pattern|
121
- cache_store.delete_matched(pattern, default_options)
122
- end
123
- elsif cache_store.respond_to?(:clear)
124
- cache_store.clear(default_options)
125
- end
126
- Maybe.none
127
- rescue => e
128
- Maybe.some(e)
129
- end
130
-
131
- # @rbs override
132
- #: -> String
133
- def store_type
134
- 'active_support'
70
+ cache_store.clear(default_options)
135
71
  end
136
72
 
137
73
  #: (Hash[Symbol, top]) -> ActiveSupport[V]
138
74
  def with_options(new_options)
139
- self.class.new(namespace, cache_store, new_options)
75
+ self.class.new(cache_store, new_options)
140
76
  end
141
77
 
142
78
  #: -> ::ActiveSupport::Cache::Store
143
79
  def raw_cache
144
80
  cache_store
145
81
  end
146
-
147
- private
148
-
149
- # Regex patterns that match keys for this namespace (with trailing colon)
150
- #: -> Array[Regexp]
151
- def namespace_prefix_patterns
152
- [
153
- /\A#{Regexp.escape(namespace.to_s)}:/,
154
- ]
155
- end
156
82
  end
157
83
  end
158
84
  end
@@ -5,18 +5,6 @@ require 'singleton'
5
5
  require 'concurrent/map'
6
6
 
7
7
  module TypedCache
8
- class MemoryStoreRegistry
9
- include Singleton
10
- extend Forwardable
11
-
12
- def_delegators :@backing_store, :[], :[]=, :delete, :key?, :keys
13
-
14
- #: -> void
15
- def initialize
16
- @backing_store = Concurrent::Map.new
17
- end
18
- end
19
-
20
8
  module Backends
21
9
  # A type-safe memory store implementation with built-in namespacing
22
10
  # @rbs generic V
@@ -57,109 +45,70 @@ module TypedCache
57
45
  end
58
46
  private_constant :Entry
59
47
 
60
- attr_reader :namespace, :ttl #: Namespace, Integer
61
- attr_reader :backing_store #: hash_like[CacheKey, Entry[V]]
48
+ attr_reader :ttl #: Integer
49
+ attr_reader :backing_store #: hash_like[String, Entry[V]]
62
50
 
63
- #: (Namespace, shared: bool, ttl: Integer) -> void
64
- def initialize(namespace, shared: false, ttl: 600)
65
- @namespace = namespace
51
+ #: (ttl: Integer) -> void
52
+ def initialize(ttl: 600)
66
53
  @ttl = ttl
67
- @backing_store = shared ? MemoryStoreRegistry.instance : {}
54
+ @backing_store = Concurrent::Map.new
68
55
  end
69
56
 
70
57
  # @rbs override
71
- #: (cache_key, **top) -> either[Error, Snapshot[V]]
58
+ #: (cache_key, **top) -> V?
72
59
  def read(key, **kwargs)
73
- key = namespaced_key(key)
74
- return Either.left(CacheMissError.new(key)) unless backing_store.key?(key)
60
+ purge_expired_keys
75
61
 
76
62
  entry = backing_store[key]
77
-
78
- if entry.expired?
79
- backing_store.delete(key)
80
- return Either.left(CacheMissError.new(key))
81
- end
82
-
83
- Either.right(Snapshot.cached(key, entry.value))
63
+ entry&.value
84
64
  end
85
65
 
86
66
  # @rbs override
87
- #: (cache_key, V, expires_in: Integer, expires_at: Time, **top) -> either[Error, Snapshot[V]]
67
+ #: (cache_key, V, expires_in: Integer, expires_at: Time, **top) -> V
88
68
  def write(key, value, expires_in: @ttl, expires_at: Clock.now + expires_in, **kwargs)
89
- key = namespaced_key(key)
90
69
  entry = Entry.new(value: value, expires_at: expires_at)
91
70
  backing_store[key] = entry
92
- Either.right(Snapshot.cached(key, value))
93
- rescue => e
94
- Either.left(StoreError.new(
95
- :set,
96
- key,
97
- "Failed to store value for key '#{key}': #{e.message}",
98
- e,
99
- ))
71
+ value
100
72
  end
101
73
 
102
74
  # @rbs override
103
- #: (cache_key) -> either[Error, Snapshot[V]]
75
+ #: (cache_key) -> V?
104
76
  def delete(key)
105
- key = namespaced_key(key)
106
77
  entry = backing_store.delete(key)
107
- if entry.nil?
108
- Either.left(CacheMissError.new(key))
109
- else
110
- Either.right(Snapshot.cached(key, entry.value))
111
- end
78
+ entry&.value
112
79
  end
113
80
 
114
81
  # @rbs override
115
82
  #: (cache_key) -> bool
116
83
  def key?(key)
117
- key = namespaced_key(key)
118
- return false unless backing_store.key?(key) && key.belongs_to?(namespace)
84
+ return false unless backing_store.key?(key)
119
85
 
120
86
  entry = backing_store[key]
121
87
  !entry.expired?
122
88
  end
123
89
 
124
90
  # @rbs override
125
- #: -> maybe[Error]
91
+ #: -> void
126
92
  def clear
127
- keys_to_delete = backing_store.keys.select { |k| k.belongs_to?(namespace) }
128
- keys_to_delete.each { |key| backing_store.delete(key) }
129
- Maybe.none
130
- rescue => e
131
- Maybe.some(e)
93
+ backing_store.clear
132
94
  end
133
95
 
134
96
  # @rbs override
135
- #: -> String
136
- def store_type
137
- 'memory'
138
- end
139
-
140
- #: -> Integer
141
- def size
97
+ #: (cache_key, ttl: Integer, **top) { () -> V? } -> V?
98
+ def fetch(key, ttl: @ttl, **opts, &block)
142
99
  purge_expired_keys
143
- backing_store.keys.count { |k| k.belongs_to?(namespace) }
144
- end
100
+ result = backing_store.compute_if_absent(key) do
101
+ Entry.new(value: yield(key), expires_at: Clock.now + ttl)
102
+ end
145
103
 
146
- #: -> Array[CacheKey]
147
- def keys
148
- purge_expired_keys
149
- backing_store.keys
150
- .select { |k| k.belongs_to?(namespace) }
104
+ result.value
151
105
  end
152
106
 
153
107
  private
154
108
 
155
- #: -> Hash[CacheKey, Entry[V]]
156
- def namespaced_entries
157
- backing_store.select { |key, _entry| key.belongs_to?(namespace) }
158
- end
159
-
160
109
  #: -> void
161
110
  def purge_expired_keys
162
- namespaced_entries.each do |key, entry|
111
+ backing_store.each do |key, entry|
163
112
  backing_store.delete(key) if entry.expired?
164
113
  end
165
114
  end
@@ -11,7 +11,7 @@ module TypedCache
11
11
  # Backend registry using composition
12
12
  REGISTRY = Registry.new('backend', {
13
13
  memory: Memory,
14
- }).freeze
14
+ }).freeze #: Registry[Backend]
15
15
 
16
16
  class << self
17
17
  extend Forwardable
@@ -23,7 +23,7 @@ module TypedCache
23
23
 
24
24
  # Convenience method delegating to registry
25
25
  # @rbs!
26
- # def resolve: (Symbol, *untyped, **untyped) -> either[Error, Store[untyped]]
26
+ # def resolve: (Symbol, *untyped, **untyped) -> either[Error, Backend[untyped]]
27
27
  # def available: -> Array[Symbol]
28
28
  # def register: (Symbol, Class) -> either[Error, void]
29
29
  # def registered?: (Symbol) -> bool