hyper-operation 1.0.alpha1.3 → 1.0.alpha1.8

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.travis.yml +1 -0
  4. data/hyper-operation.gemspec +6 -5
  5. data/lib/hyper-operation.rb +2 -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/promise.rb +32 -2
  9. data/lib/hyper-operation/railway/dispatcher.rb +0 -1
  10. data/lib/hyper-operation/railway/run.rb +57 -48
  11. data/lib/hyper-operation/railway/validations.rb +9 -2
  12. data/lib/hyper-operation/server_op.rb +6 -2
  13. data/lib/hyper-operation/transport/client_drivers.rb +41 -11
  14. data/lib/hyper-operation/transport/connection.rb +58 -136
  15. data/lib/hyper-operation/transport/connection_adapter/active_record.rb +113 -0
  16. data/lib/hyper-operation/transport/connection_adapter/active_record/auto_create.rb +26 -0
  17. data/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb +47 -0
  18. data/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb +42 -0
  19. data/lib/hyper-operation/transport/connection_adapter/redis.rb +94 -0
  20. data/lib/hyper-operation/transport/connection_adapter/redis/connection.rb +85 -0
  21. data/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb +34 -0
  22. data/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb +158 -0
  23. data/lib/hyper-operation/transport/hyperstack.rb +15 -2
  24. data/lib/hyper-operation/transport/hyperstack_controller.rb +5 -1
  25. data/lib/hyper-operation/transport/policy.rb +9 -4
  26. data/lib/hyper-operation/version.rb +1 -1
  27. metadata +66 -45
  28. data/Gemfile.lock +0 -407
  29. 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: 9684386e717f491348c6acab32279464d69eae7a30dad4177a86e5b05e34b47a
4
- data.tar.gz: 8a7ffd0abd510e70a657ce14ee796265520646a8a709fd4e98b8589c568da42e
3
+ metadata.gz: 89a7c07b2cab1760bb41bcff159333e5ea985d8eca22329f4a39f7890d295e5f
4
+ data.tar.gz: 5955718c34bba9f9af227ea92e0d0869a4f21c131deccd2ad2f0e707c97b8219
5
5
  SHA512:
6
- metadata.gz: a52bbf0183579a349caaa55afc19179d851a900c335bda1a51a13fafc0a9535ec456dd4f2c864bb33766ca2329bf912bde9fba0b13c236a65a5d5bd9763843e1
7
- data.tar.gz: 7a2127f42df5613b3a4f030afa05fc1b3c30e5d0d2afa7b6c32df05ccf33e43678584fa4e54e58562d65caad7936279baf0b700c4fd2c482cecec42ddc0eddf4
6
+ metadata.gz: 215aaf6fa7aa4084551d000b96a5dcf62c1839ff346fd8e0c75b6c66eea32274867e93eca3d1f485ccef0ed0f2cbc5e7442da994016a8446d49daa8156c51832
7
+ data.tar.gz: 18944dce11ebdbd25616d2b4a0d7565ea2ddbbf033ba78f36608102220e95278433f4a7b5e4241032758fda4f5b7a5bec99cb6d1236501f4c6ee729b17bd2736
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:
@@ -29,24 +29,25 @@ Gem::Specification.new do |spec|
29
29
  spec.add_dependency 'opal-activesupport', '~> 0.3.1'
30
30
  spec.add_dependency 'tty-table'
31
31
 
32
- spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1']
32
+ spec.add_development_dependency 'bundler'
33
33
  spec.add_development_dependency 'chromedriver-helper'
34
34
  spec.add_development_dependency 'database_cleaner'
35
35
  spec.add_development_dependency 'hyper-spec', Hyperstack::Operation::VERSION
36
36
  spec.add_development_dependency 'mysql2'
37
- spec.add_development_dependency 'opal', '>= 0.11.0', '< 0.12.0'
38
37
  spec.add_development_dependency 'opal-browser', '~> 0.2.0'
39
- spec.add_development_dependency 'opal-rails', '~> 0.9.4'
38
+ spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0'
40
39
  spec.add_development_dependency 'pry-rescue'
