Class: SolidObjects::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/solid_objects/client.rb,
sig/generated/lib/solid_objects/client.rbs

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mailbox: Mailbox.new) ⇒ Client

Returns a new instance of Client.

RBS:

  • (?mailbox: Mailbox) -> void

Parameters:

  • mailbox: (Mailbox) (defaults to: Mailbox.new)


11
12
13
# File 'lib/solid_objects/client.rb', line 11

def initialize(mailbox: Mailbox.new)
  @mailbox = mailbox
end

Instance Attribute Details

#mailboxObject (readonly)

Returns the value of attribute mailbox.

Returns:

  • (Object)


141
142
143
# File 'lib/solid_objects/client.rb', line 141

def mailbox
  @mailbox
end

Instance Method Details

#async(reference, message_name, arguments, available_at: nil, idempotency_key: nil, authorization_context: nil) ⇒ MessageReference

RBS:

  • (Reference, Symbol | String, Hash[Symbol | String, untyped], ?available_at: Time?, ?idempotency_key: String?, ?authorization_context: untyped) -> MessageReference

Parameters:

  • (Reference)
  • (Symbol, String)
  • (Hash[Symbol | String, untyped])
  • available_at: (Time, nil) (defaults to: nil)
  • idempotency_key: (String, nil) (defaults to: nil)
  • authorization_context: (Object) (defaults to: nil)

Returns:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/solid_objects/client.rb', line 16

def async(reference, message_name, arguments, available_at: nil, idempotency_key: nil, authorization_context: nil)
  actor_class = SolidObjects.registry.fetch(reference.actor_type)
  message_symbol = message_name.to_sym
  raise UnknownMessage, "unknown message #{message_name.inspect}" unless actor_class.definition.messages.key?(message_symbol)

  authorize!(
    SolidObjects.configuration.authorize_message,
    reference,
    message_name,
    arguments,
    authorization_context:
  )
  mailbox.enqueue(
    reference,
    message_name,
    arguments,
    kind: "async",
    available_at:,
    idempotency_key:
  )
end

#authorize!(hook, reference, message_name, arguments, authorization_context:) ⇒ void

This method returns an undefined value.

RBS:

  • (Proc, Reference, Symbol | String, Hash[Symbol | String, untyped], authorization_context: untyped) -> void

Parameters:

  • (Proc)
  • (Reference)
  • (Symbol, String)
  • (Hash[Symbol | String, untyped])
  • authorization_context: (Object)


196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/solid_objects/client.rb', line 196

def authorize!(hook, reference, message_name, arguments, authorization_context:)
  authorized = hook.call(
    actor_type: reference.actor_type,
    actor_id: reference.actor_id,
    message_name: message_name.to_s,
    arguments:,
    authorization_context:
  )
  return if authorized

  raise Unauthorized, "actor invocation is not authorized"
end

#authorize_destroy!(reference, authorization_context:) ⇒ void

This method returns an undefined value.

RBS:

  • (Reference, authorization_context: untyped) -> void

Parameters:

  • (Reference)
  • authorization_context: (Object)


210
211
212
213
214
215
216
217
218
219
# File 'lib/solid_objects/client.rb', line 210

def authorize_destroy!(reference, authorization_context:)
  authorized = SolidObjects.configuration.authorize_destroy.call(
    actor_type: reference.actor_type,
    actor_id: reference.actor_id,
    authorization_context:
  )
  return if authorized

  raise Unauthorized, "actor destruction is not authorized"
end

#destroy(reference, authorization_context: nil) ⇒ Boolean

RBS:

  • (Reference, ?authorization_context: untyped) -> bool

Parameters:

  • (Reference)
  • authorization_context: (Object) (defaults to: nil)

Returns:

  • (Boolean)


111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/solid_objects/client.rb', line 111

def destroy(reference, authorization_context: nil)
  raise ActorCallCycle, "actors cannot synchronously destroy another actor" if Context.current_actor

  SolidObjects.registry.fetch(reference.actor_type)
  authorize_destroy!(reference, authorization_context:)
  instance_id = SolidObjects.database_adapter.transaction do
    instance = Instance.lock.find_by(
      actor_type: reference.actor_type,
      actor_id: reference.actor_id
    )
    next unless instance

    instance_id = instance.id
    instance.delete
    instance_id
  end
  return false unless instance_id

  SolidObjects.instrument(
    :"actor.destroyed",
    instance_id:,
    actor_type: reference.actor_type,
    actor_id: reference.actor_id
  )
  SolidObjects.wake_up.signal
  true
end

#enqueue_sync(reference, message_name, arguments, idempotency_key:, timeout:) ⇒ MessageReference

RBS:

  • (Reference, Symbol | String, Hash[Symbol | String, untyped], idempotency_key: String?, timeout: Numeric) -> MessageReference

Parameters:

  • (Reference)
  • (Symbol, String)
  • (Hash[Symbol | String, untyped])
  • idempotency_key: (String, nil)
  • timeout: (Numeric)

Returns:



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/solid_objects/client.rb', line 144

def enqueue_sync(reference, message_name, arguments, idempotency_key:, timeout:)
  mailbox.enqueue(
    reference,
    message_name,
    arguments,
    kind: "sync",
    idempotency_key:
  )
