restate-sdk 0.6.0 → 0.8.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.
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: false
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Restate
@@ -11,7 +11,6 @@ module Restate
11
11
  # end
12
12
  # end
13
13
  class Service
14
- extend T::Sig
15
14
  extend ServiceDSL
16
15
 
17
16
  # Register a handler method on this service.
@@ -27,7 +26,29 @@ module Restate
27
26
  end
28
27
  return method_name unless method_name.is_a?(Symbol)
29
28
 
30
- _register_handler(method_name, **T.unsafe({ kind: nil, **opts }))
29
+ _register_handler(method_name, kind: nil, **opts)
30
+ end
31
+
32
+ # Returns a call proxy for fluent durable calls to this service.
33
+ #
34
+ # @example
35
+ # Greeter.call.greet("World").await
36
+ #
37
+ # @return [ServiceCallProxy]
38
+ def self.call
39
+ ServiceCallProxy.new(self, call_method: :service_call)
40
+ end
41
+
42
+ # Returns a send proxy for fluent fire-and-forget sends to this service.
43
+ #
44
+ # @example
45
+ # Greeter.send!.greet("World")
46
+ # Greeter.send!(delay: 60).greet("World")
47
+ #
48
+ # @param delay [Numeric, nil] optional delay in seconds
49
+ # @return [ServiceSendProxy]
50
+ def self.send!(delay: nil)
51
+ ServiceSendProxy.new(self, send_method: :service_send, delay: delay)
31
52
  end
32
53
 
33
54
  def self._service_kind
@@ -31,6 +31,70 @@ module Restate
31
31
  subclass.instance_variable_set(:@_idempotency_retention, nil)
32
32
  subclass.instance_variable_set(:@_ingress_private, nil)
33
33
  subclass.instance_variable_set(:@_invocation_retry_policy, nil)
34
+ subclass.instance_variable_set(:@_state_declarations, {})
35
+ end
36
+
37
+ # Declare a durable state entry with auto-generated getter, setter, and clear methods.
38
+ # Only available on VirtualObject and Workflow.
39
+ #
40
+ # The generated methods delegate to the current Restate context via +Thread.current+
41
+ # (fiber-scoped in Ruby 3.0+), so they work correctly across concurrent invocations.
42
+ #
43
+ # @param name [Symbol] state key name
44
+ # @param default [Object, nil] default value returned when state is not set
45
+ # @param serde [Object] serializer/deserializer (defaults to JsonSerde)
46
+ #
47
+ # @example
48
+ # class Counter < Restate::VirtualObject
49
+ # state :count, default: 0
50
+ #
51
+ # handler def add(ctx, addend)
52
+ # self.count += addend # reads then writes via ctx.get/ctx.set
53
+ # end
54
+ #
55
+ # shared def get(ctx)
56
+ # count # reads via ctx.get, returns 0 if unset
57
+ # end
58
+ # end
59
+ def state(name, default: nil, serde: nil) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
60
+ unless respond_to?(:_service_kind) && %w[object workflow].include?(_service_kind)
61
+ Kernel.raise ArgumentError, 'state declarations are only available on VirtualObject and Workflow'
62
+ end
63
+
64
+ name = name.to_sym
65
+ @_state_declarations[name] = { default: default, serde: serde }
66
+ state_key = name.to_s
67
+ state_serde = serde
68
+ state_default = default
69
+
70
+ # Getter: reads from durable state, returns default if unset
71
+ define_method(name) do
72
+ ctx = Thread.current[:restate_context]
73
+ Kernel.raise 'Not inside a Restate handler' unless ctx
74
+
75
+ val = state_serde ? ctx.get(state_key, serde: state_serde) : ctx.get(state_key)
76
+ val.nil? ? state_default : val
77
+ end
78
+
79
+ # Setter: writes to durable state
80
+ define_method(:"#{name}=") do |value|
81
+ ctx = Thread.current[:restate_context]
82
+ Kernel.raise 'Not inside a Restate handler' unless ctx
83
+
84
+ if state_serde
85
+ ctx.set(state_key, value, serde: state_serde)
86
+ else
87
+ ctx.set(state_key, value)
88
+ end
89
+ end
90
+
91
+ # Clear: removes the state entry
92
+ define_method(:"clear_#{name}") do
93
+ ctx = Thread.current[:restate_context]
94
+ Kernel.raise 'Not inside a Restate handler' unless ctx
95
+
96
+ ctx.clear(state_key)
97
+ end
34
98
  end