40
+ spec.add_development_dependency 'pry-stack_explorer'
41
41
  spec.add_development_dependency 'puma'
42
42
  spec.add_development_dependency 'pusher'
43
43
  spec.add_development_dependency 'pusher-fake'
44
- spec.add_development_dependency 'rails', '>= 4.0.0'
44
+ spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0'
45
45
  spec.add_development_dependency 'rake'
46
46
  spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0'
47
+ spec.add_development_dependency 'redis'
47
48
  spec.add_development_dependency 'rspec-rails'
48
49
  spec.add_development_dependency 'rspec-steps', '~> 2.1.1'
49
50
  spec.add_development_dependency 'rspec-wait'
50
- spec.add_development_dependency 'sqlite3'
51
+ spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153
51
52
  spec.add_development_dependency 'timecop', '~> 0.8.1'
52
53
  end
@@ -28,6 +28,7 @@ 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
32
33
  require 'tty-table'
33
34
  require 'hyperstack-config'
@@ -45,7 +46,7 @@ else
45
46
  require 'hyper-operation/transport/client_drivers'
46
47
  require 'hyper-operation/transport/acting_user'
47
48
  require 'opal-activesupport'
48
- require 'hyper-operation/delay_and_interval'
49
+ require 'hyper-operation/async_sleep'
49
50
  require 'hyper-operation/exception'
50
51
  require 'hyper-operation/promise'
51
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,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 = {})
@@ -104,6 +123,7 @@ class Promise
104
123
 
105
124
  begin
106
125
  if block = @action[:success] || @action[:always]
126
+ @realized = :resolve
107
127
  value = block.call(value)
108
128
  end
109
129
 
@@ -137,6 +157,9 @@ class Promise
137
157
 
138
158
  begin
139
159
  if block = @action[:failure] || @action[:always]
160
+ # temporarily set values so always can determine if this
161
+ # was a reject or resolve
162
+ @realized = :reject
140
163
  value = block.call(value)
141
164
  end
142
165
 
@@ -164,8 +187,15 @@ class Promise
164
187
  end
165
188
 
166
189
  def exception!(error)
167
- @exception = true
168
-
190
+ # If the error is a Promise::Fail, then
191
+ # the error becomes the error.result value
192
+ # this allows code to raise an error on an
193
+ # object that is not an error.
194
+ if error.is_a? Promise::Fail
195
+ error = error.result
196
+ else
197
+ @exception = true
198
+ end
169
199
  reject!(error)
170
200
  end
171
201
 
@@ -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,36 @@ 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
66
+ @promise_chain = @promise_chain
67
+ .always do |result|
68
+ @state = :failed if @promise_chain.rejected? && @state != :abort
69
+ apply(result, :failed, opts)
75
70
  end
76
- elsif @state == :failed
77
- apply(opts)
78
- end
79
71
  end
80
72
 
81
73
  def async(opts)
82
- apply(opts) if @state != :failed
74
+ @promise_chain = @promise_chain_start = Promise.new
75
+ @promise_chain.resolve(@last_async_result)
76
+ step(opts)
83
77
  end
84
78
 
85
- def apply(opts, in_promise = nil)
79
+ def apply(result, state, opts)
80
+ return result unless @state == state
86
81
  if opts[:scope] == :class
87
- args = [@operation, *@last_result]
82
+ args = [@operation, *result]
88
83
  instance = @operation.class
89
84
  else
90
- args = @last_result
85
+ args = result
91
86
  instance = @operation
92
87
  end
93
88
  block = opts[:run]
94
89
  block = instance.method(block) if block.is_a? Symbol
95
- @last_result =
90
+ last_result =
96
91
  if block.arity.zero?
97
92
  instance.instance_exec(&block)
98
93
  elsif args.is_a?(Array) && block.arity == args.count
@@ -100,40 +95,54 @@ module Hyperstack
100
95
  else
101
96
  instance.instance_exec(args, &block)
