typed_cache 0.2.0 → 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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/README.md +25 -3
- data/examples.md +36 -2
- data/lib/typed_cache/backends/active_support.rb +50 -5
- data/lib/typed_cache/backends/memory.rb +5 -5
- data/lib/typed_cache/cache_key.rb +9 -1
- data/lib/typed_cache/cache_ref.rb +16 -16
- data/lib/typed_cache/decorator.rb +9 -1
- data/lib/typed_cache/decorators/instrumented.rb +21 -8
- data/lib/typed_cache/either.rb +22 -0
- data/lib/typed_cache/instrumenter.rb +6 -6
- data/lib/typed_cache/instrumenters/mixins/namespaced_singleton.rb +3 -0
- data/lib/typed_cache/instrumenters.rb +2 -1
- data/lib/typed_cache/maybe.rb +18 -0
- data/lib/typed_cache/namespace.rb +33 -6
- data/lib/typed_cache/railtie.rb +15 -0
- data/lib/typed_cache/snapshot.rb +18 -10
- data/lib/typed_cache/store.rb +47 -15
- data/lib/typed_cache/version.rb +1 -1
- data/lib/typed_cache.rb +4 -0
- data/rbi/typed_cache/backend.rbi +9 -0
- data/rbi/typed_cache/backends/active_support.rbi +13 -0
- data/rbi/typed_cache/backends/memory.rbi +13 -0
- data/rbi/typed_cache/backends.rbi +19 -0
- data/rbi/typed_cache/cache_builder.rbi +23 -0
- data/rbi/typed_cache/cache_key.rbi +16 -0
- data/rbi/typed_cache/cache_ref.rbi +56 -0
- data/rbi/typed_cache/decorator.rbi +67 -0
- data/rbi/typed_cache/decorators/instrumented.rbi +13 -0
- data/rbi/typed_cache/decorators.rbi +19 -0
- data/rbi/typed_cache/either.rbi +122 -0
- data/rbi/typed_cache/errors.rbi +20 -0
- data/rbi/typed_cache/instrumenter.rbi +45 -0
- data/rbi/typed_cache/instrumenters/mixins/namedspaced_singleton.rbi +33 -0
- data/rbi/typed_cache/instrumenters.rbi +19 -0
- data/rbi/typed_cache/maybe.rbi +108 -0
- data/rbi/typed_cache/namespace.rbi +30 -0
- data/rbi/typed_cache/snapshot.rbi +54 -0
- data/rbi/typed_cache/store.rbi +71 -0
- data/rbi/typed_cache/version.rbi +5 -0
- data/rbi/typed_cache.rbi +49 -0
- data/sig/generated/typed_cache/backends/active_support.rbs +14 -2
- data/sig/generated/typed_cache/backends/memory.rbs +2 -2
- data/sig/generated/typed_cache/cache_key.rbs +5 -0
- data/sig/generated/typed_cache/cache_ref.rbs +4 -4
- data/sig/generated/typed_cache/decorator.rbs +4 -0
- data/sig/generated/typed_cache/decorators/instrumented.rbs +4 -4
- data/sig/generated/typed_cache/either.rbs +24 -0
- data/sig/generated/typed_cache/instrumenter.rbs +5 -5
- data/sig/generated/typed_cache/instrumenters/mixins/namespaced_singleton.rbs +3 -0
- data/sig/generated/typed_cache/instrumenters.rbs +2 -0
- data/sig/generated/typed_cache/maybe.rbs +20 -0
- data/sig/generated/typed_cache/namespace.rbs +24 -3
- data/sig/generated/typed_cache/railtie.rbs +6 -0
- data/sig/generated/typed_cache/snapshot.rbs +12 -6
- data/sig/generated/typed_cache/store.rbs +23 -8
- data/sig/generated/typed_cache.rbs +2 -0
- data/typed_cache.gemspec +1 -1
- data.tar.gz.sig +0 -0
- metadata +26 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52137d39af663110c085558e5d6885fbe22d02b4a185f7a1ef3f4aa2dbe6a619
|
4
|
+
data.tar.gz: 030bf378a6effe782ab28fdb876c64c73d8997e5a3a9ec543735abe55973e421
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38641da2ef43033f286833d84492b02f09dcfca291e7eab53c93bf6c9f737de5f077984a4f742de06c62a0c90db19d00bbc03cb032485143e9a542576fb5f90c
|
7
|
+
data.tar.gz: 2fb8cd40d84bb43a3f9905ccc7c0d30d853cdf0b030cf192a6e17517545ac94f256a3371f85bcd068e4f08d12c7a32a937f8eb3a87622b8b3a16429e772bd8bb
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/README.md
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
TypedCache is a lightweight, type-safe façade around your favourite Ruby cache
|
10
10
|
stores. It adds three things on top of the raw back-end implementation:
|
11
11
|
|
12
|
-
1. **Namespacing** – hierarchical `Namespace` helpers prevent key collisions.
|
12
|
+
1. **Namespacing** – hierarchical `Namespace` helpers prevent key collisions. You can create nested namespaces easily, like `Namespace.at("users", "profiles", "avatars")`.
|
13
13
|
2. **Stronger types** – RBS signatures as well as monadic types like `Either`, `Maybe`, and `Snapshot` wrap cache results so you always know whether you have a value, an error, or a cache-miss.
|
14
14
|
3. **Composable decorators** – behaviours like instrumentation can be layered
|
15
15
|
on without touching the underlying store.
|
@@ -125,9 +125,9 @@ result.fold(
|
|
125
125
|
)
|
126
126
|
```
|
127
127
|
|
128
|
-
## The `CacheRef`
|
128
|
+
## The `CacheRef` and `Store` APIs
|
129
129
|
|
130
|
-
While you can call `get`, `set`, and `fetch` directly on the `store`, the more powerful way to work with TypedCache is via the `CacheRef` object. It provides a rich, monadic API for a single cache key.
|
130
|
+
While you can call `get`, `set`, and `fetch` directly on the `store`, the more powerful way to work with TypedCache is via the `CacheRef` object. It provides a rich, monadic API for a single cache key. The `Store` also provides `fetch_all` for batch operations.
|
131
131
|
|
132
132
|
You get a `CacheRef` by calling `store.ref(key)`:
|
133
133
|
|
@@ -154,6 +154,28 @@ name_either = user_ref.map { |user| user[:name] }
|
|
154
154
|
puts "User name is: #{name_either.value.value}" # unwrap Either, then Snapshot
|
155
155
|
```
|
156
156
|
|
157
|
+
### Batch Operations with `fetch_all`
|
158
|
+
|
159
|
+
For retrieving multiple keys at once, the `Store` provides a `fetch_all` method. This is more efficient than fetching keys one by one, especially with remote back-ends like Redis.
|
160
|
+
|
161
|
+
It takes a list of keys and a block to compute the values for any missing keys.
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
user_refs = store.fetch_all("users:123", "users:456") do |missing_key|
|
165
|
+
# This block is called for each cache miss
|
166
|
+
user_id = missing_key.split(":").last
|
167
|
+
puts "Cache miss for #{missing_key}! Computing..."
|
168
|
+
{ id: user_id, name: "Fetched User #{user_id}" }
|
169
|
+
end
|
170
|
+
|
171
|
+
user_refs.each do |key, snapshot_either|
|
172
|
+
snapshot_either.fold(
|
173
|
+
->(err) { warn "Error for #{key}: #{err.message}" },
|
174
|
+
->(snapshot) { puts "Got value for #{key}: #{snapshot.value}" }
|
175
|
+
)
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
157
179
|
The `CacheRef` API encourages a functional style and makes composing cache operations safe and predictable.
|
158
180
|
|
159
181
|
## Instrumentation
|
data/examples.md
CHANGED
@@ -70,9 +70,26 @@ posts_store = base_builder.build(TypedCache::Namespace.at("posts")).value
|
|
70
70
|
comments_store = base_builder.build(TypedCache::Namespace.at("comments")).value
|
71
71
|
```
|
72
72
|
|
73
|
-
##
|
73
|
+
## Advanced Namespacing
|
74
74
|
|
75
|
-
|
75
|
+
You can create nested namespaces using variadic arguments to `Namespace.at` or by chaining `join`.
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
# These are equivalent
|
79
|
+
ns1 = TypedCache::Namespace.at("app", "v1", "users")
|
80
|
+
ns2 = TypedCache::Namespace.at("app").join("v1").join("users")
|
81
|
+
|
82
|
+
puts ns1.to_s # => "app:v1:users"
|
83
|
+
puts ns2.to_s # => "app:v1:users"
|
84
|
+
|
85
|
+
# You can then build a store with this complex namespace
|
86
|
+
user_store = base_builder.build(ns1).value
|
87
|
+
user_store.ref("123").set({ name: "Deeply Nested" })
|
88
|
+
```
|
89
|
+
|
90
|
+
## CacheRef and Store APIs
|
91
|
+
|
92
|
+
The `CacheRef` is the most powerful way to interact with a single cache key, while the `Store` provides batch operations.
|
76
93
|
|
77
94
|
```ruby
|
78
95
|
ref = store.ref("some-key") # => CacheRef
|
@@ -90,6 +107,23 @@ result.fold(
|
|
90
107
|
)
|
91
108
|
```
|
92
109
|
|
110
|
+
### Fetch all (get or compute)
|
111
|
+
|
112
|
+
The `fetch_all` method on the `store` is used for bulk-retrieving items. It gets all existing values from the cache and for the ones that are missing, it runs the block, stores the result, and returns it.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
results = store.fetch_all("user:1", "user:2") do |missing_key|
|
116
|
+
# logic to compute the value for a missing key
|
117
|
+
"computed-#{missing_key}"
|
118
|
+
end
|
119
|
+
|
120
|
+
results.each do |key, snapshot_either|
|
121
|
+
snapshot_either.map do |snapshot|
|
122
|
+
puts "#{key} -> #{snapshot.value} (from_cache?=#{snapshot.from_cache?})"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
93
127
|
### Fetch (get or compute)
|
94
128
|
|
95
129
|
The `fetch` method is the most common operation. It gets a value from the cache, but if it's missing, it runs the block, stores the result, and returns it.
|
@@ -20,24 +20,24 @@ module TypedCache
|
|
20
20
|
|
21
21
|
# @rbs override
|
22
22
|
#: (cache_key) -> either[Error, Snapshot[V]]
|
23
|
-
def
|
23
|
+
def read(key)
|
24
24
|
cache_key_str = namespaced_key(key).to_s
|
25
25
|
raw_value = cache_store.read(cache_key_str, default_options)
|
26
26
|
return Either.left(CacheMissError.new(key)) if raw_value.nil?
|
27
27
|
|
28
|
-
Either.right(Snapshot.
|
28
|
+
Either.right(Snapshot.cached(key, raw_value))
|
29
29
|
rescue => e
|
30
30
|
Either.left(StoreError.new(:get, key, "Failed to read from cache: #{e.message}", e))
|
31
31
|
end
|
32
32
|
|
33
33
|
# @rbs override
|
34
34
|
#: (cache_key, V) -> either[Error, Snapshot[V]]
|
35
|
-
def
|
35
|
+
def write(key, value)
|
36
36
|
cache_key_str = namespaced_key(key).to_s
|
37
37
|
success = cache_store.write(cache_key_str, value, default_options)
|
38
38
|
|
39
39
|
if success
|
40
|
-
Either.right(Snapshot.
|
40
|
+
Either.right(Snapshot.cached(key, value))
|
41
41
|
else
|
42
42
|
Either.left(StoreError.new(:set, key, 'Failed to write to cache', nil))
|
43
43
|
end
|
@@ -45,10 +45,19 @@ module TypedCache
|
|
45
45
|
Either.left(StoreError.new(:set, key, "Failed to write to cache: #{e.message}", e))
|
46
46
|
end
|
47
47
|
|
48
|
+
# @rbs override
|
49
|
+
#: (Hash[cache_key, V]) -> either[Error, Array[Snapshot[V]]]
|
50
|
+
def write_all(values)
|
51
|
+
results = cache_store.write_multi(values.map { |key, value| [namespaced_key(key).to_s, value] }.to_h, default_options)
|
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))
|
55
|
+
end
|
56
|
+
|
48
57
|
# @rbs override
|
49
58
|
#: (cache_key) -> either[Error, Snapshot[V]]
|
50
59
|
def delete(key)
|
51
|
-
|
60
|
+
read(key).fold(
|
52
61
|
->(error) { Either.left(error) },
|
53
62
|
->(snapshot) {
|
54
63
|
cache_key_str = namespaced_key(key).to_s
|
@@ -60,6 +69,42 @@ module TypedCache
|
|
60
69
|
Either.left(StoreError.new(:delete, key, "Failed to delete from cache: #{e.message}", e))
|
61
70
|
end
|
62
71
|
|
72
|
+
# @rbs override
|
73
|
+
#: (Array[cache_key]) -> either[Error, Array[Snapshot[V]]]
|
74
|
+
def read_all(keys)
|
75
|
+
results = cache_store.read_multi(*keys.map { |key| namespaced_key(key).to_s }, default_options)
|
76
|
+
Either.right(results.map { |key, value| [key, Snapshot.cached(key, value)] }.to_h)
|
77
|
+
end
|
78
|
+
|
79
|
+
# @rbs override
|
80
|
+
#: (Array[cache_key]) { (CacheKey) -> V? } -> either[Error, Array[Snapshot[V]]]
|
81
|
+
def fetch_all(keys, &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) 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
|
102
|
+
|
103
|
+
Either.right(snapshots)
|
104
|
+
rescue StandardError => e
|
105
|
+
Either.left(StoreError.new(:fetch_all, keys, "Failed to fetch from cache: #{e.message}", e))
|
106
|
+
end
|
107
|
+
|
63
108
|
# @rbs override
|
64
109
|
#: (cache_key) -> bool
|
65
110
|
def key?(key)
|
@@ -69,7 +69,7 @@ module TypedCache
|
|
69
69
|
|
70
70
|
# @rbs override
|
71
71
|
#: (cache_key) -> either[Error, Snapshot[V]]
|
72
|
-
def
|
72
|
+
def read(key)
|
73
73
|
key = namespaced_key(key)
|
74
74
|
return Either.left(CacheMissError.new(key)) unless backing_store.key?(key)
|
75
75
|
|
@@ -80,17 +80,17 @@ module TypedCache
|
|
80
80
|
return Either.left(CacheMissError.new(key))
|
81
81
|
end
|
82
82
|
|
83
|
-
Either.right(Snapshot.
|
83
|
+
Either.right(Snapshot.cached(key, entry.value))
|
84
84
|
end
|
85
85
|
|
86
86
|
# @rbs override
|
87
87
|
#: (cache_key, V) -> either[Error, Snapshot[V]]
|
88
|
-
def
|
88
|
+
def write(key, value)
|
89
89
|
key = namespaced_key(key)
|
90
90
|
expires_at = Clock.now + @ttl
|
91
91
|
entry = Entry.new(value: value, expires_at: expires_at)
|
92
92
|
backing_store[key] = entry
|
93
|
-
Either.right(Snapshot.
|
93
|
+
Either.right(Snapshot.cached(key, value))
|
94
94
|
rescue => e
|
95
95
|
Either.left(StoreError.new(
|
96
96
|
:set,
|
@@ -108,7 +108,7 @@ module TypedCache
|
|
108
108
|
if entry.nil?
|
109
109
|
Either.left(CacheMissError.new(key))
|
110
110
|
else
|
111
|
-
Either.right(Snapshot.
|
111
|
+
Either.right(Snapshot.cached(key, entry.value))
|
112
112
|
end
|
113
113
|
end
|
114
114
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'forwardable'
|
3
4
|
require_relative 'namespace'
|
4
5
|
|
5
6
|
module TypedCache
|
@@ -22,7 +23,7 @@ module TypedCache
|
|
22
23
|
|
23
24
|
# @rbs () -> String
|
24
25
|
def to_s
|
25
|
-
|
26
|
+
[@namespace.to_s, @key].join(delimiter)
|
26
27
|
end
|
27
28
|
|
28
29
|
alias cache_key to_s
|
@@ -43,5 +44,12 @@ module TypedCache
|
|
43
44
|
end
|
44
45
|
|
45
46
|
alias eql? ==
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# @rbs (String) -> String
|
51
|
+
def delimiter
|
52
|
+
TypedCache.config.cache_delimiter
|
53
|
+
end
|
46
54
|
end
|
47
55
|
end
|
@@ -22,14 +22,14 @@ module TypedCache
|
|
22
22
|
|
23
23
|
# Gets a value from the cache as a snapshot
|
24
24
|
#: -> either[Error, Snapshot[V]]
|
25
|
-
def
|
26
|
-
store.
|
25
|
+
def read
|
26
|
+
store.read(key)
|
27
27
|
end
|
28
28
|
|
29
29
|
# Sets a value in the cache and returns it as an updated snapshot
|
30
30
|
#: (V) -> either[Error, Snapshot[V]]
|
31
|
-
def
|
32
|
-
store.
|
31
|
+
def write(value)
|
32
|
+
store.write(key, value)
|
33
33
|
end
|
34
34
|
|
35
35
|
# Deletes the value from the cache and returns the deleted value as a snapshot
|
@@ -48,25 +48,25 @@ module TypedCache
|
|
48
48
|
# Checks if the cache contains a value for this key
|
49
49
|
#: -> bool
|
50
50
|
def present?
|
51
|
-
store.
|
51
|
+
store.read(key).right?
|
52
52
|
end
|
53
53
|
|
54
54
|
# Checks if the cache is empty for this key
|
55
55
|
#: -> bool
|
56
56
|
def empty?
|
57
|
-
store.
|
57
|
+
store.read(key).left?
|
58
58
|
end
|
59
59
|
|
60
60
|
# Maps over the cached value if it exists, preserving snapshot metadata
|
61
61
|
#: [R] () { (V) -> R } -> either[Error, Snapshot[R]]
|
62
62
|
def map(&block)
|
63
|
-
|
63
|
+
read.map { |snapshot| snapshot.map(&block) }
|
64
64
|
end
|
65
65
|
|
66
66
|
# Binds over the cached value, allowing for monadic composition with snapshots
|
67
67
|
#: [R] () { (V) -> either[Error, R] } -> either[Error, Snapshot[R]]
|
68
68
|
def bind(&block)
|
69
|
-
|
69
|
+
read.bind { |snapshot| snapshot.bind(&block) }
|
70
70
|
end
|
71
71
|
|
72
72
|
alias flat_map bind
|
@@ -75,9 +75,9 @@ module TypedCache
|
|
75
75
|
# Returns the updated value as a snapshot with source=:updated
|
76
76
|
#: () { (V) -> V } -> either[Error, Snapshot[V]]
|
77
77
|
def update(&block)
|
78
|
-
|
78
|
+
read.bind do |snapshot|
|
79
79
|
new_value = yield(snapshot.value)
|
80
|
-
|
80
|
+
write(new_value)
|
81
81
|
rescue => e
|
82
82
|
Either.left(StoreError.new(
|
83
83
|
:update,
|
@@ -91,7 +91,7 @@ module TypedCache
|
|
91
91
|
# Returns the cached value or a default if the cache is empty/errored
|
92
92
|
#: (V) -> V
|
93
93
|
def value_or(default)
|
94
|
-
|
94
|
+
read.fold(
|
95
95
|
->(_error) { default },
|
96
96
|
->(snapshot) { snapshot.value },
|
97
97
|
)
|
@@ -101,7 +101,7 @@ module TypedCache
|
|
101
101
|
# This provides a more functional approach than value_or
|
102
102
|
#: -> maybe[V]
|
103
103
|
def value_maybe
|
104
|
-
|
104
|
+
read.fold(
|
105
105
|
->(_error) { Maybe.none },
|
106
106
|
->(snapshot) { Maybe.some(snapshot.value) },
|
107
107
|
)
|
@@ -139,21 +139,21 @@ module TypedCache
|
|
139
139
|
end
|
140
140
|
|
141
141
|
# Pattern matching support for Either[Error, Snapshot[V]] results
|
142
|
-
#: [R] (
|
142
|
+
#: [R] (^(Error) -> R, ^(Snapshot[V]) -> R) -> R
|
143
143
|
def fold(left_fn, right_fn)
|
144
|
-
|
144
|
+
read.fold(left_fn, right_fn)
|
145
145
|
end
|
146
146
|
|
147
147
|
# Convenience method to work with the snapshot directly
|
148
148
|
#: [R] () { (Snapshot[V]) -> R } -> either[Error, R]
|
149
149
|
def with_snapshot(&block)
|
150
|
-
|
150
|
+
read.map(&block)
|
151
151
|
end
|
152
152
|
|
153
153
|
# Convenience method to work with just the value (losing snapshot context)
|
154
154
|
#: [R] () { (V) -> R } -> either[Error, R]
|
155
155
|
def with(&block)
|
156
|
-
|
156
|
+
read.map { |snapshot| yield(snapshot.value) }
|
157
157
|
end
|
158
158
|
end
|
159
159
|
end
|
@@ -23,7 +23,15 @@ module TypedCache
|
|
23
23
|
# @rbs override
|
24
24
|
#: (cache_key) -> either[Error, CacheRef[V]]
|
25
25
|
def ref(key)
|
26
|
-
CacheRef.new(self, key)
|
26
|
+
CacheRef.new(self, namespaced_key(key))
|
27
|
+
end
|
28
|
+
|
29
|
+
# @rbs override
|
30
|
+
#: (self) -> void
|
31
|
+
def initialize_copy(other)
|
32
|
+
super
|
33
|
+
|
34
|
+
@store = other.store.clone
|
27
35
|
end
|
28
36
|
end
|
29
37
|
end
|
@@ -24,9 +24,15 @@ module TypedCache
|
|
24
24
|
|
25
25
|
class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
26
26
|
def #{alias_prefix}_with_instrumentation(...)
|
27
|
+
return #{alias_prefix}_without_instrumentation(...) if @in_instrumentation
|
28
|
+
|
27
29
|
key = #{alias_prefix}_instrumentation_key(...)
|
28
30
|
instrumenter.instrument(:"#{operation}", key, store_type: store_type) do
|
31
|
+
@in_instrumentation = true
|
32
|
+
|
29
33
|
#{alias_prefix}_without_instrumentation(...)
|
34
|
+
ensure
|
35
|
+
@in_instrumentation = false
|
30
36
|
end
|
31
37
|
end
|
32
38
|
RUBY
|
@@ -40,6 +46,16 @@ module TypedCache
|
|
40
46
|
def initialize(store, instrumenter:)
|
41
47
|
@store = store
|
42
48
|
@instrumenter = instrumenter
|
49
|
+
|
50
|
+
# Avoid instrumenting the cache calls themselves, fetch_all may call fetch for example
|
51
|
+
@in_instrumentation = false
|
52
|
+
end
|
53
|
+
|
54
|
+
# @rbs override
|
55
|
+
#: (self) -> self
|
56
|
+
def initialize_copy(other)
|
57
|
+
super
|
58
|
+
@instrumenter = other.instrumenter
|
43
59
|
end
|
44
60
|
|
45
61
|
# @rbs override
|
@@ -49,12 +65,6 @@ module TypedCache
|
|
49
65
|
"instrumented(#{store.store_type})"
|
50
66
|
end
|
51
67
|
|
52
|
-
# @rbs override
|
53
|
-
# @rbs (key) -> CacheRef[V]
|
54
|
-
def ref(key)
|
55
|
-
CacheRef.new(self, key)
|
56
|
-
end
|
57
|
-
|
58
68
|
# Additional methods that might exist on the wrapped store
|
59
69
|
def respond_to_missing?(method_name, include_private = false)
|
60
70
|
store.respond_to?(method_name, include_private) || super
|
@@ -69,10 +79,13 @@ module TypedCache
|
|
69
79
|
end
|
70
80
|
|
71
81
|
# Instrument core operations with proper key extraction
|
72
|
-
instrument(:
|
73
|
-
instrument(:
|
82
|
+
instrument(:read) { |key, *_| key }
|
83
|
+
instrument(:read_all) { |keys, *_| keys.map(&:to_s).join('_') }
|
84
|
+
instrument(:write) { |key, *_| key }
|
85
|
+
instrument(:write_all) { |values, *_| values.map { |key, _| key.to_s }.join('_') }
|
74
86
|
instrument(:delete) { |key, *_| key }
|
75
87
|
instrument(:fetch) { |key, *_| key }
|
88
|
+
instrument(:fetch_all) { |keys, *_| keys.map(&:to_s).join('_') }
|
76
89
|
instrument(:key?) { |key, *_| key }
|
77
90
|
instrument(:clear) { 'all' }
|
78
91
|
end
|
data/lib/typed_cache/either.rb
CHANGED
@@ -27,6 +27,8 @@ module TypedCache
|
|
27
27
|
# @rbs! interface _Either[out E, out R]
|
28
28
|
# def left?: -> bool
|
29
29
|
# def right?: -> bool
|
30
|
+
# def right_or_else: (^(E) -> void) -> R
|
31
|
+
# def right_or_raise!: -> R
|
30
32
|
# def map: [T] () { (R) -> T } -> either[E, T]
|
31
33
|
# def bind: [E2, R2] () { (R) -> either[E2, R2] } -> either[E | E2, R2]
|
32
34
|
# def map_left: [F] () { (E) -> F } -> either[F, R]
|
@@ -39,6 +41,8 @@ module TypedCache
|
|
39
41
|
|
40
42
|
attr_reader :error #: E
|
41
43
|
|
44
|
+
alias value error
|
45
|
+
|
42
46
|
#: (E) -> void
|
43
47
|
def initialize(error)
|
44
48
|
@error = error
|
@@ -52,6 +56,14 @@ module TypedCache
|
|
52
56
|
#: -> false
|
53
57
|
def right? = false
|
54
58
|
|
59
|
+
# @rbs override
|
60
|
+
#: (^(E) -> void) -> bot
|
61
|
+
def right_or_else(&) = yield(error)
|
62
|
+
|
63
|
+
# @rbs override
|
64
|
+
#: -> bot
|
65
|
+
def right_or_raise! = raise(error)
|
66
|
+
|
55
67
|
# @rbs override
|
56
68
|
#: [T] () { (R) -> T } -> either[E, T]
|
57
69
|
def map(&) = self
|
@@ -82,6 +94,8 @@ module TypedCache
|
|
82
94
|
|
83
95
|
attr_reader :value #: R
|
84
96
|
|
97
|
+
alias result value
|
98
|
+
|
85
99
|
#: (R) -> void
|
86
100
|
def initialize(value)
|
87
101
|
@value = value
|
@@ -95,6 +109,14 @@ module TypedCache
|
|
95
109
|
#: -> true
|
96
110
|
def right? = true
|
97
111
|
|
112
|
+
# @rbs override
|
113
|
+
#: (^(E) -> void) -> R
|
114
|
+
def right_or_else(&) = value
|
115
|
+
|
116
|
+
# @rbs override
|
117
|
+
#: -> R
|
118
|
+
def right_or_raise! = value
|
119
|
+
|
98
120
|
# @rbs override
|
99
121
|
#: [T] () { (R) -> T } -> either[E, T]
|
100
122
|
def map(&) = Right.new(yield(value))
|
@@ -6,7 +6,7 @@ module TypedCache
|
|
6
6
|
# @rbs! type event = Dry::Events::Event | ActiveSupport::Notifications::Event
|
7
7
|
|
8
8
|
# @rbs [R](String, String, **untyped) { -> R } -> R
|
9
|
-
def instrument(event_name, key, **payload)
|
9
|
+
def instrument(event_name, key, **payload, &)
|
10
10
|
raise NotImplementedError, "#{self.class} must implement #instrument"
|
11
11
|
end
|
12
12
|
|
@@ -20,21 +20,21 @@ module TypedCache
|
|
20
20
|
config.namespace
|
21
21
|
end
|
22
22
|
|
23
|
+
# @rbs () -> bool
|
24
|
+
def enabled? = config.enabled
|
25
|
+
|
26
|
+
private
|
27
|
+
|
23
28
|
# @rbs (String, String, **untyped) -> Hash[Symbol, untyped]
|
24
29
|
def build_payload(operation, key, **payload)
|
25
30
|
{ namespace:, key:, operation: }.merge(payload)
|
26
31
|
end
|
27
32
|
|
28
|
-
# @rbs () -> bool
|
29
|
-
def enabled? = config.enabled
|
30
|
-
|
31
33
|
# @rbs (String) -> String
|
32
34
|
def event_name(operation)
|
33
35
|
"#{namespace}.#{operation}"
|
34
36
|
end
|
35
37
|
|
36
|
-
private
|
37
|
-
|
38
38
|
# @rbs () -> TypedCache::_TypedCacheInstrumentationConfig
|
39
39
|
def config
|
40
40
|
TypedCache.config.instrumentation
|
@@ -43,6 +43,9 @@ module TypedCache
|
|
43
43
|
namespace_cache.get(namespace.to_s)
|
44
44
|
end
|
45
45
|
|
46
|
+
# @rbs () -> void
|
47
|
+
def clear_namespace_cache = namespace_cache.clear
|
48
|
+
|
46
49
|
# @rbs () -> Concurrent::Map[String, Class[Instrumenter & NamespacedSingleton]]
|
47
50
|
def namespace_cache = @namespace_cache ||= Concurrent::Map.new # rubocop:disable ThreadSafety
|
48
51
|
end
|
@@ -28,11 +28,12 @@ module TypedCache
|
|
28
28
|
# @rbs () -> Registry[Symbol, Class[Instrumenter]]
|
29
29
|
def registry = REGISTRY
|
30
30
|
|
31
|
+
# @rbs! def register: (Symbol, Class[Instrumenter]) -> void
|
31
32
|
# @rbs! def resolve: (Symbol, **untyped) -> either[Error, Instrumenter]
|
32
33
|
# @rbs! def available: () -> Array[Symbol]
|
33
34
|
# @rbs! def registered?: (Symbol) -> Boolean
|
34
35
|
|
35
|
-
def_delegators :registry, :resolve, :available, :registered
|
36
|
+
def_delegators :registry, :resolve, :available, :registered?, :register
|
36
37
|
end
|
37
38
|
end
|
38
39
|
end
|
data/lib/typed_cache/maybe.rb
CHANGED
@@ -33,6 +33,8 @@ module TypedCache
|
|
33
33
|
# def nothing?: -> bool
|
34
34
|
# def map: [T] () { (V) -> T } -> maybe[T]
|
35
35
|
# def bind: [T] () { (V) -> maybe[T] } -> maybe[T]
|
36
|
+
# def value_or: [T] (T) -> T
|
37
|
+
# def value_or_raise!: -> V
|
36
38
|
# alias flat_map bind
|
37
39
|
# end
|
38
40
|
|
@@ -62,6 +64,14 @@ module TypedCache
|
|
62
64
|
#: [T] () { (V) -> maybe[T] } -> maybe[T]
|
63
65
|
def bind(&) = yield(value)
|
64
66
|
|
67
|
+
# @rbs override
|
68
|
+
#: [T] (T) -> T
|
69
|
+
def value_or(default) = value
|
70
|
+
|
71
|
+
# @rbs override
|
72
|
+
#: -> V
|
73
|
+
def value_or_raise! = value
|
74
|
+
|
65
75
|
alias flat_map bind
|
66
76
|
|
67
77
|
#: (Array[top]) -> ({ value: V })
|
@@ -88,5 +98,13 @@ module TypedCache
|
|
88
98
|
#: [T] () { (V) -> maybe[T] } -> maybe[T]
|
89
99
|
def bind(&) = self
|
90
100
|
alias flat_map bind
|
101
|
+
|
102
|
+
# @rbs override
|
103
|
+
#: [T] (T) -> T
|
104
|
+
def value_or(default) = default
|
105
|
+
|
106
|
+
# @rbs override
|
107
|
+
#: -> V
|
108
|
+
def value_or_raise! = raise TypedCache::TypeError, 'Nothing has no value'
|
91
109
|
end
|
92
110
|
end
|