pg_eventstore 1.3.4 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/docs/configuration.md +16 -15
  4. data/docs/subscriptions.md +4 -2
  5. data/lib/pg_eventstore/config.rb +3 -0
  6. data/lib/pg_eventstore/extensions/callback_handlers_extension.rb +21 -0
  7. data/lib/pg_eventstore/subscriptions/basic_runner.rb +2 -2
  8. data/lib/pg_eventstore/subscriptions/callback_handlers/commands_handler_handlers.rb +38 -0
  9. data/lib/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rb +45 -0
  10. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +103 -0
  11. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +75 -0
  12. data/lib/pg_eventstore/subscriptions/commands_handler.rb +13 -29
  13. data/lib/pg_eventstore/subscriptions/events_processor.rb +21 -43
  14. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +77 -125
  15. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +30 -55
  16. data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +70 -0
  17. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +14 -3
  18. data/lib/pg_eventstore/subscriptions/subscriptions_set_lifecycle.rb +39 -0
  19. data/lib/pg_eventstore/utils.rb +8 -0
  20. data/lib/pg_eventstore/version.rb +1 -1
  21. data/lib/pg_eventstore/web/application.rb +9 -1
  22. data/lib/pg_eventstore/web/paginator/events_collection.rb +1 -3
  23. data/lib/pg_eventstore/web/paginator/helpers.rb +4 -1
  24. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +10 -1
  25. data/lib/pg_eventstore/web/subscriptions/helpers.rb +3 -4
  26. data/lib/pg_eventstore/web/views/home/dashboard.erb +11 -0
  27. data/lib/pg_eventstore/web/views/home/partials/events.erb +6 -1
  28. data/lib/pg_eventstore/web/views/subscriptions/index.erb +2 -2
  29. data/lib/pg_eventstore.rb +1 -0
  30. data/sig/interfaces/_raw_event_handler.rbs +3 -0
  31. data/sig/pg_eventstore/config.rbs +2 -0
  32. data/sig/pg_eventstore/extensions/callback_handlers_extension.rbs +11 -0
  33. data/sig/pg_eventstore/subscriptions/callback_handlers/commands_handler_handlers.rbs +10 -0
  34. data/sig/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rbs +11 -0
  35. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rbs +31 -0
  36. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rbs +19 -0
  37. data/sig/pg_eventstore/subscriptions/events_processor.rbs +1 -1
  38. data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +2 -30
  39. data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +27 -0
  40. data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +3 -2
  41. data/sig/pg_eventstore/subscriptions/subscriptions_set_lifecycle.rbs +24 -0
  42. data/sig/pg_eventstore/utils.rbs +2 -0
  43. data/sig/pg_eventstore/web/subscriptions/helpers.rbs +1 -1
  44. metadata +17 -2
@@ -6,11 +6,8 @@ module PgEventstore
6
6
  class SubscriptionFeeder
7
7
  extend Forwardable
8
8
 
9
- # @return [Integer] number of seconds between heartbeat updates
10
- HEARTBEAT_INTERVAL = 10 # seconds
11
-
12
- def_delegators :subscriptions_set, :id
13
9
  def_delegators :@basic_runner, :start, :stop, :restore, :state, :wait_for_finish, :stop_async
10
+ def_delegators :@subscriptions_lifecycle, :force_lock!
14
11
 
15
12
  # @param config_name [Symbol]
16
13
  # @param set_name [String]
@@ -18,23 +15,27 @@ module PgEventstore
18
15
  # @param retries_interval [Integer] a delay between retries of failed SubscriptionsSet
19
16
  def initialize(config_name:, set_name:, max_retries:, retries_interval:)
20
17
  @config_name = config_name
21
- @runners = []
22
- @subscriptions_set_attrs = {
23
- name: set_name, max_restarts_number: max_retries, time_between_restarts: retries_interval
24
- }
25
- @commands_handler = CommandsHandler.new(@config_name, self, @runners)
26
18
  @basic_runner = BasicRunner.new(0.2, 0)