35
99
 
36
100
  # Get or set the service name. Defaults to the unqualified class name.
@@ -41,7 +105,7 @@ module Restate
41
105
  if name
42
106
  @_service_name = name
43
107
  else
44
- @_service_name || T.unsafe(self).name&.split('::')&.last
108
+ @_service_name ||= self.name&.split('::')&.last # rubocop:disable Naming/MemoizedInstanceVariableName
45
109
  end
46
110
  end
47
111
 
@@ -135,7 +199,7 @@ module Restate
135
199
  #
136
200
  # @return [ServiceTag]
137
201
  def service_tag
138
- ServiceTag.new(kind: T.unsafe(self)._service_kind, name: service_name,
202
+ ServiceTag.new(kind: _service_kind, name: service_name,
139
203
  description: @_description, metadata: @_metadata)
140
204
  end
141
205
 
@@ -195,7 +259,7 @@ module Restate
195
259
  def _build_handlers # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
196
260
  tag = service_tag
197
261
  result = {}
198
- instance = T.unsafe(self).allocate
262
+ instance = allocate
199
263
 
200
264
  @_handler_registry.each do |name, meta| # rubocop:disable Metrics/BlockLength
201
265
  input_serde = Serde.resolve(meta[:input])
@@ -208,10 +272,10 @@ module Restate
208
272
  output_serde: output_serde
209
273
  )
210
274
 
211
- um = T.unsafe(self).instance_method(name)
275
+ um = instance_method(name)
212
276
  arity = um.arity.abs
213
- unless [1, 2].include?(arity)
214
- Kernel.raise ArgumentError, "handler '#{name}' must accept 1 or 2 parameters (ctx[, input]), got #{arity}"
277
+ unless [0, 1].include?(arity)
278
+ Kernel.raise ArgumentError, "handler '#{name}' must accept 0 or 1 parameters ([input]), got #{arity}"
215
279
  end
216
280
 
217
281
  bound = um.bind(instance)
@@ -0,0 +1,73 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Restate
5
+ # Proxy for fluent durable calls: +Service.call.handler(arg)+
6
+ #
7
+ # Returned by the +.call+ class method on service classes. Uses +method_missing+
8
+ # to forward handler invocations to the current Restate context.
9
+ #
10
+ # @example
11
+ # # Instead of: ctx.service_call(Greeter, :greet, "World")
12
+ # Greeter.call.greet("World")
13
+ #
14
+ # # Instead of: ctx.object_call(Counter, :add, "key", 5)
15
+ # Counter.call("key").add(5)
16
+ #
17
+ # @!visibility private
18
+ class ServiceCallProxy
19
+ def initialize(service_class, key: nil, call_method: :service_call)
20
+ @service_class = service_class
21
+ @key = key
22
+ @call_method = call_method
23
+ end
24
+
25
+ def method_missing(handler_name, arg = nil, **opts)
26
+ ctx = Restate.fetch_context!
27
+ if @key
28
+ ctx.public_send(@call_method, @service_class, handler_name, @key, arg, **opts)
29
+ else
30
+ ctx.public_send(@call_method, @service_class, handler_name, arg, **opts)
31
+ end
32
+ end
33
+
34
+ def respond_to_missing?(method_name, include_private = false)
35
+ (@service_class.respond_to?(:handlers) && @service_class.handlers.key?(method_name.to_s)) || super
36
+ end
37
+ end
38
+
39
+ # Proxy for fluent fire-and-forget sends: +Service.send!.handler(arg)+
40
+ #
41
+ # Returned by the +.send!+ class method on service classes.
42
+ #
43
+ # @example
44
+ # # Instead of: ctx.service_send(Greeter, :greet, "World")
45
+ # Greeter.send!.greet("World")
46
+ #
47
+ # # Instead of: ctx.object_send(Counter, :add, "key", 5, delay: 60)
48
+ # Counter.send!("key", delay: 60).add(5)
49
+ #
50
+ # @!visibility private
51
+ class ServiceSendProxy
52
+ def initialize(service_class, key: nil, send_method: :service_send, delay: nil)
53
+ @service_class = service_class
54
+ @key = key
55
+ @send_method = send_method
56
+ @delay = delay
57
+ end
58
+
59
+ def method_missing(handler_name, arg = nil, **opts)
60
+ ctx = Restate.fetch_context!
61
+ opts[:delay] = @delay if @delay
62
+ if @key
63
+ ctx.public_send(@send_method, @service_class, handler_name, @key, arg, **opts)
64
+ else
65
+ ctx.public_send(@send_method, @service_class, handler_name, arg, **opts)
66
+ end
67
+ end
68
+
69
+ def respond_to_missing?(method_name, include_private = false)
70
+ (@service_class.respond_to?(:handlers) && @service_class.handlers.key?(method_name.to_s)) || super
71
+ end
72
+ end
73
+ end
@@ -1,4 +1,4 @@
1
- # typed: false
1
+ # typed: ignore
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'restate'
@@ -1,6 +1,6 @@
1
- # typed: strict
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Restate
5
- VERSION = '0.6.0'
5
+ VERSION = '0.8.0'
6
6
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: false
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Restate
@@ -17,7 +17,6 @@ module Restate
17
17
  # end