102
97
  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
98
+ @last_async_result = last_result unless last_result.is_a? Promise
99
+ last_result
107
100
  rescue Exit => e
108
- @state = e.state
109
- @last_result = (e.state != :failed || e.result.is_a?(Exception)) ? e.result : e
110
- raise e
101
+ # the promise chain ends with an always block which will process
102
+ # any immediate exits by checking the value of @state. All other
103
+ # step/failed/async blocks will be skipped because state will not equal
104
+ # :succeed or :failed
105
+ if e.state == :failed
106
+ @state = :abort
107
+ # exit via the final always block with the exception
108
+ raise e.result.is_a?(Exception) ? e.result : e
109
+ else
110
+ @state = :succeed
111
+ # exit via the final then block with the success value
112
+ e.result
113
+ end
111
114
  rescue Exception => e
112
115
  @state = :failed
113
- @last_result = e
114
- raise e if in_promise
116
+ raise e
115
117
  end
116
118
 
117
119
  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
120
+ # if @operation.has_errors? || @state
121
+ # @last_result ||= ValidationException.new(@operation.instance_variable_get('@errors'))
122
+ # # following handles abort out of validation. if state is already set then we are aborting
123
+ # # otherwise if state is not set but we have errors then we are failed
124
+ # @state ||= :failed
125
+ # else
126
+ # @state = :success
127
+ # end
128
+ @state ||= :success
129
+ @promise_chain_start = @promise_chain = Promise.new
130
+ @promise_chain_start.resolve(@last_result)
131
+ tracks.each { |opts| opts[:tie].bind(self).call(opts) } unless @state == :abort
127
132
  end
128
133
 
129
134
  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)
135
+ @result ||= @promise_chain.always do |e|
136
+ if %i[abort failed].include? @state
137
+ if e.is_a? Exception
138
+ raise e
139
+ else
140
+ raise Promise::Fail.new(e)
141
+ end
134
142
  else
135
- Promise.new.reject(@last_result)
143
+ e
136
144
  end
145
+ end
137
146
  end
138
147
  end
139
148
  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,8 +13,8 @@ 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]
@@ -129,6 +129,10 @@ module Hyperstack
129
129
  promise.reject e
130
130
  end
131
131
 
132
+ def headers
133
+ {}
134
+ end
135
+
132
136
  def serialize_params(hash)
133
137
  hash
134
138
  end
@@ -35,16 +35,35 @@ module Hyperstack
35
35
 
36
36
 
37
37
  if RUBY_ENGINE == 'opal'
38
+ # Patch in a dummy copy of Model.load in case we are not using models
39
+ # this will be defined properly by hyper-model
40
+ module Model
41
+ def self.load
42
+ Promise.new.tap { |promise| promise.resolve(yield) }
43
+ end unless respond_to?(:load)
44
+ end
45
+
38
46
  def self.connect(*channels)
39
47
  channels.each do |channel|
40
48
  if channel.is_a? Class
41
49
  IncomingBroadcast.connect_to(channel.name)
42
50
  elsif channel.is_a?(String) || channel.is_a?(Array)
43
51
  IncomingBroadcast.connect_to(*channel)
44
- elsif channel.id
45
- IncomingBroadcast.connect_to(channel.class.name, channel.id)
52
+ elsif channel.respond_to?(:id)
53
+ Hyperstack::Model.load do
54
+ channel.id
55
+ end.then do |id|
56
+ raise "Hyperstack.connect cannot connect to #{channel.inspect}. "\
57
+ "The id is nil. This can be caused by connecting to a model "\
58
+ "that is not saved, or that does not exist." unless id
59
+ IncomingBroadcast.connect_to(channel.class.name, id)
60
+ end
46
61
  else
47
- raise "cannot connect to model before it has been saved"
62
+ raise "Hyperstack.connect cannot connect to #{channel.inspect}.\n"\
63
+ "Channels must be either a class, or a class name,\n"\
64
+ "a string in the form 'ClassName-id',\n"\
65
+ "an array in the form [class, id] or [class-name, id],\n"\
66
+ "or an object that responds to the id method with a non-nil value"
48
67
  end