27
- @force_lock = false
28
- @subscriptions_pinged_at = Time.at(0)
19
+ @subscriptions_set_lifecycle = SubscriptionsSetLifecycle.new(
20
+ @config_name,
21
+ { name: set_name, max_restarts_number: max_retries, time_between_restarts: retries_interval }
22
+ )
23
+ @subscriptions_lifecycle = SubscriptionsLifecycle.new(@config_name, @subscriptions_set_lifecycle)
24
+ @commands_handler = CommandsHandler.new(@config_name, self, @subscriptions_lifecycle.runners)
29
25
  attach_runner_callbacks
30
26
  end
31
27
 
28
+ # @return [Integer]
29
+ def id
30
+ @subscriptions_set_lifecycle.persisted_subscriptions_set.id
31
+ end
32
+
32
33
  # Adds SubscriptionRunner to the set
33
34
  # @param runner [PgEventstore::SubscriptionRunner]
34
35
  # @return [PgEventstore::SubscriptionRunner]
35
36
  def add(runner)
36
37
  assert_proper_state!
37
- @runners.push(runner)
38
+ @subscriptions_lifecycle.runners.push(runner)
38
39
  runner
39
40
  end
40
41
 
@@ -43,7 +44,7 @@ module PgEventstore
43
44
  def start_all
44
45
  return self unless @basic_runner.running?
45
46
 
46
- @runners.each(&:start)
47
+ @subscriptions_lifecycle.runners.each(&:start)
47
48
  self
48
49
  end
49
50
 
@@ -52,135 +53,85 @@ module PgEventstore
52
53
  def stop_all
53
54
  return self unless @basic_runner.running?
54
55
 
55
- @runners.each(&:stop_async)
56
+ @subscriptions_lifecycle.runners.each(&:stop_async)
56
57
  self
57
58
  end
58
59
 
59
- # Sets the force_lock flash to true. If set - all related Subscriptions will ignore their lock state and will be
60
- # locked by the new SubscriptionsSet.
61
- # @return [void]
62
- def force_lock!
63
- @force_lock = true
64
- end
65
-
66
60
  # Produces a copy of currently running Subscriptions. This is needed, because original Subscriptions objects are
67
61
  # dangerous to use - users may incidentally break their state.
68
62
  # @return [Array<PgEventstore::Subscription>]
69
63
  def read_only_subscriptions
70
- @runners.map(&:subscription).map(&:dup)
64
+ @subscriptions_lifecycle.subscriptions.map(&:dup)
71
65
  end
72
66
 
73
67
  # Produces a copy of current SubscriptionsSet. This is needed, because original SubscriptionsSet object is
74
68
  # dangerous to use - users may incidentally break its state.
75
69
  # @return [PgEventstore::SubscriptionsSet, nil]
76
70
  def read_only_subscriptions_set
77
- @subscriptions_set&.dup
71
+ @subscriptions_set_lifecycle.subscriptions_set&.dup
78
72
  end
79
73
 
80
74
  private
81
75
 
82
- # Locks all Subscriptions behind the current SubscriptionsSet
83
- # @return [void]
84
- def lock_all
85
- @runners.each { |runner| runner.lock!(subscriptions_set.id, force: @force_lock) }
86
- end
87
-
88
- # @return [PgEventstore::SubscriptionsSet]
89
- def subscriptions_set
90
- @subscriptions_set ||= SubscriptionsSet.using_connection(@config_name).create(**@subscriptions_set_attrs)
91
- end
92
-
93
- # @return [PgEventstore::SubscriptionRunnersFeeder]
94
- def feeder
95
- SubscriptionRunnersFeeder.new(@config_name)
96
- end
97
-
98
76
  # @return [void]
99
77
  def attach_runner_callbacks