18
18
  # end
19
19
  class VirtualObject
20
- extend T::Sig
21
20
  extend ServiceDSL
22
21
 
23
22
  # Register an exclusive handler. Use as: +handler def my_method(ctx, arg)+
@@ -33,7 +32,7 @@ module Restate
33
32
  end
34
33
  return method_name unless method_name.is_a?(Symbol)
35
34
 
36
- _register_handler(method_name, **T.unsafe({ kind: kind.to_s, **opts }))
35
+ _register_handler(method_name, kind: kind.to_s, **opts)
37
36
  end
38
37
 
39
38
  # Register a shared (concurrent-access) handler.
@@ -46,7 +45,31 @@ module Restate
46
45
  "handler expects a Symbol (use `shared def #{method_name}(...)` or `shared :#{method_name}`)"
47
46
  end
48
47
 
49
- _register_handler(method_name, **T.unsafe({ kind: 'shared', **opts }))
48
+ _register_handler(method_name, kind: 'shared', **opts)
49
+ end
50
+
51
+ # Returns a call proxy for fluent durable calls to this virtual object.
52
+ #
53
+ # @example
54
+ # Counter.call("my-key").add(5).await
55
+ #
56
+ # @param key [String] the object key
57
+ # @return [ServiceCallProxy]
58
+ def self.call(key)
59
+ ServiceCallProxy.new(self, key: key, call_method: :object_call)
60
+ end
61
+
62
+ # Returns a send proxy for fluent fire-and-forget sends to this virtual object.
63
+ #
64
+ # @example
65
+ # Counter.send!("my-key").add(5)
66
+ # Counter.send!("my-key", delay: 60).add(5)
67
+ #
68
+ # @param key [String] the object key
69
+ # @param delay [Numeric, nil] optional delay in seconds
70
+ # @return [ServiceSendProxy]
71
+ def self.send!(key, delay: nil)
72
+ ServiceSendProxy.new(self, key: key, send_method: :object_send, delay: delay)
50
73
  end
51
74
 
52
75
  def self._service_kind
data/lib/restate/vm.rb CHANGED
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: ignore
2
2
  # frozen_string_literal: true
3
3
 
4
4
  begin
@@ -19,9 +19,9 @@ module Restate
19
19
  class NotReady; end
20
20
  class Suspended; end
21
21
 
22
- NOT_READY = T.let(NotReady.new.freeze, NotReady)
23
- SUSPENDED = T.let(Suspended.new.freeze, Suspended)
24
- CANCEL_HANDLE = T.let(Internal::CANCEL_NOTIFICATION_HANDLE, Integer)
22
+ NOT_READY = NotReady.new.freeze
23
+ SUSPENDED = Suspended.new.freeze
24
+ CANCEL_HANDLE = Internal::CANCEL_NOTIFICATION_HANDLE
25
25
 
26
26
  # Progress loop result types
27
27
  class DoProgressAnyCompleted; end
@@ -29,10 +29,10 @@ module Restate
29
29
  class DoProgressCancelSignalReceived; end
30
30
  class DoWaitPendingRun; end
31
31
 
