yes-core 2.5.0 → 2.6.1

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: 657f96d2dc9f3d762a91b42976783cfead117aae5be6473e547b4e1568cc8e83
4
- data.tar.gz: 0c5b51b64f133b09cf14ef1d9c99fc7fcdd94f67f479f410dca443545b13ce56
3
+ metadata.gz: 3628c3ea30a5599fa97c6533fd10d2214a0d5da009760b265fd0c11f8490349f
4
+ data.tar.gz: b223d9f7ba3ba16cc2f4d267aabfca2a2c5f6aa6335a3c4388a8f98edc5978dd
5
5
  SHA512:
6
- metadata.gz: cecd619ffb8384612a3ad7a1640fbe6ed9f8d8b73b01f0da6bc8d1892bce474af92274444d8a3c7b16e1bfc622a1cadccbc793f51d61196dd48a57abbd3e0ec5
7
- data.tar.gz: a8f5aee284c9e77715b839b395e4aefa99375275d76d47f9003e9aaf510cd77db88c5fc39c37046523247a01e958c8e1e689da8afc0044bb0928692bd311147c
6
+ metadata.gz: d676304cb10bdc7714ce0b3f4cd45bbcbf85242bd9ce75f13a1ef3683ed042e6849378f007ccaedc448af1cbf4a49b4b2e770c5606474c7e7dd052d59cd9077b
7
+ data.tar.gz: a27b098783deeee35962fa5694f4fdaff7fd26a58d58b0a66bf6f799ca0d8ad1d0c79da259e266aec823abe450cdcde3942821e8fa8f3d6caece61115779eadd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.6.1] - 2026-10-28
4
+ - See root CHANGELOG.md for details.
5
+
6
+ ## [2.6.0] - 2026-10-28
7
+ - See root CHANGELOG.md for details.
8
+
3
9
  ## [2.5.0] - 2026-10-08
4
10
  - See root CHANGELOG.md for details.
5
11
 
@@ -11,7 +11,14 @@ module Yes
11
11
  # caller never sees a `Cerbos::Error::Unavailable` for a blip that a second attempt on a
12
12
  # healthy pod would have answered.
13
13
  #
14
+ # A refused connection (a pod that stopped listening but is still routed to) also puts the
15
+ # channel into reconnect backoff, during which every call fails immediately. gRPC's default
16
+ # backoff (1s, growing to 120s) outlasts the retry window above, so a single process would
17
+ # keep failing every request for many seconds. The reconnect backoff is therefore capped to
18
+ # the same order of magnitude as the retry backoff.
19
+ #
14
20
  # @see https://grpc.io/docs/guides/retry/
21
+ # @see https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
15
22
  module CerbosGrpcRetryPolicy
16
23
  SERVICE_NAME = 'cerbos.svc.v1.CerbosService'
17
24
  MAX_ATTEMPTS = 4
@@ -23,12 +30,21 @@ module Yes
23
30
  ENABLE_RETRIES_ARG = 'grpc.enable_retries'
24
31
  SERVICE_CONFIG_ARG = 'grpc.service_config'
25
32
 
33
+ RECONNECT_BACKOFF_MS = 100
34
+ MAX_RECONNECT_BACKOFF_MS = 1_000
35
+ MIN_RECONNECT_BACKOFF_ARG = 'grpc.min_reconnect_backoff_ms'
36
+ INITIAL_RECONNECT_BACKOFF_ARG = 'grpc.initial_reconnect_backoff_ms'
37
+ MAX_RECONNECT_BACKOFF_ARG = 'grpc.max_reconnect_backoff_ms'
38
+
26
39
  class << self
27
40
  # @return [Hash{String => Integer, String}] channel arguments for `Cerbos::Client.new`
28
41
  def channel_args
29
42
  {
30
43
  ENABLE_RETRIES_ARG => 1,
31
- SERVICE_CONFIG_ARG => service_config.to_json
44
+ SERVICE_CONFIG_ARG => service_config.to_json,
45
+ MIN_RECONNECT_BACKOFF_ARG => RECONNECT_BACKOFF_MS,
46
+ INITIAL_RECONNECT_BACKOFF_ARG => RECONNECT_BACKOFF_MS,
47
+ MAX_RECONNECT_BACKOFF_ARG => MAX_RECONNECT_BACKOFF_MS
32
48
  }
33
49
  end
34
50
 
@@ -23,7 +23,13 @@ module Yes
23
23
  READ_MODEL_CLASS_REGEXP =
24
24
  /(?<context>\w+)::ReadModels::(?<version>V\d+)?(::)?(?<aggregate>\w+)/
25
25
 
26
- # Based on event type class it distributes event to a proper event handler
26
+ # Column of the read model that holds the revision of the last processed event of its stream
27
+ REVISION_COLUMN = :revision
28
+
29
+ # Based on event type class it distributes event to a proper event handler. After the handler ran, the
30
+ # stream revision of the event is stored in the read model's `revision` column (see #write_stream_revision).
31
+ # Events that were not handled (no handler, locale not available) do not change the revision, because
32
+ # the read model does not reflect them.
27
33
  # @param event [Yes::Core::Event]
28
34
  # @param read_model [ActiveRecord::Base] AR object
29
35
  # @return [void]
@@ -47,6 +53,8 @@ module Yes
47
53
  ensure
48
54
  otl_record_processed(read_model, event, handler)
49
55
  end
56
+
57
+ write_stream_revision(read_model, event)
50
58
  end
51
59
  otl_trackable :call, OpenTelemetry::OtlSpan::OtlData.new(
52
60
  span_kind: :consumer,
@@ -172,6 +180,30 @@ module Yes
172
180
  READ_MODEL_CLASS_REGEXP.match(self.class.to_s)
173
181
  end
174
182
 
183
+ # Stores the stream revision of the event in the read model's `revision` column, so a consumer can tell
184
+ # how far a read model fed by a subscription has caught up with its stream. It only moves forward and
185
+ # skips read models without the column. Per-aggregate columns (e.g. `shop_order_revision`) belong to
186
+ # the aggregates that write them and are never touched here.
187
+ # It only writes for events of the read model's own stream: a builder may also react to events of
188
+ # other streams (e.g. a product event for an order item), whose revision says nothing about this row.
189
+ # Only the stream id is compared, not the context or the stream name. A read model fed by two streams
190
+ # that share the same id would get a mixed revision, so such read models must not rely on it.
191
+ # A read model whose `revision` column an aggregate writes must not also be fed by a builder:
192
+ # use per-aggregate revision columns or project the events through the aggregate instead.
193
+ # @param read_model [ActiveRecord::Base, nil] the read model, nil if the event maps to no row
194
+ # @param event [Yes::Core::Event] the event that was handled
195
+ # @return [void]
196
+ def write_stream_revision(read_model, event)
197
+ revision = event.stream_revision
198
+ return unless revision && read_model&.persisted? && read_model.has_attribute?(REVISION_COLUMN)
199
+ return unless event.stream&.stream_id == read_model.id
200
+
201
+ # a nullable column may still be NULL before the first event
202
+ return unless revision > (read_model[REVISION_COLUMN] || -1)
203
+
204
+ read_model.update_column(REVISION_COLUMN, revision)
205
+ end
206
+
175
207
  # @param locale [Symbol] locale to check
176
208
  # @return [Boolean] true if locale is available
177
209
  def correct_locale?(locale)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Yes
4
4
  module Core
5
- VERSION = '2.5.0'
5
+ VERSION = '2.6.1'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yes-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nico Ritsche