solid_objects 0.14.0 → 0.14.2

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.
@@ -4,6 +4,14 @@ require "action_cable"
4
4
 
5
5
  module SolidObjects
6
6
  class ActorChannel < ActionCable::Channel::Base
7
+ REJECT_REASONS = {
8
+ UnknownActorType => "unregistered_actor_type",
9
+ InvalidStreamToken => "invalid_stream_token",
10
+ InvalidComponentToken => "invalid_component_token",
11
+ JSON::ParserError => "malformed_component_registration",
12
+ KeyError => "missing_subscription_parameter"
13
+ }.freeze
14
+
7
15
  # @rbs () -> void
8
16
  def subscribed
9
17
  identity = StreamToken.verify(params.fetch("token"))
@@ -15,7 +23,9 @@ module SolidObjects
15
23
  actor_id:,
16
24
  authorization_context: connection
17
25
  )
18
- return reject unless authorized
26
+ unless authorized
27
+ return reject_and_report("unauthorized", actor_type:, actor_id:)
28
+ end
19
29
 
20
30
  @reference = Reference.new(actor_type:, actor_id:)
21
31
  @scalar_observables = identity["observables"]
@@ -43,8 +53,8 @@ module SolidObjects
43
53
  JSON::ParserError,
44
54
  InvalidStreamToken,
45
55
  InvalidComponentToken,
46
- UnknownActorType
47
- reject
56
+ UnknownActorType => error
57
+ reject_and_report(reject_reason(error), actor_type:, actor_id:, error:)
48
58
  end
49
59
 
50
60
  private
@@ -54,6 +64,26 @@ module SolidObjects
54
64
  :scalar_observables,
55
65
  :payload_names
56
66
 
67
+ # The exception message stays out of the payload, because a component or
68
+ # payload error can carry actor state into logs.
69
+ # @rbs (String, actor_type: String?, actor_id: String?, ?error: Exception?) -> void
70
+ def reject_and_report(reason, actor_type:, actor_id:, error: nil)
71
+ SolidObjects.instrument(
72
+ :"subscription.rejected",
73
+ reason:,
74
+ actor_type:,
75
+ actor_id:,
76
+ error_class: error&.class&.name
77
+ )
78
+ reject
79
+ end
80
+
81
+ # @rbs (Exception) -> String
82
+ def reject_reason(error)
83
+ match = REJECT_REASONS.find { |error_class, _| error.is_a?(error_class) }
84
+ match ? match.last : "invalid_subscription"
85
+ end
86
+
57
87
  # @rbs (String) -> void
58
88
  def receive_broadcast(stream)
59
89
  invalidation = TurboStreamRenderer.invalidation(stream)
@@ -15,6 +15,10 @@ module SolidObjects
15
15
  SolidObjects::LogSubscriber.install
16
16
  end
17
17
 
18
+ initializer "solid_objects.actors" do |application|
19
+ application.config.to_prepare { ApplicationActorLoader.new.call }
20
+ end
21
+
18
22
  initializer "solid_objects.database", after: :load_config_initializers do
19
23
  ActiveSupport.on_load(:active_record) do
20
24
  require RECORD_PATH
@@ -1,5 +1,5 @@
1
1
  # rbs_inline: enabled
2
2
 
3
3
  module SolidObjects
4
- VERSION = "0.14.0"
4
+ VERSION = "0.14.2"
5
5
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module SolidObjects
4
4
  class ActorChannel < ActionCable::Channel::Base
5
+ REJECT_REASONS: untyped
6
+
5
7
  # @rbs () -> void
6
8
  def subscribed: () -> void
7
9
 
@@ -15,6 +17,14 @@ module SolidObjects
15
17
 
16
18
  attr_reader payload_names: untyped
17
19
 
20
+ # The exception message stays out of the payload, because a component or
21
+ # payload error can carry actor state into logs.
22
+ # @rbs (String, actor_type: String?, actor_id: String?, ?error: Exception?) -> void
23
+ def reject_and_report: (String, actor_type: String?, actor_id: String?, ?error: Exception?) -> void
24
+
25
+ # @rbs (Exception) -> String
26
+ def reject_reason: (Exception) -> String
27
+
18
28
  # @rbs (String) -> void
19
29
  def receive_broadcast: (String) -> void
20
30
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_objects
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.14.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Carlson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-23 00:00:00.000000000 Z
11
+ date: 2026-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actioncable
@@ -355,6 +355,7 @@ files:
355
355
  - docs/migrating-existing-state.md
356
356
  - docs/operations.md
357
357
  - docs/realtime.md
358
+ - docs/reminders.md
358
359
  - docs/research/solid_queue.md
359
360
  - docs/roadmap.md
360
361
  - docs/security.md
@@ -371,6 +372,11 @@ files:
371
372
  - examples/application/app/views/chat_rooms/show.html.erb
372
373
  - examples/application/config/initializers/solid_objects.rb
373
374
  - examples/application/config/routes.rb
375
+ - examples/at_least_once/actor.rb
376
+ - examples/at_least_once/boot.rb
377
+ - examples/at_least_once/demo.rb
378
+ - examples/at_least_once/effect_worker.rb
379
+ - examples/at_least_once/sink.rb
374
380
  - exe/solid_objects
375
381
  - lib/generators/solid_objects/install_generator.rb
376
382
  - lib/generators/solid_objects/templates/solid_objects.rb