32
- DO_PROGRESS_ANY_COMPLETED = T.let(DoProgressAnyCompleted.new.freeze, DoProgressAnyCompleted)
33
- DO_PROGRESS_READ_FROM_INPUT = T.let(DoProgressReadFromInput.new.freeze, DoProgressReadFromInput)
34
- DO_PROGRESS_CANCEL_SIGNAL_RECEIVED = T.let(DoProgressCancelSignalReceived.new.freeze, DoProgressCancelSignalReceived)
35
- DO_WAIT_PENDING_RUN = T.let(DoWaitPendingRun.new.freeze, DoWaitPendingRun)
32
+ DO_PROGRESS_ANY_COMPLETED = DoProgressAnyCompleted.new.freeze
33
+ DO_PROGRESS_READ_FROM_INPUT = DoProgressReadFromInput.new.freeze
34
+ DO_PROGRESS_CANCEL_SIGNAL_RECEIVED = DoProgressCancelSignalReceived.new.freeze
35
+ DO_WAIT_PENDING_RUN = DoWaitPendingRun.new.freeze
36
36
 
37
37
  DoProgressExecuteRun = Struct.new(:handle, keyword_init: true)
38
38
 
@@ -52,50 +52,39 @@ module Restate
52
52
 
53
53
  # Wraps the native Restate::Internal::VM, mapping native types to Ruby types.
54
54
  class VMWrapper
55
- extend T::Sig
56
-
57
- sig { params(headers: T.untyped).void }
58
55
  def initialize(headers)
59
- @vm = T.let(Internal::VM.new(headers), Internal::VM)
56
+ @vm = Internal::VM.new(headers)
60
57
  end
61
58
 
62
- sig { returns([Integer, T.untyped]) }
63
59
  def get_response_head
64
60
  result = @vm.get_response_head
65
61
  [result.status_code, result.headers]
66
62
  end
67
63
 
68
- sig { params(buf: String).void }
69
64
  def notify_input(buf)
70
65
  @vm.notify_input(buf)
71
66
  end
72
67
 
73
- sig { void }
74
68
  def notify_input_closed
75
69
  @vm.notify_input_closed
76
70
  end
77
71
 
78
- sig { params(error: String, stacktrace: T.nilable(String)).void }
79
72
  def notify_error(error, stacktrace = nil)
80
73
  @vm.notify_error(error, stacktrace)
81
74
  end
82
75
 
83
- sig { returns(T.nilable(String)) }
84
76
  def take_output
85
77
  @vm.take_output
86
78
  end
87
79
 
88
- sig { returns(T::Boolean) }
89
80
  def is_ready_to_execute
90
81
  @vm.is_ready_to_execute
91
82
  end
92
83
 
93
- sig { params(handle: Integer).returns(T::Boolean) }
94
84
  def is_completed(handle)
95
85
  @vm.is_completed(handle)
96
86
  end
97
87
 
98
- sig { params(handles: T::Array[Integer]).returns(T.untyped) }
99
88
  def do_progress(handles)
100
89
  result = @vm.do_progress(handles)
101
90
  map_do_progress(result)
@@ -103,7 +92,6 @@ module Restate
103
92
  e
104
93
  end
105
94
 
106
- sig { params(handle: Integer).returns(T.untyped) }
107
95
  def take_notification(handle)
108
96
  result = @vm.take_notification(handle)
109
97
  map_notification(result)
@@ -111,7 +99,6 @@ module Restate
111
99
  e
112
100
  end
113
101
 
114
- sig { returns(T.untyped) }
115
102
  def sys_input
116
103
  inp = @vm.sys_input
117
104
  headers = inp.headers.map { |h| [h.key, h.value] }
@@ -124,94 +111,56 @@ module Restate
124
111
  )
125
112
  end
126
113
 
127
- sig { params(name: String).returns(Integer) }
128
114
  def sys_get_state(name)
129
115
  @vm.sys_get_state(name)
130
116
  end
131
117
 
132
- sig { returns(Integer) }
133
118
  def sys_get_state_keys
134
119
  @vm.sys_get_state_keys
135
120
  end
136
121
 
137
- sig { params(name: String, value: String).void }
138
122
  def sys_set_state(name, value)
139
123
  @vm.sys_set_state(name, value)
140
124
  end
141
125
 
142
- sig { params(name: String).void }
143
126
  def sys_clear_state(name)
