hyper-operation 1.0.alpha1.1 → 1.0.alpha1.6

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.travis.yml +1 -0
  4. data/hyper-operation.gemspec +6 -4
  5. data/lib/hyper-operation.rb +4 -1
  6. data/lib/hyper-operation/api.rb +6 -2
  7. data/lib/hyper-operation/async_sleep.rb +23 -0
  8. data/lib/hyper-operation/exception.rb +29 -3
  9. data/lib/hyper-operation/promise.rb +28 -2
  10. data/lib/hyper-operation/railway/dispatcher.rb +0 -1
  11. data/lib/hyper-operation/railway/run.rb +55 -49
  12. data/lib/hyper-operation/railway/validations.rb +9 -2
  13. data/lib/hyper-operation/server_op.rb +31 -9
  14. data/lib/hyper-operation/transport/client_drivers.rb +45 -11
  15. data/lib/hyper-operation/transport/connection.rb +58 -136
  16. data/lib/hyper-operation/transport/connection_adapter/active_record.rb +113 -0
  17. data/lib/hyper-operation/transport/connection_adapter/active_record/auto_create.rb +26 -0
  18. data/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb +47 -0
  19. data/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb +42 -0
  20. data/lib/hyper-operation/transport/connection_adapter/redis.rb +94 -0
  21. data/lib/hyper-operation/transport/connection_adapter/redis/connection.rb +85 -0
  22. data/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb +34 -0
  23. data/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb +158 -0
  24. data/lib/hyper-operation/transport/hyperstack.rb +15 -2
  25. data/lib/hyper-operation/transport/hyperstack_controller.rb +6 -2
  26. data/lib/hyper-operation/transport/policy.rb +16 -26
  27. data/lib/hyper-operation/transport/policy_diagnostics.rb +106 -0
  28. data/lib/hyper-operation/version.rb +1 -1
  29. metadata +79 -38
  30. data/Gemfile.lock +0 -385
  31. data/lib/hyper-operation/delay_and_interval.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c92ceabb98fa585a85ae66c8dac864da129e8e93068394081ff799eab32559e2
4
- data.tar.gz: c2e73fba94fd2bda03bc8d1e221f2d251285025fb37f4fa113f6f675b47a63b7
3
+ metadata.gz: bf7ea211cf5d34d735df9a5318691ff4261b26e15816c6529138da58ce23ead5
4
+ data.tar.gz: bbce8cdf73f646fe7abd922a0667739f486368c723800b7f6efecbbec9ad7468
5
5
  SHA512:
6
- metadata.gz: 4eead7193a415dcc6f21aabd0eb964e1614806d9c9508bde214d71db050e88f1f8707b2b834ee97589d6c49c8ec9977b5aa2b4a086806f436d924d99767ef5ce
7
- data.tar.gz: 937c223809bc3322a6df12bd19d9b70d83061dc80f86d9294f5c0f30db7215d4de33f94952a01641619468a9cec47225c38a1362d99c00d670d4d5507d0480ef
6
+ metadata.gz: afdb25d53a08ed95794a3419f0d3ea341f04c8da594d68189029f2c5c87568b4c0edd9fe4b57df4a42042f63f66de585e4a1a65dde8efb56063d79019fddee75
7
+ data.tar.gz: c5fbd8014773c171dee7d8556f84da60fcdfc77509a7ae6f4380c3b7b729a2c438faa4cd5c1d09a92dc19ceed2b77203154314ebd75b6e3b01988383485caf88
data/.gitignore CHANGED
@@ -53,3 +53,7 @@ bower.json
53
53
  .idea
54
54
  .vscode
55
55
  *.iml
56
+
57
+ # ignore Gemfile.locks https://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
58
+ /spec/test_app/Gemfile.lock
59
+ /Gemfile.lock
data/.travis.yml CHANGED
@@ -7,6 +7,7 @@ rvm:
7
7
  - ruby-head
8
8
  services:
9
9
  - mysql
10
+ - redis-server
10
11
  env:
11
12
  - DRIVER=google-chrome TZ=Europe/Berlin
12
13
  matrix:
@@ -27,25 +27,27 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency 'hyper-component', Hyperstack::Operation::VERSION
28
28
  spec.add_dependency 'mutations'
29
29
  spec.add_dependency 'opal-activesupport', '~> 0.3.1'
