pg_eventstore 0.3.0 → 0.5.0
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 +4 -4
- data/CHANGELOG.md +14 -0
- data/CODE_OF_CONDUCT.md +1 -1
- data/README.md +2 -0
- data/db/migrations/10_create_subscription_commands.sql +15 -0
- data/db/migrations/11_create_subscriptions_set_commands.sql +15 -0
- data/db/migrations/12_improve_events_indexes.sql +1 -0
- data/db/migrations/13_remove_duplicated_index.sql +1 -0
- data/db/migrations/9_create_subscriptions.sql +46 -0
- data/docs/configuration.md +42 -21
- data/docs/linking_events.md +96 -0
- data/docs/reading_events.md +56 -0
- data/docs/subscriptions.md +170 -0
- data/lib/pg_eventstore/callbacks.rb +122 -0
- data/lib/pg_eventstore/client.rb +32 -2
- data/lib/pg_eventstore/commands/append.rb +3 -11
- data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +22 -0
- data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +24 -0
- data/lib/pg_eventstore/commands/link_to.rb +33 -0
- data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +63 -0
- data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +62 -0
- data/lib/pg_eventstore/commands.rb +5 -0
- data/lib/pg_eventstore/config.rb +35 -3
- data/lib/pg_eventstore/errors.rb +80 -0
- data/lib/pg_eventstore/{pg_result_deserializer.rb → event_deserializer.rb} +10 -22
- data/lib/pg_eventstore/extensions/callbacks_extension.rb +95 -0
- data/lib/pg_eventstore/extensions/options_extension.rb +69 -29
- data/lib/pg_eventstore/extensions/using_connection_extension.rb +35 -0
- data/lib/pg_eventstore/pg_connection.rb +20 -3
- data/lib/pg_eventstore/queries/event_queries.rb +18 -34
- data/lib/pg_eventstore/queries/event_type_queries.rb +24 -0
- data/lib/pg_eventstore/queries/preloader.rb +37 -0
- data/lib/pg_eventstore/queries/stream_queries.rb +14 -1
- data/lib/pg_eventstore/queries/subscription_command_queries.rb +81 -0
- data/lib/pg_eventstore/queries/subscription_queries.rb +166 -0
- data/lib/pg_eventstore/queries/subscriptions_set_command_queries.rb +76 -0
- data/lib/pg_eventstore/queries/subscriptions_set_queries.rb +89 -0
- data/lib/pg_eventstore/queries.rb +7 -0
- data/lib/pg_eventstore/query_builders/events_filtering_query.rb +17 -22
- data/lib/pg_eventstore/sql_builder.rb +54 -10
- data/lib/pg_eventstore/stream.rb +2 -1
- data/lib/pg_eventstore/subscriptions/basic_runner.rb +220 -0
- data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +52 -0
- data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +68 -0
- data/lib/pg_eventstore/subscriptions/commands_handler.rb +62 -0
- data/lib/pg_eventstore/subscriptions/events_processor.rb +72 -0
- data/lib/pg_eventstore/subscriptions/runner_state.rb +45 -0
- data/lib/pg_eventstore/subscriptions/subscription.rb +141 -0
- data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +171 -0
- data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +39 -0
- data/lib/pg_eventstore/subscriptions/subscription_runner.rb +125 -0
- data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +38 -0
- data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +105 -0
- data/lib/pg_eventstore/subscriptions/subscriptions_set.rb +97 -0
- data/lib/pg_eventstore/tasks/setup.rake +5 -1
- data/lib/pg_eventstore/utils.rb +66 -0
- data/lib/pg_eventstore/version.rb +1 -1
- data/lib/pg_eventstore.rb +19 -1
- metadata +38 -4
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
# Allows you to define before, around and after callbacks withing the certain action. It is especially useful during
|
5
|
+
# asynchronous programming when you need to be able to react on asynchronous actions from the outside.
|
6
|
+
# Example:
|
7
|
+
# class MyAwesomeClass
|
8
|
+
# attr_reader :callbacks
|
9
|
+
#
|
10
|
+
# def initialize
|
11
|
+
# @callbacks = PgEventstore::Callbacks.new
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# def do_something
|
15
|
+
# Thread.new do
|
16
|
+
# @callbacks.run_callbacks(:something_happened) do
|
17
|
+
# puts "I did something useful!"
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# def do_something_else
|
23
|
+
# @callbacks.run_callbacks(:something_else_happened, :foo, bar: :baz) do
|
24
|
+
# puts "Something else happened!"
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# obj = MyAwesomeClass.new
|
30
|
+
# obj.callbacks.define_callback(:something_happened, :before, proc { puts "In before callback" })
|
31
|
+
# obj.callbacks.define_callback(:something_happened, :after, proc { puts "In after callback" })
|
32
|
+
# obj.callbacks.define_callback(
|
33
|
+
# :something_happened, :around,
|
34
|
+
# proc { |action| puts "In around before action"; action.call; puts "In around after action" }
|
35
|
+
# )
|
36
|
+
# obj.do_something
|
37
|
+
# Outputs:
|
38
|
+
# In before callback
|
39
|
+
# In around callback before action
|
40
|
+
# I did something useful!
|
41
|
+
# In around callback after action
|
42
|
+
# In after callback
|
43
|
+
# Please note, that it is important to call *action.call* in around callback. Otherwise action simply won't be called.
|
44
|
+
#
|
45
|
+
# Optionally you can provide any set of arguments to {#run_callbacks} method. They will be passed to your callbacks
|
46
|
+
# functions then. Example:
|
47
|
+
#
|
48
|
+
# obj = MyAwesomeClass.new
|
49
|
+
# obj.callbacks.define_callback(
|
50
|
+
# :something_else_happened, :before,
|
51
|
+
# proc { |*args, **kwargs| puts "In before callback. Args: #{args}, kwargs: #{kwargs}." }
|
52
|
+
# )
|
53
|
+
# obj.callbacks.define_callback(
|
54
|
+
# :something_else_happened, :after,
|
55
|
+
# proc { |*args, **kwargs| puts "In after callback. Args: #{args}, kwargs: #{kwargs}." }
|
56
|
+
# )
|
57
|
+
# obj.callbacks.define_callback(
|
58
|
+
# :something_else_happened, :around,
|
59
|
+
# proc { |action, *args, **kwargs|
|
60
|
+
# puts "In around before action. Args: #{args}, kwargs: #{kwargs}."
|
61
|
+
# action.call
|
62
|
+
# puts "In around after action. Args: #{args}, kwargs: #{kwargs}."
|
63
|
+
# }
|
64
|
+
# )
|
65
|
+
# obj.do_something_else
|
66
|
+
# Outputs:
|
67
|
+
# In before callback. Args: [:foo], kwargs: {:bar=>:baz}.
|
68
|
+
# In around before action. Args: [:foo], kwargs: {:bar=>:baz}.
|
69
|
+
# I did something useful!
|
70
|
+
# In around after action. Args: [:foo], kwargs: {:bar=>:baz}.
|
71
|
+
# In after callback. Args: [:foo], kwargs: {:bar=>:baz}.
|
72
|
+
class Callbacks
|
73
|
+
def initialize
|
74
|
+
@callbacks = {}
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param action [Object] an object, that represents your action. In most cases you want to use a symbol there
|
78
|
+
# @param filter [Symbol] callback filter. Supported values are :before, :after and :around
|
79
|
+
# @param callback [#call]
|
80
|
+
# @return [void]
|
81
|
+
def define_callback(action, filter, callback)
|
82
|
+
@callbacks[action] ||= {}
|
83
|
+
@callbacks[action][filter] ||= []
|
84
|
+
@callbacks[action][filter].push(callback)
|
85
|
+
end
|
86
|
+
|
87
|
+
# @param action [Object] an action to run
|
88
|
+
# @return [Object] the result of passed block
|
89
|
+
def run_callbacks(action, *args, **kwargs, &blk)
|
90
|
+
return (yield if block_given?) unless @callbacks[action]
|
91
|
+
|
92
|
+
run_before_callbacks(action, *args, **kwargs)
|
93
|
+
result = run_around_callbacks(action, *args, **kwargs, &blk)
|
94
|
+
run_after_callbacks(action, *args, **kwargs)
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def run_before_callbacks(action, *args, **kwargs)
|
101
|
+
@callbacks[action][:before]&.each do |callback|
|
102
|
+
callback.call(*args, **kwargs)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def run_around_callbacks(action, *args, **kwargs, &blk)
|
107
|
+
result = nil
|
108
|
+
stack = [proc { result = yield if block_given? }]
|
109
|
+
@callbacks[action][:around]&.reverse_each&.with_index do |callback, index|
|
110
|
+
stack.push(proc { callback.call(stack[index], *args, **kwargs); result })
|
111
|
+
end
|
112
|
+
stack.last.call(*args, **kwargs)
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
def run_after_callbacks(action, *args, **kwargs)
|
117
|
+
@callbacks[action][:after]&.each do |callback|
|
118
|
+
callback.call(*args, **kwargs)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/pg_eventstore/client.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'commands'
|
4
4
|
require_relative 'event_serializer'
|
5
|
-
require_relative '
|
5
|
+
require_relative 'event_deserializer'
|
6
6
|
require_relative 'queries'
|
7
7
|
|
8
8
|
module PgEventstore
|
@@ -109,6 +109,36 @@ module PgEventstore
|
|
109
109
|
call(stream, options: { max_count: config.max_count }.merge(options))
|
110
110
|
end
|
111
111
|
|
112
|
+
# @see {#read} for available params
|
113
|
+
# @return [Enumerator] enumerator will yield PgEventstore::Event
|
114
|
+
def read_paginated(stream, options: {}, middlewares: nil)
|
115
|
+
cmd_class = stream.system? ? Commands::SystemStreamReadPaginated : Commands::RegularStreamReadPaginated
|
116
|
+
cmd_class.
|
117
|
+
new(Queries.new(streams: stream_queries, events: event_queries(middlewares(middlewares)))).
|
118
|
+
call(stream, options: { max_count: config.max_count }.merge(options))
|
119
|
+
end
|
120
|
+
|
121
|
+
# Links event from one stream into another stream. You can later access it by providing :resolve_link_tos option
|
122
|
+
# when reading from a stream. Only existing events can be linked.
|
123
|
+
# @param stream [PgEventstore::Stream]
|
124
|
+
# @param events_or_event [PgEventstore::Event, Array<PgEventstore::Event>]
|
125
|
+
# @param options [Hash]
|
126
|
+
# @option options [Integer] :expected_revision provide your own revision number
|
127
|
+
# @option options [Symbol] :expected_revision provide one of next values: :any, :no_stream or :stream_exists
|
128
|
+
# @param middlewares [Array] provide a list of middleware names to use. Defaults to empty array, meaning no
|
129
|
+
# middlewares will be applied to the "link" event
|
130
|
+
# @return [PgEventstore::Event, Array<PgEventstore::Event>] persisted event(s)
|
131
|
+
# @raise [PgEventstore::WrongExpectedRevisionError]
|
132
|
+
def link_to(stream, events_or_event, options: {}, middlewares: [])
|
133
|
+
result =
|
134
|
+
Commands::LinkTo.new(
|
135
|
+
Queries.new(
|
136
|
+
streams: stream_queries, events: event_queries(middlewares(middlewares)), transactions: transaction_queries
|
137
|
+
)
|
138
|
+
).call(stream, *events_or_event, options: options)
|
139
|
+
events_or_event.is_a?(Array) ? result : result.first
|
140
|
+
end
|
141
|
+
|
112
142
|
private
|
113
143
|
|
114
144
|
# @param middlewares [Array, nil]
|
@@ -140,7 +170,7 @@ module PgEventstore
|
|
140
170
|
EventQueries.new(
|
141
171
|
connection,
|
142
172
|
EventSerializer.new(middlewares),
|
143
|
-
|
173
|
+
EventDeserializer.new(middlewares, config.event_class_resolver)
|
144
174
|
)
|
145
175
|
end
|
146
176
|
end
|
@@ -9,9 +9,10 @@ module PgEventstore
|
|
9
9
|
# @param options [Hash]
|
10
10
|
# @option options [Integer] :expected_revision provide your own revision number
|
11
11
|
# @option options [Symbol] :expected_revision provide one of next values: :any, :no_stream or :stream_exists
|
12
|
+
# @param event_modifier [#call]
|
12
13
|
# @return [Array<PgEventstore::Event>] persisted events
|
13
14
|
# @raise [PgEventstore::WrongExpectedRevisionError]
|
14
|
-
def call(stream, *events, options: {})
|
15
|
+
def call(stream, *events, options: {}, event_modifier: EventModifiers::PrepareRegularEvent)
|
15
16
|
raise SystemStreamError, stream if stream.system?
|
16
17
|
|
17
18
|
queries.transactions.transaction do
|
@@ -19,7 +20,7 @@ module PgEventstore
|
|
19
20
|
revision = stream.stream_revision
|
20
21
|
assert_expected_revision!(revision, options[:expected_revision]) if options[:expected_revision]
|
21
22
|
events.map.with_index(1) do |event, index|
|
22
|
-
queries.events.insert(stream,
|
23
|
+
queries.events.insert(stream, event_modifier.call(event, revision + index))
|
23
24
|
end.tap do
|
24
25
|
queries.streams.update_stream_revision(stream, revision + events.size)
|
25
26
|
end
|
@@ -28,15 +29,6 @@ module PgEventstore
|
|
28
29
|
|
29
30
|
private
|
30
31
|
|
31
|
-
# @param event [PgEventstore::Event]
|
32
|
-
# @param revision [Integer]
|
33
|
-
# @return [PgEventstore::Event]
|
34
|
-
def prepared_event(event, revision)
|
35
|
-
event.class.new(
|
36
|
-
id: event.id, data: event.data, metadata: event.metadata, type: event.type, stream_revision: revision
|
37
|
-
)
|
38
|
-
end
|
39
|
-
|
40
32
|
# @param revision [Integer]
|
41
33
|
# @param expected_revision [Symbol, Integer]
|
42
34
|
# @raise [PgEventstore::WrongExpectedRevisionError] in case if revision does not satisfy expected revision
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
module EventModifiers
|
6
|
+
# Defines how to transform regular event into a link event
|
7
|
+
# @!visibility private
|
8
|
+
class PrepareLinkEvent
|
9
|
+
class << self
|
10
|
+
# @param event [PgEventstore::Event]
|
11
|
+
# @param revision [Integer]
|
12
|
+
# @return [PgEventstore::Event]
|
13
|
+
def call(event, revision)
|
14
|
+
Event.new(link_id: event.id, type: Event::LINK_TYPE, stream_revision: revision).tap do |e|
|
15
|
+
%i[link_id type stream_revision].each { |attr| e.readonly!(attr) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
module EventModifiers
|
6
|
+
# Defines how to transform regular event before appending it to the stream
|
7
|
+
# @!visibility private
|
8
|
+
class PrepareRegularEvent
|
9
|
+
class << self
|
10
|
+
# @param event [PgEventstore::Event]
|
11
|
+
# @param revision [Integer]
|
12
|
+
# @return [PgEventstore::Event]
|
13
|
+
def call(event, revision)
|
14
|
+
event.class.new(
|
15
|
+
id: event.id, data: event.data, metadata: event.metadata, type: event.type, stream_revision: revision
|
16
|
+
).tap do |e|
|
17
|
+
%i[link_id stream_revision].each { |attr| e.readonly!(attr) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
# @!visibility private
|
6
|
+
class LinkTo < AbstractCommand
|
7
|
+
# @param stream [PgEventstore::Stream]
|
8
|
+
# @param events [Array<PgEventstore::Event>]
|
9
|
+
# @param options [Hash]
|
10
|
+
# @option options [Integer] :expected_revision provide your own revision number
|
11
|
+
# @option options [Symbol] :expected_revision provide one of next values: :any, :no_stream or :stream_exists
|
12
|
+
# @return [Array<PgEventstore::Event>] persisted events
|
13
|
+
# @raise [PgEventstore::WrongExpectedRevisionError]
|
14
|
+
# @raise [PgEventstore::NotPersistedEventError]
|
15
|
+
def call(stream, *events, options: {})
|
16
|
+
events.each(&method(:check_id_presence))
|
17
|
+
append_cmd = Append.new(queries)
|
18
|
+
append_cmd.call(stream, *events, options: options, event_modifier: EventModifiers::PrepareLinkEvent)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Checks if Event#id is present. An event must have the #id value in order to be linked.
|
24
|
+
# @param event [PgEventstore::Event]
|
25
|
+
# @return [void]
|
26
|
+
def check_id_presence(event)
|
27
|
+
return unless event.id.nil?
|
28
|
+
|
29
|
+
raise NotPersistedEventError, event
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
# @!visibility private
|
6
|
+
class RegularStreamReadPaginated < AbstractCommand
|
7
|
+
# @see PgEventstore::Commands::Read for docs
|
8
|
+
def call(stream, options: {})
|
9
|
+
revision = calc_initial_revision(stream, options)
|
10
|
+
Enumerator.new do |yielder|
|
11
|
+
loop do
|
12
|
+
events = read_cmd.call(stream, options: options.merge(from_revision: revision))
|
13
|
+
yielder << events if events.any?
|
14
|
+
raise StopIteration if end_reached?(events, options[:max_count])
|
15
|
+
|
16
|
+
revision = calc_next_revision(events, revision, options[:direction])
|
17
|
+
raise StopIteration if revision.negative?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @param stream [PgEventstore::Stream]
|
25
|
+
# @param options [Hash]
|
26
|
+
# @return [Integer]
|
27
|
+
def calc_initial_revision(stream, options)
|
28
|
+
return options[:from_revision] if options[:from_revision]
|
29
|
+
return 0 if forwards?(options[:direction])
|
30
|
+
|
31
|
+
read_cmd.call(stream, options: options.merge(max_count: 1)).first.stream_revision
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param events [Array<PgEventstore::Event>]
|
35
|
+
# @param max_count [Integer]
|
36
|
+
# @return [Boolean]
|
37
|
+
def end_reached?(events, max_count)
|
38
|
+
events.size < max_count
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param events [Array<PgEventstore::Event>]
|
42
|
+
# @param revision [Integer]
|
43
|
+
# @param direction [String, Symbol, nil]
|
44
|
+
# @return [Integer]
|
45
|
+
def calc_next_revision(events, revision, direction)
|
46
|
+
return revision + events.size if forwards?(direction)
|
47
|
+
|
48
|
+
revision - events.size
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param direction [String, Symbol, nil]
|
52
|
+
# @return [Boolean]
|
53
|
+
def forwards?(direction)
|
54
|
+
QueryBuilders::EventsFiltering::SQL_DIRECTIONS[direction] == QueryBuilders::EventsFiltering::SQL_DIRECTIONS[:asc]
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [PgEventstore::Commands::Read]
|
58
|
+
def read_cmd
|
59
|
+
@read_cmd ||= Read.new(queries)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
# @!visibility private
|
6
|
+
class SystemStreamReadPaginated < AbstractCommand
|
7
|
+
# @see PgEventstore::Commands::Read for docs
|
8
|
+
def call(stream, options: {})
|
9
|
+
position = calc_initial_position(stream, options)
|
10
|
+
Enumerator.new do |yielder|
|
11
|
+
loop do
|
12
|
+
events = read_cmd.call(stream, options: options.merge(from_position: position))
|
13
|
+
yielder << events if events.any?
|
14
|
+
raise StopIteration if end_reached?(events, options[:max_count])
|
15
|
+
|
16
|
+
position = calc_next_position(events, options[:direction])
|
17
|
+
raise StopIteration if position <= 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @param stream [PgEventstore::Stream]
|
25
|
+
# @param options [Hash]
|
26
|
+
# @return [Integer]
|
27
|
+
def calc_initial_position(stream, options)
|
28
|
+
return options[:from_position] if options[:from_position]
|
29
|
+
return 1 if forwards?(options[:direction])
|
30
|
+
|
31
|
+
read_cmd.call(stream, options: options.merge(max_count: 1)).first.global_position
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param events [Array<PgEventstore::Event>]
|
35
|
+
# @param max_count [Integer]
|
36
|
+
# @return [Boolean]
|
37
|
+
def end_reached?(events, max_count)
|
38
|
+
events.size < max_count
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param events [Array<PgEventstore::Event>]
|
42
|
+
# @param direction [String, Symbol, nil]
|
43
|
+
# @return [Integer]
|
44
|
+
def calc_next_position(events, direction)
|
45
|
+
return events.last.global_position + 1 if forwards?(direction)
|
46
|
+
|
47
|
+
events.last.global_position - 1
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param direction [String, Symbol, nil]
|
51
|
+
# @return [Boolean]
|
52
|
+
def forwards?(direction)
|
53
|
+
QueryBuilders::EventsFiltering::SQL_DIRECTIONS[direction] == QueryBuilders::EventsFiltering::SQL_DIRECTIONS[:asc]
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [PgEventstore::Commands::Read]
|
57
|
+
def read_cmd
|
58
|
+
@read_cmd ||= Read.new(queries)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'abstract_command'
|
4
|
+
require_relative 'commands/event_modifiers/prepare_link_event'
|
5
|
+
require_relative 'commands/event_modifiers/prepare_regular_event'
|
4
6
|
require_relative 'commands/append'
|
5
7
|
require_relative 'commands/read'
|
8
|
+
require_relative 'commands/regular_stream_read_paginated'
|
9
|
+
require_relative 'commands/system_stream_read_paginated'
|
6
10
|
require_relative 'commands/multiple'
|
11
|
+
require_relative 'commands/link_to'
|
data/lib/pg_eventstore/config.rb
CHANGED
@@ -6,14 +6,46 @@ module PgEventstore
|
|
6
6
|
|
7
7
|
attr_reader :name
|
8
8
|
|
9
|
-
#
|
9
|
+
# @!attribute pg_uri
|
10
|
+
# @return [String] PostgreSQL connection URI docs
|
11
|
+
# https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS
|
10
12
|
option(:pg_uri) { 'postgresql://postgres:postgres@localhost:5432/eventstore' }
|
13
|
+
# @!attribute max_count
|
14
|
+
# @return [Integer] Number of events to return in one response when reading from a stream
|
11
15
|
option(:max_count) { 1000 }
|
16
|
+
# @!attribute middlewares
|
17
|
+
# @return [Hash{Symbol => <#serialize, #deserialize>}] A set of identified(by key) objects that respond to
|
18
|
+
# #serialize and #deserialize
|
12
19
|
option(:middlewares) { {} }
|
13
|
-
#
|
20
|
+
# @!attribute event_class_resolver
|
21
|
+
# @return [#call] A callable object that must accept a string and return a class. It is used when resolving
|
22
|
+
# event's class during event's deserialization process
|
14
23
|
option(:event_class_resolver) { EventClassResolver.new }
|
24
|
+
# @!attribute connection_pool_size
|
25
|
+
# @return [Integer] Max number of connections per ruby process
|
15
26
|
option(:connection_pool_size) { 5 }
|
16
|
-
|
27
|
+
# @!attribute connection_pool_timeout
|
28
|
+
# @return [Integer] Time in seconds to wait for the connection in pool to be released
|
29
|
+
option(:connection_pool_timeout) { 5 }
|
30
|
+
# @!attribute subscription_pull_interval
|
31
|
+
# @return [Integer] How often Subscription should pull new events
|
32
|
+
option(:subscription_pull_interval) { 2 }
|
33
|
+
# @!attribute subscription_max_retries
|
34
|
+
# @return [Integer] max number of retries of failed Subscription
|
35
|
+
option(:subscription_max_retries) { 100 }
|
36
|
+
# @!attribute subscription_retries_interval
|
37
|
+
# @return [Integer] interval in seconds between retries of failed Subscription
|
38
|
+
option(:subscription_retries_interval) { 1 }
|
39
|
+
# @!attribute subscription_restart_terminator
|
40
|
+
# @return [#call, nil] provide callable object that accepts Subscription object to decide whether to prevent
|
41
|
+
# further Subscription restarts
|
42
|
+
option(:subscription_restart_terminator)
|
43
|
+
# @!attribute subscriptions_set_max_retries
|
44
|
+
# @return [Integer] max number of retries of failed SubscriptionsSet
|
45
|
+
option(:subscriptions_set_max_retries) { 10 }
|
46
|
+
# @!attribute subscriptions_set_retries_interval
|
47
|
+
# @return [Integer] interval in seconds between retries of failed SubscriptionsSet
|
48
|
+
option(:subscriptions_set_retries_interval) { 1 }
|
17
49
|
|
18
50
|
# @param name [Symbol] config's name. Its value matches the appropriate key in PgEventstore.config hash
|
19
51
|
def initialize(name:, **options)
|
data/lib/pg_eventstore/errors.rb
CHANGED
@@ -104,4 +104,84 @@ module PgEventstore
|
|
104
104
|
TEXT
|
105
105
|
end
|
106
106
|
end
|
107
|
+
|
108
|
+
class RecordNotFound < Error
|
109
|
+
attr_reader :table_name, :id
|
110
|
+
|
111
|
+
# @param table_name [String]
|
112
|
+
# @param id [Integer, String]
|
113
|
+
def initialize(table_name, id)
|
114
|
+
@table_name = table_name
|
115
|
+
@id = id
|
116
|
+
super(user_friendly_message)
|
117
|
+
end
|
118
|
+
|
119
|
+
# @return [String]
|
120
|
+
def user_friendly_message
|
121
|
+
"Could not find/update #{table_name.inspect} record with #{id.inspect} id."
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class SubscriptionAlreadyLockedError < Error
|
126
|
+
attr_reader :set, :name, :lock_id
|
127
|
+
|
128
|
+
# @param set [String] subscriptions set name
|
129
|
+
# @param name [String] subscription's name
|
130
|
+
# @param lock_id [String] UUIDv4
|
131
|
+
def initialize(set, name, lock_id)
|
132
|
+
@set = set
|
133
|
+
@name = name
|
134
|
+
@lock_id = lock_id
|
135
|
+
super(user_friendly_message)
|
136
|
+
end
|
137
|
+
|
138
|
+
# @return [String]
|
139
|
+
def user_friendly_message
|
140
|
+
<<~TEXT.strip
|
141
|
+
Could not lock subscription from #{set.inspect} set with #{name.inspect} name. It is already locked by \
|
142
|
+
#{lock_id.inspect} set.
|
143
|
+
TEXT
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class SubscriptionUnlockError < Error
|
148
|
+
attr_reader :set, :name, :expected_locked_by, :actual_locked_by
|
149
|
+
|
150
|
+
# @param set [String] subscription's set name
|
151
|
+
# @param name [String] subscription's name
|
152
|
+
# @param expected_locked_by [String] UUIDv4
|
153
|
+
# @param actual_locked_by [String, nil] UUIDv4
|
154
|
+
def initialize(set, name, expected_locked_by, actual_locked_by)
|
155
|
+
@set = set
|
156
|
+
@name = name
|
157
|
+
@expected_locked_by = expected_locked_by
|
158
|
+
@actual_locked_by = actual_locked_by
|
159
|
+
super(user_friendly_message)
|
160
|
+
end
|
161
|
+
|
162
|
+
# @return [String]
|
163
|
+
def user_friendly_message
|
164
|
+
<<~TEXT.strip
|
165
|
+
Failed to unlock subscription from #{set.inspect} set with #{name.inspect} name by \
|
166
|
+
#{expected_locked_by.inspect} lock id. It is currently locked by #{actual_locked_by.inspect} lock id.
|
167
|
+
TEXT
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class NotPersistedEventError < Error
|
172
|
+
attr_reader :event
|
173
|
+
|
174
|
+
# @param event [PgEventstore::Event]
|
175
|
+
def initialize(event)
|
176
|
+
@event = event
|
177
|
+
super(user_friendly_message)
|
178
|
+
end
|
179
|
+
|
180
|
+
# @return [String]
|
181
|
+
def user_friendly_message
|
182
|
+
<<~TEXT.strip
|
183
|
+
Event#id must be present, got #{event.id.inspect} instead.
|
184
|
+
TEXT
|
185
|
+
end
|
186
|
+
end
|
107
187
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module PgEventstore
|
4
4
|
# @!visibility private
|
5
|
-
class
|
5
|
+
class EventDeserializer
|
6
6
|
attr_reader :middlewares, :event_class_resolver
|
7
7
|
|
8
8
|
# @param middlewares [Array<Object<#deserialize, #serialize>>]
|
@@ -12,31 +12,14 @@ module PgEventstore
|
|
12
12
|
@event_class_resolver = event_class_resolver
|
13
13
|
end
|
14
14
|
|
15
|
-
# @param
|
16
|
-
|
17
|
-
|
18
|
-
pg_result.map(&method(:_deserialize))
|
15
|
+
# @param raw_events [Array<Hash>]
|
16
|
+
def deserialize_many(raw_events)
|
17
|
+
raw_events.map(&method(:deserialize))
|
19
18
|
end
|
20
|
-
alias deserialize_many deserialize
|
21
|
-
|
22
|
-
# @param pg_result [PG::Result]
|
23
|
-
# @return [PgEventstore::Event, nil]
|
24
|
-
def deserialize_one(pg_result)
|
25
|
-
return if pg_result.ntuples.zero?
|
26
|
-
|
27
|
-
_deserialize(pg_result.first)
|
28
|
-
end
|
29
|
-
|
30
|
-
# @return [PgEventstore::PgResultDeserializer]
|
31
|
-
def without_middlewares
|
32
|
-
self.class.new([], event_class_resolver)
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
19
|
|
37
20
|
# @param attrs [Hash]
|
38
21
|
# @return [PgEventstore::Event]
|
39
|
-
def
|
22
|
+
def deserialize(attrs)
|
40
23
|
event = event_class_resolver.call(attrs['type']).new(**attrs.transform_keys(&:to_sym))
|
41
24
|
middlewares.each do |middleware|
|
42
25
|
middleware.deserialize(event)
|
@@ -44,5 +27,10 @@ module PgEventstore
|
|
44
27
|
event.stream = PgEventstore::Stream.new(**attrs['stream'].transform_keys(&:to_sym)) if attrs.key?('stream')
|
45
28
|
event
|
46
29
|
end
|
30
|
+
|
31
|
+
# @return [PgEventstore::EventDeserializer]
|
32
|
+
def without_middlewares
|
33
|
+
self.class.new([], event_class_resolver)
|
34
|
+
end
|
47
35
|
end
|
48
36
|
end
|