pg_eventstore 1.6.0 → 1.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 337db5e3df424d6d72b3e08367a730af7c77ebfd907d38b309309e30a563e81e
4
- data.tar.gz: 71c36ea38c4d756adea73939ab15e75a2b4500d17768416e0f37013160f0b696
3
+ metadata.gz: ff514958069cbf4b819b2ad959eac80ca9f24445bca19690ed4cb3ff23989804
4
+ data.tar.gz: bd91be800c3739a43edf4c17c996156642f00f0d977446201c84784a1bcc6e74
5
5
  SHA512:
6
- metadata.gz: 70699c2d05ddb69f64922c6e0e67d46a523b5ed2f70d9278134435a144bc002013fe0657a408a0657ccb9f0fe2d1f9cd7a1521a50e128375b1c1d8ec1e127584
7
- data.tar.gz: c1125f8dc9af55a1ce1752b94a9bb3273e1153c70e726845867bb91cad9a4b407bcc63537d2bb5859bef20508ce5bfb4391ed56218a690bef2119cf1351597d7
6
+ metadata.gz: 1d4946c58c314f0bfb04c0c20ca1308e4c8afe915adacee8722fcd703404b26dbc429bdda5bc5a44f94654cb366813065226b42c930620bf30fcd4a345f592b1
7
+ data.tar.gz: 89738f55a626cbcd4eab9d336b7f4b9ff2e3520fc30a7fc7d87dfac5e755cd6431392438d1522cec706188aa855942db4cfd6fa96bc83c5e0b67086d6e05cef8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.7.0]
4
+ - Implement reading from `"$streams"` system stream
5
+ - Disable Host authorization introduced in sinatra v4.1
6
+
3
7
  ## [1.6.0]