30
+ spec.add_dependency 'tty-table'
30
31
 
31
32
  spec.add_development_dependency 'bundler'
32
33
  spec.add_development_dependency 'chromedriver-helper'
33
34
  spec.add_development_dependency 'database_cleaner'
34
35
  spec.add_development_dependency 'hyper-spec', Hyperstack::Operation::VERSION
35
36
  spec.add_development_dependency 'mysql2'
36
- spec.add_development_dependency 'opal', '>= 0.11.0', '< 0.12.0'
37
37
  spec.add_development_dependency 'opal-browser', '~> 0.2.0'
38
- spec.add_development_dependency 'opal-rails', '~> 0.9.4'
38
+ spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0'
39
39
  spec.add_development_dependency 'pry-rescue'
40
+ spec.add_development_dependency 'pry-stack_explorer'
40
41
  spec.add_development_dependency 'puma'
41
42
  spec.add_development_dependency 'pusher'
42
43
  spec.add_development_dependency 'pusher-fake'
43
- spec.add_development_dependency 'rails', '>= 4.0.0'
44
+ spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0'
44
45
  spec.add_development_dependency 'rake'
45
46
  spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0'
47
+ spec.add_development_dependency 'redis'
46
48
  spec.add_development_dependency 'rspec-rails'
47
49
  spec.add_development_dependency 'rspec-steps', '~> 2.1.1'
48
50
  spec.add_development_dependency 'rspec-wait'
49
- spec.add_development_dependency 'sqlite3'
51
+ spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153
50
52
  spec.add_development_dependency 'timecop', '~> 0.8.1'
51
53
  end
@@ -28,7 +28,9 @@ if RUBY_ENGINE == 'opal'
28
28
  require 'hyper-operation/railway/validations'
29
29
  require 'hyper-operation/server_op'
30
30
  require 'hyper-operation/boot'
31
+ require 'hyper-operation/async_sleep'
31
32
  else
33
+ require 'tty-table'
32
34
  require 'hyperstack-config'
33
35
  require 'mutations'
34
36
  Mutations::HashFilter.register_additional_filter(Mutations::DuckFilter, :duck)
@@ -40,10 +42,11 @@ else
40
42
  require 'hyper-operation/transport/connection'
41
43
  require 'hyper-operation/transport/hyperstack'
42
44
  require 'hyper-operation/transport/policy'
45
+ require 'hyper-operation/transport/policy_diagnostics'
43
46
  require 'hyper-operation/transport/client_drivers'
44
47
  require 'hyper-operation/transport/acting_user'
45
48
  require 'opal-activesupport'
46
- require 'hyper-operation/delay_and_interval'
49
+ require 'hyper-operation/async_sleep'
47
50
  require 'hyper-operation/exception'
48
51
  require 'hyper-operation/promise'
49
52
  require 'hyper-operation/railway'
@@ -46,8 +46,12 @@ module Hyperstack
46
46
  @_railway.process_params(args)
47
47
  @_railway.process_validations
48
48
  @_railway.run
49
- @_railway.dispatch
50
- @_railway.result
49
+ # return the result from dispatch in case there is an error
50
+ if (dispatch_result = @_railway.dispatch).rejected?
51
+ dispatch_result
52
+ else
53
+ @_railway.result
54
+ end
51
55
  end
52
56
  end
53
57
 
@@ -0,0 +1,23 @@
1
+ module Hyperstack
2
+ module AsyncSleep
3
+ if RUBY_ENGINE == 'opal'
4
+ def self.every(*args, &block)
5
+ every(*args, &block)
6
+ end
7
+
8
+ def self.after(*args, &block)
9
+ after(*args, &block)
10
+ end
11
+ else
12
+ extend self
13
+
14
+ def every(time, &block)
15
+ Thread.new { loop { sleep time; block.call } }
16
+ end
17
+
18
+ def after(time, &block)
19
+ Thread.new { sleep time; block.call }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,13 +1,39 @@
1
+ module Mutations
2
+ class ErrorArray
3
+ def self.new_from_error_hash(errors)
4
+ new(errors.collect do |key, values|
5
+ ErrorAtom.new(key, values[:symbol], values)
6
+ end)
7
+ end
8
+ end
9
+ end
10
+
1
11
  module Hyperstack
2
12
  class AccessViolation < StandardError
