solid_objects 0.10.3 → 0.12.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +72 -16
  4. data/app/controllers/solid_objects/components_controller.rb +4 -4
  5. data/app/models/solid_objects/message.rb +1 -1
  6. data/app/views/solid_objects/dead_letters/index.html.erb +1 -1
  7. data/app/views/solid_objects/instances/show.html.erb +1 -1
  8. data/benchmark/support.rb +10 -10
  9. data/db/migrate/20260813000000_rename_message_dispatch_columns.rb +53 -0
  10. data/docs/architecture.md +11 -9
  11. data/docs/authorization.md +2 -2
  12. data/docs/database-schema.md +4 -4
  13. data/docs/development.md +1 -1
  14. data/docs/migrating-existing-state.md +1 -3
  15. data/docs/roadmap.md +1 -1
  16. data/lib/solid_objects/actor.rb +43 -32
  17. data/lib/solid_objects/actor_channel.rb +3 -3
  18. data/lib/solid_objects/actor_definition.rb +9 -6
  19. data/lib/solid_objects/actor_view.rb +1 -1
  20. data/lib/solid_objects/application_write_guard.rb +1 -1
  21. data/lib/solid_objects/client.rb +52 -52
  22. data/lib/solid_objects/component_registration.rb +2 -2
  23. data/lib/solid_objects/component_renderer.rb +1 -1
  24. data/lib/solid_objects/component_subscriptions.rb +21 -21
  25. data/lib/solid_objects/configuration.rb +42 -0
  26. data/lib/solid_objects/database_adapters/sqlite.rb +7 -1
  27. data/lib/solid_objects/dead_letter_manager.rb +4 -4
  28. data/lib/solid_objects/doctor.rb +8 -8
  29. data/lib/solid_objects/effect_executor.rb +22 -22
  30. data/lib/solid_objects/errors.rb +27 -27
  31. data/lib/solid_objects/executor.rb +40 -38
  32. data/lib/solid_objects/lease.rb +3 -3
  33. data/lib/solid_objects/mailbox.rb +34 -27
  34. data/lib/solid_objects/operation_dispatcher.rb +51 -0
  35. data/lib/solid_objects/payload_broadcast.rb +1 -1
  36. data/lib/solid_objects/reference.rb +60 -29
  37. data/lib/solid_objects/reminder_scheduler.rb +6 -6
  38. data/lib/solid_objects/supervisor.rb +50 -9
  39. data/lib/solid_objects/sync_diagnostics.rb +6 -6
  40. data/lib/solid_objects/turbo_stream_renderer.rb +13 -13
  41. data/lib/solid_objects/version.rb +1 -1
  42. data/lib/solid_objects.rb +1 -0
  43. data/sig/generated/controllers/solid_objects/components_controller.rbs +2 -2
  44. data/sig/generated/lib/solid_objects/actor.rbs +17 -17
  45. data/sig/generated/lib/solid_objects/actor_definition.rbs +4 -4
  46. data/sig/generated/lib/solid_objects/client.rbs +8 -8
  47. data/sig/generated/lib/solid_objects/component_registration.rbs +2 -2
  48. data/sig/generated/lib/solid_objects/component_subscriptions.rbs +8 -8
  49. data/sig/generated/lib/solid_objects/configuration.rbs +32 -4
  50. data/sig/generated/lib/solid_objects/database_adapters/sqlite.rbs +3 -0
  51. data/sig/generated/lib/solid_objects/effect_executor.rbs +2 -2
  52. data/sig/generated/lib/solid_objects/errors.rbs +18 -18
  53. data/sig/generated/lib/solid_objects/executor.rbs +10 -10
  54. data/sig/generated/lib/solid_objects/lease.rbs +2 -2
  55. data/sig/generated/lib/solid_objects/mailbox.rbs +8 -8
  56. data/sig/generated/lib/solid_objects/operation_dispatcher.rbs +35 -0
  57. data/sig/generated/lib/solid_objects/reference.rbs +7 -4
  58. data/sig/generated/lib/solid_objects/supervisor.rbs +19 -2
  59. data/sig/generated/lib/solid_objects/sync_diagnostics.rbs +2 -2
  60. data/sig/generated/lib/solid_objects/turbo_stream_renderer.rbs +8 -8
  61. metadata +5 -2
@@ -16,41 +16,50 @@ module SolidObjects
16
16
  freeze
17
17
  end
18
18
 
