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

Sign up to get free protection for your applications and to get access to all the features.
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