3
- def message
4
- "Hyperstack::Operation::AccessViolation: #{super}"
13
+ attr_accessor :details
14
+
15
+ def initialize(message = nil, details = nil)
16
+ super("Hyperstack::AccessViolation#{':' + message.to_s if message}")
17
+ @details = details
18
+ end
19
+
20
+ def __hyperstack_on_error(operation, params, fmted_message)
21
+ Hyperstack.on_error(operation, self, params, fmted_message)
5
22
  end
6
23
  end
7
24
 
8
25
  class Operation
9
26
  class ValidationException < Mutations::ValidationException
27
+ def as_json(*)
28
+ errors.as_json
29
+ end
30
+
31
+ def initialize(errors)
32
+ unless errors.is_a? Mutations::ErrorHash
33
+ errors = Mutations::ErrorArray.new_from_error_hash(errors)
34
+ end
35
+ super(errors)
36
+ end
10
37
  end
11
38
  end
12
-
13
39
  end
@@ -1,3 +1,15 @@
1
+ # Caution. For now Hyperstack maintains its own copy of the Promise class.
2
+ # Eventually the diff between hyperstacks version and the official Opal version
3
+ # should be put into a PR.
4
+
5
+ # A key feature add is the Fail Exception class which is simply there to allow
6
+ # a `always` block to reject without raising an error. To use this see the run.rb
7
+ # module.
8
+
9
+ # Also see exception! method for the part of the code that detects the Fail exception.
10
+
11
+ # See https://github.com/opal/opal/issues/1967 for details.
12
+
1
13
  class Promise
2
14
  def self.value(value)
3
15
  new.resolve(value)
@@ -11,6 +23,13 @@ class Promise
11
23
  When.new(promises)
12
24
  end
13
25
 
26
+ class Fail < StandardError
27
+ attr_reader :result
28
+ def initialize(result)
29
+ @result = result
30
+ end
31
+ end
32
+
14
33
  attr_reader :error, :prev, :next
15
34
 
16
35
  def initialize(action = {})
@@ -164,8 +183,15 @@ class Promise
164
183
  end
165
184
 
166
185
  def exception!(error)
167
- @exception = true
168
-
186
+ # If the error is a Promise::Fail, then
187
+ # the error becomes the error.result value
188
+ # this allows code to raise an error on an
189
+ # object that is not an error.
190
+ if error.is_a? Promise::Fail
191
+ error = error.result
192
+ else
193
+ @exception = true
194
+ end
169
195
  reject!(error)
170
196
  end
171
197
 
@@ -1,7 +1,6 @@
1
1
  module Hyperstack
2
2
  class Operation
3
3
  class Railway
4
-
5
4
  def receivers
6
5
  self.class.receivers
7
6
  end
@@ -4,10 +4,13 @@ module Hyperstack
4
4
  class Exit < StandardError
5
5
  attr_reader :state
6
6
  attr_reader :result
7
- def initialize(state, result)
7
+ def initialize(state, result = nil)
8
8
  @state = state
9
9
  @result = result
10
10
  end
11
+ def to_s
12
+ @state
13
+ end
11
14
  end
12
15
 
13
16
  class Railway
@@ -21,7 +24,7 @@ module Hyperstack
21
24
  @tracks ||= []
22
25
  end
23
26
 
24
- def to_opts(tie, args, block)
27
+ def build_tie(tie, args, block)
25
28
  if args.count.zero?
26
29
  { run: block }
27
30
  elsif args[0].is_a?(Hash)
@@ -41,7 +44,7 @@ module Hyperstack
41
44
 
42
45
  [:step, :failed, :async].each do |tie|
43
46
  define_method :"add_#{tie}" do |*args, &block|
44
- tracks << to_opts(tie, args, block)
47
+ tracks << build_tie(tie, args, block)
45
48
  end
46
49
  end
47
50
 
@@ -55,44 +58,33 @@ module Hyperstack
55
58
  end
56
59
 
57
60
  def step(opts)
58
- if @last_result.is_a? Promise
59
- @last_result = @last_result.then do |*result|
60
- @last_result = result
61
- apply(opts, :in_promise)
62
- end
63
- elsif @state == :success
64
- apply(opts)
65
- end
61
+ @promise_chain = @promise_chain
62
+ .then { |result| apply(result, :success, opts) }
66
63
  end
67
64
 
68
65
  def failed(opts)