rescue DatabaseDeadlineExceeded
  SolidObjects.instrument(
    :"sync.enqueue_timeout",
    actor_type: reference.actor_type,
    actor_id: reference.actor_id,
    message_name: message_name.to_s
  )
  raise SyncEnqueueTimeout.new(
    timeout:,
    actor_type: reference.actor_type,
    actor_id: reference.actor_id,
    message_name: message_name.to_s
  )
end

#reject_sync_inside_transaction!(reference, message_name) ⇒ void

This method returns an undefined value.

RBS:

  • (Reference, Symbol | String) -> void

Parameters:



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/solid_objects/client.rb', line 179

def reject_sync_inside_transaction!(reference, message_name)
  return unless SolidObjects::Record.connection.transaction_open?

  SolidObjects.instrument(
    :"sync.transaction_rejected",
    actor_type: reference.actor_type,
    actor_id: reference.actor_id,
    message_name: message_name.to_s
  )
  raise SyncInsideTransaction.new(
    actor_type: reference.actor_type,
    actor_id: reference.actor_id,
    message_name: message_name.to_s
  )
end

#snapshot(reference, authorization_context: nil) ⇒ StateSnapshot

RBS:

  • (Reference, ?authorization_context: untyped) -> StateSnapshot

Parameters:

  • (Reference)
  • authorization_context: (Object) (defaults to: nil)

Returns:



98
99
100
101
102
103
104
105
106
107
108
# File 'lib/solid_objects/client.rb', line 98

def snapshot(reference, authorization_context: nil)
  SolidObjects.registry.fetch(reference.actor_type)
  authorize!(
    SolidObjects.configuration.authorize_query,
    reference,
    "__snapshot__",
    {},
    authorization_context:
  )
  StateSnapshot.new(reference)
end

#sync(reference, message_name, arguments, timeout:, idempotency_key: nil, authorization_context: nil) ⇒ Object

RBS:

  • (Reference, Symbol | String, Hash[Symbol | String, untyped], timeout: Numeric, ?idempotency_key: String?, ?authorization_context: untyped) -> untyped

Parameters:

  • (Reference)
  • (Symbol, String)
  • (Hash[Symbol | String, untyped])
  • timeout: (Numeric)
  • idempotency_key: (String, nil) (defaults to: nil)
  • authorization_context: (Object) (defaults to: nil)

Returns:

  • (Object)


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/solid_objects/client.rb', line 39

def sync(reference, message_name, arguments, timeout:, idempotency_key: nil, authorization_context: nil)
  actor_class = SolidObjects.registry.fetch(reference.actor_type)
  message_symbol = message_name.to_sym
  query = actor_class.definition.queries.key?(message_symbol)
  actor_message = actor_class.definition.messages.key?(message_symbol)
  raise UnknownMessage, "unknown message #{message_name.inspect}" unless query || actor_message

  authorize!(
    query ? SolidObjects.configuration.authorize_query : SolidObjects.configuration.authorize_message,
    reference,
    message_name,
    arguments,
    authorization_context:
  )
  reject_sync_inside_transaction!(reference, message_name)
  SyncDeadline.with(timeout:) do
    message_reference = enqueue_sync(
      reference,
      message_name,
      arguments,
      idempotency_key:,
      timeout:
    )
    SynchronousInvocation.new.call(message_reference, timeout:)
  end
end

#validate_message_reference!(message_reference, message) ⇒ void

This method returns an undefined value.

RBS:

  • (MessageReference, Message) -> void

Parameters:



168
169
170
171
172
173
174
175
176
# File 'lib/solid_objects/client.rb', line 168

def validate_message_reference!(message_reference, message)
  valid = message_reference.request_id == message.request_id &&
    message_reference.actor_type == message.actor_type &&
    message_reference.actor_id == message.actor_id &&
    message_reference.sequence == message.sequence
  return if valid

  raise ActorDestroyed, "message reference no longer identifies this invocation"
end

#wait(message_reference, timeout:, authorization_context: nil) ⇒ Object

RBS:

  • (MessageReference, timeout: Numeric, ?authorization_context: untyped) -> untyped

Parameters:

  • (MessageReference)
  • timeout: (Numeric)
  • authorization_context: (Object) (defaults to: nil)

Returns:

  • (Object)


67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/solid_objects/client.rb', line 67

def wait(message_reference, timeout:, authorization_context: nil)
  SyncDeadline.with(timeout:) do
    message = SolidObjects.database_adapter.with_lock_retry do
      Message.find(message_reference.id)
    end
    validate_message_reference!(message_reference, message)
    reference = Reference.new(
      actor_type: message.actor_type,
      actor_id: message.actor_id
    )
    actor_class = SolidObjects.registry.fetch(reference.actor_type)
    query = actor_class.definition.queries.key?(message.message_name.to_sym)
    actor_message = actor_class.definition.messages.key?(message.message_name.to_sym)
    unless query || actor_message
      raise UnknownMessage, "unknown message #{message.message_name.inspect}"
    end
    authorize!(
      query ? SolidObjects.configuration.authorize_query : SolidObjects.configuration.authorize_message,
      reference,
      message.message_name,
      message.arguments,
      authorization_context:
    )
    reject_sync_inside_transaction!(reference, message.message_name)
    SynchronousInvocation.new.call(message_reference, timeout:)
  end
rescue DatabaseDeadlineExceeded
  raise SyncDiagnostics.new.database_contention_for(message_reference, timeout:)
end