19
- # @rbs (Symbol | String, ?available_at: Time?, ?idempotency_key: String?, ?authorization_context: untyped, **untyped) -> MessageReference?
20
- def async(message_name, available_at: nil, idempotency_key: nil, authorization_context: nil, **arguments)
21
- if (actor = Context.current_actor)
22
- actor.stage_outbound_message(
23
- self,
24
- message_name,
25
- arguments,
19
+ # @rbs (?available_at: Time?, ?idempotency_key: String?, ?authorization_context: untyped) -> OperationDispatcher
20
+ def async(available_at: nil, idempotency_key: nil, authorization_context: nil)
21
+ OperationDispatcher.new(
22
+ actor_type:,
23
+ handlers: actor_definition.messages
24
+ ) do |operation, arguments|
25
+ if (actor = Context.current_actor)
26
+ actor.stage_outbound_message(
27
+ reference: self,
28
+ operation:,
29
+ arguments:,
30
+ available_at:,
31
+ idempotency_key:
32
+ )
33
+ next
34
+ end
35
+
36
+ SolidObjects.client.async(
37
+ reference: self,
38
+ operation:,
39
+ arguments:,
26
40
  available_at:,
27
- idempotency_key:
41
+ idempotency_key:,
42
+ authorization_context:
28
43
  )
29
- return
30
44
  end
31
-
32
- SolidObjects.client.async(
33
- self,
34
- message_name,
35
- arguments,
36
- available_at:,
37
- idempotency_key:,
38
- authorization_context:
39
- )
40
45
  end
41
46
 
42
- # @rbs (Symbol | String, ?timeout: Numeric, ?idempotency_key: String?, ?authorization_context: untyped, **untyped) -> untyped
43
- def sync(message_name, timeout: 5.seconds, idempotency_key: nil, authorization_context: nil, **arguments)
47
+ # @rbs (?timeout: Numeric, ?idempotency_key: String?, ?authorization_context: untyped) -> OperationDispatcher
48
+ def sync(timeout: 5.seconds, idempotency_key: nil, authorization_context: nil)
44
49
  raise ActorCallCycle, "actors cannot synchronously wait for another actor" if Context.current_actor
45
50
 
46
- SolidObjects.client.sync(
47
- self,
48
- message_name,
49
- arguments,
50
- timeout:,
51
- idempotency_key:,
52
- authorization_context:
53
- )
51
+ OperationDispatcher.new(
52
+ actor_type:,
53
+ handlers: actor_definition.messages.merge(actor_definition.queries)
54
+ ) do |operation, arguments|
55
+ invoke_synchronously(
56
+ operation,
57
+ arguments,
58
+ timeout:,
59
+ idempotency_key:,
60
+ authorization_context:
61
+ )
62
+ end
54
63
  end
55
64
 
56
65
  # @rbs (?authorization_context: untyped) -> bool
@@ -66,7 +75,15 @@ module SolidObjects
66
75
  # @rbs (Symbol, *untyped, **untyped) -> untyped
67
76
  def method_missing(name, *arguments, **keywords)
68
77
  return super if arguments.any? || block_given?
69
- return sync(name, **keywords) if actor_message?(name) || actor_query?(name)
78
+ if actor_message?(name) || actor_query?(name)
79
+ return invoke_synchronously(
80
+ name,
81
+ keywords,
82
+ timeout: 5.seconds,
83
+ idempotency_key: nil,
84
+ authorization_context: nil
85
+ )
86
+ end
70
87
 
71
88
  super
72
89
  end
@@ -83,6 +100,20 @@ module SolidObjects
83
100
 
84
101
  private
85
102
 
103
+ # @rbs (Symbol | String, Hash[Symbol, untyped], timeout: Numeric, idempotency_key: String?, authorization_context: untyped) -> untyped
104
+ def invoke_synchronously(operation, arguments, timeout:, idempotency_key:, authorization_context:)
105
+ raise ActorCallCycle, "actors cannot synchronously wait for another actor" if Context.current_actor
106
+
107
+ SolidObjects.client.sync(
108
+ reference: self,
109
+ operation:,
110
+ arguments:,
111
+ timeout:,
112
+ idempotency_key:,
113
+ authorization_context:
114
+ )
115
+ end
116
+
86
117
  # @rbs (Symbol | String) -> bool
87
118
  def actor_message?(name)
88
119
  actor_definition.messages.key?(name.to_sym)
@@ -97,8 +97,8 @@ module SolidObjects
97
97
  # @rbs (Reminder) -> MessageReference?
98
98
  def enqueue(reminder)
99
99
  actor_class = SolidObjects.registry.fetch(reminder.actor_type)
100
- unless actor_class.definition.messages.key?(reminder.message_name.to_sym)
101
- raise UnknownMessage, "unknown reminder message #{reminder.message_name.inspect}"
100
+ unless actor_class.definition.messages.key?(reminder.operation.to_sym)
101
+ raise UnknownMessage, "unknown reminder operation #{reminder.operation.inspect}"
102
102
  end
103
103
 
104
104
  mailbox = Mailbox.new(database_adapter:)
@@ -111,10 +111,10 @@ module SolidObjects
111
111
  verify_claim!(locked_reminder)
112
112
  now = database_adapter.database_now
113
113
  message = mailbox.enqueue_in_transaction(
114
- Reference.new(actor_type: instance.actor_type, actor_id: instance.actor_id),
115
- locked_reminder.message_name,
116
- locked_reminder.arguments,
117
- kind: "internal",
114
+ reference: Reference.new(actor_type: instance.actor_type, actor_id: instance.actor_id),
115
+ operation: locked_reminder.operation,
116
+ arguments: locked_reminder.arguments,
117
+ delivery_mode: "internal",
118
118
  idempotency_key: "reminder:#{locked_reminder.id}:#{locked_reminder.occurrence}",
119
119
  actor_class:
120
120
  )
@@ -19,12 +19,13 @@ module SolidObjects
19
19
  broadcast_worker_count: SolidObjects.configuration.broadcast_worker_count,
20
20
  reminder_scheduler_count: SolidObjects.configuration.reminder_scheduler_count
21
21
  )
