dry-effects 0.1.0.alpha2 → 0.1.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
- 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
|