dry-effects 0.1.0.alpha2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -4
- data/examples/{amb.rb → cmp.rb} +8 -8
- data/lib/dry/effects.rb +33 -2
- data/lib/dry/effects/all.rb +3 -2
- data/lib/dry/effects/constructors.rb +4 -0
- data/lib/dry/effects/effects/{amb.rb → cmp.rb} +3 -3
- data/lib/dry/effects/effects/current_time.rb +9 -0
- data/lib/dry/effects/effects/resolve.rb +1 -3
- data/lib/dry/effects/effects/retry.rb +7 -2
- data/lib/dry/effects/effects/state.rb +12 -3
- data/lib/dry/effects/effects/timeout.rb +31 -0
- data/lib/dry/effects/errors.rb +19 -0
- data/lib/dry/effects/frame.rb +80 -0
- data/lib/dry/effects/handler.rb +11 -44
- data/lib/dry/effects/provider.rb +19 -0
- data/lib/dry/effects/provider/class_interface.rb +4 -3
- data/lib/dry/effects/providers/async.rb +4 -1
- data/lib/dry/effects/providers/cache.rb +8 -0
- data/lib/dry/effects/providers/cmp.rb +50 -0
- data/lib/dry/effects/providers/current_time.rb +38 -21
- data/lib/dry/effects/providers/defer.rb +8 -3
- data/lib/dry/effects/providers/env.rb +10 -0
- data/lib/dry/effects/providers/fork.rb +1 -1
- data/lib/dry/effects/providers/implicit.rb +6 -0
- data/lib/dry/effects/providers/interrupt.rb +10 -2
- data/lib/dry/effects/providers/lock.rb +7 -0
- data/lib/dry/effects/providers/parallel.rb +4 -1
- data/lib/dry/effects/providers/reader.rb +8 -1
- data/lib/dry/effects/providers/resolve.rb +17 -0
- data/lib/dry/effects/providers/retry.rb +6 -1
- data/lib/dry/effects/providers/state.rb +6 -0
- data/lib/dry/effects/providers/timeout.rb +47 -0
- data/lib/dry/effects/providers/timestamp.rb +27 -16
- data/lib/dry/effects/version.rb +1 -1
- metadata +11 -8
- data/lib/dry/effects/providers/amb.rb +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b4b516150a36745c1fac9a030dc079a629737b0a79b35b5e6901c413e7019479
|
4
|
+
data.tar.gz: 0f81389b62e44b1196535332407cec3d894ccc7f6413f97e32766d0b5109426f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97c935e9b3b37f026c74b1e5e055f43f17b718dad48095b80f730c610d685990a0e9d1cd473859154d78fa388f44ae211f861ec066ec268b95b58d1543b2d07b
|
7
|
+
data.tar.gz: dc49f0fd42b63ec247322a177e73334a30f336e70b904c03c9708b2991dd85344b9b6f62833b768501082434b49fc69060980111ca0a137c922b66186d27d7a9
|
data/.travis.yml
CHANGED
@@ -7,9 +7,9 @@ before_script:
|
|
7
7
|
- chmod +x ./cc-test-reporter
|
8
8
|
- ./cc-test-reporter before-build
|
9
9
|
rvm:
|
10
|
-
- 2.4.
|
11
|
-
- 2.5.
|
12
|
-
- 2.6.
|
10
|
+
- 2.4.7
|
11
|
+
- 2.5.6
|
12
|
+
- 2.6.4
|
13
13
|
- truffleruby
|
14
14
|
after_script:
|
15
15
|
- "[ -d coverage ] && ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT"
|
@@ -20,7 +20,7 @@ matrix:
|
|
20
20
|
allow_failures:
|
21
21
|
- rvm: truffleruby
|
22
22
|
include:
|
23
|
-
- rvm: jruby-9.2.
|
23
|
+
- rvm: jruby-9.2.8.0
|
24
24
|
jdk: openjdk8
|
25
25
|
notifications:
|
26
26
|
email: false
|
data/examples/{amb.rb → cmp.rb}
RENAMED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class Operation
|
4
4
|
include Dry::Effects.State(:counter)
|
5
|
-
include Dry::Effects.
|
5
|
+
include Dry::Effects.Cmp(:feature_enabled)
|
6
6
|
|
7
7
|
def call
|
8
8
|
if feature_enabled?
|
@@ -17,10 +17,10 @@ end
|
|
17
17
|
|
18
18
|
module Handler
|
19
19
|
include Dry::Effects::Handler.State(:counter, as: :with_counter)
|
20
|
-
include Dry::Effects::Handler.
|
20
|
+
include Dry::Effects::Handler.Cmp(:feature_enabled, as: :test_feature)
|
21
21
|
end
|
22
22
|
|
23
|
-
class
|
23
|
+
class CmpState
|
24
24
|
include Handler
|
25
25
|
|
26
26
|
def initialize
|
@@ -32,7 +32,7 @@ class AmbState
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
class
|
35
|
+
class StateCmp
|
36
36
|
include Handler
|
37
37
|
|
38
38
|
def initialize
|
@@ -44,8 +44,8 @@ class StateAmb
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
cmp_then_state = CmpState.new
|
48
|
+
state_then_cmp = State.Cmp.new
|
49
49
|
|
50
|
-
|
51
|
-
|
50
|
+
cmp_then_state.() # => [[1, :without_feature], [10, :with_feature]]
|
51
|
+
state_then_cmp.() # => [11, [:without_feature, :with_feature]]
|
data/lib/dry/effects.rb
CHANGED
@@ -18,6 +18,21 @@ module Dry
|
|
18
18
|
class << self
|
19
19
|
attr_reader :effects, :providers
|
20
20
|
|
21
|
+
# Handle an effect.
|
22
|
+
# If no handler is present in the stack it will either
|
23
|
+
# raise an exception and yield a block if given.
|
24
|
+
# It is not recommended to build effects manually, hence
|
25
|
+
# this method shouldn't be used often.
|
26
|
+
#
|
27
|
+
# @example getting current user with yield
|
28
|
+
#
|
29
|
+
# require 'dry/effects/effects/reader'
|
30
|
+
# extend Dry::Effects::Constructors
|
31
|
+
# Dry::Effects.yield(Read(:current_user))
|
32
|
+
#
|
33
|
+
# @param [Effect] effect
|
34
|
+
# @return [Object] Result value is determined by effect type
|
35
|
+
# @api public
|
21
36
|
def yield(effect)
|
22
37
|
result = ::Fiber.yield(effect)
|
23
38
|
|
@@ -26,17 +41,33 @@ module Dry
|
|
26
41
|
else
|
27
42
|
result
|
28
43
|
end
|
29
|
-
rescue FiberError => e
|
44
|
+
rescue ::FiberError => e
|
30
45
|
if block_given?
|
31
46
|
yield(effect, e)
|
32
47
|
else
|
33
48
|
raise Errors::UnhandledEffectError, effect
|
34
49
|
end
|
35
50
|
end
|
51
|
+
|
52
|
+
# Build a handler.
|
53
|
+
# Normally, handlers are built via mixins.
|
54
|
+
# This method is useful for demonstration purposes.
|
55
|
+
#
|
56
|
+
# @example providing current user
|
57
|
+
#
|
58
|
+
# Dry::Effects[:reader, :current_user].(User.new) do
|
59
|
+
# code_using_current_user.()
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# @param [Array<Object>] args Handler parameters
|
63
|
+
# @return [Handler]
|
64
|
+
# @api public
|
65
|
+
def [](*args)
|
66
|
+
Handler.new(*args)
|
67
|
+
end
|
36
68
|
end
|
37
69
|
end
|
38
70
|
end
|
39
71
|
|
40
|
-
require 'dry/effects/handler'
|
41
72
|
require 'dry/effects/all'
|
42
73
|
require 'dry/effects/extensions'
|
data/lib/dry/effects/all.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'concurrent/map'
|
4
|
-
require 'dry/effects'
|
5
4
|
require 'dry/effects/inflector'
|
5
|
+
require 'dry/effects/handler'
|
6
6
|
|
7
7
|
module Dry
|
8
8
|
module Effects
|
9
9
|
default = %i[
|
10
10
|
cache current_time random resolve defer
|
11
|
-
state interrupt
|
11
|
+
state interrupt cmp retry fork parallel
|
12
12
|
async implicit env lock reader timestamp
|
13
|
+
timeout
|
13
14
|
]
|
14
15
|
|
15
16
|
effect_modules = ::Concurrent::Map.new
|
@@ -5,13 +5,13 @@ require 'dry/effects/effect'
|
|
5
5
|
module Dry
|
6
6
|
module Effects
|
7
7
|
module Effects
|
8
|
-
class
|
9
|
-
class
|
8
|
+
class Cmp < ::Module
|
9
|
+
class CmpEffect < Effect
|
10
10
|
option :id
|
11
11
|
end
|
12
12
|
|
13
13
|
def initialize(id)
|
14
|
-
get =
|
14
|
+
get = CmpEffect.new(type: :cmp, name: :get, id: id)
|
15
15
|
|
16
16
|
module_eval do
|
17
17
|
define_method(:"#{id}?") { ::Dry::Effects.yield(get) }
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'dry/effects/effect'
|
4
|
+
require 'dry/effects/constructors'
|
4
5
|
|
5
6
|
module Dry
|
6
7
|
module Effects
|
@@ -8,6 +9,14 @@ module Dry
|
|
8
9
|
class CurrentTime < ::Module
|
9
10
|
CurrentTime = Effect.new(type: :current_time)
|
10
11
|
|
12
|
+
Constructors.register(:CurrentTime) do |**kwargs|
|
13
|
+
if kwargs.empty?
|
14
|
+
CurrentTime
|
15
|
+
else
|
16
|
+
CurrentTime.payload(kwargs)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
11
20
|
def initialize(options = EMPTY_HASH)
|
12
21
|
module_eval do
|
13
22
|
define_method(:current_time) do |round: Undefined, refresh: false|
|
@@ -1,21 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'dry/effects/effect'
|
4
|
+
require 'dry/effects/constructors'
|
4
5
|
|
5
6
|
module Dry
|
6
7
|
module Effects
|
7
8
|
module Effects
|
8
9
|
class Retry < ::Module
|
9
|
-
class
|
10
|
+
class Retry < Effect
|
10
11
|
include ::Dry::Equalizer(:type, :name, :payload, :scope)
|
11
12
|
|
12
13
|
option :scope
|
13
14
|
end
|
14
15
|
|
16
|
+
Constructors.register(:Retry) do |scope|
|
17
|
+
Retry.new(type: :retry, scope: scope)
|
18
|
+
end
|
19
|
+
|
15
20
|
def initialize
|
16
21
|
module_eval do
|
17
22
|
define_method(:repeat) do |scope|
|
18
|
-
effect =
|
23
|
+
effect = Retry.new(type: :retry, scope: scope)
|
19
24
|
::Dry::Effects.yield(effect)
|
20
25
|
end
|
21
26
|
end
|
@@ -1,20 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'dry/effects/effect'
|
4
|
+
require 'dry/effects/constructors'
|
4
5
|
|
5
6
|
module Dry
|
6
7
|
module Effects
|
7
8
|
module Effects
|
8
9
|
class State < ::Module
|
9
|
-
class
|
10
|
+
class State < Effect
|
10
11
|
include ::Dry::Equalizer(:type, :name, :payload, :scope)
|
11
12
|
|
12
13
|
option :scope
|
13
14
|
end
|
14
15
|
|
16
|
+
Constructors.register(:Read) do |scope|
|
17
|
+
State.new(type: :state, name: :read, scope: scope)
|
18
|
+
end
|
19
|
+
|
20
|
+
Constructors.register(:Write) do |scope, value|
|
21
|
+
State.new(type: :state, name: :write, scope: scope, payload: [value])
|
22
|
+
end
|
23
|
+
|
15
24
|
def initialize(scope, default: Undefined, writer: true, as: scope)
|
16
|
-
read =
|
17
|
-
write =
|
25
|
+
read = State.new(type: :state, name: :read, scope: scope)
|
26
|
+
write = State.new(type: :state, name: :write, scope: scope)
|
18
27
|
|
19
28
|
module_eval do
|
20
29
|
if Undefined.equal?(default)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/effects/effect'
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Effects
|
7
|
+
module Effects
|
8
|
+
class Timeout < ::Module
|
9
|
+
class TimeoutEffect < Effect
|
10
|
+
include ::Dry::Equalizer(:type, :name, :scope)
|
11
|
+
|
12
|
+
option :scope
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(scope)
|
16
|
+
timeout = TimeoutEffect.new(type: :timeout, name: :timeout, scope: scope)
|
17
|
+
|
18
|
+
module_eval do
|
19
|
+
define_method(:timeout) do
|
20
|
+
::Dry::Effects.yield(timeout)
|
21
|
+
end
|
22
|
+
|
23
|
+
def timed_out?
|
24
|
+
timeout.zero?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/dry/effects/errors.rb
CHANGED
@@ -6,6 +6,9 @@ module Dry
|
|
6
6
|
module Error
|
7
7
|
end
|
8
8
|
|
9
|
+
# No handler in the stack
|
10
|
+
#
|
11
|
+
# @api private
|
9
12
|
class UnhandledEffectError < RuntimeError
|
10
13
|
include Error
|
11
14
|
|
@@ -23,6 +26,9 @@ module Dry
|
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
29
|
+
# No state handler
|
30
|
+
#
|
31
|
+
# @api private
|
26
32
|
class MissingStateError < UnhandledEffectError
|
27
33
|
def initialize(effect)
|
28
34
|
message = "Value of +#{effect.scope}+ is not set, "\
|
@@ -32,6 +38,9 @@ module Dry
|
|
32
38
|
end
|
33
39
|
end
|
34
40
|
|
41
|
+
# Uninitialized state accessed
|
42
|
+
#
|
43
|
+
# @api private
|
35
44
|
class UndefinedStateError < RuntimeError
|
36
45
|
include Error
|
37
46
|
|
@@ -44,10 +53,17 @@ module Dry
|
|
44
53
|
end
|
45
54
|
end
|
46
55
|
|
56
|
+
# Effect cannot be handled
|
57
|
+
# Some effects are not compatible without re
|
58
|
+
#
|
59
|
+
# @api private
|
47
60
|
class EffectRejectedError < RuntimeError
|
48
61
|
include Error
|
49
62
|
end
|
50
63
|
|
64
|
+
# Unresolved dependency
|
65
|
+
#
|
66
|
+
# @api private
|
51
67
|
class ResolutionError < RuntimeError
|
52
68
|
include Error
|
53
69
|
|
@@ -56,6 +72,9 @@ module Dry
|
|
56
72
|
end
|
57
73
|
end
|
58
74
|
|
75
|
+
# State value has invalid type
|
76
|
+
#
|
77
|
+
# @api private
|
59
78
|
class InvalidValueError < ArgumentError
|
60
79
|
include Error
|
61
80
|
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fiber'
|
4
|
+
require 'dry/effects/initializer'
|
5
|
+
require 'dry/effects/effect'
|
6
|
+
require 'dry/effects/errors'
|
7
|
+
require 'dry/effects/stack'
|
8
|
+
require 'dry/effects/instructions/raise'
|
9
|
+
|
10
|
+
module Dry
|
11
|
+
module Effects
|
12
|
+
# Stack frame
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
class Frame
|
16
|
+
class << self
|
17
|
+
# Accessing current stack of effect handlers.
|
18
|
+
# It is inherently thread/fiber-local.
|
19
|
+
#
|
20
|
+
# @return [Stack]
|
21
|
+
# @api private
|
22
|
+
def stack
|
23
|
+
::Thread.current[:dry_effects_stack] ||= Stack.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [Stack] stack
|
27
|
+
# @api private
|
28
|
+
def stack=(stack)
|
29
|
+
::Thread.current[:dry_effects_stack] = stack
|
30
|
+
end
|
31
|
+
|
32
|
+
# Spawn a new fiber with a stack of effect handlers
|
33
|
+
#
|
34
|
+
# @param [Stack] stack
|
35
|
+
# @api private
|
36
|
+
def spawn_fiber(stack)
|
37
|
+
fiber = ::Fiber.new do
|
38
|
+
self.stack = stack
|
39
|
+
yield
|
40
|
+
end
|
41
|
+
result = fiber.resume
|
42
|
+
|
43
|
+
loop do
|
44
|
+
break result unless fiber.alive?
|
45
|
+
|
46
|
+
provided = stack.(result) do
|
47
|
+
::Dry::Effects.yield(result) do |_, error|
|
48
|
+
Instructions.Raise(error)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
result = fiber.resume(provided)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
extend Initializer
|
58
|
+
|
59
|
+
# @!attribute provider
|
60
|
+
# @return [Provider] Effects provider
|
61
|
+
param :provider
|
62
|
+
|
63
|
+
# Add new handler to the current stack
|
64
|
+
# and run the given block
|
65
|
+
#
|
66
|
+
# @param [Array<Object>] args Handler arguments
|
67
|
+
# @param [Proc] block Program to run
|
68
|
+
# @api private
|
69
|
+
def call(args = EMPTY_ARRAY, &block)
|
70
|
+
stack = Frame.stack
|
71
|
+
|
72
|
+
if stack.empty?
|
73
|
+
stack.push(provider.dup, args) { Frame.spawn_fiber(stack, &block) }
|
74
|
+
else
|
75
|
+
stack.push(provider.dup, args, &block)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|