4
8
  - Introduce subscriptions CLI. Type `pg-eventstore subscriptions --help` to see available commands. The main purpose of it is to provide the single way to start/stop subscription processes. Check [Subscriptions](docs/subscriptions.md#creating-a-subscription) docs about the new way to start and keep running a subscriptions process.
5
9
 
data/README.md CHANGED
@@ -53,6 +53,51 @@ Documentation chapters:
53
53
 
54
54
  The gem is shipped with its own CLI. Use `pg-eventstore --help` to find out its capabilities.
55
55
 
56
+ ## RSpec
57
+
58
+ ### Clean up test db
59
+
60
+ The gem provides a class to clean up your `pg_eventstore` test db between tests. Example usage(in your `spec/spec_helper.rb`:
61
+
62
+ ```ruby
63
+ require 'pg_eventstore/rspec/test_helpers'
64
+
65
+ RSpec.configure do |config|
66
+ config.before do
67
+ PgEventstore::TestHelpers.clean_up_db
68
+ end
69
+ end
70
+ ```
71
+
72
+ ### RSpec matcher for OptionsExtension
73
+
74
+ If you would like to be able to test the functional, provided by `PgEventstore::Extensions::OptionsExtension` extension - there is a rspec matcher. Load custom matcher in you `spec_helper.rb`:
75
+
76
+ ```ruby
77
+ require 'pg_eventstore/rspec/has_option_matcher'
78
+ ```
79
+
80
+ Let's say you have next class:
81
+ ```ruby
82
+ class SomeClass
83
+ include PgEventstore::Extensions::OptionsExtension
84
+
85
+ option(:some_opt, metadata: { foo: :bar }) { '1' }
86
+ end
87
+ ```
88
+
89
+ To test that its instance has the proper option with the proper default value and proper metadata you can use this matcher:
90
+ ```ruby
91
+ RSpec.describe SomeClass do
92
+ subject { described_class.new }
93
+
94
+ # Check that :some_opt is present
95
+ it { is_expected.to have_option(:some_opt) }
96
+ # Check that :some_opt is present and has the correct default value
97
+ it { is_expected.to have_option(:some_opt).with_default_value('1').with_metadata(foo: :bar) }
98
+ end
99
+ ```
100
+
56
101
  ## Development
57
102
 
58
103
  After checking out the repo, run:
@@ -0,0 +1,2 @@
1
+ CREATE INDEX idx_events_0_stream_revision_global_position ON events USING btree (stream_revision, global_position) WHERE stream_revision = 0;
2
+ CREATE VIEW "$streams" AS SELECT * FROM events WHERE stream_revision = 0;
@@ -53,7 +53,7 @@ In case a stream with given name does not exist, a `PgEventstore::StreamNotFound
53
53
  ```ruby
54
54
  begin
55
55
  stream = PgEventstore::Stream.new(context: 'non-existing-context', stream_name: 'User', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
56
- PgEventstore.client.read(stream)
56
+ PgEventstore.client.read(stream, options: { max_count: 1 })
57
57
  rescue PgEventstore::StreamNotFoundError => e
58
58
  puts e.message # => Stream #<PgEventstore::Stream:0x01> does not exist.
59
59
  puts e.stream # => #<PgEventstore::Stream:0x01>
@@ -80,6 +80,15 @@ You can read from a specific position of the "all" stream. This is very similar
80
80
  PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { from_position: 9023, direction: 'Backwards' })
81
81
  ```
82
82
 
83
+ ## Reading from "$streams" system stream
84
+
85
+ `"$streams"` is a special stream which consists of events with `stream_revision == 0`. This allows you to effectively query all streams. Example:
86
+
87
+ ```ruby
88
+ stream = PgEventstore::Stream.system_stream("$streams")
89
+ PgEventstore.client.read(stream).map(&:stream) # => array of unique streams
90
+ ```
91
+
83
92
  ## Middlewares
84
93
 
85
94
  If you would like to skip some of your registered middlewares from processing events after they being read from a stream - you should use the `:middlewares` argument which allows you to override the list of middlewares you would like to use.
@@ -160,6 +169,12 @@ You can also mix filtering by stream's attributes and event types. The result wi
160
169
  PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { filter: { streams: [{ context: 'MyAwesomeContext' }], event_types: %w[Foo Bar] } })
161
170
  ```
162
171
 
172
+ ### "$streams" stream filtering
173
+
174
+ When reading from `"$streams"` same rules apply as when reading from "all" stream. For example, read all streams which have `context == "MyAwesomeContext"` and start from events with event type either `"Foo"` or `"Bar"`:
175
+ ```ruby
176
+ PgEventstore.client.read(PgEventstore::Stream.system_stream("$streams"), options: { filter: { streams: [{ context: 'MyAwesomeContext' }], event_types: %w[Foo Bar] } })
177
+ ```
163
178
 
164
179
  ## Pagination
165
180
 
@@ -15,7 +15,7 @@ module PgEventstore
15
15
  # @return [Array<PgEventstore::Event>]
16
16
  # @raise [PgEventstore::StreamNotFoundError]
17
17
  def call(stream, options: {})
18
- queries.events.stream_revision(stream) || raise(StreamNotFoundError, stream) unless stream.all_stream?
18
+ queries.events.stream_revision(stream) || raise(StreamNotFoundError, stream) unless stream.system?
19
19
 
20
20
  queries.events.stream_events(stream, options)
21
21
  end
@@ -70,7 +70,7 @@ module PgEventstore
70
70
  # @param options [Hash]
71
71
  # @return [Array<PgEventstore::Event>]
72
72
  def stream_events(stream, options)
73
- exec_params = events_filtering(stream, options).to_exec_params
73
+ exec_params = QueryBuilders::EventsFiltering.events_filtering(stream, options).to_exec_params
74
74
  raw_events = connection.with do |conn|
75
75
  conn.exec_params(*exec_params)
76
76
  end.to_a
@@ -128,15 +128,6 @@ module PgEventstore
128
128
  [sql_rows_for_insert, values]
129
129
  end
130
130
 
131
- # @param stream [PgEventstore::Stream]
132
- # @param options [Hash]
133
- # @return [PgEventstore::EventsFiltering]
134
- def events_filtering(stream, options)
135
- return QueryBuilders::EventsFiltering.all_stream_filtering(options) if stream.all_stream?
136
-
137
- QueryBuilders::EventsFiltering.specific_stream_filtering(stream, options)
138
- end
139
-
140
131
  # @return [PgEventstore::LinksResolver]
141
132
  def links_resolver
142
133
  LinksResolver.new(connection)
@@ -21,6 +21,18 @@ module PgEventstore
21
21
  SUBSCRIPTIONS_OPTIONS = %i[from_position resolve_link_tos filter max_count].freeze
22
22
 
23
23
  class << self
24
+ # @param stream [PgEventstore::Stream]
25
+ # @param options [Hash]
26
+ # @return [PgEventstore::EventsFiltering]
27
+ def events_filtering(stream, options)
28
+ return all_stream_filtering(options) if stream.all_stream?
29
+ if stream.system? && Stream::KNOWN_SYSTEM_STREAMS.include?(stream.context)
30
+ return system_stream_filtering(stream, options)
31
+ end
32
+
33
+ specific_stream_filtering(stream, options)
34
+ end
35
+
24
36
  # @param options [Hash]
25
37
  # @return [PgEventstore::QueryBuilders::EventsFiltering]
26
38
  def subscriptions_events_filtering(options)
@@ -54,6 +66,15 @@ module PgEventstore
54
66
  event_filter.add_stream_direction(options[:direction])
55
67
  event_filter
56
68
  end
69
+
70
+ # @param stream [PgEventstore::Stream] system stream
71
+ # @param options [Hash]
72
+ # @return [PgEventstore::QueryBuilders::EventsFiltering]
73
+ def system_stream_filtering(stream, options)
74
+ all_stream_filtering(options).tap do |event_filter|
75
+ event_filter.set_source(stream.context)
76
+ end
77
+ end
57
78
  end
58
79
 
59
80
  def initialize
@@ -139,6 +160,12 @@ module PgEventstore
139
160
  @sql_builder.to_exec_params
140
161
  end
141
162
 
163
+ # @param table_name [String] system stream view name
164
+ # @return [void]
165
+ def set_source(table_name)
166
+ @sql_builder.from(%{ "#{PG::Connection.escape(table_name)}" events })
167
+ end
168
+
142
169
  private
143
170
 
144
171
  # @param stream_attrs [Hash]
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # This matcher is defined to test options which are defined by using
4
- # EventStoreClient::Extensions::OptionsExtension option. Example:
4
+ # PgEventstore::Extensions::OptionsExtension option. Example:
5
5
  # Let's say you have next class
6
6
  # class SomeClass
7
7
  # include PgEventstore::Extensions::OptionsExtension
8
8
  #
9
- # option(:some_opt) { '1' }
9
+ # option(:some_opt, metadata: { foo: :bar }) { '1' }
10
10
  # end
11
11
  #
12
- # To test that its instance has the proper option with the proper default value you can use this
12
+ # To test that its instance has the proper option with the proper default value and proper metadata you can use this
13
13
  # matcher:
14
14
  # RSpec.describe SomeClass do
15
15
  # subject { described_class.new }
@@ -17,31 +17,7 @@
17
17
  # # Check that :some_opt is present
18
18
  # it { is_expected.to have_option(:some_opt) }
19
19
  # # Check that :some_opt is present and has the correct default value
20
- # it { is_expected.to have_option(:some_opt).with_default_value('1') }
21
- # end
22
- #
23
- # If you have more complex implementation of default value of your option - you should handle it
24
- # customly. For example:
25
- # class SomeClass
26
- # include PgEventstore::Extensions::OptionsExtension
27
- #
28
- # option(:some_opt) { calc_value }
29
- # end
30
- # You could test it like so:
31
- # RSpec.described SomeClass do
32
- # let(:instance) { described_class.new }
33
- #
34
- # describe ':some_opt default value' do
35
- # subject { instance.some_opt }
36
- #
37
- # let(:value) { 'some val' }
38
- #
39
- # before do
40
- # allow(instance).to receive(:calc_value).and_return(value)
41
- # end
42
- #
43
- # it { is_expected.to eq(value) }
44
- # end
20
+ # it { is_expected.to have_option(:some_opt).with_default_value('1').with_metadata(foo: :bar) }
45
21
  # end
46
22
  RSpec::Matchers.define :has_option do |option_name|
47
23
  match do |obj|
@@ -8,6 +8,8 @@ module PgEventstore
8
8
  SYSTEM_STREAM_PREFIX = '$'
9
9
  # @return [Integer]
10
10
  NON_EXISTING_STREAM_REVISION = -1
11
+ # @return [Array<String>]
12
+ KNOWN_SYSTEM_STREAMS = %w[$streams].freeze
11
13
 
12
14
  class << self
13
15
  # Produces "all" stream instance. "all" stream does not represent any specific stream. Instead, it indicates that
@@ -18,6 +20,12 @@ module PgEventstore
18
20
  stream.instance_variable_set(:@all_stream, true)
19
21
  end
20
22
  end
23
+
24
+ # @param name [String]
25
+ # @return [PgEventstore::Stream]
26
+ def system_stream(name)
27
+ new(context: name, stream_name: '', stream_id: '')
28
+ end
21
29
  end
22
30
 
23
31
  # @!attribute context
@@ -2,5 +2,5 @@
2
2
 
3
3
  module PgEventstore
4
4
  # @return [String]
5
- VERSION = "1.6.0"
5
+ VERSION = "1.7.0"
6
6
  end
@@ -9,6 +9,7 @@ module PgEventstore
9
9
  set :environment, -> { (ENV['RACK_ENV'] || ENV['RAILS_ENV'] || ENV['APP_ENV'])&.to_sym || :development }
10
10
  set :logging, -> { environment == :development || environment == :test }
11
11
  set :erb, layout: :'layouts/application'
12
+ set :host_authorization, { allow_if: ->(_env) { true } }
12
13
 
13
14
  helpers(Paginator::Helpers, Subscriptions::Helpers) do
14
15
  # @return [Array<Hash>, nil]
@@ -19,6 +20,12 @@ module PgEventstore
19
20
  end&.reject { _1.empty? }
20
21
  end
21
22
 
23
+ # @return [String, nil]
24
+ def system_stream
25
+ params in { filter: { system_stream: String => system_stream } }
26
+ system_stream if Stream::KNOWN_SYSTEM_STREAMS.include?(system_stream)
27
+ end
28
+
22
29
  # @return [Array<String>, nil]
23
30
  def events_filter
24
31
  params in { filter: { events: Array => events } }
@@ -31,6 +38,8 @@ module PgEventstore
31
38
  PgEventstore.available_configs.include?(config) ? config : :default
32
39
  end
33
40
 
41
+ # @param val [Object]
42
+ # @return [void]
34
43
  def current_config=(val)
35
44
  response.set_cookie('current_config', { value: val.to_s, http_only: true, same_site: :lax })
36
45
  end
@@ -40,7 +49,7 @@ module PgEventstore
40
49
  PgEventstore.connection(current_config)
41
50
  end
42
51
 
43
- # @param collection [PgEventstore::Paginator::BaseCollection]
52
+ # @param collection [PgEventstore::Web::Paginator::BaseCollection]
44
53
  # @return [void]
45
54
  def paginated_json_response(collection)
46
55
  halt 200, {
@@ -85,7 +94,8 @@ module PgEventstore
85
94
  options: {
86
95
  filter: { event_types: events_filter, streams: streams_filter },
87
96
  resolve_link_tos: resolve_link_tos?
88
- }
97
+ },
98
+ system_stream: system_stream
89
99
  )
90
100
 
91
101
  if request.xhr?
@@ -19,10 +19,21 @@ module PgEventstore
19
19
  # count instead because of the potential performance degradation.
20
20
  MAX_NUMBER_TO_COUNT = 10_000
21
21
 
22
+ # @param config_name [Symbol]
23
+ # @param starting_id [String, Integer, nil]
24
+ # @param per_page [Integer]
25
+ # @param order [Symbol] :asc or :desc
26
+ # @param options [Hash] additional options to filter the collection
27
+ # @param system_stream [String, nil] a name of system stream
28
+ def initialize(config_name, starting_id:, per_page:, order:, options: {}, system_stream: nil)
29
+ super(config_name, starting_id: starting_id, per_page: per_page, order: order, options: options)
30
+ @stream = system_stream ? PgEventstore::Stream.system_stream(system_stream) : PgEventstore::Stream.all_stream
31
+ end
32
+
22
33
  # @return [Array<PgEventstore::Event>]
23
34
  def collection
24
35
  @_collection ||= PgEventstore.client(config_name).read(
25
- PgEventstore::Stream.all_stream,
36
+ @stream,
26
37
  options: options.merge(from_position: starting_id, max_count: per_page, direction: order),
27
38
  middlewares: []
28
39
  )
@@ -33,7 +44,8 @@ module PgEventstore
33
44
  return unless collection.size == per_page
34
45
 
35
46
  from_position = event_global_position(collection.first)
36
- sql_builder = QueryBuilders::EventsFiltering.all_stream_filtering(
47
+ sql_builder = QueryBuilders::EventsFiltering.events_filtering(
48
+ @stream,
37
49
  options.merge(from_position: from_position, max_count: 1, direction: order)
38
50
  ).to_sql_builder.unselect.select('global_position').offset(per_page)
39
51
  global_position(sql_builder)
@@ -42,7 +54,8 @@ module PgEventstore
42
54
  # @return [Integer, nil]
43
55
  def prev_page_starting_id
44
56
  from_position = event_global_position(collection.first) || starting_id
45
- sql_builder = QueryBuilders::EventsFiltering.all_stream_filtering(
57
+ sql_builder = QueryBuilders::EventsFiltering.events_filtering(
58
+ @stream,
46
59
  options.merge(from_position: from_position, max_count: per_page, direction: order == :asc ? :desc : :asc)
47
60
  ).to_sql_builder.unselect.select('global_position').offset(1)
48
61
  sql, params = sql_builder.to_exec_params
@@ -57,7 +70,7 @@ module PgEventstore
57
70
  @_total_count ||=
58
71
  begin
59
72
  sql_builder =
60
- QueryBuilders::EventsFiltering.all_stream_filtering(options).
73
+ QueryBuilders::EventsFiltering.events_filtering(@stream, options).
61
74
  to_sql_builder.remove_limit.remove_group.remove_order
62
75
  count = estimate_count(sql_builder)
63
76
  return count if count > MAX_NUMBER_TO_COUNT
@@ -68,7 +81,7 @@ module PgEventstore
68
81
 
69
82
  private
70
83
 
71
- # @param event [PgEventstore::Event]
84
+ # @param event [PgEventstore::Event, nil]
72
85
  # @return [Integer, nil]
73
86
  def event_global_position(event)
74
87
  event&.link&.global_position || event&.global_position
@@ -60,6 +60,13 @@ $(function(){
60
60
  });
61
61
  }
62
62
 
63
+ let initSystemStreamFilterAutocomplete = function($filter){
64
+ let $streamNameSelect = $filter.find('select');
65
+ $streamNameSelect.select2({
66
+ allowClear: true
67
+ });
68
+ }
69
+
63
70
  let initEventTypeFilterAutocomplete = function($filter) {
64
71
  let $eventTypeSelect = $filter.find('select');
65
72
  $eventTypeSelect.select2({
@@ -86,6 +93,8 @@ $(function(){
86
93
  let $filtersForm = $('#filters-form');
87
94
  // Stream filter template
88
95
  let streamFilterTmpl = $('#stream-filter-tmpl').text();
96
+ // System stream filter template
97
+ let systemStreamFilterTmpl = $('#system-stream-filter-tmpl').text();
89
98
  // Event type filter template
90
99
  let eventFilterTmpl = $('#event-type-filter-tmpl').text();
91
100
 
@@ -93,21 +102,34 @@ $(function(){
93
102
  $filtersForm.on('click', '.remove-filter', function(){
94
103
  $(this).parents('.form-row').remove();
95
104
  });
96
- // Add stream filter button
105
+ // "Add stream filter" button
97
106
  $filtersForm.on('click', '.add-stream-filter', function(){
98
107
  let filtersNum = $filtersForm.find('.stream-filters').children().length + '';
99
108
  $filtersForm.find('.stream-filters').append(streamFilterTmpl.replace(/%NUM%/g, filtersNum));
100
109
  initStreamFilterAutocomplete($filtersForm.find('.stream-filters').children().last());
101
110
  });
102
- // Add event type filter button
111
+ // "Add system stream filter" button
112
+ $filtersForm.on('click', '.add-system-stream-filter', function(){
113
+ if($filtersForm.find('.system-stream-filter').children().length > 0)
114
+ return;
115
+
116
+ $filtersForm.find('.system-stream-filter').append(systemStreamFilterTmpl);
117
+ initSystemStreamFilterAutocomplete($filtersForm.find('.system-stream-filter').children().last());
118
+ });
119
+ // "Add event type filter" button
103
120
  $filtersForm.on('click', '.add-event-filter', function(){
104
121
  $filtersForm.find('.event-filters').append(eventFilterTmpl);
105
122
  initEventTypeFilterAutocomplete($filtersForm.find('.event-filters').children().last());
106
123
  });
124
+
107
125
  // Init select2 for stream filters which were initially rendered
108
126
  $filtersForm.find('.stream-filters').children().each(function(){
109
127
  initStreamFilterAutocomplete($(this));
110
128
  });
129
+ // Init select2 for system stream filter which were initially rendered
130
+ $filtersForm.find('.system-stream-filter').children().each(function(){
131
+ initSystemStreamFilterAutocomplete($(this));
132
+ });
111
133
  // Init select2 for event type filters which were initially rendered
112
134
  $filtersForm.find('.event-filters').children().each(function(){
113
135
  initEventTypeFilterAutocomplete($(this));
@@ -1,6 +1,9 @@
1
1
  <script type="text/html" id="stream-filter-tmpl">
2
2
  <%= erb :'home/partials/stream_filter', { layout: false }, { stream: {} } %>
3
3
  </script>
4
+ <script type="text/html" id="system-stream-filter-tmpl">
5
+ <%= erb :'home/partials/system_stream_filter', { layout: false }, { stream: nil } %>
6
+ </script>
4
7
  <script type="text/html" id="event-type-filter-tmpl">
5
8
  <%= erb :'home/partials/event_filter', { layout: false }, { event_type: '' } %>
6
9
  </script>
@@ -27,6 +30,11 @@
27
30
  <form id="filters-form" action="<%= url('/') %>" method="GET" data-parsley-validate class="form-horizontal form-label-left">
28
31
  <input type="hidden" name="per_page" value="<%= params[:per_page].to_i %>">
29
32
  <input type="hidden" name="resolve_link_tos" value="<%= resolve_link_tos? %>">
33
+ <div class="system-stream-filter">
34
+ <% if system_stream %>
35
+ <%= erb :'home/partials/system_stream_filter', { layout: false }, { stream: system_stream } %>
36
+ <% end %>
37
+ </div>
30
38
  <div class="stream-filters">
31
39
  <% streams_filter&.each do |attrs| %>
32
40
  <%= erb :'home/partials/stream_filter', { layout: false }, { stream: attrs } %>
@@ -48,6 +56,7 @@
48
56
  </button>
49
57
  <div class="dropdown-menu">
50
58
  <a class="dropdown-item add-stream-filter" href="javascript: void(0)">Add stream filter</a>
59
+ <a class="dropdown-item add-system-stream-filter" href="javascript: void(0)">Add system stream filter</a>
51
60
  <a class="dropdown-item add-event-filter" href="javascript: void(0)">Add event filter</a>
52
61
  </div>
53
62
  </div>
@@ -6,10 +6,13 @@
6
6
  <td><%= h event.stream.stream_name %></td>
7
7
  <td><a href="<%= stream_path(event) %>"><%= event.stream.stream_id %></a></td>
8
8
  <td>
9
- <%= h event.type %>
9
+ <p class="float-left"><%= h event.type %></p>
10
10
  <% if event.link %>
11
- <i class="fa fa-link"></i>
11
+ <p class="float-left ml-2">
12
+ <i class="fa fa-link"></i>
13
+ </p>
12
14
  <% end %>
15
+ <div class="clearfix"></div>
13
16
  </td>
14
17
  <td><%= event.created_at.strftime('%F %T') %></td>
15
18
  <td><%= event.id %></td>
@@ -0,0 +1,15 @@
1
+ <div class="form-row align-items-center">
2
+ <div class="col-3">
3
+ <select name="filter[system_stream]" class="form-control mb-2" data-placeholder="Select system stream">
4
+ <option></option>
5
+ <% PgEventstore::Stream::KNOWN_SYSTEM_STREAMS.each do |stream_name| %>
6
+ <option value="<%= stream_name %>" <% if stream == stream_name %> selected <% end %>><%= stream_name %></option>
7
+ <% end %>
8
+ </select>
9
+ </div>
10
+ <div class="col-1">
11
+ <a class="btn btn-default remove-filter" href="javascript: void(0);">
12
+ <i class="fa fa-minus-circle"></i>
13
+ </a>
14
+ </div>
15
+ </div>
@@ -87,7 +87,7 @@
87
87
  <div class="dropdown-menu dropdown-usermenu pull-right" aria-labelledby="navbarDropdown">
88
88
  <% PgEventstore.available_configs.each do |config| %>
89
89
  <form action="<%= url('/change_config') %>" method="POST">
90
- <input type="hidden" name="config" value="<%= h config %>">
90
+ <input type="hidden" name="config" value="<%= h config.to_s %>">
91
91
  <button type="submit" class="dropdown-item"><%= h config.inspect %></button>
92
92
  </form>
93
93
  <% end %>
@@ -31,11 +31,6 @@ module PgEventstore
31
31
  # _@param_ `events`
32
32
  def prepared_statements: (PgEventstore::Stream stream, ::Array[PgEventstore::Event] events) -> ::Array[(::Array[String] | ::Array[Object])]
33
33
 
34
- # _@param_ `stream`
35
- #
36
- # _@param_ `options`
37
- def events_filtering: (PgEventstore::Stream stream, ::Hash[untyped, untyped] options) -> PgEventstore::QueryBuilders::EventsFiltering
38
-
39
34
  def links_resolver: () -> PgEventstore::LinksResolver
40
35
 
41
36
  # Returns the value of attribute connection.
@@ -6,6 +6,10 @@ module PgEventstore
6
6
  SQL_DIRECTIONS: Hash[String | Symbol, String]
7
7
  SUBSCRIPTIONS_OPTIONS: ::Array[Symbol]
8
8
 
9
+ @sql_builder: PgEventstore::SQLBuilder
10
+
11
+ def self.events_filtering: (PgEventstore::Stream stream, ::Hash[untyped, untyped] options) -> PgEventstore::QueryBuilders::EventsFiltering
12
+
9
13
  # _@param_ `options`
10
14
  def self.subscriptions_events_filtering: (::Hash[untyped, untyped] options) -> PgEventstore::QueryBuilders::EventsFiltering
11
15
 
@@ -17,6 +21,8 @@ module PgEventstore
17
21
  # _@param_ `options`
18
22
  def self.specific_stream_filtering: (PgEventstore::Stream stream, ::Hash[untyped, untyped] options) -> PgEventstore::QueryBuilders::EventsFiltering
19
23
 
24
+ def self.system_stream_filtering: (PgEventstore::Stream stream, Hash[untyped, untyped] options) -> PgEventstore::QueryBuilders::EventsFiltering
25
+
20
26
  def initialize: () -> void
21
27
 
22
28
  # _@param_ `context`
@@ -1,10 +1,13 @@
1
1
  module PgEventstore
2
2
  class Stream
3
+ KNOWN_SYSTEM_STREAMS: Array[String]
3
4
  SYSTEM_STREAM_PREFIX: String
4
5
  NON_EXISTING_STREAM_REVISION: Integer
5
6
 
6
7
  def self.all_stream: () -> PgEventstore::Stream
7
8
 
9
+ def self.system_stream: (String) -> PgEventstore::Stream
10
+
8
11
  # _@param_ `context`
9
12
  #
10
13
  # _@param_ `stream_name`
@@ -0,0 +1,27 @@
1
+ module PgEventstore
2
+ module Web
3
+ class Application
4
+ def asset_url: (String path) -> String
5
+
6
+ def connection: -> PgEventstore::Connection
7
+
8
+ def current_config: -> Symbol
9
+
10
+ def current_config=: (untyped val) -> void
11
+
12
+ def events_filter: -> Array[String]?
13
+
14
+ def h: (String text) -> String
15
+
16
+ def paginated_json_response: (PgEventstore::Web::Paginator::BaseCollection collection) -> void
17
+
18
+ def redirect_back_url: (fallback_url: String) -> String
19
+
20
+ def resolve_link_tos?: -> bool
21
+
22
+ def streams_filter: -> Array[Hash[untyped, untyped]]?
23
+
24
+ def system_stream: -> String?
25
+ end
26
+ end
27
+ end
@@ -2,15 +2,6 @@ module PgEventstore
2
2
  module Web
3
3
  module Paginator
4
4
  class BaseCollection
5
- # _@param_ `config_name`
6
- #
7
- # _@param_ `starting_id`
8
- #
9
- # _@param_ `per_page`
10
- #
11
- # _@param_ `order` — :asc or :desc
12
- #
13
- # _@param_ `options` — additional options to filter the collection
14
5
  def initialize: (
15
6
  Symbol config_name,
16
7
  starting_id: (String | Integer)?,
@@ -4,6 +4,15 @@ module PgEventstore
4
4
  class EventTypesCollection < PgEventstore::Web::Paginator::BaseCollection
5
5
  PER_PAGE: Integer
6
6
 
7
+ def initialize: (
8
+ Symbol config_name,
9
+ starting_id: (String | Integer)?,
10
+ per_page: Integer,
11
+ order: Symbol,
12
+ ?options: ::Hash[untyped, untyped],
13
+ ?system_stream: String?
14
+ ) -> void
15
+
7
16
  def collection: () -> ::Array[::Hash[String, String]]
8
17
 
9
18
  def next_page_starting_id: () -> String?
@@ -6,6 +6,8 @@ module PgEventstore
6
6
  PER_PAGE: ::Hash[String, Integer]
7
7
  MAX_NUMBER_TO_COUNT: Integer
8
8
 
9
+ @stream: PgEventstore::Stream
10
+
9
11
  def collection: () -> ::Array[PgEventstore::Event]
10
12
 
11
13
  def next_page_starting_id: () -> Integer?
@@ -15,7 +17,7 @@ module PgEventstore
15
17
  def total_count: () -> Integer
16
18
 
17
19
  # _@param_ `event`
18
- def event_global_position: (PgEventstore::Event event) -> Integer?
20
+ def event_global_position: (PgEventstore::Event? event) -> Integer?
19
21
 
20
22
  # _@param_ `sql_builder`
21
23
  def estimate_count: (PgEventstore::SQLBuilder sql_builder) -> Integer
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_eventstore
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Dzyzenko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-12 00:00:00.000000000 Z
11
+ date: 2025-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -77,6 +77,7 @@ files:
77
77
  - db/migrations/4_create_subscriptions_set_commands.sql
78
78
  - db/migrations/5_partitions.sql
79
79
  - db/migrations/6_add_commands_data.sql
80
+ - db/migrations/7_support_reading_streams_system_stream.sql
80
81
  - docs/admin_ui.md
81
82
  - docs/appending_events.md
82
83
  - docs/configuration.md
@@ -226,6 +227,7 @@ files:
226
227
  - lib/pg_eventstore/web/views/home/partials/events.erb
227
228
  - lib/pg_eventstore/web/views/home/partials/pagination_links.erb
228
229
  - lib/pg_eventstore/web/views/home/partials/stream_filter.erb
230
+ - lib/pg_eventstore/web/views/home/partials/system_stream_filter.erb
229
231
  - lib/pg_eventstore/web/views/layouts/application.erb
230
232
  - lib/pg_eventstore/web/views/subscriptions/index.erb
231
233
  - pg_eventstore.gemspec
@@ -331,6 +333,7 @@ files:
331
333
  - sig/pg_eventstore/subscriptions/subscriptions_set_lifecycle.rbs
332
334
  - sig/pg_eventstore/utils.rbs
333
335
  - sig/pg_eventstore/version.rbs
336
+ - sig/pg_eventstore/web/application.rbs
334
337
  - sig/pg_eventstore/web/paginator/base_collection.rbs
335
338
  - sig/pg_eventstore/web/paginator/event_types_collection.rbs
336
339
  - sig/pg_eventstore/web/paginator/events_collection.rbs