100
- @basic_runner.define_callback(:change_state, :after, method(:update_subscriptions_set_state))
101
- @basic_runner.define_callback(:before_runner_started, :before, method(:before_runner_started))
102
- @basic_runner.define_callback(:after_runner_died, :before, method(:after_runner_died))
103
- @basic_runner.define_callback(:after_runner_died, :after, method(:restart_runner))
104
- @basic_runner.define_callback(:process_async, :before, method(:ping_subscriptions_set))
105
- @basic_runner.define_callback(:process_async, :before, method(:process_async))
106
- @basic_runner.define_callback(:process_async, :after, method(:ping_subscriptions))
107
- @basic_runner.define_callback(:after_runner_stopped, :before, method(:after_runner_stopped))
108
- @basic_runner.define_callback(:before_runner_restored, :after, method(:update_runner_restarts))
109
- end
110
-
111
- # @return [void]
112
- def before_runner_started
113
- lock_all
114
- @runners.each(&:start)
115
- @commands_handler.start
116
- rescue PgEventstore::SubscriptionAlreadyLockedError
117
- @subscriptions_set&.delete
118
- @subscriptions_set = nil
119
- raise
120
- end
121
-
122
- # @param error [StandardError]
123
- # @return [void]
124
- def after_runner_died(error)
125
- subscriptions_set.update(last_error: Utils.error_info(error), last_error_occurred_at: Time.now.utc)
126
- end
127
-
128
- # @param _error [StandardError]
129
- # @return [void]
130
- def restart_runner(_error)
131
- return if subscriptions_set.restart_count >= subscriptions_set.max_restarts_number
132
-
133
- Thread.new do
134
- sleep subscriptions_set.time_between_restarts
135
- restore
136
- end
137
- end
138
-
139
- # @return [void]
140
- def update_runner_restarts
141
- subscriptions_set.update(last_restarted_at: Time.now.utc, restart_count: subscriptions_set.restart_count + 1)
142
- end
143
-
144
- # @return [void]
145
- def process_async
146
- feeder.feed(@runners)
147
- end
148
-
149
- # @return [void]
150
- def ping_subscriptions_set
151
- return if subscriptions_set.updated_at > Time.now.utc - HEARTBEAT_INTERVAL
152
-
153
- subscriptions_set.update(updated_at: Time.now.utc)
154
- end
155
-
156
- # @return [void]
157
- def ping_subscriptions
158
- return if @subscriptions_pinged_at > Time.now.utc - HEARTBEAT_INTERVAL
159
-
160
- runners = @runners.select do |runner|
161
- next false unless runner.running?
162
-
163
- runner.subscription.updated_at < Time.now.utc - HEARTBEAT_INTERVAL
164
- end
165
- unless runners.empty?
166
- Subscription.using_connection(@config_name).ping_all(subscriptions_set.id, runners.map(&:subscription))
167
- end
168
-
169
- @subscriptions_pinged_at = Time.now.utc
170
- end
171
-
172
- # @return [void]
173
- def after_runner_stopped
174
- @runners.each(&:stop_async).each(&:wait_for_finish)
175
- @subscriptions_set&.delete
176
- @subscriptions_set = nil
177
- @commands_handler.stop
178
- end
179
-
180
- # @param state [String]
181
- # @return [void]
182
- def update_subscriptions_set_state(state)
183
- subscriptions_set.update(state: state)
78
+ @basic_runner.define_callback(
79
+ :change_state, :after,
80
+ SubscriptionFeederHandlers.setup_handler(:update_subscriptions_set_state, @subscriptions_set_lifecycle)
81
+ )
82
+
83
+ @basic_runner.define_callback(
84
+ :before_runner_started, :before,
85
+ SubscriptionFeederHandlers.setup_handler(:lock_subscriptions, @subscriptions_lifecycle)
86
+ )
87
+ @basic_runner.define_callback(
88
+ :before_runner_started, :before,
89
+ SubscriptionFeederHandlers.setup_handler(:start_runners, @subscriptions_lifecycle)
90
+ )
91
+ @basic_runner.define_callback(
92
+ :before_runner_started, :before,
93
+ SubscriptionFeederHandlers.setup_handler(:start_cmds_handler, @commands_handler)
94
+ )
95
+
96
+ @basic_runner.define_callback(
97
+ :after_runner_died, :before,
98
+ SubscriptionFeederHandlers.setup_handler(:persist_error_info, @subscriptions_set_lifecycle)
99
+ )
100
+ @basic_runner.define_callback(
101
+ :after_runner_died, :before,
102
+ SubscriptionFeederHandlers.setup_handler(:restart_runner, @subscriptions_set_lifecycle, @basic_runner)
103
+ )
104
+
105
+ @basic_runner.define_callback(
106
+ :process_async, :before,
107
+ SubscriptionFeederHandlers.setup_handler(:ping_subscriptions_set, @subscriptions_set_lifecycle)
108
+ )
109
+ @basic_runner.define_callback(
110
+ :process_async, :before,
111
+ SubscriptionFeederHandlers.setup_handler(:feed_runners, @subscriptions_lifecycle, @config_name)
112
+ )
113
+ @basic_runner.define_callback(
114
+ :process_async, :after,
115
+ SubscriptionFeederHandlers.setup_handler(:ping_subscriptions, @subscriptions_lifecycle)
116
+ )
117
+
118
+ @basic_runner.define_callback(
119
+ :after_runner_stopped, :before,
120
+ SubscriptionFeederHandlers.setup_handler(:stop_runners, @subscriptions_lifecycle)
121
+ )
122
+ @basic_runner.define_callback(
123
+ :after_runner_stopped, :before,
124
+ SubscriptionFeederHandlers.setup_handler(:reset_subscriptions_set, @subscriptions_set_lifecycle)
125
+ )
126
+ @basic_runner.define_callback(
127
+ :after_runner_stopped, :before,
128
+ SubscriptionFeederHandlers.setup_handler(:stop_commands_handler, @commands_handler)
129
+ )
130
+
131
+ @basic_runner.define_callback(
132
+ :before_runner_restored, :after,
133
+ SubscriptionFeederHandlers.setup_handler(:update_subscriptions_set_restarts, @subscriptions_set_lifecycle)
134
+ )
184
135
  end
