solid_objects 0.11.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +39 -16
- data/app/controllers/solid_objects/components_controller.rb +4 -4
- data/app/models/solid_objects/message.rb +1 -1
- data/app/views/solid_objects/dead_letters/index.html.erb +1 -1
- data/app/views/solid_objects/instances/show.html.erb +1 -1
- data/benchmark/support.rb +10 -10
- data/db/migrate/20260813000000_rename_message_dispatch_columns.rb +53 -0
- data/docs/architecture.md +11 -9
- data/docs/authorization.md +2 -2
- data/docs/database-schema.md +4 -4
- data/docs/development.md +1 -1
- data/docs/migrating-existing-state.md +1 -3
- data/docs/roadmap.md +1 -1
- data/lib/solid_objects/actor.rb +43 -32
- data/lib/solid_objects/actor_channel.rb +3 -3
- data/lib/solid_objects/actor_definition.rb +9 -6
- data/lib/solid_objects/actor_view.rb +1 -1
- data/lib/solid_objects/application_write_guard.rb +1 -1
- data/lib/solid_objects/client.rb +52 -52
- data/lib/solid_objects/component_registration.rb +2 -2
- data/lib/solid_objects/component_renderer.rb +1 -1
- data/lib/solid_objects/component_subscriptions.rb +21 -21
- data/lib/solid_objects/database_adapters/sqlite.rb +7 -1
- data/lib/solid_objects/dead_letter_manager.rb +4 -4
- data/lib/solid_objects/doctor.rb +8 -8
- data/lib/solid_objects/effect_executor.rb +22 -22
- data/lib/solid_objects/errors.rb +27 -27
- data/lib/solid_objects/executor.rb +40 -38
- data/lib/solid_objects/lease.rb +3 -3
- data/lib/solid_objects/mailbox.rb +34 -27
- data/lib/solid_objects/operation_dispatcher.rb +51 -0
- data/lib/solid_objects/payload_broadcast.rb +1 -1
- data/lib/solid_objects/reference.rb +60 -29
- data/lib/solid_objects/reminder_scheduler.rb +6 -6
- data/lib/solid_objects/sync_diagnostics.rb +6 -6
- data/lib/solid_objects/turbo_stream_renderer.rb +13 -13
- data/lib/solid_objects/version.rb +1 -1
- data/lib/solid_objects.rb +1 -0
- data/sig/generated/controllers/solid_objects/components_controller.rbs +2 -2
- data/sig/generated/lib/solid_objects/actor.rbs +17 -17
- data/sig/generated/lib/solid_objects/actor_definition.rbs +4 -4
- data/sig/generated/lib/solid_objects/client.rbs +8 -8
- data/sig/generated/lib/solid_objects/component_registration.rbs +2 -2
- data/sig/generated/lib/solid_objects/component_subscriptions.rbs +8 -8
- data/sig/generated/lib/solid_objects/database_adapters/sqlite.rbs +3 -0
- data/sig/generated/lib/solid_objects/effect_executor.rbs +2 -2
- data/sig/generated/lib/solid_objects/errors.rbs +18 -18
- data/sig/generated/lib/solid_objects/executor.rbs +10 -10
- data/sig/generated/lib/solid_objects/lease.rbs +2 -2
- data/sig/generated/lib/solid_objects/mailbox.rbs +8 -8
- data/sig/generated/lib/solid_objects/operation_dispatcher.rbs +35 -0
- data/sig/generated/lib/solid_objects/reference.rbs +7 -4
- data/sig/generated/lib/solid_objects/sync_diagnostics.rbs +2 -2
- data/sig/generated/lib/solid_objects/turbo_stream_renderer.rbs +8 -8
- metadata +5 -2
|
@@ -32,9 +32,9 @@ module SolidObjects
|
|
|
32
32
|
snapshot = ActorSnapshot.new(reference)
|
|
33
33
|
scalar_observable_names(snapshot).each do |name|
|
|
34
34
|
transmit TurboStreamRenderer.observable_value(
|
|
35
|
-
reference
|
|
36
|
-
name
|
|
37
|
-
snapshot.observable_value(name)
|
|
35
|
+
reference:,
|
|
36
|
+
name:,
|
|
37
|
+
value: snapshot.observable_value(name)
|
|
38
38
|
)
|
|
39
39
|
end
|
|
40
40
|
refresh_outdated_components(snapshot)
|
|
@@ -45,6 +45,7 @@ module SolidObjects
|
|
|
45
45
|
# @rbs (Symbol | String, default: untyped) -> StateDefinition::Attribute
|
|
46
46
|
def add_attribute(name, default:)
|
|
47
47
|
attribute_name = name.to_sym
|
|
48
|
+
OperationDispatcher.validate_operation_name!(attribute_name)
|
|
48
49
|
if messages.key?(attribute_name) || queries.key?(attribute_name)
|
|
49
50
|
raise InvalidActor, "#{attribute_name.inspect} is already defined"
|
|
50
51
|
end
|
|
@@ -60,7 +61,7 @@ module SolidObjects
|
|
|
60
61
|
|
|
61
62
|
# @rbs (Symbol | String, Proc) -> Handler
|
|
62
63
|
def add_message(name, block)
|
|
63
|
-
add_handler(messages, name
|
|
64
|
+
add_handler(collection: messages, name:, block:)
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
# @rbs (Symbol | String, Proc) -> Handler
|
|
@@ -72,7 +73,7 @@ module SolidObjects
|
|
|
72
73
|
end
|
|
73
74
|
end
|
|
74
75
|
|
|
75
|
-
add_handler(queries, name
|
|
76
|
+
add_handler(collection: queries, name:, block:)
|
|
76
77
|
end
|
|
77
78
|
|
|
78
79
|
# @rbs (Symbol | String, Proc?) -> Handler
|
|
@@ -121,8 +122,8 @@ module SolidObjects
|
|
|
121
122
|
@state_version = version
|
|
122
123
|
end
|
|
123
124
|
|
|
124
|
-
# @rbs (Integer, Integer, Proc) -> StateMigration
|
|
125
|
-
def add_state_migration(from
|
|
125
|
+
# @rbs (from: Integer, to: Integer, block: Proc) -> StateMigration
|
|
126
|
+
def add_state_migration(from:, to:, block:)
|
|
126
127
|
unless to == from + 1
|
|
127
128
|
raise InvalidActor, "state migrations must advance exactly one version"
|
|
128
129
|
end
|
|
@@ -183,6 +184,7 @@ module SolidObjects
|
|
|
183
184
|
|
|
184
185
|
# @rbs (Symbol) -> Handler
|
|
185
186
|
def add_method_message(name)
|
|
187
|
+
OperationDispatcher.validate_operation_name!(name)
|
|
186
188
|
if messages.key?(name) || queries.key?(name)
|
|
187
189
|
raise InvalidActor, "#{name.inspect} is already defined"
|
|
188
190
|
end
|
|
@@ -222,9 +224,10 @@ module SolidObjects
|
|
|
222
224
|
end
|
|
223
225
|
end
|
|
224
226
|
|
|
225
|
-
# @rbs (Hash[Symbol, Handler], Symbol | String, Proc) -> Handler
|
|
226
|
-
def add_handler(collection
|
|
227
|
+
# @rbs (collection: Hash[Symbol, Handler], name: Symbol | String, block: Proc) -> Handler
|
|
228
|
+
def add_handler(collection:, name:, block:)
|
|
227
229
|
handler_name = name.to_sym
|
|
230
|
+
OperationDispatcher.validate_operation_name!(handler_name)
|
|
228
231
|
raise InvalidActor, "#{handler_name.inspect} is already defined" if messages.key?(handler_name) || queries.key?(handler_name)
|
|
229
232
|
|
|
230
233
|
Handler.new(name: handler_name, block:).tap { |handler| collection[handler_name] = handler }
|
|
@@ -154,7 +154,7 @@ module SolidObjects
|
|
|
154
154
|
authorized = SolidObjects.configuration.authorize_query.call(
|
|
155
155
|
actor_type: reference.actor_type,
|
|
156
156
|
actor_id: reference.actor_id,
|
|
157
|
-
|
|
157
|
+
operation: name.to_s,
|
|
158
158
|
arguments: {},
|
|
159
159
|
authorization_context:
|
|
160
160
|
)
|
data/lib/solid_objects/client.rb
CHANGED
|
@@ -12,50 +12,50 @@ module SolidObjects
|
|
|
12
12
|
@mailbox = mailbox
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
# @rbs (Reference, Symbol | String, Hash[Symbol | String, untyped], ?available_at: Time?, ?idempotency_key: String?, ?authorization_context: untyped) -> MessageReference
|
|
16
|
-
def async(reference
|
|
15
|
+
# @rbs (reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], ?available_at: Time?, ?idempotency_key: String?, ?authorization_context: untyped) -> MessageReference
|
|
16
|
+
def async(reference:, operation:, arguments:, available_at: nil, idempotency_key: nil, authorization_context: nil)
|
|
17
17
|
actor_class = SolidObjects.registry.fetch(reference.actor_type)
|
|
18
|
-
|
|
19
|
-
raise UnknownMessage, "unknown
|
|
18
|
+
operation_symbol = operation.to_sym
|
|
19
|
+
raise UnknownMessage, "unknown operation #{operation.inspect}" unless actor_class.definition.messages.key?(operation_symbol)
|
|
20
20
|
|
|
21
21
|
authorize!(
|
|
22
|
-
SolidObjects.configuration.authorize_message,
|
|
23
|
-
reference
|
|
24
|
-
|
|
25
|
-
arguments
|
|
22
|
+
hook: SolidObjects.configuration.authorize_message,
|
|
23
|
+
reference:,
|
|
24
|
+
operation:,
|
|
25
|
+
arguments:,
|
|
26
26
|
authorization_context:
|
|
27
27
|
)
|
|
28
28
|
mailbox.enqueue(
|
|
29
|
-
reference
|
|
30
|
-
|
|
31
|
-
arguments
|
|
32
|
-
|
|
29
|
+
reference:,
|
|
30
|
+
operation:,
|
|
31
|
+
arguments:,
|
|
32
|
+
delivery_mode: "async",
|
|
33
33
|
available_at:,
|
|
34
34
|
idempotency_key:
|
|
35
35
|
)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
# @rbs (Reference, Symbol | String, Hash[Symbol | String, untyped], timeout: Numeric, ?idempotency_key: String?, ?authorization_context: untyped) -> untyped
|
|
39
|
-
def sync(reference
|
|
38
|
+
# @rbs (reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], timeout: Numeric, ?idempotency_key: String?, ?authorization_context: untyped) -> untyped
|
|
39
|
+
def sync(reference:, operation:, arguments:, timeout:, idempotency_key: nil, authorization_context: nil)
|
|
40
40
|
actor_class = SolidObjects.registry.fetch(reference.actor_type)
|
|
41
|
-
|
|
42
|
-
query = actor_class.definition.queries.key?(
|
|
43
|
-
actor_message = actor_class.definition.messages.key?(
|
|
44
|
-
raise UnknownMessage, "unknown
|
|
41
|
+
operation_symbol = operation.to_sym
|
|
42
|
+
query = actor_class.definition.queries.key?(operation_symbol)
|
|
43
|
+
actor_message = actor_class.definition.messages.key?(operation_symbol)
|
|
44
|
+
raise UnknownMessage, "unknown operation #{operation.inspect}" unless query || actor_message
|
|
45
45
|
|
|
46
46
|
authorize!(
|
|
47
|
-
query ? SolidObjects.configuration.authorize_query : SolidObjects.configuration.authorize_message,
|
|
48
|
-
reference
|
|
49
|
-
|
|
50
|
-
arguments
|
|
47
|
+
hook: query ? SolidObjects.configuration.authorize_query : SolidObjects.configuration.authorize_message,
|
|
48
|
+
reference:,
|
|
49
|
+
operation:,
|
|
50
|
+
arguments:,
|
|
51
51
|
authorization_context:
|
|
52
52
|
)
|
|
53
|
-
reject_sync_inside_transaction!(reference,
|
|
53
|
+
reject_sync_inside_transaction!(reference, operation)
|
|
54
54
|
SyncDeadline.with(timeout:) do
|
|
55
55
|
message_reference = enqueue_sync(
|
|
56
|
-
reference
|
|
57
|
-
|
|
58
|
-
arguments
|
|
56
|
+
reference:,
|
|
57
|
+
operation:,
|
|
58
|
+
arguments:,
|
|
59
59
|
idempotency_key:,
|
|
60
60
|
timeout:
|
|
61
61
|
)
|
|
@@ -75,19 +75,19 @@ module SolidObjects
|
|
|
75
75
|
actor_id: message.actor_id
|
|
76
76
|
)
|
|
77
77
|
actor_class = SolidObjects.registry.fetch(reference.actor_type)
|
|
78
|
-
query = actor_class.definition.queries.key?(message.
|
|
79
|
-
actor_message = actor_class.definition.messages.key?(message.
|
|
78
|
+
query = actor_class.definition.queries.key?(message.operation.to_sym)
|
|
79
|
+
actor_message = actor_class.definition.messages.key?(message.operation.to_sym)
|
|
80
80
|
unless query || actor_message
|
|
81
|
-
raise UnknownMessage, "unknown
|
|
81
|
+
raise UnknownMessage, "unknown operation #{message.operation.inspect}"
|
|
82
82
|
end
|
|
83
83
|
authorize!(
|
|
84
|
-
query ? SolidObjects.configuration.authorize_query : SolidObjects.configuration.authorize_message,
|
|
85
|
-
reference
|
|
86
|
-
message.
|
|
87
|
-
message.arguments,
|
|
84
|
+
hook: query ? SolidObjects.configuration.authorize_query : SolidObjects.configuration.authorize_message,
|
|
85
|
+
reference:,
|
|
86
|
+
operation: message.operation,
|
|
87
|
+
arguments: message.arguments,
|
|
88
88
|
authorization_context:
|
|
89
89
|
)
|
|
90
|
-
reject_sync_inside_transaction!(reference, message.
|
|
90
|
+
reject_sync_inside_transaction!(reference, message.operation)
|
|
91
91
|
SynchronousInvocation.new.call(message_reference, timeout:)
|
|
92
92
|
end
|
|
93
93
|
rescue DatabaseDeadlineExceeded
|
|
@@ -98,10 +98,10 @@ module SolidObjects
|
|
|
98
98
|
def snapshot(reference, authorization_context: nil)
|
|
99
99
|
SolidObjects.registry.fetch(reference.actor_type)
|
|
100
100
|
authorize!(
|
|
101
|
-
SolidObjects.configuration.authorize_query,
|
|
102
|
-
reference
|
|
103
|
-
"__snapshot__",
|
|
104
|
-
{},
|
|
101
|
+
hook: SolidObjects.configuration.authorize_query,
|
|
102
|
+
reference:,
|
|
103
|
+
operation: "__snapshot__",
|
|
104
|
+
arguments: {},
|
|
105
105
|
authorization_context:
|
|
106
106
|
)
|
|
107
107
|
StateSnapshot.new(reference)
|
|
@@ -140,13 +140,13 @@ module SolidObjects
|
|
|
140
140
|
|
|
141
141
|
attr_reader :mailbox
|
|
142
142
|
|
|
143
|
-
# @rbs (Reference, Symbol | String, Hash[Symbol | String, untyped], idempotency_key: String?, timeout: Numeric) -> MessageReference
|
|
144
|
-
def enqueue_sync(reference
|
|
143
|
+
# @rbs (reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], idempotency_key: String?, timeout: Numeric) -> MessageReference
|
|
144
|
+
def enqueue_sync(reference:, operation:, arguments:, idempotency_key:, timeout:)
|
|
145
145
|
mailbox.enqueue(
|
|
146
|
-
reference
|
|
147
|
-
|
|
148
|
-
arguments
|
|
149
|
-
|
|
146
|
+
reference:,
|
|
147
|
+
operation:,
|
|
148
|
+
arguments:,
|
|
149
|
+
delivery_mode: "sync",
|
|
150
150
|
idempotency_key:
|
|
151
151
|
)
|
|
152
152
|
rescue DatabaseDeadlineExceeded
|
|
@@ -154,13 +154,13 @@ module SolidObjects
|
|
|
154
154
|
:"sync.enqueue_timeout",
|
|
155
155
|
actor_type: reference.actor_type,
|
|
156
156
|
actor_id: reference.actor_id,
|
|
157
|
-
|
|
157
|
+
operation: operation.to_s
|
|
158
158
|
)
|
|
159
159
|
raise SyncEnqueueTimeout.new(
|
|
160
160
|
timeout:,
|
|
161
161
|
actor_type: reference.actor_type,
|
|
162
162
|
actor_id: reference.actor_id,
|
|
163
|
-
|
|
163
|
+
operation: operation.to_s
|
|
164
164
|
)
|
|
165
165
|
end
|
|
166
166
|
|
|
@@ -176,28 +176,28 @@ module SolidObjects
|
|
|
176
176
|
end
|
|
177
177
|
|
|
178
178
|
# @rbs (Reference, Symbol | String) -> void
|
|
179
|
-
def reject_sync_inside_transaction!(reference,
|
|
179
|
+
def reject_sync_inside_transaction!(reference, operation)
|
|
180
180
|
return unless SolidObjects::Record.connection.transaction_open?
|
|
181
181
|
|
|
182
182
|
SolidObjects.instrument(
|
|
183
183
|
:"sync.transaction_rejected",
|
|
184
184
|
actor_type: reference.actor_type,
|
|
185
185
|
actor_id: reference.actor_id,
|
|
186
|
-
|
|
186
|
+
operation: operation.to_s
|
|
187
187
|
)
|
|
188
188
|
raise SyncInsideTransaction.new(
|
|
189
189
|
actor_type: reference.actor_type,
|
|
190
190
|
actor_id: reference.actor_id,
|
|
191
|
-
|
|
191
|
+
operation: operation.to_s
|
|
192
192
|
)
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
-
# @rbs (Proc, Reference, Symbol | String, Hash[Symbol | String, untyped], authorization_context: untyped) -> void
|
|
196
|
-
def authorize!(hook
|
|
195
|
+
# @rbs (hook: Proc, reference: Reference, operation: Symbol | String, arguments: Hash[Symbol | String, untyped], authorization_context: untyped) -> void
|
|
196
|
+
def authorize!(hook:, reference:, operation:, arguments:, authorization_context:)
|
|
197
197
|
authorized = hook.call(
|
|
198
198
|
actor_type: reference.actor_type,
|
|
199
199
|
actor_id: reference.actor_id,
|
|
200
|
-
|
|
200
|
+
operation: operation.to_s,
|
|
201
201
|
arguments:,
|
|
202
202
|
authorization_context:
|
|
203
203
|
)
|
|
@@ -156,8 +156,8 @@ module SolidObjects
|
|
|
156
156
|
locals.merge("component_key" => component_key).freeze
|
|
157
157
|
end
|
|
158
158
|
|
|
159
|
-
# @rbs (Array[ComponentRegistration], Integer, Integer) -> String
|
|
160
|
-
def batch_refresh_url(registrations
|
|
159
|
+
# @rbs (registrations: Array[ComponentRegistration], instance_id: Integer, revision: Integer) -> String
|
|
160
|
+
def batch_refresh_url(registrations:, instance_id:, revision:)
|
|
161
161
|
query = URI.encode_www_form(
|
|
162
162
|
[
|
|
163
163
|
[ "instance_id", instance_id ],
|
|
@@ -60,7 +60,7 @@ module SolidObjects
|
|
|
60
60
|
authorized = SolidObjects.configuration.authorize_query.call(
|
|
61
61
|
actor_type: snapshot.reference.actor_type,
|
|
62
62
|
actor_id: snapshot.reference.actor_id,
|
|
63
|
-
|
|
63
|
+
operation: observable_name,
|
|
64
64
|
arguments: registration.authorization_arguments,
|
|
65
65
|
authorization_context:
|
|
66
66
|
)
|
|
@@ -50,21 +50,21 @@ module SolidObjects
|
|
|
50
50
|
revision = invalidation.fetch("revision")
|
|
51
51
|
changed = registrations.select do |registration|
|
|
52
52
|
registration.dependencies.include?(observable_name) &&
|
|
53
|
-
newer_revision?(registration.dom_id, instance_id
|
|
53
|
+
newer_revision?(dom_id: registration.dom_id, instance_id:, revision:)
|
|
54
54
|
end
|
|
55
|
-
refresh_streams(changed
|
|
55
|
+
refresh_streams(changed:, instance_id:, revision:)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# @rbs (ActorSnapshot) -> Array[String]
|
|
59
59
|
def reconnect_refreshes(snapshot)
|
|
60
60
|
stale = registrations.select do |registration|
|
|
61
61
|
newer_revision?(
|
|
62
|
-
registration.dom_id,
|
|
63
|
-
snapshot.instance_id,
|
|
64
|
-
snapshot.revision
|
|
62
|
+
dom_id: registration.dom_id,
|
|
63
|
+
instance_id: snapshot.instance_id,
|
|
64
|
+
revision: snapshot.revision
|
|
65
65
|
)
|
|
66
66
|
end
|
|
67
|
-
refresh_streams(stale, snapshot.instance_id, snapshot.revision)
|
|
67
|
+
refresh_streams(changed: stale, instance_id: snapshot.instance_id, revision: snapshot.revision)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
class << self
|
|
@@ -86,36 +86,36 @@ module SolidObjects
|
|
|
86
86
|
|
|
87
87
|
# Live invalidations and reconnect replays share this, so a reconnecting
|
|
88
88
|
# client pays the same number of requests a connected one does.
|
|
89
|
-
# @rbs (Array[ComponentRegistration], Integer, Integer) -> Array[String]
|
|
90
|
-
def refresh_streams(changed
|
|
89
|
+
# @rbs (changed: Array[ComponentRegistration], instance_id: Integer, revision: Integer) -> Array[String]
|
|
90
|
+
def refresh_streams(changed:, instance_id:, revision:)
|
|
91
91
|
batched, individual = changed.partition(&:batch)
|
|
92
92
|
streams = individual.map do |registration|
|
|
93
|
-
refresh(registration
|
|
93
|
+
refresh(registration:, instance_id:, revision:)
|
|
94
94
|
end
|
|
95
95
|
batched.group_by(&:batch).each_value do |group|
|
|
96
|
-
group.each { |registration| record_revision(registration
|
|
97
|
-
streams << TurboStreamRenderer.batch_refresh(group, instance_id
|
|
96
|
+
group.each { |registration| record_revision(registration:, instance_id:, revision:) }
|
|
97
|
+
streams << TurboStreamRenderer.batch_refresh(registrations: group, instance_id:, revision:)
|
|
98
98
|
end
|
|
99
99
|
streams
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
# @rbs (ComponentRegistration, Integer, Integer) -> void
|
|
103
|
-
def record_revision(registration
|
|
102
|
+
# @rbs (registration: ComponentRegistration, instance_id: Integer, revision: Integer) -> void
|
|
103
|
+
def record_revision(registration:, instance_id:, revision:)
|
|
104
104
|
revisions[registration.dom_id] = [ instance_id, revision ]
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
# @rbs (ComponentRegistration, Integer, Integer) -> String
|
|
108
|
-
def refresh(registration
|
|
109
|
-
record_revision(registration
|
|
107
|
+
# @rbs (registration: ComponentRegistration, instance_id: Integer, revision: Integer) -> String
|
|
108
|
+
def refresh(registration:, instance_id:, revision:)
|
|
109
|
+
record_revision(registration:, instance_id:, revision:)
|
|
110
110
|
TurboStreamRenderer.component_refresh(
|
|
111
|
-
registration
|
|
112
|
-
instance_id
|
|
113
|
-
revision
|
|
111
|
+
registration:,
|
|
112
|
+
instance_id:,
|
|
113
|
+
revision:
|
|
114
114
|
)
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
-
# @rbs (String, Integer, Integer) -> bool
|
|
118
|
-
def newer_revision?(dom_id
|
|
117
|
+
# @rbs (dom_id: String, instance_id: Integer, revision: Integer) -> bool
|
|
118
|
+
def newer_revision?(dom_id:, instance_id:, revision:)
|
|
119
119
|
current = revisions.fetch(dom_id)
|
|
120
120
|
(current <=> [ instance_id, revision ]) == -1
|
|
121
121
|
end
|
|
@@ -141,7 +141,13 @@ module SolidObjects
|
|
|
141
141
|
def deadline_error?(error)
|
|
142
142
|
return false unless SyncDeadline.active?
|
|
143
143
|
|
|
144
|
-
busy_error?(error)
|
|
144
|
+
busy_error?(error) || reconnect_blocked_error?(error)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @rbs (Exception) -> bool
|
|
148
|
+
def reconnect_blocked_error?(error)
|
|
149
|
+
error.message.include?("SQLite3::CantOpenException") &&
|
|
150
|
+
error.message.include?("PRAGMA journal_mode='wal'")
|
|
145
151
|
end
|
|
146
152
|
|
|
147
153
|
# @rbs (Exception) -> bool
|
|
@@ -16,13 +16,13 @@ module SolidObjects
|
|
|
16
16
|
|
|
17
17
|
original_message = dead_letter.message
|
|
18
18
|
message_reference = Mailbox.new.enqueue(
|
|
19
|
-
Reference.new(
|
|
19
|
+
reference: Reference.new(
|
|
20
20
|
actor_type: dead_letter.actor_type,
|
|
21
21
|
actor_id: dead_letter.actor_id
|
|
22
22
|
),
|
|
23
|
-
dead_letter.
|
|
24
|
-
dead_letter.arguments,
|
|
25
|
-
|
|
23
|
+
operation: dead_letter.operation,
|
|
24
|
+
arguments: dead_letter.arguments,
|
|
25
|
+
delivery_mode: original_message.delivery_mode,
|
|
26
26
|
idempotency_key: "dead-letter:#{dead_letter.id}"
|
|
27
27
|
)
|
|
28
28
|
dead_letter.update!(retried_message_id: message_reference.id)
|
data/lib/solid_objects/doctor.rb
CHANGED
|
@@ -68,7 +68,7 @@ module SolidObjects
|
|
|
68
68
|
activation_generation
|
|
69
69
|
],
|
|
70
70
|
messages: %w[
|
|
71
|
-
id instance_id
|
|
71
|
+
id instance_id delivery_mode arguments sequence attempt_count request_id
|
|
72
72
|
result error rejection completed_at rejected_at
|
|
73
73
|
],
|
|
74
74
|
ready_messages: %w[id message_id instance_id sequence available_at],
|
|
@@ -76,7 +76,7 @@ module SolidObjects
|
|
|
76
76
|
id message_id instance_id process_id activation_token
|
|
77
77
|
activation_generation claimed_at
|
|
78
78
|
],
|
|
79
|
-
reminders: %w[id instance_id
|
|
79
|
+
reminders: %w[id instance_id operation next_run_at status],
|
|
80
80
|
effects: %w[id message_id instance_id effect_id status available_at],
|
|
81
81
|
broadcasts: %w[id message_id instance_id broadcast_id status available_at],
|
|
82
82
|
dead_letters: %w[id message_id instance_id actor_type actor_id attempts]
|
|
@@ -247,10 +247,10 @@ module SolidObjects
|
|
|
247
247
|
probe_registry.register(kind: "caller", metadata: { execution: "doctor" })
|
|
248
248
|
value = SecureRandom.hex(8)
|
|
249
249
|
message_reference = Mailbox.new.enqueue(
|
|
250
|
-
ProbeActor.ref(actor_id),
|
|
251
|
-
:ping,
|
|
252
|
-
{ value: },
|
|
253
|
-
|
|
250
|
+
reference: ProbeActor.ref(actor_id),
|
|
251
|
+
operation: :ping,
|
|
252
|
+
arguments: { value: },
|
|
253
|
+
delivery_mode: "sync"
|
|
254
254
|
)
|
|
255
255
|
result = SynchronousInvocation
|
|
256
256
|
.new(process_registry: probe_registry)
|
|
@@ -314,11 +314,11 @@ module SolidObjects
|
|
|
314
314
|
}
|
|
315
315
|
{
|
|
316
316
|
authorize_message: actor_arguments.merge(
|
|
317
|
-
|
|
317
|
+
operation: "ping",
|
|
318
318
|
arguments: { "value" => "doctor" }
|
|
319
319
|
),
|
|
320
320
|
authorize_query: actor_arguments.merge(
|
|
321
|
-
|
|
321
|
+
operation: "value",
|
|
322
322
|
arguments: {}
|
|
323
323
|
),
|
|
324
324
|
authorize_destroy: actor_arguments,
|
|
@@ -122,18 +122,18 @@ module SolidObjects
|
|
|
122
122
|
actor_id: arguments.fetch("actor_id")
|
|
123
123
|
)
|
|
124
124
|
actor_class = SolidObjects.registry.fetch(reference.actor_type)
|
|
125
|
-
|
|
126
|
-
unless actor_class.definition.messages.key?(
|
|
127
|
-
raise UnknownMessage, "unknown actor-to-actor
|
|
125
|
+
operation = arguments.fetch("operation")
|
|
126
|
+
unless actor_class.definition.messages.key?(operation.to_sym)
|
|
127
|
+
raise UnknownMessage, "unknown actor-to-actor operation #{operation.inspect}"
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
available_at = arguments["available_at"]
|
|
131
131
|
available_at = Time.iso8601(available_at) if available_at
|
|
132
132
|
Mailbox.new.enqueue(
|
|
133
|
-
reference
|
|
134
|
-
|
|
135
|
-
arguments.fetch("arguments"),
|
|
136
|
-
|
|
133
|
+
reference:,
|
|
134
|
+
operation:,
|
|
135
|
+
arguments: arguments.fetch("arguments"),
|
|
136
|
+
delivery_mode: "internal",
|
|
137
137
|
available_at:,
|
|
138
138
|
idempotency_key: effect.effect_id
|
|
139
139
|
).id
|
|
@@ -150,10 +150,10 @@ module SolidObjects
|
|
|
150
150
|
locked_effect = Effect.lock.find(effect.id)
|
|
151
151
|
verify_claim!(locked_effect)
|
|
152
152
|
result_message = enqueue_result_message(
|
|
153
|
-
locked_effect,
|
|
154
|
-
locked_effect.
|
|
155
|
-
"success",
|
|
156
|
-
{ "effect_id" => locked_effect.effect_id, "result" => serialized_result }
|
|
153
|
+
effect: locked_effect,
|
|
154
|
+
operation: locked_effect.success_operation,
|
|
155
|
+
outcome: "success",
|
|
156
|
+
arguments: { "effect_id" => locked_effect.effect_id, "result" => serialized_result }
|
|
157
157
|
)
|
|
158
158
|
locked_effect.update!(
|
|
159
159
|
status: "completed",
|
|
@@ -188,10 +188,10 @@ module SolidObjects
|
|
|
188
188
|
}
|
|
189
189
|
if dead
|
|
190
190
|
result_message = enqueue_result_message(
|
|
191
|
-
locked_effect,
|
|
192
|
-
locked_effect.
|
|
193
|
-
"failure",
|
|
194
|
-
{ "effect_id" => locked_effect.effect_id, "error" => error_details }
|
|
191
|
+
effect: locked_effect,
|
|
192
|
+
operation: locked_effect.failure_operation,
|
|
193
|
+
outcome: "failure",
|
|
194
|
+
arguments: { "effect_id" => locked_effect.effect_id, "error" => error_details }
|
|
195
195
|
)
|
|
196
196
|
end
|
|
197
197
|
locked_effect.update!(
|
|
@@ -216,18 +216,18 @@ module SolidObjects
|
|
|
216
216
|
raise LostActivation, "effect claim changed"
|
|
217
217
|
end
|
|
218
218
|
|
|
219
|
-
# @rbs (Effect, String?, String, Hash[String, untyped]) -> Message?
|
|
220
|
-
def enqueue_result_message(effect
|
|
221
|
-
return unless
|
|
219
|
+
# @rbs (effect: Effect, operation: String?, outcome: String, arguments: Hash[String, untyped]) -> Message?
|
|
220
|
+
def enqueue_result_message(effect:, operation:, outcome:, arguments:)
|
|
221
|
+
return unless operation
|
|
222
222
|
|
|
223
223
|
Mailbox.new.enqueue_in_transaction(
|
|
224
|
-
Reference.new(
|
|
224
|
+
reference: Reference.new(
|
|
225
225
|
actor_type: effect.instance.actor_type,
|
|
226
226
|
actor_id: effect.instance.actor_id
|
|
227
227
|
),
|
|
228
|
-
|
|
229
|
-
arguments
|
|
230
|
-
|
|
228
|
+
operation:,
|
|
229
|
+
arguments:,
|
|
230
|
+
delivery_mode: "internal",
|
|
231
231
|
idempotency_key: "effect:#{effect.effect_id}:#{outcome}"
|
|
232
232
|
)
|
|
233
233
|
end
|