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 +4 -4
- data/.travis.yml +1 -0
- data/hyper-operation.gemspec +6 -5
- data/lib/hyper-operation.rb +2 -1
- data/lib/hyper-operation/api.rb +6 -2
- data/lib/hyper-operation/async_sleep.rb +23 -0
- data/lib/hyper-operation/railway/dispatcher.rb +0 -1
- data/lib/hyper-operation/railway/run.rb +4 -1
- data/lib/hyper-operation/server_op.rb +1 -1
- data/lib/hyper-operation/transport/client_drivers.rb +2 -2
- data/lib/hyper-operation/transport/connection.rb +58 -136
- data/lib/hyper-operation/transport/connection_adapter/active_record.rb +113 -0
- data/lib/hyper-operation/transport/connection_adapter/active_record/auto_create.rb +26 -0
- data/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb +47 -0
- data/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb +42 -0
- data/lib/hyper-operation/transport/connection_adapter/redis.rb +94 -0
- data/lib/hyper-operation/transport/connection_adapter/redis/connection.rb +85 -0
- data/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb +34 -0
- data/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb +158 -0
- data/lib/hyper-operation/transport/hyperstack.rb +9 -1
- data/lib/hyper-operation/transport/hyperstack_controller.rb +5 -1
- data/lib/hyper-operation/transport/policy.rb +9 -4
- data/lib/hyper-operation/version.rb +1 -1
- metadata +64 -42
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf7ea211cf5d34d735df9a5318691ff4261b26e15816c6529138da58ce23ead5
|
4
|
+
data.tar.gz: bbce8cdf73f646fe7abd922a0667739f486368c723800b7f6efecbbec9ad7468
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afdb25d53a08ed95794a3419f0d3ea341f04c8da594d68189029f2c5c87568b4c0edd9fe4b57df4a42042f63f66de585e4a1a65dde8efb56063d79019fddee75
|
7
|
+
data.tar.gz: c5fbd8014773c171dee7d8556f84da60fcdfc77509a7ae6f4380c3b7b729a2c438faa4cd5c1d09a92dc19ceed2b77203154314ebd75b6e3b01988383485caf88
|
data/.travis.yml
CHANGED
data/hyper-operation.gemspec
CHANGED
@@ -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'
|
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', '
|
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', '>=
|
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.
|
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
|
data/lib/hyper-operation.rb
CHANGED
@@ -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/
|
49
|
+
require 'hyper-operation/async_sleep'
|
49
50
|
require 'hyper-operation/exception'
|
50
51
|
require 'hyper-operation/promise'
|
51
52
|
require 'hyper-operation/railway'
|
data/lib/hyper-operation/api.rb
CHANGED
@@ -46,8 +46,12 @@ module Hyperstack
|
|
46
46
|
@_railway.process_params(args)
|
47
47
|
@_railway.process_validations
|
48
48
|
@_railway.run
|
49
|
-
|
50
|
-
@_railway.
|
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
|
@@ -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: {
|
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', #{
|
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
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
46
|
-
|
25
|
+
adapter_name = adapter_name.camelize
|
26
|
+
"Hyperstack::ConnectionAdapter::#{adapter_name}".constantize
|
47
27
|
end
|
48
28
|
|
49
|
-
def
|
50
|
-
|
29
|
+
def build_tables
|
30
|
+
adapter.build_tables
|
51
31
|
end
|
52
|
-
end
|
53
|
-
|
54
|
-
extend AutoCreate
|
55
32
|
|
56
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
118
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
68
|
+
adapter.disconnect(channel)
|
149
69
|
end
|
150
70
|
|
151
71
|
def root_path=(path)
|
152
|
-
|
72
|
+
adapter.root_path = path
|
153
73
|
end
|
154
74
|
|
155
75
|
def root_path
|
156
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
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
|