69
- if @last_result.is_a? Promise
70
- @last_result = @last_result.fail do |e|
71
- @last_result = e
72
- apply(opts, :in_promise)
73
- raise @last_result if @last_result.is_a? Exception
74
- raise e
75
- end
76
- elsif @state == :failed
77
- apply(opts)
78
- end
66
+ @promise_chain = @promise_chain
67
+ .always { |result| apply(result, :failed, opts) }
79
68
  end
80
69
 
81
70
  def async(opts)
82
- apply(opts) if @state != :failed
71
+ @promise_chain = @promise_chain_start = Promise.new
72
+ @promise_chain.resolve(@last_async_result)
73
+ step(opts)
83
74
  end
84
75
 
85
- def apply(opts, in_promise = nil)
76
+ def apply(result, state, opts)
77
+ return result unless @state == state
86
78
  if opts[:scope] == :class
87
- args = [@operation, *@last_result]
79
+ args = [@operation, *result]
88
80
  instance = @operation.class
89
81
  else
90
- args = @last_result
82
+ args = result
91
83
  instance = @operation
92
84
  end
93
85
  block = opts[:run]
94
86
  block = instance.method(block) if block.is_a? Symbol
95
- @last_result =
87
+ last_result =
96
88
  if block.arity.zero?
97
89
  instance.instance_exec(&block)
98
90
  elsif args.is_a?(Array) && block.arity == args.count
@@ -100,40 +92,54 @@ module Hyperstack
100
92
  else
101
93
  instance.instance_exec(args, &block)
102
94
  end
103
- return @last_result unless @last_result.is_a? Promise
104
- raise @last_result.error if @last_result.rejected?
105
- @last_result = @last_result.value if @last_result.resolved?
106
- @last_result
95
+ @last_async_result = last_result unless last_result.is_a? Promise
96
+ last_result
107
97
  rescue Exit => e
108
- @state = e.state
109
- @last_result = (e.state != :failed || e.result.is_a?(Exception)) ? e.result : e
110
- raise e
98
+ # the promise chain ends with an always block which will process
99
+ # any immediate exits by checking the value of @state. All other
100
+ # step/failed/async blocks will be skipped because state will not equal
101
+ # :succeed or :failed
102
+ if e.state == :failed
103
+ @state = :abort
104
+ # exit via the final always block with the exception
105
+ raise e.result.is_a?(Exception) ? e.result : e
106
+ else
107
+ @state = :succeed
108
+ # exit via the final then block with the success value
109
+ e.result
110
+ end
111
111
  rescue Exception => e
112
112
  @state = :failed
113
- @last_result = e
114
- raise e if in_promise
113
+ raise e
115
114
  end
116
115
 
117
116
  def run
118
- if @operation.has_errors? || @state
119
- @last_result ||= ValidationException.new(@operation.instance_variable_get('@errors'))
120
- return if @state # handles abort out of validation
121
- @state = :failed
122
- else
123
- @state = :success
124
- end
125
- tracks.each { |opts| opts[:tie].bind(self).call(opts) }
126
- rescue Exit
117
+ # if @operation.has_errors? || @state
118
+ # @last_result ||= ValidationException.new(@operation.instance_variable_get('@errors'))
119
+ # # following handles abort out of validation. if state is already set then we are aborting
120
+ # # otherwise if state is not set but we have errors then we are failed
121
+ # @state ||= :failed
122
+ # else
123
+ # @state = :success
124
+ # end
125
+ @state ||= :success
126
+ @promise_chain_start = @promise_chain = Promise.new
127
+ @promise_chain_start.resolve(@last_result)
128
+ tracks.each { |opts| opts[:tie].bind(self).call(opts) } unless @state == :abort
127
129
  end
128
130
 
129
131
  def result
130
- return @last_result if @last_result.is_a? Promise
131
- @last_result =
132
- if @state == :success
133
- Promise.new.resolve(@last_result)
132
+ @result ||= @promise_chain.always do |e|
133
+ if %i[abort failed].include? @state
134
+ if e.is_a? Exception
135
+ raise e
136
+ else
137
+ raise Promise::Fail.new(e)
138
+ end
134
139
  else
135
- Promise.new.reject(@last_result)
140
+ e
136
141
  end
142
+ end
137
143
  end
138
144
  end
139
145
  end
@@ -28,6 +28,8 @@ module Hyperstack
28
28
  rescue Exit => e