22
- @components = build_components(
22
+ @builders = component_builders(
23
23
  worker_count:,
24
24
  effect_worker_count:,
25
25
  broadcast_worker_count:,
26
26
  reminder_scheduler_count:
27
27
  )
28
+ @components = build_all(@builders)
28
29
  @threads = []
29
30
  @monitor = nil
30
31
  @started = false
@@ -77,7 +78,7 @@ module SolidObjects
77
78
 
78
79
  private
79
80
 
80
- attr_reader :components, :threads
81
+ attr_reader :components, :threads, :builders
81
82
 
82
83
  # A role that raises leaves its thread dead. Without replacement the
83
84
  # process keeps running while quietly doing less work, so the supervisor
@@ -116,7 +117,11 @@ module SolidObjects
116
117
  replaced = @lifecycle.synchronize do
117
118
  next false unless @started
118
119
 
119
- replacement = component.class.new
120
+ # A component built by this supervisor has a builder, which carries
121
+ # whatever the constructor was given. A component put in place by
122
+ # other means has none, so the class is the only thing left to go on.
123
+ builder = builders[index] || -> { component.class.new }
124
+ replacement = builder.call
120
125
  components[index] = replacement
121
126
  threads[index] = supervise(replacement)
122
127
  replacement
@@ -250,17 +255,53 @@ module SolidObjects
250
255
  nil
251
256
  end
252
257
 
253
- # @rbs (worker_count: Integer, effect_worker_count: Integer, broadcast_worker_count: Integer, reminder_scheduler_count: Integer) -> Array[Worker | EffectExecutor | ReminderScheduler | BroadcastExecutor]
254
- def build_components(
258
+ # A constructor can take a resource, and a later builder can raise. Without
259
+ # this, the components built first would be dropped while still holding
260
+ # whatever they took, and nothing would ever give it back.
261
+ # @rbs (Array[^() -> untyped]) -> Array[untyped]
262
+ def build_all(builders)
263
+ built = []
264
+ builders.each do |builder|
265
+ # The component joins the list before the contract check, so a
266
+ # component that fails the check is stopped along with the rest.
267
+ built << (component = builder.call)
268
+ SolidObjects.configuration.validate_component!(component)
269
+ end
270
+ built
271
+ rescue Exception # rubocop:disable Lint/RescueException
272
+ built.each { |component| stop_after_failed_build(component) }
273
+ raise
274
+ end
275
+
276
+ # The failure that stopped the build is the one worth reporting, so a
277
+ # failure inside the cleanup never replaces it.
278
+ # @rbs (untyped) -> void
279
+ def stop_after_failed_build(component)
280
+ component.stop if component.respond_to?(:stop)
281
+ rescue Exception => error # rubocop:disable Lint/RescueException
282
+ SolidObjects.instrument(
283
+ :"supervisor.component_cleanup_failed",
284
+ role: component.class.name,
285
+ error_class: error.class.name
286
+ )
287
+ end
288
+
289
+ # Each component keeps the builder that made it, so a replacement after a
290
+ # crash is built the same way as the original. Components registered
291
+ # through the configuration run beside the built in ones, under the same
292
+ # supervision, restart, and shutdown timeout.
293
+ # @rbs (worker_count: Integer, effect_worker_count: Integer, broadcast_worker_count: Integer, reminder_scheduler_count: Integer) -> Array[^() -> untyped]
294
+ def component_builders(
255
295
  worker_count:,
256
296
  effect_worker_count:,
257
297
  broadcast_worker_count:,
258
298
  reminder_scheduler_count:
259
299
  )
260
- Array.new(worker_count) { Worker.new } +
261
- Array.new(effect_worker_count) { EffectExecutor.new } +
262
- Array.new(broadcast_worker_count) { BroadcastExecutor.new } +
263
- Array.new(reminder_scheduler_count) { ReminderScheduler.new }
300
+ Array.new(worker_count) { -> { Worker.new } } +
301
+ Array.new(effect_worker_count) { -> { EffectExecutor.new } } +
302
+ Array.new(broadcast_worker_count) { -> { BroadcastExecutor.new } } +
303
+ Array.new(reminder_scheduler_count) { -> { ReminderScheduler.new } } +
304
+ SolidObjects.configuration.additional_components
264
305
  end
265
306
 
266
307
  # @rbs () -> void
@@ -8,7 +8,7 @@ module SolidObjects
8
8
  instance = Instance.uncached { Instance.find(message.instance_id) }
9
9
  blocker = earlier_blocker(message)
10
10
  status = message_status(message)
11
- waiting_on = waiting_reason(message, instance, blocker)
11
+ waiting_on = waiting_reason(message:, instance:, blocker:)
12
12
  activation = activation_details(instance)
13
13
  build_error(
14
14
  message,
@@ -38,7 +38,7 @@ module SolidObjects
38
38
  timeout:,
39
39
  actor_type: message_reference.actor_type,
40
40
  actor_id: message_reference.actor_id,
41
- message_name: "unknown",
41
+ operation: "unknown",
42
42
  message_id: message_reference.id,
43
43
  request_id: message_reference.request_id,
44
44
  sequence: message_reference.sequence,
@@ -70,7 +70,7 @@ module SolidObjects
70
70
  timeout:,
71
71
  actor_type: message.actor_type,
72
72
  actor_id: message.actor_id,
73
- message_name: message.message_name,
73
+ operation: message.operation,
74
74
  message_id: message.id,
75
75
  request_id: message.request_id,
76
76
  sequence: message.sequence,
@@ -105,8 +105,8 @@ module SolidObjects
105
105
  "unknown"
106
106
  end
107
107
 
108
- # @rbs (Message, Instance, Message?) -> String
109
- def waiting_reason(message, instance, blocker)
108
+ # @rbs (message: Message, instance: Instance, blocker: Message?) -> String
109
+ def waiting_reason(message:, instance:, blocker:)
110
110
  return "actor_paused" if instance.paused_at
111
111
  return "activation_held" if live_activation?(instance)
112
112
  return "earlier_message" if blocker
@@ -179,7 +179,7 @@ module SolidObjects
179
179
  {
180
180
  "message_id" => blocker.id,
181
181
  "sequence" => blocker.sequence,
182
- "message_name" => blocker.message_name,
182
+ "operation" => blocker.operation,
183
183
  "status" => message_status(blocker)
184
184
  }
185
185
  end
@@ -20,9 +20,9 @@ module SolidObjects
20
20
  ""
21
21
  else
22
22
  observable_value(
23
- reference,
24
- broadcast.observable_name,
25
- broadcast.value
23
+ reference:,
24
+ name: broadcast.observable_name,
25
+ value: broadcast.value
26
26
  )
27
27
  end
28
28
  metadata = Base64.urlsafe_encode64(
@@ -35,16 +35,16 @@ module SolidObjects
35
35
  "#{stream}<!--#{INVALIDATION_MARKER}#{metadata}-->"
36
36
  end
37
37
 
38
- # @rbs (Reference, Symbol | String, untyped) -> String
39
- def observable_value(reference, name, value)
38
+ # @rbs (reference: Reference, name: Symbol | String, value: untyped) -> String
39
+ def observable_value(reference:, name:, value:)
40
40
  target = DomIdentity.observable(reference, name)
41
41
  content = ERB::Util.html_escape(display_value(value))
42
42
  %(<turbo-stream action="replace" target="#{target}"><template><span id="#{target}">#{content}</span></template></turbo-stream>)
43
43
  end
44
44
 
45
- # @rbs (ComponentRegistration, Integer, Integer) -> String
46
- def component_refresh(registration, instance_id, revision)
47
- return morph_component_refresh(registration, instance_id, revision) if registration.morph?
45
+ # @rbs (registration: ComponentRegistration, instance_id: Integer, revision: Integer) -> String
46
+ def component_refresh(registration:, instance_id:, revision:)
47
+ return morph_component_refresh(registration:, instance_id:, revision:) if registration.morph?
48
48
 
49
49
  target = registration.dom_id
50
50
  source = ERB::Util.html_escape(
@@ -54,13 +54,13 @@ module SolidObjects
54
54
  %(<turbo-stream action="replace" target="#{target}"><template><turbo-frame id="#{target}" src="#{source}" data-solid-objects-revision="#{revision_value}" data-solid-objects-refresh="replace"></turbo-frame></template></turbo-stream>)
55
55
  end
56
56
 
57
- # @rbs (Array[ComponentRegistration], Integer, Integer) -> String
58
- def batch_refresh(registrations, instance_id, revision)
57
+ # @rbs (registrations: Array[ComponentRegistration], instance_id: Integer, revision: Integer) -> String
58
+ def batch_refresh(registrations:, instance_id:, revision:)
59
59
  first = registrations.first
60
60
  scope = DomIdentity.scope(first.reference)
61
61
  batch = ERB::Util.html_escape(first.batch)
62
62
  source = ERB::Util.html_escape(
63
- first.batch_refresh_url(registrations, instance_id, revision)
63
+ first.batch_refresh_url(registrations:, instance_id:, revision:)
64
64
  )
65
65
  targets = ERB::Util.html_escape(registrations.map(&:dom_id).join(" "))
66
66
  %(<turbo-stream action="append" target="#{scope}"><template><solid-objects-batch-refresh data-batch="#{batch}" data-revision="#{instance_id}:#{revision}" data-targets="#{targets}" data-source="#{source}"></solid-objects-batch-refresh></template></turbo-stream>)
@@ -104,8 +104,8 @@ module SolidObjects
104
104
  end
105
105
  private_class_method :display_value
106
106
 
107
- # @rbs (ComponentRegistration, Integer, Integer) -> String
108
- def morph_component_refresh(registration, instance_id, revision)
107
+ # @rbs (registration: ComponentRegistration, instance_id: Integer, revision: Integer) -> String
108
+ def morph_component_refresh(registration:, instance_id:, revision:)
109
109
  source = ERB::Util.html_escape(
110
110
  registration.refresh_url(instance_id, revision)
111
111
  )
@@ -1,5 +1,5 @@
1
1
  # rbs_inline: enabled
2
2
 
3
3
  module SolidObjects
4
- VERSION = "0.10.3"
4
+ VERSION = "0.12.0"
5
5
  end
data/lib/solid_objects.rb CHANGED
@@ -21,6 +21,7 @@ require "solid_objects/serialization"
21
21
  require "solid_objects/context"
22
22
  require "solid_objects/actor_registry"
23
23
  require "solid_objects/state"
24
+ require "solid_objects/operation_dispatcher"
24
25
  require "solid_objects/actor_definition"
25
26
  require "solid_objects/application_write_guard"
26
27
  require "solid_objects/actor"
@@ -39,7 +39,7 @@ module SolidObjects
39
39
  # @rbs () -> untyped
40
40
  def component_view_context: () -> untyped
41
41
 
42
- # @rbs (ComponentRegistration, ActorSnapshot, untyped) -> String
43
- def component_frame: (ComponentRegistration, ActorSnapshot, untyped) -> String
42
+ # @rbs (registration: ComponentRegistration, snapshot: ActorSnapshot, rendered: untyped) -> String
43
+ def component_frame: (registration: ComponentRegistration, snapshot: ActorSnapshot, rendered: untyped) -> String
44
44
  end
45
45
  end
@@ -7,16 +7,16 @@ module SolidObjects
7
7
 
8
8
  attr_reader arguments(): untyped
9
9
 
10
- attr_reader success_message_name(): untyped
10
+ attr_reader success_operation(): untyped
11
11
 
12
- attr_reader failure_message_name(): untyped
12
+ attr_reader failure_operation(): untyped
13
13
 
14
- def self.new: (untyped name, untyped arguments, untyped success_message_name, untyped failure_message_name) -> instance
15
- | (name: untyped, arguments: untyped, success_message_name: untyped, failure_message_name: untyped) -> instance
14
+ def self.new: (untyped name, untyped arguments, untyped success_operation, untyped failure_operation) -> instance
15
+ | (name: untyped, arguments: untyped, success_operation: untyped, failure_operation: untyped) -> instance
16
16
 
17
- def self.members: () -> [ :name, :arguments, :success_message_name, :failure_message_name ]
17
+ def self.members: () -> [ :name, :arguments, :success_operation, :failure_operation ]
18
18
 
19
- def members: () -> [ :name, :arguments, :success_message_name, :failure_message_name ]
19
+ def members: () -> [ :name, :arguments, :success_operation, :failure_operation ]
20
20
  end
21
21
 
22
22
  class CommitActionIntent < Data
@@ -56,7 +56,7 @@ module SolidObjects
56
56
 
57
57
  attr_reader actor_id(): untyped
58
58
 
59
- attr_reader message_name(): untyped
59
+ attr_reader operation(): untyped
60
60
 
61
61
  attr_reader arguments(): untyped
62
62
 
@@ -64,12 +64,12 @@ module SolidObjects
64
64
 
65
65
  attr_reader idempotency_key(): untyped
66
66
 
67
- def self.new: (untyped actor_type, untyped actor_id, untyped message_name, untyped arguments, untyped available_at, untyped idempotency_key) -> instance
68
- | (actor_type: untyped, actor_id: untyped, message_name: untyped, arguments: untyped, available_at: untyped, idempotency_key: untyped) -> instance
67
+ def self.new: (untyped actor_type, untyped actor_id, untyped operation, untyped arguments, untyped available_at, untyped idempotency_key) -> instance
68
+ | (actor_type: untyped, actor_id: untyped, operation: untyped, arguments: untyped, available_at: untyped, idempotency_key: untyped) -> instance
69
69
 
70
- def self.members: () -> [ :actor_type, :actor_id, :message_name, :arguments, :available_at, :idempotency_key ]
70
+ def self.members: () -> [ :actor_type, :actor_id, :operation, :arguments, :available_at, :idempotency_key ]
71
71
 
72
- def members: () -> [ :actor_type, :actor_id, :message_name, :arguments, :available_at, :idempotency_key ]
72
+ def members: () -> [ :actor_type, :actor_id, :operation, :arguments, :available_at, :idempotency_key ]
73
73
  end
74
74
 
75
75
  # @rbs (Class) -> void
@@ -157,11 +157,11 @@ module SolidObjects
157
157
  # @rbs (Symbol | String, **untyped) -> nil
158
158
  def commit_action: (Symbol | String, **untyped) -> nil
159
159
 
160
- # @rbs (Symbol | String, at: Time, ?every: Numeric?, ?missed: Symbol | String, arguments: Hash[Symbol | String, untyped]) -> nil
161
- def schedule: (Symbol | String, at: Time, arguments: Hash[Symbol | String, untyped], ?every: Numeric?, ?missed: Symbol | String) -> nil
160
+ # @rbs (at: Time, ?every: Numeric?, ?missed: Symbol | String) -> OperationDispatcher
161
+ def schedule: (at: Time, ?every: Numeric?, ?missed: Symbol | String) -> OperationDispatcher
162
162
 
163
- # @rbs (Reference, Symbol | String, ?available_at: Time?, ?idempotency_key: String?, **untyped) -> nil
164
- def send_to: (Reference, Symbol | String, ?available_at: Time?, ?idempotency_key: String?, **untyped) -> nil
163
+ # @rbs (Reference, ?available_at: Time?, ?idempotency_key: String?) -> OperationDispatcher
164
+ def send_to: (Reference, ?available_at: Time?, ?idempotency_key: String?) -> OperationDispatcher
165
165
 
166
166
  # @rbs (Symbol | String, Hash[String, untyped]) -> untyped
167
167
  def invoke: (Symbol | String, Hash[String, untyped]) -> untyped
@@ -178,8 +178,8 @@ module SolidObjects
178
178
  # @rbs () -> void
179
179
  def deactivate: () -> void
180
180
 
181
- # @rbs (Reference, Symbol | String, Hash[Symbol | String, untyped], ?available_at: Time?, idempotency_key: String?) -> OutboundMessageIntent
182
- def stage_outbound_message: (Reference, Symbol | String, Hash[Symbol | String, untyped], idempotency_key: String?, ?available_at: Time?) -> OutboundMessageIntent
181
+ # @rbs (reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], ?available_at: Time?, idempotency_key: String?) -> OutboundMessageIntent
182
+ def stage_outbound_message: (reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], idempotency_key: String?, ?available_at: Time?) -> OutboundMessageIntent
183
183
 
184
184
  # @rbs () -> Array[EffectIntent]
185
185
  def drain_effect_intents: () -> Array[EffectIntent]
@@ -94,8 +94,8 @@ module SolidObjects
94
94
  # @rbs (Integer) -> Integer
95
95
  def set_state_version: (Integer) -> Integer
96
96
 
97
- # @rbs (Integer, Integer, Proc) -> StateMigration
98
- def add_state_migration: (Integer, Integer, Proc) -> StateMigration
97
+ # @rbs (from: Integer, to: Integer, block: Proc) -> StateMigration
98
+ def add_state_migration: (from: Integer, to: Integer, block: Proc) -> StateMigration
99
99
 
100
100
  # @rbs (Integer, Hash[String, untyped]) -> Hash[String, untyped]
101
101
  def migrate_state: (Integer, Hash[String, untyped]) -> Hash[String, untyped]
@@ -118,7 +118,7 @@ module SolidObjects
118
118
  # @rbs (Array[Symbol]) -> void
119
119
  def validate_attribute_methods!: (Array[Symbol]) -> void
120
120
 
121
- # @rbs (Hash[Symbol, Handler], Symbol | String, Proc) -> Handler
122
- def add_handler: (Hash[Symbol, Handler], Symbol | String, Proc) -> Handler
121
+ # @rbs (collection: Hash[Symbol, Handler], name: Symbol | String, block: Proc) -> Handler
122
+ def add_handler: (collection: Hash[Symbol, Handler], name: Symbol | String, block: Proc) -> Handler
123
123
  end
124
124
  end
@@ -7,11 +7,11 @@ module SolidObjects
7
7
  # @rbs (?mailbox: Mailbox) -> void
8
8
  def initialize: (?mailbox: Mailbox) -> void
9
9
 
10
- # @rbs (Reference, Symbol | String, Hash[Symbol | String, untyped], ?available_at: Time?, ?idempotency_key: String?, ?authorization_context: untyped) -> MessageReference
11
- def async: (Reference, Symbol | String, Hash[Symbol | String, untyped], ?available_at: Time?, ?idempotency_key: String?, ?authorization_context: untyped) -> MessageReference
10
+ # @rbs (reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], ?available_at: Time?, ?idempotency_key: String?, ?authorization_context: untyped) -> MessageReference
11
+ def async: (reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], ?available_at: Time?, ?idempotency_key: String?, ?authorization_context: untyped) -> MessageReference
12
12
 
13
- # @rbs (Reference, Symbol | String, Hash[Symbol | String, untyped], timeout: Numeric, ?idempotency_key: String?, ?authorization_context: untyped) -> untyped
14
- def sync: (Reference, Symbol | String, Hash[Symbol | String, untyped], timeout: Numeric, ?idempotency_key: String?, ?authorization_context: untyped) -> untyped
13
+ # @rbs (reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], timeout: Numeric, ?idempotency_key: String?, ?authorization_context: untyped) -> untyped
14
+ def sync: (reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], timeout: Numeric, ?idempotency_key: String?, ?authorization_context: untyped) -> untyped
15
15
 
16
16
  # @rbs (MessageReference, timeout: Numeric, ?authorization_context: untyped) -> untyped
17
17
  def wait: (MessageReference, timeout: Numeric, ?authorization_context: untyped) -> untyped
@@ -26,8 +26,8 @@ module SolidObjects
26
26
 
27
27
  attr_reader mailbox: untyped
28
28
 
29
- # @rbs (Reference, Symbol | String, Hash[Symbol | String, untyped], idempotency_key: String?, timeout: Numeric) -> MessageReference
30
- def enqueue_sync: (Reference, Symbol | String, Hash[Symbol | String, untyped], idempotency_key: String?, timeout: Numeric) -> MessageReference
29
+ # @rbs (reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], idempotency_key: String?, timeout: Numeric) -> MessageReference
30
+ def enqueue_sync: (reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], idempotency_key: String?, timeout: Numeric) -> MessageReference
31
31
 
32
32
  # @rbs (MessageReference, Message) -> void
33
33
  def validate_message_reference!: (MessageReference, Message) -> void
@@ -35,8 +35,8 @@ module SolidObjects
35
35
  # @rbs (Reference, Symbol | String) -> void
36
36
  def reject_sync_inside_transaction!: (Reference, Symbol | String) -> void
37
37
 
38
- # @rbs (Proc, Reference, Symbol | String, Hash[Symbol | String, untyped], authorization_context: untyped) -> void
39
- def authorize!: (Proc, Reference, Symbol | String, Hash[Symbol | String, untyped], authorization_context: untyped) -> void
38
+ # @rbs (hook: Proc, reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], authorization_context: untyped) -> void
39
+ def authorize!: (hook: Proc, reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], authorization_context: untyped) -> void
40
40
 
41
41
  # @rbs (Reference, authorization_context: untyped) -> void
42
42
  def authorize_destroy!: (Reference, authorization_context: untyped) -> void
@@ -73,8 +73,8 @@ module SolidObjects
73
73
  # @rbs () -> Hash[String, untyped]
74
74
  def authorization_arguments: () -> Hash[String, untyped]
75
75
 
76
- # @rbs (Array[ComponentRegistration], Integer, Integer) -> String
77
- def batch_refresh_url: (Array[ComponentRegistration], Integer, Integer) -> String
76
+ # @rbs (registrations: Array[ComponentRegistration], instance_id: Integer, revision: Integer) -> String
77
+ def batch_refresh_url: (registrations: Array[ComponentRegistration], instance_id: Integer, revision: Integer) -> String
78
78
 
79
79
  # @rbs (Integer, Integer) -> String
80
80
  def refresh_url: (Integer, Integer) -> String
@@ -33,16 +33,16 @@ module SolidObjects
33
33
 
34
34
  # Live invalidations and reconnect replays share this, so a reconnecting
35
35
  # client pays the same number of requests a connected one does.
36
- # @rbs (Array[ComponentRegistration], Integer, Integer) -> Array[String]
37
- def refresh_streams: (Array[ComponentRegistration], Integer, Integer) -> Array[String]
36
+ # @rbs (changed: Array[ComponentRegistration], instance_id: Integer, revision: Integer) -> Array[String]
37
+ def refresh_streams: (changed: Array[ComponentRegistration], instance_id: Integer, revision: Integer) -> Array[String]
38
38
 
39
- # @rbs (ComponentRegistration, Integer, Integer) -> void
40
- def record_revision: (ComponentRegistration, Integer, Integer) -> void
39
+ # @rbs (registration: ComponentRegistration, instance_id: Integer, revision: Integer) -> void
40
+ def record_revision: (registration: ComponentRegistration, instance_id: Integer, revision: Integer) -> void
41
41
 
42
- # @rbs (ComponentRegistration, Integer, Integer) -> String
43
- def refresh: (ComponentRegistration, Integer, Integer) -> String
42
+ # @rbs (registration: ComponentRegistration, instance_id: Integer, revision: Integer) -> String
43
+ def refresh: (registration: ComponentRegistration, instance_id: Integer, revision: Integer) -> String
44
44
 
45
- # @rbs (String, Integer, Integer) -> bool
46
- def newer_revision?: (String, Integer, Integer) -> bool
45
+ # @rbs (dom_id: String, instance_id: Integer, revision: Integer) -> bool
46
+ def newer_revision?: (dom_id: String, instance_id: Integer, revision: Integer) -> bool
47
47
  end
48
48
  end
@@ -2,6 +2,10 @@
2
2
 
3
3
  module SolidObjects
4
4
  class Configuration
5
+ @process_alive_threshold: Float
6
+
7
+ @shutdown_timeout: Float
8
+
5
9
  @supervisor_monitor_interval: Float
6
10
 
7
11
  @retention_interval: Float
@@ -86,10 +90,6 @@ module SolidObjects
86
90
 
87
91
  @process_heartbeat_interval: Float
88
92
 
89
- @process_alive_threshold: Float
90
-
91
- @shutdown_timeout: Float
92
-
93
93
  attr_accessor table_name_prefix: untyped
94
94
 
95
95
  attr_accessor polling_interval: untyped
@@ -178,9 +178,37 @@ module SolidObjects
178
178
 
179
179
  attr_accessor authorize_administration: untyped
180
180
 
181
+ # @rbs @additional_components: Array[untyped]
182
+ attr_reader additional_components: untyped
183
+
181
184
  # @rbs () -> void
182
185
  def initialize: () -> void
183
186
 
187
+ # Registers a long running component that the supervisor runs beside its own
188
+ # workers. An extension gem uses this to share one process, rather than ask
189
+ # an operator to run and monitor a second one.
190
+ #
191
+ # The block must return an object that answers `run`, `request_shutdown`,
192
+ # `stopped?`, and `stop`, which is the contract the built in components
193
+ # already keep. The supervisor calls the block once for each supervisor it
194
+ # builds, and again when it replaces a crashed component, so two
195
+ # supervisors never share one component instance.
196
+ #
197
+ # @rbs (?count: Integer) { () -> untyped } -> void
198
+ def register_component: (?count: Integer) { () -> untyped } -> void
199
+
200
+ # The supervisor checks the contract here rather than at registration,
201
+ # because a component often needs a database connection to exist, and
202
+ # registration happens while the application boots.
203
+ # @rbs () -> Array[untyped]
204
+ def build_additional_components: () -> Array[untyped]
205
+
206
+ # A component that misses part of the contract would hang the supervisor at
207
+ # shutdown, or crash the moment it starts. The build fails instead, where
208
+ # the caller can read the reason.
209
+ # @rbs (untyped) -> void
210
+ def validate_component!: (untyped) -> void
211
+
184
212
  # @rbs () -> self
185
213
  def validate!: () -> self
186
214
 
@@ -49,6 +49,9 @@ module SolidObjects
49
49
  # @rbs (Exception) -> bool
50
50
  def deadline_error?: (Exception) -> bool
51
51
 
52
+ # @rbs (Exception) -> bool
53
+ def reconnect_blocked_error?: (Exception) -> bool
54
+
52
55
  # @rbs (Exception) -> bool
53
56
  def busy_error?: (Exception) -> bool
54
57
 
@@ -76,7 +76,7 @@ module SolidObjects
76
76
  # @rbs (Effect) -> void
77
77
  def verify_claim!: (Effect) -> void
78
78
 
79
- # @rbs (Effect, String?, String, Hash[String, untyped]) -> Message?
80
- def enqueue_result_message: (Effect, String?, String, Hash[String, untyped]) -> Message?
79
+ # @rbs (effect: Effect, operation: String?, outcome: String, arguments: Hash[String, untyped]) -> Message?
80
+ def enqueue_result_message: (effect: Effect, operation: String?, outcome: String, arguments: Hash[String, untyped]) -> Message?
81
81
  end
82
82
  end