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

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65b352cb39f23e41aa0f4adc29ba892a8d9cf269c7345314851f2a68bccddf86
4
- data.tar.gz: 71f10e8f1dfc3f38b968b694c0535169211a374dc404fa3da79a864791e075f2
3
+ metadata.gz: bf7ea211cf5d34d735df9a5318691ff4261b26e15816c6529138da58ce23ead5
4
+ data.tar.gz: bbce8cdf73f646fe7abd922a0667739f486368c723800b7f6efecbbec9ad7468
5
5
  SHA512:
6
- metadata.gz: ba63a89809105c28b96d246a9958d0aab4ea121bcd59d5ee0f7fb1f96d88b1832dcafe35421f8220a13b401acba73d08cf7b27ce798c382336a2c9d844e7533f
7
- data.tar.gz: afaea58dfe7c97007f3d70cebe53d02258c938e3fa4c863788a259f2478fbf45dcf7bc8472fc46967c60b797b693594d1e2468b45671b58281bcaed2925e7e0f
6
+ metadata.gz: afdb25d53a08ed95794a3419f0d3ea341f04c8da594d68189029f2c5c87568b4c0edd9fe4b57df4a42042f63f66de585e4a1a65dde8efb56063d79019fddee75
7
+ data.tar.gz: c5fbd8014773c171dee7d8556f84da60fcdfc77509a7ae6f4380c3b7b729a2c438faa4cd5c1d09a92dc19ceed2b77203154314ebd75b6e3b01988383485caf88
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', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153
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,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
@@ -13,7 +13,7 @@ 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},
16
+ payload: {hyperstack_secured_json: {operation: name, params: hash}.to_json},
17
17
  headers: headers.merge('X-CSRF-Token' => Hyperstack::ClientDrivers.opts[:form_authenticity_token])
18
18
  )
19
19
  .then do |response|
@@ -97,7 +97,7 @@ module Hyperstack
97
97
  %x{
98
98
  var channel = #{ClientDrivers.opts[:pusher_api]}.subscribe(#{channel.gsub('::', '==')});
99
99
  channel.bind('dispatch', #{ClientDrivers.opts[:dispatch]})
100
- 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)}})
101
101
  }
102
102
  @pusher_dispatcher_registered = true
103
103
  elsif ClientDrivers.opts[:transport] == :action_cable