185
136
 
186
137
  # This method helps to ensure that no Subscription is added after SubscriptionFeeder's runner is working
@@ -188,10 +139,11 @@ module PgEventstore
188
139
  # @raise [RuntimeError]
189
140
  def assert_proper_state!
190
141
  return if @basic_runner.initial? || @basic_runner.stopped?
142
+ subscriptions_set = @subscriptions_set_lifecycle.persisted_subscriptions_set
191
143
 
192
144
  error_message = <<~TEXT
193
- Could not add subscription - #{subscriptions_set.name}##{subscriptions_set.id} must be either in the initial \
194
- or in the stopped state, but it is in the #{@basic_runner.state} state now.
145
+ Could not add subscription - #{subscriptions_set.name}##{subscriptions_set.id} must be \
146
+ either in the initial or in the stopped state, but it is in the #{@basic_runner.state} state now.
195
147
  TEXT
196
148
  raise error_message
197
149
  end
@@ -74,66 +74,41 @@ module PgEventstore
74
74
 
75
75
  # @return [void]
76
76
  def attach_callbacks
77
- @events_processor.define_callback(:process, :around, method(:track_exec_time))
78
- @events_processor.define_callback(:process, :after, method(:update_subscription_stats))
79
- @events_processor.define_callback(:error, :after, method(:update_subscription_error))
80
- @events_processor.define_callback(:error, :after, method(:restart_subscription))
81
- @events_processor.define_callback(:feed, :after, method(:update_subscription_chunk_stats))
82
- @events_processor.define_callback(:restart, :after, method(:update_subscription_restarts))
83
- @events_processor.define_callback(:change_state, :after, method(:update_subscription_state))
84
- end
85
-
86
- # @param action [Proc]
87
- # @return [void]
88
- def track_exec_time(action, *)
89
- @stats.track_exec_time { action.call }
90
- end
91
-
92
- # @param current_position [Integer]
93
- # @return [void]
94
- def update_subscription_stats(current_position)
95
- @subscription.update(
96
- average_event_processing_time: @stats.average_event_processing_time,
97
- current_position: current_position,
98
- total_processed_events: @subscription.total_processed_events + 1
77
+ @events_processor.define_callback(
78
+ :process, :around,
79
+ SubscriptionRunnerHandlers.setup_handler(:track_exec_time, @stats)
80
+ )
81
+ @events_processor.define_callback(
82
+ :process, :after,
83
+ SubscriptionRunnerHandlers.setup_handler(:update_subscription_stats, @subscription, @stats)
99
84
  )
100
- end
101
-
102
- # @param state [String]
103
- # @return [void]
104
- def update_subscription_state(state)
105
- @subscription.update(state: state)
106
- end
107
85
 
108
- # @return [void]
109
- def update_subscription_restarts
110
- @subscription.update(last_restarted_at: Time.now.utc, restart_count: @subscription.restart_count + 1)
111
- end
86
+ @events_processor.define_callback(
87
+ :error, :after,
88
+ SubscriptionRunnerHandlers.setup_handler(:update_subscription_error, @subscription)
89
+ )
90
+ @events_processor.define_callback(
91
+ :error, :after,
92
+ SubscriptionRunnerHandlers.setup_handler(
93
+ :restart_events_processor,
94
+ @subscription, @restart_terminator, @failed_subscription_notifier, @events_processor
95
+ )
96
+ )
112
97
 
113
- # @param error [StandardError]
114
- # @return [void]
115
- def update_subscription_error(error)
116
- @subscription.update(last_error: Utils.error_info(error), last_error_occurred_at: Time.now.utc)
117
- end
98
+ @events_processor.define_callback(
99
+ :feed, :after,
100
+ SubscriptionRunnerHandlers.setup_handler(:update_subscription_chunk_stats, @subscription)
101
+ )
118
102
 
119
- # @param global_position [Integer]
120
- # @return [void]
121
- def update_subscription_chunk_stats(global_position)
122
- @subscription.update(last_chunk_fed_at: Time.now.utc, last_chunk_greatest_position: global_position)
123
- end
103
+ @events_processor.define_callback(
104
+ :restart, :after,
105
+ SubscriptionRunnerHandlers.setup_handler(:update_subscription_restarts, @subscription)
106
+ )
124
107
 
125
- # @param error [StandardError]
126
- # @return [void]
127
- def restart_subscription(error)
128
- return if @restart_terminator&.call(@subscription.dup)
129
- if @subscription.restart_count >= @subscription.max_restarts_number
130
- return @failed_subscription_notifier&.call(@subscription.dup, error)
131
- end
132
-
133
- Thread.new do
134
- sleep @subscription.time_between_restarts
135
- restore
136
- end
108
+ @events_processor.define_callback(
109
+ :change_state, :after,
110
+ SubscriptionRunnerHandlers.setup_handler(:update_subscription_state, @subscription)
111
+ )
137
112
  end
138
113
  end
139
114
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ class SubscriptionsLifecycle
5
+ extend Forwardable
6
+
7
+ # @return [Integer] number of seconds between heartbeat updates
8
+ HEARTBEAT_INTERVAL = 10 # seconds
9
+
10
+ # @!attribute runners
11
+ # @return [Array<PgEventstore::SubscriptionRunner>]
12
+ attr_reader :runners
13
+
14
+ # @param config_name [Symbol]
15
+ # @param subscriptions_set_lifecycle [PgEventstore::SubscriptionsSetLifecycle]
16
+ def initialize(config_name, subscriptions_set_lifecycle)
17
+ @config_name = config_name
18
+ @subscriptions_set_lifecycle = subscriptions_set_lifecycle
19
+ @runners = []
20
+ @subscriptions_pinged_at = Time.at(0)
21
+ @force_lock = false
22
+ end
23
+
24
+ # Locks all Subscriptions behind the current SubscriptionsSet
25
+ # @return [void]
26
+ def lock_all
27
+ @runners.each do |runner|
28
+ runner.lock!(@subscriptions_set_lifecycle.persisted_subscriptions_set.id, force: force_locked?)
29
+ end
30
+ rescue PgEventstore::SubscriptionAlreadyLockedError
31
+ @subscriptions_set_lifecycle.reset_subscriptions_set
32
+ raise
33
+ end
34
+
35
+ # @return [void]
36
+ def ping_subscriptions
37
+ return if @subscriptions_pinged_at > Time.now.utc - HEARTBEAT_INTERVAL
38
+
39
+ runners = @runners.select do |runner|
40
+ next false unless runner.running?
41
+
42
+ runner.subscription.updated_at < Time.now.utc - HEARTBEAT_INTERVAL
43
+ end
44
+ unless runners.empty?
45
+ Subscription.using_connection(@config_name).ping_all(
46
+ @subscriptions_set_lifecycle.persisted_subscriptions_set.id, runners.map(&:subscription)
47
+ )
48
+ end
49
+
50
+ @subscriptions_pinged_at = Time.now.utc
51
+ end
52
+
53
+ # @return [Array<PgEventstore::Subscription>]
54
+ def subscriptions
55
+ @runners.map(&:subscription)
56
+ end
57
+
58
+ # Sets the force_lock flag to true. If set - all related Subscriptions will ignore their lock state and will be
59
+ # locked by the new SubscriptionsSet.
60
+ # @return [void]
61
+ def force_lock!
62
+ @force_lock = true
63
+ end
64
+
65
+ # @return [Boolean]
66
+ def force_locked?
67
+ @force_lock
68
+ end
69
+ end
70
+ end
@@ -9,6 +9,12 @@ require_relative 'subscription_handler_performance'
9
9
  require_relative 'subscription_runner'
10
10
  require_relative 'subscriptions_set'
11
11
  require_relative 'subscription_runners_feeder'
12
+ require_relative 'subscriptions_set_lifecycle'
13
+ require_relative 'subscriptions_lifecycle'
14
+ require_relative 'callback_handlers/subscription_feeder_handlers'
15
+ require_relative 'callback_handlers/subscription_runner_handlers'
16
+ require_relative 'callback_handlers/events_processor_handlers'
17
+ require_relative 'callback_handlers/commands_handler_handlers'
12
18
  require_relative 'subscription_feeder'
13
19
  require_relative 'extensions/command_class_lookup_extension'
14
20
  require_relative 'extensions/base_command_extension'
@@ -67,20 +73,25 @@ module PgEventstore
67
73
  # PgEventstore::Subscription instance and error instance after the related subscription died due to error and no
68
74
  # longer can be automatically restarted due to max retries number reached. You can use this hook to send a
69
75
  # notification about failed subscription.
76
+ # @param graceful_shutdown_timeout [integer, Float] the number of seconds to wait until force-shutdown the
77
+ # subscription during the stop process
70
78
  # @return [void]
71
79
  def subscribe(subscription_name, handler:, options: {}, middlewares: nil,
72
80
  pull_interval: config.subscription_pull_interval,
73
81
  max_retries: config.subscription_max_retries,
74
82
  retries_interval: config.subscription_retries_interval,
75
83
  restart_terminator: config.subscription_restart_terminator,
76
- failed_subscription_notifier: config.failed_subscription_notifier)
84
+ failed_subscription_notifier: config.failed_subscription_notifier,
85
+ graceful_shutdown_timeout: config.subscription_graceful_shutdown_timeout)
77
86
  subscription = Subscription.using_connection(config.name).new(
78
87
  set: @set_name, name: subscription_name, options: options, chunk_query_interval: pull_interval,
79
88
  max_restarts_number: max_retries, time_between_restarts: retries_interval
80
89
  )
