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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -4
  3. data/examples/{amb.rb → cmp.rb} +8 -8
  4. data/lib/dry/effects.rb +33 -2
  5. data/lib/dry/effects/all.rb +3 -2
  6. data/lib/dry/effects/constructors.rb +4 -0
  7. data/lib/dry/effects/effects/{amb.rb → cmp.rb} +3 -3
  8. data/lib/dry/effects/effects/current_time.rb +9 -0
  9. data/lib/dry/effects/effects/resolve.rb +1 -3
  10. data/lib/dry/effects/effects/retry.rb +7 -2
  11. data/lib/dry/effects/effects/state.rb +12 -3
  12. data/lib/dry/effects/effects/timeout.rb +31 -0
  13. data/lib/dry/effects/errors.rb +19 -0
  14. data/lib/dry/effects/frame.rb +80 -0
  15. data/lib/dry/effects/handler.rb +11 -44
  16. data/lib/dry/effects/provider.rb +19 -0
  17. data/lib/dry/effects/provider/class_interface.rb +4 -3
  18. data/lib/dry/effects/providers/async.rb +4 -1
  19. data/lib/dry/effects/providers/cache.rb +8 -0
  20. data/lib/dry/effects/providers/cmp.rb +50 -0
  21. data/lib/dry/effects/providers/current_time.rb +38 -21
  22. data/lib/dry/effects/providers/defer.rb +8 -3
  23. data/lib/dry/effects/providers/env.rb +10 -0
  24. data/lib/dry/effects/providers/fork.rb +1 -1
  25. data/lib/dry/effects/providers/implicit.rb +6 -0
  26. data/lib/dry/effects/providers/interrupt.rb +10 -2
  27. data/lib/dry/effects/providers/lock.rb +7 -0
  28. data/lib/dry/effects/providers/parallel.rb +4 -1
  29. data/lib/dry/effects/providers/reader.rb +8 -1
  30. data/lib/dry/effects/providers/resolve.rb +17 -0
  31. data/lib/dry/effects/providers/retry.rb +6 -1
  32. data/lib/dry/effects/providers/state.rb +6 -0
  33. data/lib/dry/effects/providers/timeout.rb +47 -0
  34. data/lib/dry/effects/providers/timestamp.rb +27 -16
  35. data/lib/dry/effects/version.rb +1 -1
  36. metadata +11 -8
  37. data/lib/dry/effects/providers/amb.rb +0 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e467f4de9697568b05a68ccc58c3d7acc44ab3f196e6a93c07c574b6aaeca6f9
4
- data.tar.gz: 4f3b5093c7aabb77aeab310080bc1a1e2091537da7724c5c1b70587cbcbce12b
3
+ metadata.gz: b4b516150a36745c1fac9a030dc079a629737b0a79b35b5e6901c413e7019479
4
+ data.tar.gz: 0f81389b62e44b1196535332407cec3d894ccc7f6413f97e32766d0b5109426f
5
5
  SHA512:
6
- metadata.gz: bc7202bfd43a789fd1221323126eee3000479c45508ccb08874a29b91b86ee089a6248b7f33a64a651aa296b84a4ae425bcdab689b18709144ea33ab3cd9e582
7
- data.tar.gz: 1e2a61bcfe1e64991317b028b1bae6e3ddd663c3b658c9f67ecb14dc0570610d75bccc94ad8ec9cb7d8f935099086dadcb6e075bc702a920820fc07b90bb8571
6
+ metadata.gz: 97c935e9b3b37f026c74b1e5e055f43f17b718dad48095b80f730c610d685990a0e9d1cd473859154d78fa388f44ae211f861ec066ec268b95b58d1543b2d07b
7
+ data.tar.gz: dc49f0fd42b63ec247322a177e73334a30f336e70b904c03c9708b2991dd85344b9b6f62833b768501082434b49fc69060980111ca0a137c922b66186d27d7a9
@@ -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.6
11
- - 2.5.5
12
- - 2.6.3
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.7.0
23
+ - rvm: jruby-9.2.8.0
24
24
  jdk: openjdk8
25
25
  notifications:
26
26
  email: false
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Operation
4
4
  include Dry::Effects.State(:counter)
5
- include Dry::Effects.Amb(:feature_enabled)
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.Amb(:feature_enabled, as: :test_feature)
20
+ include Dry::Effects::Handler.Cmp(:feature_enabled, as: :test_feature)
21
21
  end
22
22
 
23
- class AmbState
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 StateAmb
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
- amb_then_state = AmbState.new
48
- state_then_amb = StateAmb.new
47
+ cmp_then_state = CmpState.new
48
+ state_then_cmp = State.Cmp.new
49
49
 
50
- amb_then_state.() # => [[1, :without_feature], [10, :with_feature]]
51
- state_then_amb.() # => [11, [:without_feature, :with_feature]]
50
+ cmp_then_state.() # => [[1, :without_feature], [10, :with_feature]]
51
+ state_then_cmp.() # => [11, [:without_feature, :with_feature]]
@@ -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'
@@ -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 amb retry fork parallel
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
@@ -3,6 +3,10 @@
3
3
  module Dry
4
4
  module Effects
5
5
  module Constructors
6
+ def self.register(name, &block)
7
+ define_method(name, &block)
8
+ module_function name
9
+ end
6
10
  end
7
11
  end
8
12
  end
@@ -5,13 +5,13 @@ require 'dry/effects/effect'
5
5
  module Dry
6
6
  module Effects
7
7
  module Effects
8
- class Amb < ::Module
9
- class AmbEffect < Effect
8
+ class Cmp < ::Module
9
+ class CmpEffect < Effect
10
10
  option :id
11
11
  end
12
12
 
13
13
  def initialize(id)
14
- get = AmbEffect.new(type: :amb, name: :get, id: id)
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|
@@ -9,9 +9,7 @@ module Dry
9
9
  class Resolve < ::Module
10
10
  Resolve = Effect.new(type: :resolve)
11
11
 
12
- def Constructors.Resolve(key)
13
- Resolve.(key)
14
- end
12
+ Constructors.register(:Resolve) { |key| Resolve.(key) }
15
13
 
16
14
  def initialize(*keys, **aliases)
17
15
  module_eval do
@@ -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 RetryEffect < Effect
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 = RetryEffect.new(type: :retry, name: :repeat, scope: scope)
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 StateEffect < Effect
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 = StateEffect.new(type: :state, name: :read, scope: scope)
17
- write = StateEffect.new(type: :state, name: :write, scope: scope)
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
@@ -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