49
68
  end
50
69
  end
@@ -65,19 +84,22 @@ module Hyperstack
65
84
 
66
85
  def self.add_connection(channel_name, id = nil)
67
86
  channel_string = "#{channel_name}#{'-'+id.to_s if id}"
87
+ return if open_channels.include? channel_string
68
88
  open_channels << channel_string
69
89
  channel_string
70
90
  end
71
91
 
72
92
  def self.connect_to(channel_name, id = nil)
73
93
  channel_string = add_connection(channel_name, id)
94
+ return unless channel_string # already connected!
74
95
  if ClientDrivers.opts[:transport] == :pusher
75
96
  channel = "#{ClientDrivers.opts[:channel]}-#{channel_string}"
76
97
  %x{
77
98
  var channel = #{ClientDrivers.opts[:pusher_api]}.subscribe(#{channel.gsub('::', '==')});
78
99
  channel.bind('dispatch', #{ClientDrivers.opts[:dispatch]})
79
- channel.bind('pusher:subscription_succeeded', #{lambda {ClientDrivers.get_queued_data("connect-to-transport", channel_string)}})
100
+ channel.bind('pusher:subscription_succeeded', #{->(*) { ClientDrivers.get_queued_data("connect-to-transport", channel_string)}})
80
101
  }
102
+ @pusher_dispatcher_registered = true
81
103
  elsif ClientDrivers.opts[:transport] == :action_cable
82
104
  channel = "#{ClientDrivers.opts[:channel]}-#{channel_string}"
83
105
  Hyperstack::HTTP.post(ClientDrivers.polling_path('action-cable-auth', channel), headers: { 'X-CSRF-Token' => ClientDrivers.opts[:form_authenticity_token] }).then do |response|
@@ -147,7 +169,7 @@ module Hyperstack
147
169
  config_hash = {
148
170
  transport: Hyperstack.transport,
149
171
  id: id,
150
- acting_user_id: (controller.acting_user && controller.acting_user.id),
172
+ acting_user_id: (controller.acting_user.respond_to?(:id) && controller.acting_user.id),
151
173
  env: ::Rails.env,
152
174
  client_logging: Hyperstack.client_logging,
153
175
  pusher_fake_js: pusher_fake_js,
@@ -163,12 +185,17 @@ module Hyperstack
163
185
  # not sure why the second check is needed. It happens in the test app
164
186
  route.app == Hyperstack::Engine or (route.app.respond_to?(:app) and route.app.app == Hyperstack::Engine)
165
187
  end
166
- raise 'Hyperstack::Engine mount point not found. Check your config/routes.rb file' unless path
167
- path = path.path.spec
168
- "<script type='text/javascript'>\n"\
169
- "window.HyperstackEnginePath = '#{path}';\n"\
170
- "window.HyperstackOpts = #{config_hash.to_json}\n"\
171
- "</script>\n"
188
+ if path
189
+ path = path.path.spec
190
+ "<script type='text/javascript'>\n"\
191
+ "window.HyperstackEnginePath = '#{path}';\n"\
192
+ "window.HyperstackOpts = #{config_hash.to_json}\n"\
193
+ "</script>\n"
194
+ else
195
+ "<script type='text/javascript'>\n"\
196
+ "window.HyperstackOpts = #{config_hash.to_json}\n"\
197
+ "</script>\n"
198
+ end
172
199
  end if RUBY_ENGINE != 'opal'
173
200
 
174
201
  class << self
@@ -222,6 +249,9 @@ module Hyperstack
222
249
 
223
250
  @opts = Hash.new(`window.HyperstackOpts`)
224
251
 
252
+ if opts[:transport] != :none && `typeof(window.HyperstackEnginePath) == 'undefined'`
253
+ raise "No hyperstack mount point found!\nCheck your Rails routes.rb file";
254
+ end
225
255
 
226
256
  if opts[:transport] == :pusher
227
257