81
90
  runner = SubscriptionRunner.new(
82
91
  stats: SubscriptionHandlerPerformance.new,
83
- events_processor: EventsProcessor.new(create_event_handler(middlewares, handler)),
92
+ events_processor: EventsProcessor.new(
93
+ create_raw_event_handler(middlewares, handler), graceful_shutdown_timeout: graceful_shutdown_timeout
94
+ ),
84
95
  subscription: subscription,
85
96
  restart_terminator: restart_terminator,
86
97
  failed_subscription_notifier: failed_subscription_notifier
@@ -113,7 +124,7 @@ module PgEventstore
113
124
  # @param middlewares [Array<Symbol>, nil]
114
125
  # @param handler [#call]
115
126
  # @return [Proc]
116
- def create_event_handler(middlewares, handler)
127
+ def create_raw_event_handler(middlewares, handler)
117
128
  deserializer = EventDeserializer.new(select_middlewares(middlewares), config.event_class_resolver)
118
129
  ->(raw_event) { handler.call(deserializer.deserialize(raw_event)) }
119
130
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ class SubscriptionsSetLifecycle
5
+ # @return [Integer] number of seconds between heartbeat updates
6
+ HEARTBEAT_INTERVAL = 10 # seconds
7
+
8
+ # @!attribute subscriptions_set
9
+ # @return [PgEventstore::SubscriptionsSet, nil]
10
+ attr_reader :subscriptions_set
11
+
12
+ # @param config_name [Symbol]
13
+ # @param subscriptions_set_attrs [Hash]
14
+ def initialize(config_name, subscriptions_set_attrs)
15
+ @config_name = config_name
16
+ @subscriptions_set_attrs = subscriptions_set_attrs
17
+ @subscriptions_set_pinged_at = Time.at(0)
18
+ end
19
+
20
+ # @return [void]
21
+ def ping_subscriptions_set
22
+ return if @subscriptions_set_pinged_at > Time.now.utc - HEARTBEAT_INTERVAL
23
+
24
+ persisted_subscriptions_set.update(updated_at: Time.now.utc)
25
+ @subscriptions_set_pinged_at = Time.now.utc
26
+ end
27
+
28
+ # @return [PgEventstore::SubscriptionsSet]
29
+ def persisted_subscriptions_set
30
+ @subscriptions_set ||= SubscriptionsSet.using_connection(@config_name).create(**@subscriptions_set_attrs)
31
+ end
32
+
33
+ # @return [void]
34
+ def reset_subscriptions_set
35
+ @subscriptions_set&.delete
36
+ @subscriptions_set = nil
37
+ end
38
+ end
39
+ end
@@ -61,6 +61,14 @@ module PgEventstore
61
61
  str.gsub!(/[A-Z]/) { |letter| '_' + letter.downcase }
62
62
  str
63
63
  end
64
+
65
+ # Detect the global position of the event record in the database. If it is a link event - we pick a
66
+ # global_position of the link instead of picking a global_position of an event this link points to.
67
+ # @param raw_event [Hash]
68
+ # @return [Integer]
69
+ def original_global_position(raw_event)
70
+ raw_event['link'] ? raw_event['link']['global_position'] : raw_event['global_position']
71
+ end
64
72
  end
65
73
  end
66
74
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module PgEventstore
4
4
  # @return [String]
5
- VERSION = "1.3.4"
5
+ VERSION = "1.5.0"
6
6
  end
@@ -69,6 +69,11 @@ module PgEventstore
69
69
  def asset_url(path)
70
70
  url("#{path}?v=#{PgEventstore::VERSION}")
71
71
  end
72
+
73
+ # @return [Boolean]
74
+ def resolve_link_tos?
75
+ params.key?(:resolve_link_tos) ? params[:resolve_link_tos] == 'true' : true
76
+ end
72
77
  end
73
78
 
74
79
  get '/' do
@@ -77,7 +82,10 @@ module PgEventstore
77
82
  starting_id: params[:starting_id]&.to_i,
78
83
  per_page: Paginator::EventsCollection::PER_PAGE[params[:per_page]],
79
84
  order: Paginator::EventsCollection::SQL_DIRECTIONS[params[:order]],
80
- options: { filter: { event_types: events_filter, streams: streams_filter } }
85
+ options: {
86
+ filter: { event_types: events_filter, streams: streams_filter },
87
+ resolve_link_tos: resolve_link_tos?
88
+ }
81
89
  )
