typed_cache 0.1.1 → 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 +139 -20
- data/examples.md +140 -50
- data/lib/typed_cache/backends/active_support.rb +50 -5
- data/lib/typed_cache/backends/memory.rb +14 -11
- data/lib/typed_cache/backends.rb +6 -8
- data/lib/typed_cache/cache_builder.rb +72 -19
- data/lib/typed_cache/cache_key.rb +11 -1
- data/lib/typed_cache/cache_ref.rb +20 -16
- data/lib/typed_cache/clock.rb +31 -14
- data/lib/typed_cache/decorator.rb +25 -0
- data/lib/typed_cache/decorators/instrumented.rb +92 -0
- data/lib/typed_cache/decorators.rb +7 -3
- data/lib/typed_cache/either.rb +22 -0
- data/lib/typed_cache/errors.rb +9 -1
- data/lib/typed_cache/instrumenter.rb +43 -0
- data/lib/typed_cache/instrumenters/active_support.rb +28 -0
- data/lib/typed_cache/instrumenters/mixins/namespaced_singleton.rb +55 -0
- data/lib/typed_cache/instrumenters/mixins.rb +8 -0
- data/lib/typed_cache/instrumenters/monitor.rb +27 -0
- data/lib/typed_cache/instrumenters/null.rb +26 -0
- data/lib/typed_cache/instrumenters.rb +39 -0
- 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/registry.rb +15 -0
- data/lib/typed_cache/snapshot.rb +18 -10
- data/lib/typed_cache/store.rb +50 -15
- data/lib/typed_cache/version.rb +1 -1
- data/lib/typed_cache.rb +34 -14
- 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/backends.rbs +2 -0
- data/sig/generated/typed_cache/cache_builder.rbs +13 -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/clock.rbs +19 -9
- data/sig/generated/typed_cache/decorator.rbs +12 -0
- data/sig/generated/typed_cache/decorators/instrumented.rbs +35 -0
- data/sig/generated/typed_cache/decorators.rbs +2 -0
- data/sig/generated/typed_cache/either.rbs +24 -0
- data/sig/generated/typed_cache/errors.rbs +2 -0
- data/sig/generated/typed_cache/instrumenter.rbs +31 -0
- data/sig/generated/typed_cache/instrumenters/active_support.rbs +20 -0
- data/sig/generated/typed_cache/instrumenters/mixins/namespaced_singleton.rbs +36 -0
- data/sig/generated/typed_cache/instrumenters/mixins.rbs +8 -0
- data/sig/generated/typed_cache/instrumenters/monitor.rbs +19 -0
- data/sig/generated/typed_cache/instrumenters/null.rbs +21 -0
- data/sig/generated/typed_cache/instrumenters.rbs +26 -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/registry.rbs +8 -0
- data/sig/generated/typed_cache/snapshot.rbs +12 -6
- data/sig/generated/typed_cache/store/instrumented.rbs +2 -6
- data/sig/generated/typed_cache/store.rbs +26 -8
- data/sig/generated/typed_cache.rbs +8 -6
- data/typed_cache.gemspec +5 -4
- data.tar.gz.sig +0 -0
- metadata +48 -27
- metadata.gz.sig +0 -0
- data/lib/typed_cache/instrumentation.rb +0 -112
- data/lib/typed_cache/store/instrumented.rb +0 -83
- data/sig/generated/typed_cache/instrumentation.rbs +0 -30
- data/sig/handwritten/gems/zeitwerk/2.7/zeitwerk.rbs +0 -9
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TypedCache
|
4
|
+
module Instrumenters
|
5
|
+
module Mixins
|
6
|
+
module NamespacedSingleton
|
7
|
+
class << self
|
8
|
+
# @rbs () -> Array[Class[Instrumenter & NamespacedSingleton]]
|
9
|
+
def all = @all ||= [] # rubocop:disable ThreadSafety
|
10
|
+
|
11
|
+
# @rbs (Class[Instrumenter & NamespacedSingleton]) -> void
|
12
|
+
def included(base)
|
13
|
+
base.singleton_class.class_eval do
|
14
|
+
alias private_new new
|
15
|
+
private(:private_new)
|
16
|
+
end
|
17
|
+
|
18
|
+
base.extend(ClassMethods)
|
19
|
+
|
20
|
+
all << base
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @rbs override
|
25
|
+
# @rbs () -> String
|
26
|
+
def namespace
|
27
|
+
@namespace
|
28
|
+
end
|
29
|
+
|
30
|
+
# @rbs (String | Namespace) -> void
|
31
|
+
def initialize(namespace)
|
32
|
+
@namespace = namespace.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
# @rbs (String | Namespace) -> class
|
37
|
+
def new(namespace: TypedCache.config.instrumentation.namespace)
|
38
|
+
namespace_cache.compute_if_absent(namespace.to_s) { private_new(namespace) }
|
39
|
+
end
|
40
|
+
|
41
|
+
# @rbs (String) -> maybe[class]
|
42
|
+
def get(namespace)
|
43
|
+
namespace_cache.get(namespace.to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @rbs () -> void
|
47
|
+
def clear_namespace_cache = namespace_cache.clear
|
48
|
+
|
49
|
+
# @rbs () -> Concurrent::Map[String, Class[Instrumenter & NamespacedSingleton]]
|
50
|
+
def namespace_cache = @namespace_cache ||= Concurrent::Map.new # rubocop:disable ThreadSafety
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/monitor'
|
4
|
+
|
5
|
+
module TypedCache
|
6
|
+
module Instrumenters
|
7
|
+
class Monitor
|
8
|
+
include Instrumenter
|
9
|
+
include Mixins::NamespacedSingleton
|
10
|
+
|
11
|
+
# @rbs override
|
12
|
+
#: [R] (String, String, **untyped) { -> R } -> R
|
13
|
+
def instrument(operation, key, **payload, &block)
|
14
|
+
return yield unless enabled?
|
15
|
+
|
16
|
+
payload = build_payload(operation, key, **payload)
|
17
|
+
Dry::Monitor::Notifications.instrument(event_name(operation), payload, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @rbs override
|
21
|
+
# @rbs (String, **top) { (event) -> void } -> void
|
22
|
+
def subscribe(operation, **filters, &block)
|
23
|
+
Dry::Monitor::Notifications.subscribe(event_name(operation), **filters, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module TypedCache
|
6
|
+
module Instrumenters
|
7
|
+
# A no-op implementation used when instrumentation is disabled.
|
8
|
+
# It fulfils the Instrumenter contract but simply yields.
|
9
|
+
class Null
|
10
|
+
include Instrumenter
|
11
|
+
include Mixins::NamespacedSingleton
|
12
|
+
|
13
|
+
# @rbs override
|
14
|
+
# [R] (String, String, **untyped) { -> R } -> R
|
15
|
+
def instrument(_operation, _key, **_payload)
|
16
|
+
yield
|
17
|
+
end
|
18
|
+
|
19
|
+
# @rbs override
|
20
|
+
# @rbs (String, **top) { (event) -> void } -> void
|
21
|
+
def subscribe(_event_name, **_filters, &_block)
|
22
|
+
# no-op
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'typed_cache/instrumenters/mixins'
|
4
|
+
require 'typed_cache/instrumenters/mixins/namespaced_singleton'
|
5
|
+
|
6
|
+
module TypedCache
|
7
|
+
module Instrumenters
|
8
|
+
autoload :ActiveSupport, 'typed_cache/instrumenters/active_support'
|
9
|
+
autoload :Monitor, 'typed_cache/instrumenters/monitor'
|
10
|
+
autoload :Null, 'typed_cache/instrumenters/null'
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
# Registry mapping symbols to instrumenter classes. We can't reuse the generic
|
14
|
+
# Registry class directly because many instrumenters mix in `Singleton`,
|
15
|
+
# making `.new` inaccessible. Instead we implement a thin facade that
|
16
|
+
# returns either the singleton instance (preferred) or a fresh instance.
|
17
|
+
REGISTRY = Registry.new('instrumenter', {
|
18
|
+
dry: Monitor,
|
19
|
+
rails: ActiveSupport,
|
20
|
+
default: Null,
|
21
|
+
null: Null,
|
22
|
+
}) #: Registry[Symbol, Class[Instrumenter]]
|
23
|
+
|
24
|
+
class << self
|
25
|
+
extend Forwardable
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
# @rbs () -> Registry[Symbol, Class[Instrumenter]]
|
29
|
+
def registry = REGISTRY
|
30
|
+
|
31
|
+
# @rbs! def register: (Symbol, Class[Instrumenter]) -> void
|
32
|
+
# @rbs! def resolve: (Symbol, **untyped) -> either[Error, Instrumenter]
|
33
|
+
# @rbs! def available: () -> Array[Symbol]
|
34
|
+
# @rbs! def registered?: (Symbol) -> Boolean
|
35
|
+
|
36
|
+
def_delegators :registry, :resolve, :available, :registered?, :register
|
37
|
+
end
|
38
|
+
end
|
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
|
@@ -20,16 +20,17 @@ module TypedCache
|
|
20
20
|
# Returns a new Namespace instance rooted at the given namespace string.
|
21
21
|
#
|
22
22
|
# @param namespace [String] the root namespace
|
23
|
+
# @param namespaces [Array<String>] additional namespaces to join
|
23
24
|
# @return [Namespace] a new Namespace instance at the given root
|
24
25
|
#
|
25
26
|
# Example:
|
26
|
-
# TypedCache::Namespace.at("users") # => #<TypedCache::Namespace namespace=users>
|
27
|
+
# TypedCache::Namespace.at("users", "sessions") # => #<TypedCache::Namespace namespace=users:sessions>
|
27
28
|
#
|
28
29
|
# The returned Namespace can be further nested or used to generate cache keys.
|
29
30
|
#
|
30
|
-
# @rbs (String) -> Namespace
|
31
|
-
def at(namespace)
|
32
|
-
root.
|
31
|
+
# @rbs (String, *String) -> Namespace
|
32
|
+
def at(namespace, *namespaces)
|
33
|
+
root.join(namespace, *namespaces)
|
33
34
|
end
|
34
35
|
|
35
36
|
# Returns the root Namespace instance (with an empty namespace).
|
@@ -79,7 +80,26 @@ module TypedCache
|
|
79
80
|
def nested(namespace, &key_factory)
|
80
81
|
key_factory ||= @key_factory
|
81
82
|
|
82
|
-
self.class.new(
|
83
|
+
self.class.new([@namespace, namespace].join(delimiter), &key_factory)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Creates a new namespace by joining the current namespace with the given namespaces.
|
87
|
+
#
|
88
|
+
# @param namespaces [Array<String>] the namespaces to join
|
89
|
+
# @param key_factory [Proc, nil] optional custom key factory for the joined namespace
|
90
|
+
# @return [Namespace] a new Namespace instance with the combined namespace
|
91
|
+
#
|
92
|
+
# Example:
|
93
|
+
# ns = Namespace.at("users")
|
94
|
+
# ns.join("sessions", "admin") # => #<TypedCache::Namespace namespace=users:sessions:admin>
|
95
|
+
#
|
96
|
+
# If no key_factory is provided, the parent's key factory is inherited.
|
97
|
+
#
|
98
|
+
# @rbs (*String) ?{ (Namespace, String) -> CacheKey } -> Namespace
|
99
|
+
def join(*namespaces, &key_factory)
|
100
|
+
key_factory ||= @key_factory
|
101
|
+
|
102
|
+
self.class.new([@namespace, *namespaces].join(delimiter), &key_factory)
|
83
103
|
end
|
84
104
|
|
85
105
|
# Returns the parent namespace by removing the last namespace segment.
|
@@ -96,7 +116,7 @@ module TypedCache
|
|
96
116
|
def parent_namespace
|
97
117
|
return self if @namespace.empty?
|
98
118
|
|
99
|
-
case pathsep_idx = @namespace.rindex(
|
119
|
+
case pathsep_idx = @namespace.rindex(delimiter)
|
100
120
|
when nil
|
101
121
|
self.class.root
|
102
122
|
else
|
@@ -158,5 +178,12 @@ module TypedCache
|
|
158
178
|
end
|
159
179
|
|
160
180
|
alias eql? ==
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
# @rbs (String) -> String
|
185
|
+
def delimiter
|
186
|
+
TypedCache.config.cache_delimiter
|
187
|
+
end
|
161
188
|
end
|
162
189
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TypedCache
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
config.to_prepare do
|
6
|
+
# Set the default instrumenter to Rails
|
7
|
+
::TypedCache.configure do |config|
|
8
|
+
config.instrumentation.instrumenter = :rails
|
9
|
+
end
|
10
|
+
|
11
|
+
# Register the ActiveSupport backend
|
12
|
+
::TypedCache.backends.register(:active_support, ::TypedCache::Instrumenters::ActiveSupport)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/typed_cache/registry.rb
CHANGED
@@ -10,6 +10,17 @@ module TypedCache
|
|
10
10
|
@registry = defaults.dup
|
11
11
|
end
|
12
12
|
|
13
|
+
# @rbs (Registry[T]) -> Registry[T]
|
14
|
+
def initialize_copy(other)
|
15
|
+
super
|
16
|
+
@registry = other.registry.dup
|
17
|
+
end
|
18
|
+
|
19
|
+
# @rbs () -> void
|
20
|
+
def clear
|
21
|
+
@registry.clear
|
22
|
+
end
|
23
|
+
|
13
24
|
# @rbs (Symbol, *untyped, **untyped) -> either[Error, T]
|
14
25
|
def resolve(key, *, **, &)
|
15
26
|
klass = @registry[key]
|
@@ -51,5 +62,9 @@ module TypedCache
|
|
51
62
|
def registered?(key)
|
52
63
|
@registry.key?(key)
|
53
64
|
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
attr_reader :registry #: Hash[Symbol, Class[T]]
|
54
69
|
end
|
55
70
|
end
|
data/lib/typed_cache/snapshot.rb
CHANGED
@@ -4,12 +4,14 @@ module TypedCache
|
|
4
4
|
# Immutable snapshot of a cached value with metadata about its source and age
|
5
5
|
# @rbs generic V
|
6
6
|
class Snapshot
|
7
|
+
attr_reader :key #: CacheKey
|
7
8
|
attr_reader :value #: V
|
8
9
|
attr_reader :retrieved_at #: Time
|
9
10
|
attr_reader :source #: Symbol
|
10
11
|
|
11
|
-
#: (V, source: Symbol, retrieved_at: Time) -> void
|
12
|
-
def initialize(value, source:, retrieved_at: Time.now)
|
12
|
+
#: (CacheKey, V, source: Symbol, retrieved_at: Time) -> void
|
13
|
+
def initialize(key, value, source:, retrieved_at: Time.now)
|
14
|
+
@key = key
|
13
15
|
@value = value
|
14
16
|
@retrieved_at = retrieved_at
|
15
17
|
@source = source
|
@@ -43,29 +45,35 @@ module TypedCache
|
|
43
45
|
#: [R] () { (V) -> R } -> Snapshot[R]
|
44
46
|
def map(&block)
|
45
47
|
new_value = yield(value)
|
46
|
-
Snapshot.new(new_value, source: source, retrieved_at: retrieved_at)
|
48
|
+
Snapshot.new(key, new_value, source: source, retrieved_at: retrieved_at)
|
47
49
|
end
|
48
50
|
|
49
51
|
# Bind over the value with Either error handling
|
50
52
|
#: [R] () { (V) -> either[Error, R] } -> either[Error, Snapshot[R]]
|
51
53
|
def bind(&block)
|
52
54
|
result = yield(value)
|
53
|
-
result.map { |new_value| Snapshot.new(new_value, source: source, retrieved_at: retrieved_at) }
|
55
|
+
result.map { |new_value| Snapshot.new(key, new_value, source: source, retrieved_at: retrieved_at) }
|
54
56
|
end
|
55
57
|
|
56
58
|
alias flat_map bind
|
57
59
|
|
58
60
|
class << self
|
61
|
+
# Creates a snapshot for a cached value
|
62
|
+
#: [V] (CacheKey, V) -> Snapshot[V]
|
63
|
+
def cached(key, value)
|
64
|
+
new(key, value, source: :cache)
|
65
|
+
end
|
66
|
+
|
59
67
|
# Creates a snapshot for a computed value
|
60
|
-
#: [V] (V) -> Snapshot[V]
|
61
|
-
def computed(value)
|
62
|
-
new(value, source: :computed)
|
68
|
+
#: [V] (CacheKey, V) -> Snapshot[V]
|
69
|
+
def computed(key, value)
|
70
|
+
new(key, value, source: :computed)
|
63
71
|
end
|
64
72
|
|
65
73
|
# Creates a snapshot for an updated value
|
66
|
-
#: [V] (V) -> Snapshot[V]
|
67
|
-
def updated(value)
|
68
|
-
new(value, source: :updated)
|
74
|
+
#: [V] (CacheKey, V) -> Snapshot[V]
|
75
|
+
def updated(key, value)
|
76
|
+
new(key, value, source: :updated)
|
69
77
|
end
|
70
78
|
end
|
71
79
|
end
|
data/lib/typed_cache/store.rb
CHANGED
@@ -17,13 +17,16 @@ module TypedCache
|
|
17
17
|
|
18
18
|
# @rbs!
|
19
19
|
# interface _Store[V]
|
20
|
-
# def
|
20
|
+
# def read: (cache_key) -> either[Error, Snapshot[V]]
|
21
|
+
# def read_all: (Array[cache_key]) -> either[Error, Array[Snapshot[V]]]
|
21
22
|
# def ref: (cache_key) -> CacheRef[V]
|
22
|
-
# def
|
23
|
+
# def write: (cache_key, V) -> either[Error, Snapshot[V]]
|
24
|
+
# def write_all: (Hash[cache_key, V]) -> either[Error, Array[Snapshot[V]]]
|
23
25
|
# def delete: (cache_key) -> either[Error, Snapshot[V]]
|
24
26
|
# def key?: (cache_key) -> bool
|
25
27
|
# def clear: () -> maybe[Error]
|
26
|
-
# def fetch: (cache_key) { () -> V } -> either[Error, Snapshot[V]]
|
28
|
+
# def fetch: (cache_key) { () -> V? } -> either[Error, Snapshot[maybe[V]]]
|
29
|
+
# def fetch_all: (Array[cache_key]) { (CacheKey) -> V? } -> either[Error, Array[Snapshot[V]]]
|
27
30
|
# def namespace: () -> Namespace
|
28
31
|
# def with_namespace: (Namespace) -> Store[V]
|
29
32
|
# def store_type: () -> String
|
@@ -43,8 +46,17 @@ module TypedCache
|
|
43
46
|
|
44
47
|
# Retrieves a value from the cache
|
45
48
|
# @rbs (cache_key) -> either[Error, Snapshot[V]]
|
46
|
-
def
|
47
|
-
Either.left(NotImplementedError.new("#{self.class} must implement #
|
49
|
+
def read(key)
|
50
|
+
Either.left(NotImplementedError.new("#{self.class} must implement #read"))
|
51
|
+
end
|
52
|
+
|
53
|
+
# @rbs (Array[cache_key]) -> either[Error, Hash[cache_key, Snapshot[V]]]
|
54
|
+
def read_all(keys)
|
55
|
+
keys.map { |key| read(key) }.reduce(Either.right({})) do |acc, result|
|
56
|
+
acc.bind do |values|
|
57
|
+
result.map { |value| values.merge(value.key => value) }
|
58
|
+
end
|
59
|
+
end
|
48
60
|
end
|
49
61
|
|
50
62
|
# Retrieves a cache reference for a key
|
@@ -55,8 +67,17 @@ module TypedCache
|
|
55
67
|
|
56
68
|
# Stores a value in the cache
|
57
69
|
# @rbs (cache_key, V) -> either[Error, Snapshot[V]]
|
58
|
-
def
|
59
|
-
Either.left(NotImplementedError.new("#{self.class} must implement #
|
70
|
+
def write(key, value)
|
71
|
+
Either.left(NotImplementedError.new("#{self.class} must implement #write"))
|
72
|
+
end
|
73
|
+
|
74
|
+
# @rbs (Hash[cache_key, V]) -> either[Error, Hash[cache_key, Snapshot[V]]]
|
75
|
+
def write_all(values)
|
76
|
+
values.map { |key, value| write(key, value) }.reduce(Either.right({})) do |acc, result|
|
77
|
+
acc.bind do |values|
|
78
|
+
result.map { |value| values.merge(value.key => value) }
|
79
|
+
end
|
80
|
+
end
|
60
81
|
end
|
61
82
|
|
62
83
|
# Removes a value from the cache, returning the removed value
|
@@ -78,26 +99,39 @@ module TypedCache
|
|
78
99
|
end
|
79
100
|
|
80
101
|
# Fetches a value from cache, computing and storing it if not found
|
81
|
-
# This is an atomic operation that combines
|
102
|
+
# This is an atomic operation that combines read and write
|
82
103
|
# @rbs (cache_key) { () -> V } -> either[Error, Snapshot[V]]
|
83
104
|
def fetch(key, &block)
|
84
|
-
# Default implementation using
|
85
|
-
|
86
|
-
return
|
105
|
+
# Default implementation using read/write pattern
|
106
|
+
read_result = read(key)
|
107
|
+
return read_result if read_result.right?
|
87
108
|
|
88
109
|
# Only proceed if it's a cache miss
|
89
|
-
return
|
110
|
+
return read_result unless read_result.error.is_a?(CacheMissError)
|
90
111
|
|
91
112
|
# Compute and store new value
|
92
113
|
begin
|
93
114
|
computed_value = yield
|
94
|
-
|
95
|
-
Either.right(Snapshot.computed(computed_value))
|
115
|
+
write(key, computed_value)
|
116
|
+
Either.right(Snapshot.computed(key, computed_value))
|
96
117
|
rescue => e
|
97
118
|
Either.left(StoreError.new(:fetch, key, "Failed to compute value for key '#{key}': #{e.message}", e))
|
98
119
|
end
|
99
120
|
end
|
100
121
|
|
122
|
+
# @rbs (Array[cache_key]) { (CacheKey) -> V } -> either[Error, Array[Snapshot[V]]]
|
123
|
+
def fetch_all(keys, &block)
|
124
|
+
keys = keys.map { |key| namespaced_key(key) }
|
125
|
+
keys.reduce(Either.right([])) do |acc, key|
|
126
|
+
acc.bind do |values|
|
127
|
+
fetch(key) { yield(key) }.map { |value| values + [value] }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# @rbs () -> Instrumenter
|
133
|
+
def instrumenter = Instrumenters::Null.instance
|
134
|
+
|
101
135
|
# Returns the namespace for this store (for instrumentation/debugging)
|
102
136
|
# @rbs () -> Namespace
|
103
137
|
def namespace
|
@@ -106,11 +140,12 @@ module TypedCache
|
|
106
140
|
|
107
141
|
# Accepts a String segment or a fully-formed Namespace and returns a cloned
|
108
142
|
# store scoped to that namespace.
|
109
|
-
|
143
|
+
#: (Namespace | String | Array[String]) -> Store[V]
|
110
144
|
def with_namespace(ns)
|
111
145
|
new_namespace =
|
112
146
|
case ns
|
113
147
|
when Namespace then ns
|
148
|
+
when Array then namespace.join(*ns)
|
114
149
|
else
|
115
150
|
# treat as nested segment under the current namespace
|
116
151
|
namespace.nested(ns.to_s)
|
data/lib/typed_cache/version.rb
CHANGED
data/lib/typed_cache.rb
CHANGED
@@ -1,18 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'dry
|
5
|
-
require '
|
3
|
+
require 'dry/configurable'
|
4
|
+
require 'dry/struct'
|
5
|
+
require 'dry/types'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
require_relative 'typed_cache/errors'
|
7
|
+
require 'typed_cache/either'
|
8
|
+
require 'typed_cache/maybe'
|
10
9
|
|
11
|
-
|
10
|
+
require 'typed_cache/cache_key'
|
11
|
+
require 'typed_cache/errors'
|
12
|
+
require 'typed_cache/namespace'
|
13
|
+
require 'typed_cache/registry'
|
14
|
+
require 'typed_cache/clock'
|
15
|
+
|
16
|
+
require 'typed_cache/store'
|
17
|
+
require 'typed_cache/instrumenter'
|
18
|
+
require 'typed_cache/backend'
|
19
|
+
require 'typed_cache/decorator'
|
20
|
+
|
21
|
+
require 'typed_cache/snapshot'
|
22
|
+
require 'typed_cache/cache_ref'
|
23
|
+
|
24
|
+
require 'typed_cache/cache_builder'
|
12
25
|
|
13
26
|
module TypedCache
|
14
27
|
extend Dry::Configurable
|
15
28
|
|
29
|
+
autoload :Backends, 'typed_cache/backends'
|
30
|
+
autoload :Decorators, 'typed_cache/decorators'
|
31
|
+
autoload :Instrumenters, 'typed_cache/instrumenters'
|
32
|
+
|
16
33
|
# @rbs!
|
17
34
|
# interface _TypedCacheInstrumentationConfig
|
18
35
|
# def enabled: -> bool
|
@@ -22,6 +39,7 @@ module TypedCache
|
|
22
39
|
# @rbs!
|
23
40
|
# interface _TypedCacheConfig
|
24
41
|
# def default_namespace: -> String
|
42
|
+
# def cache_delimiter: -> String
|
25
43
|
# def instrumentation: -> _TypedCacheInstrumentationConfig
|
26
44
|
# end
|
27
45
|
|
@@ -29,10 +47,12 @@ module TypedCache
|
|
29
47
|
|
30
48
|
# Configuration
|
31
49
|
setting :default_namespace, default: 'typed_cache'
|
50
|
+
setting :cache_delimiter, default: ':'
|
32
51
|
|
33
52
|
setting :instrumentation do
|
34
53
|
setting :enabled, default: false
|
35
54
|
setting :namespace, default: 'typed_cache'
|
55
|
+
setting :instrumenter, default: :default
|
36
56
|
end
|
37
57
|
|
38
58
|
class << self
|
@@ -44,15 +64,15 @@ module TypedCache
|
|
44
64
|
|
45
65
|
# @rbs! def config: -> _TypedCacheConfig
|
46
66
|
|
47
|
-
# @rbs () -> singleton(
|
48
|
-
def instrumentation
|
49
|
-
Instrumentation
|
50
|
-
end
|
51
|
-
|
52
|
-
# @rbs () -> Registry[backend[untyped]]
|
67
|
+
# @rbs () -> singleton(Backends)
|
53
68
|
def backends = Backends
|
54
69
|
|
55
|
-
# @rbs () ->
|
70
|
+
# @rbs () -> singleton(Decorators)
|
56
71
|
def decorators = Decorators
|
72
|
+
|
73
|
+
# @rbs () -> singleton(Instrumenters)
|
74
|
+
def instrumenters = Instrumenters
|
57
75
|
end
|
58
76
|
end
|
77
|
+
|
78
|
+
require 'typed_cache/railtie' if defined?(Rails::Railtie)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module TypedCache
|
4
|
+
module Backends
|
5
|
+
class << self
|
6
|
+
sig { params(name: Symbol, klass: T::Class[::TypedCache::Backend[T.anything]]).returns(T.self_type) }
|
7
|
+
def register(name, klass); end
|
8
|
+
|
9
|
+
sig { params(name: Symbol, args: T::Array[T.anything], options: T::Hash[Symbol, T.anything]).returns(T.self_type) }
|
10
|
+
def resolve(name, *args, **options); end
|
11
|
+
|
12
|
+
sig { returns(T::Array[T::Class[::TypedCache::Backend[T.anything]]]) }
|
13
|
+
def available; end
|
14
|
+
|
15
|
+
sig { params(name: Symbol).returns(T::Boolean) }
|
16
|
+
def registered?(name); end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module TypedCache
|
4
|
+
class CacheBuilder
|
5
|
+
Error = T.type_alias { TypedCache::Error }
|
6
|
+
Config = T.type_alias { ::TypedCache::Private::Configuration }
|
7
|
+
InstrumenterSource = T.type_alias { T.any(Symbol, ::TypedCache::Instrumenter) }
|
8
|
+
|
9
|
+
private_constant :Config, :Error, :InstrumenterSource
|
10
|
+
|
11
|
+
sig { params(namespace: ::TypedCache::Namespace).returns(::TypedCache::Either[Error, ::TypedCache::Store[T.anything]]) }
|
12
|
+
def build(namespace = T.unsafe(nil)); end
|
13
|
+
|
14
|
+
sig { params(name: Symbol, args: T.untyped, options: T::Hash[Symbol, T.anything]).returns(T.self_type) }
|
15
|
+
def with_backend(name, *args, **options); end
|
16
|
+
|
17
|
+
sig { params(name: Symbol, options: T::Hash[Symbol, T.anything]).returns(T.self_type) }
|
18
|
+
def with_decorator(name, **options); end
|
19
|
+
|
20
|
+
sig { params(source: InstrumenterSource).returns(T.self_type) }
|
21
|
+
def with_instrumentation(source = T.unsafe(nil)); end
|
22
|
+
end
|
23
|
+
end
|