@@ -169,7 +169,7 @@ module Hyperstack
169
169
  config_hash = {
170
170
  transport: Hyperstack.transport,
171
171
  id: id,
172
- acting_user_id: (controller.acting_user && controller.acting_user.id),
172
+ acting_user_id: (controller.acting_user.respond_to?(:id) && controller.acting_user.id),
173
173
  env: ::Rails.env,
174
174
  client_logging: Hyperstack.client_logging,
175
175
  pusher_fake_js: pusher_fake_js,
@@ -1,173 +1,95 @@
1
- module Hyperstack
2
- module AutoCreate
3
- def table_exists?
4
- # works with both rails 4 and 5 without deprecation warnings
5
- if connection.respond_to?(:data_sources)
6
- connection.data_sources.include?(table_name)
7
- else
8
- connection.tables.include?(table_name)
9
- end
10
- end
11
-
12
- def needs_init?
13
- Hyperstack.transport != :none && Hyperstack.on_server? && !table_exists?
14
- end
15
-
16
- def create_table(*args, &block)
17
- connection.create_table(table_name, *args, &block) if needs_init?
18
- end
19
- end
20
-
21
- class Connection < ActiveRecord::Base
22
- class QueuedMessage < ActiveRecord::Base
23
-
24
- extend AutoCreate
25
-
26
- self.table_name = 'hyperstack_queued_messages'
27
-
28
- do_not_synchronize
29
-
30
- serialize :data
1
+ # frozen_string_literal: true
31
2
 
32
- belongs_to :hyperstack_connection,
33
- class_name: 'Hyperstack::Connection',
34
- foreign_key: 'connection_id'
35
-
36
- scope :for_session,
37
- ->(session) { joins(:hyperstack_connection).where('session = ?', session) }
38
-
39
- # For simplicity we use QueuedMessage with connection_id 0
40
- # to store the current path which is used by consoles to
41
- # communicate back to the server
42
-
43
- default_scope { where('connection_id IS NULL OR connection_id != 0') }
3
+ module Hyperstack
4
+ class Connection
5
+ class << self
6
+ attr_accessor :transport, :connection_adapter, :show_diagnostics
7
+
8
+ def adapter
9
+ adapter_name = Hyperstack.connection[:adapter].to_s
10
+ adapter_path = "hyper-operation/transport/connection_adapter/#{adapter_name}"
11
+
12
+ begin
13
+ require adapter_path
14
+ rescue LoadError => e
15
+ if e.path == adapter_path
16
+ raise e.class, "Could not load the '#{adapter_name}' adapter. Make sure the adapter is spelled correctly in your Hyperstack config, and the necessary gems are in your Gemfile.", e.backtrace
17
+
18
+ # Bubbled up from the adapter require. Prefix the exception message
19
+ # with some guidance about how to address it and reraise.
20
+ else
21
+ raise e.class, "Error loading the '#{adapter_name}' adapter. Missing a gem it depends on? #{e.message}", e.backtrace
22
+ end
23
+ end
44
24
 
45
- def self.root_path=(path)
46
- unscoped.find_or_create_by(connection_id: 0).update(data: path)
25
+ adapter_name = adapter_name.camelize
26
+ "Hyperstack::ConnectionAdapter::#{adapter_name}".constantize
47
27
  end
48
28
 
49
- def self.root_path
50
- unscoped.find_or_create_by(connection_id: 0).data
29
+ def build_tables
30
+ adapter.build_tables
51
31
  end
52
- end
53
-
54
- extend AutoCreate
55
32
 
56
- def self.build_tables
57
- create_table(force: :cascade) do |t|
58
- t.string :channel
59
- t.string :session
60
- t.datetime :created_at
61
- t.datetime :expires_at
62
- t.datetime :refresh_at
63
- end
64
- QueuedMessage.create_table(force: :cascade) do |t|
65
- t.text :data
66
- t.integer :connection_id
33
+ def build_tables?
34
+ adapter.respond_to?(:build_tables)
67
35
  end
68
- end
69
-
70
- do_not_synchronize
71
-
72
- self.table_name = 'hyperstack_connections'
73
-
74
- has_many :messages,
75
- foreign_key: 'connection_id',
76
- class_name: 'Hyperstack::Connection::QueuedMessage',
77
- dependent: :destroy
78
- scope :expired,
79
- -> { where('expires_at IS NOT NULL AND expires_at < ?', Time.zone.now) }
80
- scope :pending_for,
81
- ->(channel) { where(channel: channel).where('session IS NOT NULL') }
82
- scope :inactive,
83
- -> { where('session IS NULL AND refresh_at < ?', Time.zone.now) }
84
-
85
- def self.needs_refresh?
86
- exists?(['refresh_at IS NOT NULL AND refresh_at < ?', Time.zone.now])
87
- end
88
-
89
- def transport
90
- self.class.transport
91
- end
92
-
93
- before_create do
94
- if session
95
- self.expires_at = Time.now + transport.expire_new_connection_in
96
- elsif transport.refresh_channels_every != :never
97
- self.refresh_at = Time.now + transport.refresh_channels_every
98
- end
99
- end
100
-
101
- class << self
102
- attr_accessor :transport
103
36
 
104
37
  def active
105
- # if table doesn't exist then we are either calling from within
106
- # a migration or from a console before the server has ever started
107
- # in these cases there are no channels so we return nothing
108
- return [] unless table_exists?
109
- if Hyperstack.on_server?
110
- expired.delete_all
111
- refresh_connections if needs_refresh?
112
- end
113
- all.pluck(:channel).uniq
38
+ adapter.active
114
39
  end
115
40
 
116
41
  def open(channel, session = nil, root_path = nil)
117
- self.root_path = root_path
118
- find_or_create_by(channel: channel, session: session)
42
+ puts "open(#{channel}, #{session}, #{root_path})" if show_diagnostics
43
+
44
+ adapter.open(channel, session, root_path).tap do |c|
45
+ puts " - open returning #{c}" if show_diagnostics
46
+ end
119
47
  end
120
48
 
121
49
  def send_to_channel(channel, data)
122
- pending_for(channel).each do |connection|
123
- QueuedMessage.create(data: data, hyperstack_connection: connection)
124
- end
125
- transport.send_data(channel, data) if exists?(channel: channel, session: nil)
50
+ puts "send_to_channel(#{channel}, #{data})" if show_diagnostics
51
+
52
+ adapter.send_to_channel(channel, data)
126
53
  end
127
54
 
128
55
  def read(session, root_path)
129
- self.root_path = root_path
130
- where(session: session)
131
- .update_all(expires_at: Time.now + transport.expire_polled_connection_in)
132
- QueuedMessage.for_session(session).destroy_all.pluck(:data)
56
+ puts "read(#{session}, #{root_path})" if show_diagnostics
57
+
58
+ adapter.read(session, root_path)
133
59
  end
134
60
 
135
61
  def connect_to_transport(channel, session, root_path)
136
- self.root_path = root_path
137
- if (connection = find_by(channel: channel, session: session))
138
- messages = connection.messages.pluck(:data)
139
- connection.destroy
140
- else
141
- messages = []
142
- end
143
- open(channel)
144
- messages
62
+ puts "connect_to_transport(#{channel}, #{session}, #{root_path})" if show_diagnostics
63
+
64
+ adapter.connect_to_transport(channel, session, root_path)
145
65
  end
146
66
 
147
67
  def disconnect(channel)
148
- find_by(channel: channel, session: nil).destroy
68
+ adapter.disconnect(channel)
149
69
  end
150
70
 
151
71
  def root_path=(path)
152
- QueuedMessage.root_path = path if path
72
+ adapter.root_path = path
153
73
  end
154
74
 
155
75
  def root_path
156
- # if the QueuedMessage table doesn't exist then we are either calling from within
157
- # a migration or from a console before the server has ever started
158
- # in these cases there is no root path to the server
159
- QueuedMessage.root_path if QueuedMessage.table_exists?
76
+ adapter.root_path
160
77
  end
161
78
 
162
79
  def refresh_connections
163
- refresh_started_at = Time.zone.now
164
- channels = transport.refresh_channels
165
- next_refresh = refresh_started_at + transport.refresh_channels_every
166
- channels.each do |channel|
167
- connection = find_by(channel: channel, session: nil)
168
- connection.update(refresh_at: next_refresh) if connection
80
+ adapter.refresh_connections
81
+ end
82
+
83
+ def method_missing(method_name, *args, &block)
84
+ if adapter::Connection.respond_to?(method_name)
85
+ adapter::Connection.send(method_name, *args, &block)
86
+ else
87
+ super
169
88
  end
170
- inactive.delete_all
89
+ end
90
+
91
+ def respond_to_missing?(method_name, include_private = false)
92
+ adapter::Connection.respond_to?(method_name)
171
93
  end
172
94
  end
173
95
  end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'active_record/connection'
4
+ require_relative 'active_record/queued_message'
5
+
6
+ module Hyperstack
7
+ module ConnectionAdapter
8
+ module ActiveRecord
9
+ class << self
10
+ def build_tables
11
+ Connection.create_table(force: :cascade) do |t|
12
+ t.string :channel
13
+ t.string :session
14
+ t.datetime :created_at
15
+ t.datetime :expires_at
16
+ t.datetime :refresh_at
17
+ end
18
+
19
+ QueuedMessage.create_table(force: :cascade) do |t|
20
+ t.text :data
21
+ t.integer :connection_id
22
+ end
23
+ end
24
+
25
+ def transport
26
+ Hyperstack::Connection.transport
27
+ end
28
+
29
+ def active
30
+ # if table doesn't exist then we are either calling from within
31
+ # a migration or from a console before the server has ever started
32
+ # in these cases there are no channels so we return nothing
33
+ return [] unless Connection.table_exists?
34
+
35
+ if Hyperstack.on_server?
36
+ Connection.expired.delete_all
37
+ refresh_connections if Connection.needs_refresh?
38
+ end
39
+
40
+ Connection.all.pluck(:channel).uniq
41
+ rescue ::ActiveRecord::StatementInvalid
42
+ []
43
+ end
44
+
45
+ def open(channel, session = nil, root_path = nil)
46
+ self.root_path = root_path
47
+
48
+ Connection.find_or_create_by(channel: channel, session: session)
49
+ end
50
+
51
+ def send_to_channel(channel, data)
52
+ Connection.pending_for(channel).each do |connection|
53
+ QueuedMessage.create(data: data, hyperstack_connection: connection)
54
+ end
55
+
56
+ transport.send_data(channel, data) if Connection.exists?(channel: channel, session: nil)
57
+ end
58
+
59
+ def read(session, root_path)
60
+ self.root_path = root_path
61
+
62
+ Connection.where(session: session)
63
+ .update_all(expires_at: Time.current + transport.expire_polled_connection_in)
64
+
65
+ QueuedMessage.for_session(session).destroy_all.pluck(:data)
66
+ end
67
+
68
+ def connect_to_transport(channel, session, root_path)
69
+ self.root_path = root_path
70
+
71
+ if (connection = Connection.find_by(channel: channel, session: session))
72
+ messages = connection.messages.pluck(:data)
73
+ connection.destroy
74
+ else
75
+ messages = []
76
+ end
77
+
78
+ open(channel)
79
+
80
+ messages
81
+ end
82
+
83
+ def disconnect(channel)
84
+ Connection.find_by(channel: channel, session: nil).destroy
85
+ end
86
+
87
+ def root_path=(path)
88
+ QueuedMessage.root_path = path if path
89
+ end
90
+
91
+ def root_path
92
+ # if the QueuedMessage table doesn't exist then we are either calling from within
93
+ # a migration or from a console before the server has ever started
94
+ # in these cases there is no root path to the server
95
+ QueuedMessage.root_path if QueuedMessage.table_exists?
96
+ end
97
+
98
+ def refresh_connections
99
+ refresh_started_at = Time.current
100
+ channels = transport.refresh_channels
101
+ next_refresh = refresh_started_at + transport.refresh_channels_every
102
+
103
+ channels.each do |channel|
104
+ connection = Connection.find_by(channel: channel, session: nil)
105
+ connection.update(refresh_at: next_refresh) if connection
106
+ end
107
+
108
+ Connection.inactive.delete_all
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end