144
127
  @vm.sys_clear_state(name)
145
128
  end
146
129
 
147
- sig { void }
148
130
  def sys_clear_all_state
149
131
  @vm.sys_clear_all_state
150
132
  end
151
133
 
152
- sig { params(millis: Integer, name: T.nilable(String)).returns(Integer) }
153
134
  def sys_sleep(millis, name = nil)
154
135
  # Rust side always expects 2 args: (millis, name_or_nil)
155
136
  @vm.sys_sleep(millis, name)
156
137
  end
157
138
 
158
- sig do
159
- params(
160
- service: String,
161
- handler: String,
162
- parameter: String,
163
- key: T.nilable(String),
164
- idempotency_key: T.nilable(String),
165
- headers: T.nilable(T::Hash[String, String])
166
- ).returns(Internal::CallHandle)
167
- end
168
139
  def sys_call(service:, handler:, parameter:, key: nil, idempotency_key: nil, headers: nil)
169
140
  # Rust side expects 6 args: (service, handler, buffer, key_or_nil, idem_key_or_nil, headers_or_nil)
170
141
  hdr_array = headers&.map { |k, v| [k, v] }
171
142
  @vm.sys_call(service, handler, parameter, key, idempotency_key, hdr_array)
172
143
  end
173
144
 
174
- sig do
175
- params(
176
- service: String,
177
- handler: String,
178
- parameter: String,
179
- key: T.nilable(String),
180
- delay: T.nilable(Integer),
181
- idempotency_key: T.nilable(String),
182
- headers: T.nilable(T::Hash[String, String])
183
- ).returns(Integer)
184
- end
185
145
  def sys_send(service:, handler:, parameter:, key: nil, delay: nil, idempotency_key: nil, headers: nil)
186
146
  # Rust side expects 7 args
187
147
  hdr_array = headers&.map { |k, v| [k, v] }
188
148
  @vm.sys_send(service, handler, parameter, key, delay, idempotency_key, hdr_array)
189
149
  end
190
150
 
191
- sig { params(name: String).returns(Integer) }
192
151
  def sys_run(name)
193
152
  @vm.sys_run(name)
194
153
  end
195
154
 
196
- sig { params(handle: Integer, output: String).void }
197
155
  def propose_run_completion_success(handle, output)
198
156
  @vm.propose_run_completion_success(handle, output)
199
157
  end
200
158
 
201
- sig { params(handle: Integer, failure: T.untyped).void }
202
159
  def propose_run_completion_failure(handle, failure)
203
160
  native_failure = Internal::Failure.new(failure.code, failure.message, nil)
204
161
  @vm.propose_run_completion_failure(handle, native_failure)
205
162
  end
206
163
 
207
- sig do
208
- params(
209
- handle: Integer,
210
- failure: T.untyped,
211
- attempt_duration_ms: Integer,
212
- config: T.untyped
213
- ).void
214
- end
215
164
  def propose_run_completion_transient(handle, failure:, attempt_duration_ms:, config:)
216
165
  native_failure = Internal::Failure.new(failure.code, failure.message, failure.stacktrace)