29
29
  raise e unless e.state == :failed
30
30
  add_error(param, symbol, message)
31
+ # use a bogus exit state which will skip adding
32
+ # a validation error (see catch block in process_validations method)
31
33
  raise Exit.new(:abort_from_add_error, e.result)
32
34
  end
33
35
  end
@@ -47,17 +49,22 @@ module Hyperstack
47
49
  when :failed
48
50
  add_validation_error(i, "param validation #{i+1} aborted")
49
51
  end
50
- @state = :failed
52
+ @state = :abort
51
53
  return # break does not work in Opal
52
54
  rescue AccessViolation => e
53
55
  add_validation_error(i, e)
54
- @state = :failed
56
+ @state = :abort
55
57
  @last_result = e
56
58
  return # break does not work in Opal
57
59
  rescue Exception => e
58
60
  add_validation_error(i, e)
59
61
  end
60
62
  end
63
+ ensure
64
+ if @operation.has_errors?
65
+ @last_result ||= ValidationException.new(@operation.instance_variable_get('@errors'))
66
+ @state ||= :failed
67
+ end
61
68
  end
62
69
  end
63
70
  end
@@ -13,14 +13,18 @@ module Hyperstack
13
13
  hash = serialize_params(hash)
14
14
  Hyperstack::HTTP.post(
15
15
  "#{`window.HyperstackEnginePath`}/execute_remote",
16
- payload: {json: {operation: name, params: hash}.to_json},
17
- headers: {'X-CSRF-Token' => Hyperstack::ClientDrivers.opts[:form_authenticity_token] }
16
+ payload: {hyperstack_secured_json: {operation: name, params: hash}.to_json},
17
+ headers: headers.merge('X-CSRF-Token' => Hyperstack::ClientDrivers.opts[:form_authenticity_token])
18
18
  )
19
19
  .then do |response|
20
20
  deserialize_response response.json[:response]
21
21
  end
22
22
  .fail do |response|
23
- Exception.new response.json[:error]
23
+ begin
24
+ const_get(response.json[:error_class]).new(response.json[:error])
25
+ rescue
26
+ Exception.new response.json[:error]
27
+ end
24
28
  end
25
29
  end
26
30
  elsif on_opal_server?
@@ -74,7 +78,7 @@ module Hyperstack
74
78
  if _Railway.params_wrapper.method_defined?(:controller)
75
79
  params[:controller] = controller
76
80
  elsif !_Railway.params_wrapper.method_defined?(security_param)
77
- raise AccessViolation
81
+ raise AccessViolation.new(:remote_access_not_allowed)
78
82
  end
79
83
  run(deserialize_params(params))
80
84
  .then { |r| return { json: { response: serialize_response(r) } } }
@@ -84,13 +88,27 @@ module Hyperstack
84
88
  handle_exception(e, operation, params)
85
89
  end
86
90
 
91
+ def status(e)
92
+ if e.is_a? AccessViolation
93
+ 403
94
+ elsif e.is_a? Operation::ValidationException
95
+ 400
96
+ else
97
+ 500
98
+ end
99
+ end
100
+
87
101
  def handle_exception(e, operation, params)
88
- if defined? ::Rails
89
- params.delete(:controller)
90
- ::Rails.logger.debug "\033[0;31;1mERROR: Hyperstack::ServerOp exception caught when running "\
91
- "#{operation} with params \"#{params}\": #{e}\033[0;30;21m"
102
+ if e.respond_to? :__hyperstack_on_error
103
+ params = params.to_h
104
+ message = []
105
+ message << Pastel.new.red("HYPERSTACK ERROR during #{operation} #{e.inspect}")
106
+ params.each { |param, value| message << " #{param} => #{value.inspect.truncate(120, separator: '...')}" }
107
+ message << "\n#{e.details}" if e.respond_to? :details
108
+ e.__hyperstack_on_error(operation, params, message.join("\n"))
92
109
  end
93
- { json: { error: e }, status: 500 }
110
+
111
+ { json: { error_class: e.class.to_s, error: e}, status: status(e) }
94
112
  end
95
113
 
96
114
 
@@ -111,6 +129,10 @@ module Hyperstack
111
129
  promise.reject e
112
130
  end
113
131
 
132
+ def headers
133
+ {}
134
+ end
135
+
114
136
  def serialize_params(hash)
115
137
  hash
116
138
  end