82
90
 
83
91
  if request.xhr?
@@ -23,9 +23,7 @@ module PgEventstore
23
23
  def collection
24
24
  @_collection ||= PgEventstore.client(config_name).read(
25
25
  PgEventstore::Stream.all_stream,
26
- options: options.merge(
27
- from_position: starting_id, max_count: per_page, direction: order, resolve_link_tos: true
28
- ),
26
+ options: options.merge(from_position: starting_id, max_count: per_page, direction: order),
29
27
  middlewares: []
30
28
  )
31
29
  end
@@ -50,6 +50,10 @@ module PgEventstore
50
50
  build_path(params.merge(order: order))
51
51
  end
52
52
 
53
+ def resolve_link_tos_url(should_resolve)
54
+ build_path(params.merge(resolve_link_tos: should_resolve))
55
+ end
56
+
53
57
  # @param number [Integer] total number of events by the current filter
54
58
  # @return [String]
55
59
  def total_count(number)
@@ -92,7 +96,6 @@ module PgEventstore
92
96
  }
93
97
  }
94
98
  )
95
-
96
99
  end
97
100
 
98
101
  private
@@ -113,12 +113,12 @@ $(function(){
113
113
  initEventTypeFilterAutocomplete($(this));
114
114
  });
115
115
 
116
+ // Automatically refresh events list every two seconds
116
117
  let autoRefreshInterval;