217
166
  native_config = Internal::ExponentialRetryConfig.new(
@@ -222,73 +171,60 @@ module Restate
222
171
  @vm.propose_run_completion_failure_transient(handle, native_failure, attempt_duration_ms, native_config)
223
172
  end
224
173
 
225
- sig { params(output: String).void }
226
174
  def sys_write_output_success(output)
227
175
  @vm.sys_write_output_success(output)
228
176
  end
229
177
 
230
- sig { params(failure: T.untyped).void }
231
178
  def sys_write_output_failure(failure)
232
179
  native_failure = Internal::Failure.new(failure.code, failure.message, nil)
233
180
  @vm.sys_write_output_failure(native_failure)
234
181
  end
235
182
 
236
- sig { void }
237
183
  def sys_end
238
184
  @vm.sys_end
239
185
  end
240
186
 
241
- sig { returns(T::Boolean) }
242
187
  def is_replaying
243
188
  @vm.is_replaying
244
189
  end
245
190
 
246
191
  # Returns [awakeable_id (String), notification_handle (Integer)]
247
- sig { returns([String, Integer]) }
248
192
  def sys_awakeable
249
193
  @vm.sys_awakeable
250
194
  end
251
195
 
252
- sig { params(awakeable_id: String, value: String).void }
253
196
  def sys_complete_awakeable_success(awakeable_id, value)
254
197
  @vm.sys_complete_awakeable_success(awakeable_id, value)
255
198
  end
256
199
 
257
- sig { params(awakeable_id: String, failure: T.untyped).void }
258
200
  def sys_complete_awakeable_failure(awakeable_id, failure)
259
201
  native_failure = Internal::Failure.new(failure.code, failure.message, nil)
260
202
  @vm.sys_complete_awakeable_failure(awakeable_id, native_failure)
261
203
  end
262
204
 
263
- sig { params(key: String).returns(Integer) }
264
205
  def sys_get_promise(key)
265
206
  @vm.sys_get_promise(key)
266
207
  end
267
208
 
268
- sig { params(key: String).returns(Integer) }
269
209
  def sys_peek_promise(key)
270
210
  @vm.sys_peek_promise(key)
271
211
  end
272
212
 
273
- sig { params(key: String, value: String).returns(Integer) }
274
213
  def sys_complete_promise_success(key, value)
275
214
  @vm.sys_complete_promise_success(key, value)
276
215
  end
277
216
 
278
- sig { params(key: String, failure: T.untyped).returns(Integer) }
279
217
  def sys_complete_promise_failure(key, failure)
280
218
  native_failure = Internal::Failure.new(failure.code, failure.message, nil)
281
219
  @vm.sys_complete_promise_failure(key, native_failure)
282
220
  end
283
221
 
284
- sig { params(invocation_id: String).void }
285
222
  def sys_cancel_invocation(invocation_id)
286
223
  @vm.sys_cancel_invocation(invocation_id)
287
224
  end
288
225
 
289
226
  private
290
227
 
291
- sig { params(result: T.untyped).returns(T.untyped) }
292
228
  def map_do_progress(result)
293
229
  case result
294
230
  when Internal::Suspended
@@ -308,7 +244,6 @@ module Restate
308
244
  end
309
245
  end
310
246
 
311
- sig { params(result: T.untyped).returns(T.untyped) }
312
247
  def map_notification(result)
313
248
  case result
314
249
  when Internal::Suspended
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: false
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Restate
@@ -15,7 +15,6 @@ module Restate
15
15
  # end
16
16
  # end
17
17
  class Workflow
18
- extend T::Sig
19
18
  extend ServiceDSL
20
19
 
21
20
  # Register the main workflow entry point.
@@ -31,7 +30,7 @@ module Restate
31
30
  end
32
31
  return method_name unless method_name.is_a?(Symbol)
33
32
 
34
- _register_handler(method_name, **T.unsafe({ kind: 'workflow', **opts }))
33
+ _register_handler(method_name, kind: 'workflow', **opts)
35
34
  end
36
35
 
37
36
  # Register a shared handler on this workflow.
@@ -46,7 +45,31 @@ module Restate
46
45
  end
47
46
  return method_name unless method_name.is_a?(Symbol)
48
47
 
49
- _register_handler(method_name, **T.unsafe({ kind: 'shared', **opts }))
48
+ _register_handler(method_name, kind: 'shared', **opts)
49
+ end
50
+
51
+ # Returns a call proxy for fluent durable calls to this workflow.
52
+ #
53
+ # @example
54
+ # UserSignup.call("user42").run("user@example.com").await
55
+ #
56
+ # @param key [String] the workflow key
57
+ # @return [ServiceCallProxy]
58
+ def self.call(key)
59
+ ServiceCallProxy.new(self, key: key, call_method: :workflow_call)
60
+ end
61
+
62
+ # Returns a send proxy for fluent fire-and-forget sends to this workflow.
63
+ #
64
+ # @example
65
+ # UserSignup.send!("user42").run("user@example.com")
66
+ # UserSignup.send!("user42", delay: 60).run("user@example.com")
67
+ #
68
+ # @param key [String] the workflow key
69
+ # @param delay [Numeric, nil] optional delay in seconds
70
+ # @return [ServiceSendProxy]
71
+ def self.send!(key, delay: nil)
72
+ ServiceSendProxy.new(self, key: key, send_method: :workflow_send, delay: delay)
50
73
  end
51
74
 
52
75
  def self._service_kind