solid_objects 0.1.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 +7 -0
- data/CHANGELOG.md +17 -0
- data/MIT-LICENSE +19 -0
- data/README.md +744 -0
- data/Rakefile +40 -0
- data/app/controllers/solid_objects/application_controller.rb +23 -0
- data/app/controllers/solid_objects/dead_letters_controller.rb +23 -0
- data/app/controllers/solid_objects/instances_controller.rb +29 -0
- data/app/helpers/solid_objects/actor_helper.rb +25 -0
- data/app/models/solid_objects/broadcast.rb +10 -0
- data/app/models/solid_objects/claimed_message.rb +14 -0
- data/app/models/solid_objects/dead_letter.rb +13 -0
- data/app/models/solid_objects/effect.rb +10 -0
- data/app/models/solid_objects/instance.rb +93 -0
- data/app/models/solid_objects/message.rb +53 -0
- data/app/models/solid_objects/process.rb +13 -0
- data/app/models/solid_objects/ready_message.rb +10 -0
- data/app/models/solid_objects/record.rb +17 -0
- data/app/models/solid_objects/reminder.rb +9 -0
- data/app/views/solid_objects/dead_letters/index.html.erb +26 -0
- data/app/views/solid_objects/instances/index.html.erb +24 -0
- data/app/views/solid_objects/instances/show.html.erb +35 -0
- data/benchmark/activation_cache.rb +5 -0
- data/benchmark/ask_latency.rb +5 -0
- data/benchmark/claim.rb +5 -0
- data/benchmark/cold_actors.rb +5 -0
- data/benchmark/concurrent_actors.rb +5 -0
- data/benchmark/enqueue.rb +5 -0
- data/benchmark/hot_actor.rb +5 -0
- data/benchmark/processing.rb +5 -0
- data/benchmark/query_count.rb +5 -0
- data/benchmark/support.rb +271 -0
- data/config/routes.rb +8 -0
- data/db/migrate/20260805000000_create_solid_objects_tables.rb +319 -0
- data/docs/adr/0001-postgresql-backend.md +21 -0
- data/docs/adr/0002-jsonb-actor-state.md +21 -0
- data/docs/adr/0003-mailbox-ordering.md +30 -0
- data/docs/adr/0004-activation-leasing.md +21 -0
- data/docs/adr/0005-fencing-tokens.md +25 -0
- data/docs/adr/0006-at-least-once-delivery.md +24 -0
- data/docs/adr/0007-transactional-outbox.md +21 -0
- data/docs/adr/0008-actor-communication.md +21 -0
- data/docs/adr/0009-realtime-updates.md +21 -0
- data/docs/adr/0010-state-versioning.md +29 -0
- data/docs/adr/0011-wake-up-strategy.md +34 -0
- data/docs/adr/0012-not-active-jobs.md +21 -0
- data/docs/adr/0013-database-adapters.md +48 -0
- data/docs/architecture.md +615 -0
- data/docs/benchmarks.md +26 -0
- data/docs/correctness.md +124 -0
- data/docs/database-schema.md +111 -0
- data/docs/development.md +87 -0
- data/docs/implementation-plan.md +518 -0
- data/docs/operations.md +123 -0
- data/docs/realtime.md +51 -0
- data/docs/research/solid_queue.md +545 -0
- data/docs/roadmap.md +53 -0
- data/docs/security.md +61 -0
- data/docs/state-migrations.md +46 -0
- data/examples/application/README.md +16 -0
- data/examples/application/app/actors/chat_room_actor.rb +34 -0
- data/examples/application/app/actors/shopping_cart_actor.rb +79 -0
- data/examples/application/app/controllers/cart_controller.rb +54 -0
- data/examples/application/app/controllers/chat_rooms_controller.rb +44 -0
- data/examples/application/app/views/actors/chat_room_actor/_messages.html.erb +8 -0
- data/examples/application/app/views/actors/shopping_cart_actor/_summary.html.erb +10 -0
- data/examples/application/app/views/cart/show.html.erb +13 -0
- data/examples/application/app/views/chat_rooms/show.html.erb +8 -0
- data/examples/application/config/initializers/solid_objects.rb +23 -0
- data/examples/application/config/routes.rb +20 -0
- data/exe/solid_objects +9 -0
- data/lib/generators/solid_objects/install_generator.rb +21 -0
- data/lib/generators/solid_objects/templates/solid_objects.rb +13 -0
- data/lib/solid_objects/action_cable_broadcast_adapter.rb +19 -0
- data/lib/solid_objects/activation.rb +183 -0
- data/lib/solid_objects/activation_manager.rb +102 -0
- data/lib/solid_objects/actor.rb +271 -0
- data/lib/solid_objects/actor_channel.rb +29 -0
- data/lib/solid_objects/actor_definition.rb +212 -0
- data/lib/solid_objects/actor_registry.rb +65 -0
- data/lib/solid_objects/actor_snapshot.rb +42 -0
- data/lib/solid_objects/actor_view.rb +117 -0
- data/lib/solid_objects/broadcast_executor.rb +162 -0
- data/lib/solid_objects/cli.rb +118 -0
- data/lib/solid_objects/client.rb +153 -0
- data/lib/solid_objects/configuration.rb +168 -0
- data/lib/solid_objects/context.rb +41 -0
- data/lib/solid_objects/database_adapter.rb +82 -0
- data/lib/solid_objects/database_adapters/mysql.rb +22 -0
- data/lib/solid_objects/database_adapters/postgresql.rb +17 -0
- data/lib/solid_objects/database_adapters/sqlite.rb +12 -0
- data/lib/solid_objects/dead_letter_manager.rb +47 -0
- data/lib/solid_objects/dom_identity.rb +38 -0
- data/lib/solid_objects/effect_executor.rb +235 -0
- data/lib/solid_objects/effect_registry.rb +34 -0
- data/lib/solid_objects/engine.rb +33 -0
- data/lib/solid_objects/errors.rb +65 -0
- data/lib/solid_objects/executor.rb +290 -0
- data/lib/solid_objects/instrumentation.rb +10 -0
- data/lib/solid_objects/lease.rb +172 -0
- data/lib/solid_objects/lease_renewer.rb +70 -0
- data/lib/solid_objects/log_subscriber.rb +29 -0
- data/lib/solid_objects/mailbox.rb +178 -0
- data/lib/solid_objects/message_reference.rb +52 -0
- data/lib/solid_objects/process_registry.rb +143 -0
- data/lib/solid_objects/reference.rb +96 -0
- data/lib/solid_objects/reminder_scheduler.rb +168 -0
- data/lib/solid_objects/serialization.rb +99 -0
- data/lib/solid_objects/state.rb +111 -0
- data/lib/solid_objects/stream_name.rb +29 -0
- data/lib/solid_objects/stream_token.rb +59 -0
- data/lib/solid_objects/supervisor.rb +87 -0
- data/lib/solid_objects/turbo_stream_renderer.rb +35 -0
- data/lib/solid_objects/version.rb +5 -0
- data/lib/solid_objects/wake_up.rb +28 -0
- data/lib/solid_objects/worker.rb +139 -0
- data/lib/solid_objects.rb +118 -0
- data/sig/generated/controllers/solid_objects/application_controller.rbs +10 -0
- data/sig/generated/controllers/solid_objects/dead_letters_controller.rbs +11 -0
- data/sig/generated/controllers/solid_objects/instances_controller.rbs +11 -0
- data/sig/generated/helpers/solid_objects/actor_helper.rbs +8 -0
- data/sig/generated/lib/generators/solid_objects/install_generator.rbs +13 -0
- data/sig/generated/lib/solid_objects/action_cable_broadcast_adapter.rbs +8 -0
- data/sig/generated/lib/solid_objects/activation.rbs +65 -0
- data/sig/generated/lib/solid_objects/activation_manager.rbs +36 -0
- data/sig/generated/lib/solid_objects/actor.rbs +183 -0
- data/sig/generated/lib/solid_objects/actor_channel.rbs +8 -0
- data/sig/generated/lib/solid_objects/actor_definition.rbs +117 -0
- data/sig/generated/lib/solid_objects/actor_registry.rbs +36 -0
- data/sig/generated/lib/solid_objects/actor_snapshot.rbs +28 -0
- data/sig/generated/lib/solid_objects/actor_view.rbs +56 -0
- data/sig/generated/lib/solid_objects/broadcast_executor.rbs +55 -0
- data/sig/generated/lib/solid_objects/cli.rbs +31 -0
- data/sig/generated/lib/solid_objects/client.rbs +35 -0
- data/sig/generated/lib/solid_objects/configuration.rbs +147 -0
- data/sig/generated/lib/solid_objects/context.rbs +56 -0
- data/sig/generated/lib/solid_objects/database_adapter.rbs +42 -0
- data/sig/generated/lib/solid_objects/database_adapters/mysql.rbs +16 -0
- data/sig/generated/lib/solid_objects/database_adapters/postgresql.rbs +13 -0
- data/sig/generated/lib/solid_objects/database_adapters/sqlite.rbs +10 -0
- data/sig/generated/lib/solid_objects/dead_letter_manager.rbs +16 -0
- data/sig/generated/lib/solid_objects/dom_identity.rbs +20 -0
- data/sig/generated/lib/solid_objects/effect_executor.rbs +82 -0
- data/sig/generated/lib/solid_objects/effect_registry.rbs +24 -0
- data/sig/generated/lib/solid_objects/engine.rbs +7 -0
- data/sig/generated/lib/solid_objects/errors.rbs +64 -0
- data/sig/generated/lib/solid_objects/executor.rbs +60 -0
- data/sig/generated/lib/solid_objects/instrumentation.rbs +8 -0
- data/sig/generated/lib/solid_objects/lease.rbs +57 -0
- data/sig/generated/lib/solid_objects/lease_renewer.rbs +42 -0
- data/sig/generated/lib/solid_objects/log_subscriber.rbs +11 -0
- data/sig/generated/lib/solid_objects/mailbox.rbs +46 -0
- data/sig/generated/lib/solid_objects/message_reference.rbs +37 -0
- data/sig/generated/lib/solid_objects/process_registry.rbs +46 -0
- data/sig/generated/lib/solid_objects/reference.rbs +45 -0
- data/sig/generated/lib/solid_objects/reminder_scheduler.rbs +55 -0
- data/sig/generated/lib/solid_objects/serialization.rbs +31 -0
- data/sig/generated/lib/solid_objects/state.rbs +72 -0
- data/sig/generated/lib/solid_objects/stream_name.rbs +11 -0
- data/sig/generated/lib/solid_objects/stream_token.rbs +19 -0
- data/sig/generated/lib/solid_objects/supervisor.rbs +38 -0
- data/sig/generated/lib/solid_objects/turbo_stream_renderer.rbs +14 -0
- data/sig/generated/lib/solid_objects/version.rbs +5 -0
- data/sig/generated/lib/solid_objects/wake_up.rbs +24 -0
- data/sig/generated/lib/solid_objects/worker.rbs +56 -0
- data/sig/generated/lib/solid_objects.rbs +41 -0
- data/sig/generated/models/solid_objects/broadcast.rbs +6 -0
- data/sig/generated/models/solid_objects/claimed_message.rbs +6 -0
- data/sig/generated/models/solid_objects/dead_letter.rbs +6 -0
- data/sig/generated/models/solid_objects/effect.rbs +6 -0
- data/sig/generated/models/solid_objects/instance.rbs +25 -0
- data/sig/generated/models/solid_objects/message.rbs +22 -0
- data/sig/generated/models/solid_objects/process.rbs +6 -0
- data/sig/generated/models/solid_objects/ready_message.rbs +6 -0
- data/sig/generated/models/solid_objects/record.rbs +8 -0
- data/sig/generated/models/solid_objects/reminder.rbs +6 -0
- data/sig/support/framework.rbs +37 -0
- metadata +467 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "rake/testtask"
|
|
6
|
+
|
|
7
|
+
Rake::TestTask.new do |task|
|
|
8
|
+
task.libs << "test"
|
|
9
|
+
task.pattern = "test/**/*_test.rb"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
desc "Generate RBS signatures from inline annotations"
|
|
13
|
+
task :rbs do
|
|
14
|
+
FileUtils.rm_rf(File.expand_path("sig/generated", __dir__))
|
|
15
|
+
sh "bundle exec rbs-inline --base lib --base app --output sig/generated lib app"
|
|
16
|
+
FileUtils.rm_f(File.expand_path("sig/generated/lib/generators/solid_objects/templates/solid_objects.rbs", __dir__))
|
|
17
|
+
sh "bundle exec rbs -I sig/generated -I sig/support validate"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Run Standard Ruby"
|
|
21
|
+
task :standard do
|
|
22
|
+
sh "bundle exec standardrb"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "Run Solid Queue's RuboCop policy"
|
|
26
|
+
task :rubocop do
|
|
27
|
+
sh "bundle exec rubocop"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
desc "Type-check generated inline RBS signatures"
|
|
31
|
+
task :steep do
|
|
32
|
+
sh "bundle exec steep check"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
desc "Scan the Rails engine for security warnings"
|
|
36
|
+
task :security do
|
|
37
|
+
sh "bundle exec brakeman --force --no-pager -q ."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
task default: %i[test standard rubocop rbs steep security]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
module SolidObjects
|
|
4
|
+
class ApplicationController < ActionController::Base
|
|
5
|
+
protect_from_forgery with: :exception
|
|
6
|
+
before_action :authorize_administration!
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
# @rbs () -> void
|
|
11
|
+
def authorize_administration!
|
|
12
|
+
authorized = SolidObjects.configuration.authorize_administration.call(
|
|
13
|
+
action: action_name,
|
|
14
|
+
resource: controller_name,
|
|
15
|
+
resource_id: params[:id],
|
|
16
|
+
authorization_context: self
|
|
17
|
+
)
|
|
18
|
+
return if authorized
|
|
19
|
+
|
|
20
|
+
raise Unauthorized, "actor administration is not authorized"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
module SolidObjects
|
|
4
|
+
class DeadLettersController < ApplicationController
|
|
5
|
+
# @rbs () -> void
|
|
6
|
+
def index
|
|
7
|
+
@dead_letters = DeadLetter.order(last_failed_at: :desc, id: :desc).limit(250)
|
|
8
|
+
respond_to do |format|
|
|
9
|
+
format.html
|
|
10
|
+
format.json { render json: @dead_letters }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @rbs () -> void
|
|
15
|
+
def retry
|
|
16
|
+
message_reference = SolidObjects.dead_letters.retry(
|
|
17
|
+
params[:id].to_i,
|
|
18
|
+
authorization_context: self
|
|
19
|
+
)
|
|
20
|
+
redirect_to dead_letters_path, notice: "Retried as message #{message_reference.id}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
module SolidObjects
|
|
4
|
+
class InstancesController < ApplicationController
|
|
5
|
+
# @rbs () -> void
|
|
6
|
+
def index
|
|
7
|
+
@instances = Instance.order(updated_at: :desc, id: :desc).limit(250)
|
|
8
|
+
respond_to do |format|
|
|
9
|
+
format.html
|
|
10
|
+
format.json { render json: @instances }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @rbs () -> void
|
|
15
|
+
def show
|
|
16
|
+
@instance = Instance.find(params[:id])
|
|
17
|
+
@messages = @instance.messages.order(sequence: :desc).limit(250)
|
|
18
|
+
respond_to do |format|
|
|
19
|
+
format.html
|
|
20
|
+
format.json do
|
|
21
|
+
render json: {
|
|
22
|
+
instance: @instance,
|
|
23
|
+
messages: @messages
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
module SolidObjects
|
|
4
|
+
module ActorHelper
|
|
5
|
+
# @rbs (Reference, ?authorization_context: untyped) { (ActorView) -> untyped } -> untyped
|
|
6
|
+
def solid_object(reference, authorization_context: self, &block)
|
|
7
|
+
actor = ActorView.new(
|
|
8
|
+
reference:,
|
|
9
|
+
view_context: self,
|
|
10
|
+
authorization_context:
|
|
11
|
+
)
|
|
12
|
+
subscription = tag.turbo_cable_stream_source(
|
|
13
|
+
channel: "SolidObjects::ActorChannel",
|
|
14
|
+
token: StreamToken.generate(reference)
|
|
15
|
+
)
|
|
16
|
+
content = capture(actor, &block)
|
|
17
|
+
|
|
18
|
+
content_tag(
|
|
19
|
+
:div,
|
|
20
|
+
safe_join([ subscription, content ]),
|
|
21
|
+
id: DomIdentity.scope(reference)
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
module SolidObjects
|
|
4
|
+
class Broadcast < Record
|
|
5
|
+
self.table_name = SolidObjects.table_name(:broadcasts)
|
|
6
|
+
|
|
7
|
+
belongs_to :message, class_name: "SolidObjects::Message"
|
|
8
|
+
belongs_to :instance, class_name: "SolidObjects::Instance"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
module SolidObjects
|
|
4
|
+
class ClaimedMessage < Record
|
|
5
|
+
self.table_name = SolidObjects.table_name(:claimed_messages)
|
|
6
|
+
|
|
7
|
+
belongs_to :message, class_name: "SolidObjects::Message", inverse_of: :claimed_message
|
|
8
|
+
belongs_to :instance, class_name: "SolidObjects::Instance", inverse_of: :claimed_messages
|
|
9
|
+
belongs_to :process_record,
|
|
10
|
+
class_name: "SolidObjects::Process",
|
|
11
|
+
foreign_key: :process_id,
|
|
12
|
+
optional: true
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
module SolidObjects
|
|
4
|
+
class DeadLetter < Record
|
|
5
|
+
self.table_name = SolidObjects.table_name(:dead_letters)
|
|
6
|
+
|
|
7
|
+
belongs_to :message, class_name: "SolidObjects::Message", inverse_of: :dead_letter
|
|
8
|
+
belongs_to :instance, class_name: "SolidObjects::Instance"
|
|
9
|
+
belongs_to :retried_message,
|
|
10
|
+
class_name: "SolidObjects::Message",
|
|
11
|
+
optional: true
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
module SolidObjects
|
|
4
|
+
class Instance < Record
|
|
5
|
+
self.table_name = SolidObjects.table_name(:instances)
|
|
6
|
+
|
|
7
|
+
belongs_to :activation_owner,
|
|
8
|
+
class_name: "SolidObjects::Process",
|
|
9
|
+
optional: true,
|
|
10
|
+
inverse_of: :instances
|
|
11
|
+
has_many :messages,
|
|
12
|
+
class_name: "SolidObjects::Message",
|
|
13
|
+
inverse_of: :instance,
|
|
14
|
+
dependent: :destroy
|
|
15
|
+
has_many :ready_messages,
|
|
16
|
+
class_name: "SolidObjects::ReadyMessage",
|
|
17
|
+
inverse_of: :instance,
|
|
18
|
+
dependent: :destroy
|
|
19
|
+
has_many :claimed_messages,
|
|
20
|
+
class_name: "SolidObjects::ClaimedMessage",
|
|
21
|
+
inverse_of: :instance,
|
|
22
|
+
dependent: :destroy
|
|
23
|
+
|
|
24
|
+
before_validation :supply_defaults
|
|
25
|
+
|
|
26
|
+
validates :actor_type, :actor_id, presence: true, length: { maximum: 191 }
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
# @rbs (?actor_type: String?) -> ActiveRecord::Relation[Instance]
|
|
30
|
+
def active(actor_type: nil)
|
|
31
|
+
relation = where(paused_at: nil)
|
|
32
|
+
actor_type ? relation.where(actor_type:) : relation
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @rbs (quiet_for: Numeric, ?now: Time) -> ActiveRecord::Relation[Instance]
|
|
36
|
+
def without_pending_work(quiet_for:, now: SolidObjects.database_adapter.database_now)
|
|
37
|
+
cutoff = now - quiet_for
|
|
38
|
+
where("COALESCE(last_used_at, created_at) <= ?", cutoff)
|
|
39
|
+
.where.not(id: ReadyMessage.select(:instance_id))
|
|
40
|
+
.where.not(id: ClaimedMessage.select(:instance_id))
|
|
41
|
+
.where.not(
|
|
42
|
+
id: Reminder.where(status: "scheduled").select(:instance_id)
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @rbs (actor_type: String, owner: ActiveRecord::Relation[untyped] | Class) -> ActiveRecord::Relation[Instance]
|
|
47
|
+
def orphaned(actor_type:, owner:)
|
|
48
|
+
owner_relation = owner.is_a?(ActiveRecord::Relation) ? owner : owner.all
|
|
49
|
+
owner_class = owner_relation.klass
|
|
50
|
+
owner_table = owner_class.arel_table
|
|
51
|
+
owner_primary_key = owner_table[owner_class.primary_key]
|
|
52
|
+
cast_type = Arel::Nodes::SqlLiteral.new(owner_id_cast_type)
|
|
53
|
+
cast_id = Arel::Nodes::NamedFunction.new(
|
|
54
|
+
"CAST",
|
|
55
|
+
[ Arel::Nodes::As.new(owner_primary_key, cast_type) ]
|
|
56
|
+
)
|
|
57
|
+
owner_ids = owner_relation
|
|
58
|
+
.except(:select)
|
|
59
|
+
.select(cast_id)
|
|
60
|
+
|
|
61
|
+
where(actor_type:).where.not(actor_id: owner_ids)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @rbs (actor_type: String, actor_ids: Array[String]) -> Hash[String, Hash[String, untyped]]
|
|
65
|
+
def states_for(actor_type:, actor_ids:)
|
|
66
|
+
where(actor_type:, actor_id: actor_ids)
|
|
67
|
+
.pluck(:actor_id, :state)
|
|
68
|
+
.to_h
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# @rbs () -> String
|
|
74
|
+
def owner_id_cast_type
|
|
75
|
+
case connection.adapter_name
|
|
76
|
+
when /mysql/i
|
|
77
|
+
"CHAR"
|
|
78
|
+
when /postgres/i
|
|
79
|
+
"VARCHAR"
|
|
80
|
+
else
|
|
81
|
+
"TEXT"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
# @rbs () -> void
|
|
89
|
+
def supply_defaults
|
|
90
|
+
self.state ||= {}
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
module SolidObjects
|
|
4
|
+
class Message < Record
|
|
5
|
+
self.table_name = SolidObjects.table_name(:messages)
|
|
6
|
+
|
|
7
|
+
belongs_to :instance, class_name: "SolidObjects::Instance", inverse_of: :messages
|
|
8
|
+
has_one :ready_message,
|
|
9
|
+
class_name: "SolidObjects::ReadyMessage",
|
|
10
|
+
inverse_of: :message,
|
|
11
|
+
dependent: :destroy
|
|
12
|
+
has_one :claimed_message,
|
|
13
|
+
class_name: "SolidObjects::ClaimedMessage",
|
|
14
|
+
inverse_of: :message,
|
|
15
|
+
dependent: :destroy
|
|
16
|
+
has_one :dead_letter,
|
|
17
|
+
class_name: "SolidObjects::DeadLetter",
|
|
18
|
+
inverse_of: :message,
|
|
19
|
+
dependent: :destroy
|
|
20
|
+
|
|
21
|
+
before_validation :supply_defaults
|
|
22
|
+
|
|
23
|
+
validates :message_kind, inclusion: { in: %w[tell ask internal] }
|
|
24
|
+
|
|
25
|
+
# @rbs () -> bool
|
|
26
|
+
def ready?
|
|
27
|
+
ready_message.present?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @rbs () -> bool
|
|
31
|
+
def claimed?
|
|
32
|
+
claimed_message.present?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @rbs () -> bool
|
|
36
|
+
def completed?
|
|
37
|
+
completed_at.present?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @rbs () -> bool
|
|
41
|
+
def dead?
|
|
42
|
+
dead_letter.present?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# @rbs () -> void
|
|
48
|
+
def supply_defaults
|
|
49
|
+
self.arguments ||= {}
|
|
50
|
+
self.available_at ||= enqueued_at || Time.current
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
module SolidObjects
|
|
4
|
+
class Process < Record
|
|
5
|
+
self.table_name = SolidObjects.table_name(:processes)
|
|
6
|
+
|
|
7
|
+
has_many :instances,
|
|
8
|
+
class_name: "SolidObjects::Instance",
|
|
9
|
+
foreign_key: :activation_owner_id,
|
|
10
|
+
inverse_of: :activation_owner,
|
|
11
|
+
dependent: :nullify
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
module SolidObjects
|
|
4
|
+
class ReadyMessage < Record
|
|
5
|
+
self.table_name = SolidObjects.table_name(:ready_messages)
|
|
6
|
+
|
|
7
|
+
belongs_to :message, class_name: "SolidObjects::Message", inverse_of: :ready_message
|
|
8
|
+
belongs_to :instance, class_name: "SolidObjects::Instance", inverse_of: :ready_messages
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
module SolidObjects
|
|
4
|
+
class Record < ActiveRecord::Base
|
|
5
|
+
self.abstract_class = true
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
# @rbs () -> void
|
|
9
|
+
def configure_connection
|
|
10
|
+
connection_configuration = SolidObjects.configuration.connects_to
|
|
11
|
+
return unless connection_configuration
|
|
12
|
+
|
|
13
|
+
connects_to(**connection_configuration)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<h1>Dead letters</h1>
|
|
2
|
+
|
|
3
|
+
<table>
|
|
4
|
+
<thead>
|
|
5
|
+
<tr>
|
|
6
|
+
<th>Actor</th>
|
|
7
|
+
<th>Message</th>
|
|
8
|
+
<th>Attempts</th>
|
|
9
|
+
<th>Error</th>
|
|
10
|
+
<th>Failed</th>
|
|
11
|
+
<th></th>
|
|
12
|
+
</tr>
|
|
13
|
+
</thead>
|
|
14
|
+
<tbody>
|
|
15
|
+
<% @dead_letters.each do |dead_letter| %>
|
|
16
|
+
<tr>
|
|
17
|
+
<td><%= dead_letter.actor_type %>(<%= dead_letter.actor_id %>)</td>
|
|
18
|
+
<td><%= dead_letter.message_name %></td>
|
|
19
|
+
<td><%= dead_letter.attempts %></td>
|
|
20
|
+
<td><%= dead_letter.exception_class %>: <%= dead_letter.exception_message %></td>
|
|
21
|
+
<td><%= dead_letter.last_failed_at %></td>
|
|
22
|
+
<td><%= button_to "Retry", retry_dead_letter_path(dead_letter), method: :post %></td>
|
|
23
|
+
</tr>
|
|
24
|
+
<% end %>
|
|
25
|
+
</tbody>
|
|
26
|
+
</table>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<h1>Actors</h1>
|
|
2
|
+
|
|
3
|
+
<table>
|
|
4
|
+
<thead>
|
|
5
|
+
<tr>
|
|
6
|
+
<th>Type</th>
|
|
7
|
+
<th>ID</th>
|
|
8
|
+
<th>State version</th>
|
|
9
|
+
<th>Generation</th>
|
|
10
|
+
<th>Last used</th>
|
|
11
|
+
</tr>
|
|
12
|
+
</thead>
|
|
13
|
+
<tbody>
|
|
14
|
+
<% @instances.each do |instance| %>
|
|
15
|
+
<tr>
|
|
16
|
+
<td><%= link_to instance.actor_type, instance_path(instance) %></td>
|
|
17
|
+
<td><%= instance.actor_id %></td>
|
|
18
|
+
<td><%= instance.state_version %></td>
|
|
19
|
+
<td><%= instance.activation_generation %></td>
|
|
20
|
+
<td><%= instance.last_used_at %></td>
|
|
21
|
+
</tr>
|
|
22
|
+
<% end %>
|
|
23
|
+
</tbody>
|
|
24
|
+
</table>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<h1><%= @instance.actor_type %>(<%= @instance.actor_id %>)</h1>
|
|
2
|
+
|
|
3
|
+
<dl>
|
|
4
|
+
<dt>State version</dt>
|
|
5
|
+
<dd><%= @instance.state_version %></dd>
|
|
6
|
+
<dt>Activation generation</dt>
|
|
7
|
+
<dd><%= @instance.activation_generation %></dd>
|
|
8
|
+
<dt>Activation owner</dt>
|
|
9
|
+
<dd><%= @instance.activation_owner_id || "inactive" %></dd>
|
|
10
|
+
</dl>
|
|
11
|
+
|
|
12
|
+
<h2>State</h2>
|
|
13
|
+
<pre><%= JSON.pretty_generate(@instance.state) %></pre>
|
|
14
|
+
|
|
15
|
+
<h2>Recent messages</h2>
|
|
16
|
+
<table>
|
|
17
|
+
<thead>
|
|
18
|
+
<tr>
|
|
19
|
+
<th>Sequence</th>
|
|
20
|
+
<th>Name</th>
|
|
21
|
+
<th>Attempts</th>
|
|
22
|
+
<th>Completed</th>
|
|
23
|
+
</tr>
|
|
24
|
+
</thead>
|
|
25
|
+
<tbody>
|
|
26
|
+
<% @messages.each do |message| %>
|
|
27
|
+
<tr>
|
|
28
|
+
<td><%= message.sequence %></td>
|
|
29
|
+
<td><%= message.message_name %></td>
|
|
30
|
+
<td><%= message.attempt_count %></td>
|
|
31
|
+
<td><%= message.completed_at %></td>
|
|
32
|
+
</tr>
|
|
33
|
+
<% end %>
|
|
34
|
+
</tbody>
|
|
35
|
+
</table>
|
data/benchmark/claim.rb
ADDED