117
118
  $('#auto-refresh').change(function(){
118
119
  if($(this).is(':checked')) {
119
120
  autoRefreshInterval = setInterval(function(){
120
121
  $.getJSON(window.location.href).success(function(response, textStatus, xhr){
121
- console.log(textStatus);
122
122
  if(textStatus === 'success') {
123
123
  $('#events-table').find('tbody').html(response.events);
124
124
  $('#total-count').html(response.total_count);
@@ -131,6 +131,15 @@ $(function(){
131
131
  }
132
132
  });
133
133
 
134
+ // Resolve links checkbox
135
+ $('#resolve-link-tos').change(function(){
136
+ if($(this).is(':checked')) {
137
+ window.location.href = $(this).data('url-checked');
138
+ } else {
139
+ window.location.href = $(this).data('url-unchecked');
140
+ }
141
+ });
142
+
134
143
  // Toggle event's JSON data/metadata details
135
144
  $('#events-table').on('click', '.toggle-event-data', function(){
136
145
  $(this).parents('tr').next().toggleClass('d-none');
@@ -78,13 +78,12 @@ module PgEventstore
78
78
  # @param state [String]
79
79
  # @param updated_at [Time]
80
80
  # @return [String] html status
81
- def colored_state(state, updated_at)
81
+ def colored_state(state, interval, updated_at)
82
82
  if state == RunnerState::STATES[:running]
83
83
  # -1 is added as a margin to prevent false-positive result
84
- if updated_at < Time.now.utc - SubscriptionFeeder::HEARTBEAT_INTERVAL - 1
84
+ if updated_at < Time.now.utc - interval - 1
85
85
  title = <<~TEXT
86
- Something is wrong. Last update was more than #{SubscriptionFeeder::HEARTBEAT_INTERVAL} seconds \
87
- ago(#{updated_at}).
86
+ Something is wrong. Last update was more than #{interval} seconds ago(#{updated_at}).
88
87
  TEXT
89
88
  <<~HTML
90
89
  <span class="text-warning text-nowrap">