hyper-operation 1.0.alpha1.5 → 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.
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