restate-sdk 0.7.0 → 0.9.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
  require 'async'
@@ -18,35 +18,27 @@ module Restate
18
18
  class ServerContext
19
19
  include WorkflowContext
20
20
  include WorkflowSharedContext
21
- extend T::Sig
22
21
 
23
- LOGGER = T.let(Logger.new($stdout, progname: 'Restate::ServerContext'), Logger)
22
+ LOGGER = Logger.new($stdout, progname: 'Restate::ServerContext')
24
23
 
25
- sig { returns(VMWrapper) }
26
- attr_reader :vm
24
+ attr_reader :vm, :invocation
27
25
 
28
- sig { returns(T.untyped) }
29
- attr_reader :invocation
30
-
31
- sig do
32
- params(vm: VMWrapper, handler: T.untyped, invocation: T.untyped, send_output: T.untyped,
33
- input_queue: Async::Queue, middleware: T::Array[T.untyped]).void
34
- end
35
- def initialize(vm:, handler:, invocation:, send_output:, input_queue:, middleware: [])
36
- @vm = T.let(vm, VMWrapper)
37
- @handler = T.let(handler, T.untyped)
38
- @invocation = T.let(invocation, T.untyped)
39
- @send_output = T.let(send_output, T.untyped)
40
- @input_queue = T.let(input_queue, Async::Queue)
41
- @run_coros_to_execute = T.let({}, T::Hash[Integer, T.untyped])
42
- @attempt_finished_event = T.let(AttemptFinishedEvent.new, AttemptFinishedEvent)
43
- @middleware = T.let(middleware, T::Array[T.untyped])
26
+ def initialize(vm:, handler:, invocation:, send_output:, input_queue:, middleware: [],
27
+ outbound_middleware: [])
28
+ @vm = vm
29
+ @handler = handler
30
+ @invocation = invocation
31
+ @send_output = send_output
32
+ @input_queue = input_queue
33
+ @run_coros_to_execute = {}
34
+ @attempt_finished_event = AttemptFinishedEvent.new
35
+ @middleware = middleware
36
+ @outbound_middleware = outbound_middleware
44
37
  end
45
38
 
46
39
  # ── Main entry point ──
47
40
 
48
41
  # Runs the handler to completion, writing the output (or failure) to the journal.
49
- sig { void }
50
42
  def enter
51
43
  Thread.current[:restate_context] = self
52
44
  Thread.current[:restate_service_kind] = @handler.service_tag.kind
@@ -54,7 +46,7 @@ module Restate
54
46
  in_buffer = @invocation.input_buffer
55
47
  out_buffer = Restate.invoke_handler(handler: @handler, ctx: self, in_buffer: in_buffer,
56
48
  middleware: @middleware)
57
- @vm.sys_write_output_success(out_buffer.b)
49
+ @vm.sys_write_output_success(out_buffer)
58
50
  @vm.sys_end
59
51
  rescue TerminalError => e
60
52
  failure = Failure.new(code: e.status_code, message: e.message)
@@ -66,8 +58,8 @@ module Restate
66
58
  raise
67
59
  rescue StandardError => e
68
60
  # Walk the cause chain for TerminalError or internal exceptions
69
- cause = T.let(e, T.nilable(Exception))
70
- handled = T.let(false, T::Boolean)
61
+ cause = e
62
+ handled = false
71
63
  while cause
72
64
  if cause.is_a?(TerminalError)
73
65
  f = Failure.new(code: cause.status_code, message: cause.message)
@@ -95,7 +87,6 @@ module Restate
95
87
  # Called by the server when the attempt ends (handler completed, disconnected,
96
88
  # or transient error). Signals the attempt_finished_event so that user code
97
89
  # and background pool jobs can clean up.
98
- sig { void }
99
90
  def on_attempt_finished
100
91
  @attempt_finished_event.set!
101
92
  end
@@ -103,44 +94,37 @@ module Restate
103
94
  # ── State operations ──
104
95
 
105
96
  # Durably retrieves a state entry by name. Returns nil if unset.
106
- sig { override.params(name: String, serde: T.untyped).returns(T.untyped) }
107
97
  def get(name, serde: JsonSerde)
108
98
  get_async(name, serde: serde).await
109
99
  end
110
100
 
111
101
  # Returns a DurableFuture for a state entry. Resolves to nil if unset.
112
- sig { override.params(name: String, serde: T.untyped).returns(DurableFuture) }
113
102
  def get_async(name, serde: JsonSerde)
114
103
  handle = @vm.sys_get_state(name)
115
104
  DurableFuture.new(self, handle, serde: serde)
116
105
  end
117
106
 
118
107
  # Durably sets a state entry. The value is serialized via +serde+.
119
- sig { override.params(name: String, value: T.untyped, serde: T.untyped).void }
120
108
  def set(name, value, serde: JsonSerde)
121
- @vm.sys_set_state(name, serde.serialize(value).b)
109
+ @vm.sys_set_state(name, serde.serialize(value))
122
110
  end
123
111
 
124
112
  # Durably removes a single state entry by name.
125
- sig { override.params(name: String).void }
126
113
  def clear(name)
127
114
  @vm.sys_clear_state(name)
128
115
  end
129
116
 
130
117
  # Durably removes all state entries for this virtual object or workflow.
131
- sig { override.void }
132
118
  def clear_all
133
119
  @vm.sys_clear_all_state
134
120
  end
135
121
 
136
122
  # Returns the list of all state entry names for this virtual object or workflow.
137
- sig { override.returns(T.untyped) }
138
123
  def state_keys
139
124
  state_keys_async.await
140
125
  end
141
126
 
142
127
  # Returns a DurableFuture for the list of all state entry names.
143
- sig { override.returns(DurableFuture) }
144
128
  def state_keys_async
145
129
  handle = @vm.sys_get_state_keys
146
130
  DurableFuture.new(self, handle)
@@ -150,7 +134,6 @@ module Restate
150
134
 
151
135
  # Returns a durable future that completes after the given duration.
152
136
  # The timer survives handler restarts.
153
- sig { params(seconds: Numeric).returns(DurableFuture) }
154
137
  def sleep(seconds)
155
138
  millis = (seconds * 1000).to_i
156
139
  handle = @vm.sys_sleep(millis)
@@ -158,45 +141,31 @@ module Restate
158
141
  end
159
142
 
160
143
  # Block until a previously created handle completes. Returns the value.
161
- sig { params(handle: Integer).returns(T.untyped) }
162
144
  def resolve_handle(handle)
163
145
  poll_and_take(handle)
164
146
  end
165
147
 
166
148
  # Wait until any of the given handles completes. Does not take notifications.
167
- sig { params(handles: T::Array[Integer]).void }
168
149
  def wait_any_handle(handles)
169
150
  poll_or_cancel(handles) unless handles.any? { |h| @vm.is_completed(h) }
170
151
  end
171
152
 
172
153
  # Check if a handle is completed (non-blocking).
173
- sig { params(handle: Integer).returns(T::Boolean) }
174
154
  def completed?(handle)
175
155
  @vm.is_completed(handle)
176
156
  end
177
157
 
178
158
  # Take a completed handle's notification, returning the value.
179
159
  # Raises TerminalError if the handle resolved to a failure.
180
- sig { params(handle: Integer).returns(T.untyped) }
181
160
  def take_completed(handle)
182
161
  must_take_notification(handle)
183
162
  end
184
163
 
185
164
  # Wait until any of the given futures completes. Returns [completed, remaining].
186
- sig { override.params(futures: DurableFuture).returns([T::Array[DurableFuture], T::Array[DurableFuture]]) }
187
165
  def wait_any(*futures)
188
166
  handles = futures.map(&:handle)
189
167
  wait_any_handle(handles)
190
- completed = []
191
- remaining = []
192
- futures.each do |f|
193
- if f.completed?
194
- completed << f
195
- else
196
- remaining << f
197
- end
198
- end
199
- [completed, remaining]
168
+ futures.partition(&:completed?)
200
169
  end
201
170
 
202
171
  # ── Durable run (side effect) ──
@@ -207,20 +176,15 @@ module Restate
207
176
  # Pass +background: true+ to run the block in a real OS Thread, keeping the
208
177
  # fiber event loop responsive for other concurrent handlers. Use this for
209
178
  # CPU-intensive work.
210
- sig do
211
- override.params(
212
- name: String,
213
- serde: T.untyped,
214
- retry_policy: T.nilable(RunRetryPolicy),
215
- background: T::Boolean,
216
- action: T.proc.returns(T.untyped)
217
- ).returns(DurableFuture)
218
- end
219
179
  def run(name, serde: JsonSerde, retry_policy: nil, background: false, &action)
220
180
  handle = @vm.sys_run(name)
221
181
 
222
- executor = background ? :execute_run_threaded : :execute_run
223
- @run_coros_to_execute[handle] = -> { send(executor, handle, action, serde, retry_policy) }
182
+ @run_coros_to_execute[handle] =
183
+ if background
184
+ -> { execute_run_threaded(handle, action, serde, retry_policy) }
185
+ else
186
+ -> { execute_run(handle, action, serde, retry_policy) }
187
+ end
224
188
 
225
189
  DurableFuture.new(self, handle, serde: serde)
226
190
  end
@@ -229,15 +193,6 @@ module Restate
229
193
  # and returns the result directly.
230
194
  #
231
195
  # Accepts all the same options as +run+, including +background: true+.
232
- sig do
233
- override.params(
234
- name: String,
235
- serde: T.untyped,
236
- retry_policy: T.nilable(RunRetryPolicy),
237
- background: T::Boolean,
238
- action: T.proc.returns(T.untyped)
239
- ).returns(T.untyped)
240
- end
241
196
  def run_sync(name, serde: JsonSerde, retry_policy: nil, background: false, &action)
242
197
  run(name, serde: serde, retry_policy: retry_policy, background: background, &action).await
243
198
  end
@@ -245,124 +200,72 @@ module Restate
245
200
  # ── Service calls ──
246
201
 
247
202
  # Durably calls a handler on a Restate service and returns a future for its result.
248
- sig do
249
- override.params(
250
- service: T.any(String, T::Class[T.anything]),
251
- handler: T.any(String, Symbol),
252
- arg: T.untyped,
253
- key: T.nilable(String),
254
- idempotency_key: T.nilable(String),
255
- headers: T.nilable(T::Hash[String, String]),
256
- input_serde: T.untyped,
257
- output_serde: T.untyped
258
- ).returns(DurableCallFuture)
259
- end
260
203
  def service_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil,
261
204
  input_serde: NOT_SET, output_serde: NOT_SET)
262
205
  svc_name, handler_name, handler_meta = resolve_call_target(service, handler)
263
206
  in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
264
207
  out_serde = resolve_serde(output_serde, handler_meta, :output_serde)
265
208
  parameter = in_serde.serialize(arg)
266
- call_handle = @vm.sys_call(
267
- service: svc_name, handler: handler_name, parameter: parameter.b,
268
- key: key, idempotency_key: idempotency_key, headers: headers
269
- )
270
- DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
271
- output_serde: out_serde)
209
+ with_outbound_middleware(svc_name, handler_name, headers) do |hdrs|
210
+ call_handle = @vm.sys_call(
211
+ service: svc_name, handler: handler_name, parameter: parameter,
212
+ key: key, idempotency_key: idempotency_key, headers: hdrs
213
+ )
214
+ DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
215
+ output_serde: out_serde)
216
+ end
272
217
  end
273
218
 
274
219
  # Sends a one-way invocation to a Restate service handler (fire-and-forget).
275
- sig do
276
- override.params(
277
- service: T.any(String, T::Class[T.anything]),
278
- handler: T.any(String, Symbol),
279
- arg: T.untyped,
280
- key: T.nilable(String),
281
- delay: T.nilable(Numeric),
282
- idempotency_key: T.nilable(String),
283
- headers: T.nilable(T::Hash[String, String]),
284
- input_serde: T.untyped
285
- ).returns(SendHandle)
286
- end
287
220
  def service_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil,
288
221
  input_serde: NOT_SET)
289
222
  svc_name, handler_name, handler_meta = resolve_call_target(service, handler)
290
223
  in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
291
224
  parameter = in_serde.serialize(arg)
292
225
  delay_ms = delay ? (delay * 1000).to_i : nil
293
- invocation_id_handle = @vm.sys_send(
294
- service: svc_name, handler: handler_name, parameter: parameter.b,
295
- key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: headers
296
- )
297
- SendHandle.new(self, invocation_id_handle)
226
+ with_outbound_middleware(svc_name, handler_name, headers) do |hdrs|
227
+ invocation_id_handle = @vm.sys_send(
228
+ service: svc_name, handler: handler_name, parameter: parameter,
229
+ key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs
230
+ )
231
+ SendHandle.new(self, invocation_id_handle)
232
+ end
298
233
  end
299
234
 
300
235
  # Durably calls a handler on a Restate virtual object, keyed by +key+.
301
- sig do
302
- override.params(
303
- service: T.any(String, T::Class[T.anything]),
304
- handler: T.any(String, Symbol),
305
- key: String,
306
- arg: T.untyped,
307
- idempotency_key: T.nilable(String),
308
- headers: T.nilable(T::Hash[String, String]),
309
- input_serde: T.untyped,
310
- output_serde: T.untyped
311
- ).returns(DurableCallFuture)
312
- end
313
236
  def object_call(service, handler, key, arg, idempotency_key: nil, headers: nil,
314
237
  input_serde: NOT_SET, output_serde: NOT_SET)
315
238
  svc_name, handler_name, handler_meta = resolve_call_target(service, handler)
316
239
  in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
317
240
  out_serde = resolve_serde(output_serde, handler_meta, :output_serde)
318
241
  parameter = in_serde.serialize(arg)
319
- call_handle = @vm.sys_call(
320
- service: svc_name, handler: handler_name, parameter: parameter.b,
321
- key: key, idempotency_key: idempotency_key, headers: headers
322
- )
323
- DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
324
- output_serde: out_serde)
242
+ with_outbound_middleware(svc_name, handler_name, headers) do |hdrs|
243
+ call_handle = @vm.sys_call(
244
+ service: svc_name, handler: handler_name, parameter: parameter,
245
+ key: key, idempotency_key: idempotency_key, headers: hdrs
246
+ )
247
+ DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
248
+ output_serde: out_serde)
249
+ end
325
250
  end
326
251
 
327
252
  # Sends a one-way invocation to a Restate virtual object handler (fire-and-forget).
328
- sig do
329
- override.params(
330
- service: T.any(String, T::Class[T.anything]),
331
- handler: T.any(String, Symbol),
332
- key: String,
333
- arg: T.untyped,
334
- delay: T.nilable(Numeric),
335
- idempotency_key: T.nilable(String),
336
- headers: T.nilable(T::Hash[String, String]),
337
- input_serde: T.untyped
338
- ).returns(SendHandle)
339
- end
340
253
  def object_send(service, handler, key, arg, delay: nil, idempotency_key: nil, headers: nil,
341
254
  input_serde: NOT_SET)
342
255
  svc_name, handler_name, handler_meta = resolve_call_target(service, handler)
343
256
  in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
344
257
  parameter = in_serde.serialize(arg)
345
258
  delay_ms = delay ? (delay * 1000).to_i : nil
346
- invocation_id_handle = @vm.sys_send(
347
- service: svc_name, handler: handler_name, parameter: parameter.b,
348
- key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: headers
349
- )
350
- SendHandle.new(self, invocation_id_handle)
259
+ with_outbound_middleware(svc_name, handler_name, headers) do |hdrs|
260
+ invocation_id_handle = @vm.sys_send(
261
+ service: svc_name, handler: handler_name, parameter: parameter,
262
+ key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs
263
+ )
264
+ SendHandle.new(self, invocation_id_handle)
265
+ end
351
266
  end
352
267
 
353
268
  # Durably calls a handler on a Restate workflow, keyed by +key+.
354
- sig do
355
- override.params(
356
- service: T.any(String, T::Class[T.anything]),
357
- handler: T.any(String, Symbol),
358
- key: String,
359
- arg: T.untyped,
360
- idempotency_key: T.nilable(String),
361
- headers: T.nilable(T::Hash[String, String]),
362
- input_serde: T.untyped,
363
- output_serde: T.untyped
364
- ).returns(DurableCallFuture)
365
- end
366
269
  def workflow_call(service, handler, key, arg, idempotency_key: nil, headers: nil,
367
270
  input_serde: NOT_SET, output_serde: NOT_SET)
368
271
  object_call(service, handler, key, arg, idempotency_key: idempotency_key, headers: headers,
@@ -370,18 +273,6 @@ module Restate
370
273
  end
371
274
 
372
275
  # Sends a one-way invocation to a Restate workflow handler (fire-and-forget).
373
- sig do
374
- override.params(
375
- service: T.any(String, T::Class[T.anything]),
376
- handler: T.any(String, Symbol),
377
- key: String,
378
- arg: T.untyped,
379
- delay: T.nilable(Numeric),
380
- idempotency_key: T.nilable(String),
381
- headers: T.nilable(T::Hash[String, String]),
382
- input_serde: T.untyped
383
- ).returns(SendHandle)
384
- end
385
276
  def workflow_send(service, handler, key, arg, delay: nil, idempotency_key: nil, headers: nil,
386
277
  input_serde: NOT_SET)
387
278
  object_send(service, handler, key, arg, delay: delay, idempotency_key: idempotency_key, headers: headers,
@@ -391,20 +282,17 @@ module Restate
391
282
  # ── Awakeables ──
392
283
 
393
284
  # Creates an awakeable and returns [awakeable_id, DurableFuture].
394
- sig { override.params(serde: T.untyped).returns([String, DurableFuture]) }
395
285
  def awakeable(serde: JsonSerde)
396
286
  id, handle = @vm.sys_awakeable
397
287
  [id, DurableFuture.new(self, handle, serde: serde)]
398
288
  end
399
289
 
400
290
  # Resolves an awakeable with a success value.
401
- sig { override.params(awakeable_id: String, payload: T.untyped, serde: T.untyped).void }
402
291
  def resolve_awakeable(awakeable_id, payload, serde: JsonSerde)
403
- @vm.sys_complete_awakeable_success(awakeable_id, serde.serialize(payload).b)
292
+ @vm.sys_complete_awakeable_success(awakeable_id, serde.serialize(payload))
404
293
  end
405
294
 
406
295
  # Rejects an awakeable with a terminal failure.
407
- sig { override.params(awakeable_id: String, message: String, code: Integer).void }
408
296
  def reject_awakeable(awakeable_id, message, code: 500)
409
297
  failure = Failure.new(code: code, message: message)
410
298
  @vm.sys_complete_awakeable_failure(awakeable_id, failure)
@@ -413,7 +301,6 @@ module Restate
413
301
  # ── Promises (Workflow API) ──
414
302
 
415
303
  # Gets a durable promise value, blocking until resolved.
416
- sig { override.params(name: String, serde: T.untyped).returns(T.untyped) }
417
304
  def promise(name, serde: JsonSerde)
418
305
  handle = @vm.sys_get_promise(name)
419
306
  poll_and_take(handle) do |raw|
@@ -422,7 +309,6 @@ module Restate
422
309
  end
423
310
 
424
311
  # Peeks at a durable promise value without blocking. Returns nil if not yet resolved.
425
- sig { override.params(name: String, serde: T.untyped).returns(T.untyped) }
426
312
  def peek_promise(name, serde: JsonSerde)
427
313
  handle = @vm.sys_peek_promise(name)
428
314
  poll_and_take(handle) do |raw|
@@ -431,15 +317,13 @@ module Restate
431
317
  end
432
318
 
433
319
  # Resolves a durable promise with a success value.
434
- sig { override.params(name: String, payload: T.untyped, serde: T.untyped).void }
435
320
  def resolve_promise(name, payload, serde: JsonSerde)
436
- handle = @vm.sys_complete_promise_success(name, serde.serialize(payload).b)
321
+ handle = @vm.sys_complete_promise_success(name, serde.serialize(payload))
437
322
  poll_and_take(handle)
438
323
  nil
439
324
  end
440
325
 
441
326
  # Rejects a durable promise with a terminal failure.
442
- sig { override.params(name: String, message: String, code: Integer).void }
443
327
  def reject_promise(name, message, code: 500)
444
328
  failure = Failure.new(code: code, message: message)
445
329
  handle = @vm.sys_complete_promise_failure(name, failure)
@@ -450,7 +334,6 @@ module Restate
450
334
  # ── Cancel invocation ──
451
335
 
452
336
  # Requests cancellation of another invocation by its id.
453
- sig { override.params(invocation_id: String).void }
454
337
  def cancel_invocation(invocation_id)
455
338
  @vm.sys_cancel_invocation(invocation_id)
456
339
  end
@@ -458,50 +341,32 @@ module Restate
458
341
  # ── Generic calls (raw bytes, no serde) ──
459
342
 
460
343
  # Durably calls a handler using raw bytes (no serialization). Useful for proxying.
461
- sig do
462
- override.params(
463
- service: String,
464
- handler: String,
465
- arg: String,
466
- key: T.nilable(String),
467
- idempotency_key: T.nilable(String),
468
- headers: T.nilable(T::Hash[String, String])
469
- ).returns(DurableCallFuture)
470
- end
471
344
  def generic_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil)
472
- call_handle = @vm.sys_call(
473
- service: service, handler: handler, parameter: arg.b,
474
- key: key, idempotency_key: idempotency_key, headers: headers
475
- )
476
- DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
477
- output_serde: nil)
345
+ with_outbound_middleware(service, handler, headers) do |hdrs|
346
+ call_handle = @vm.sys_call(
347
+ service: service, handler: handler, parameter: arg,
348
+ key: key, idempotency_key: idempotency_key, headers: hdrs
349
+ )
350
+ DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
351
+ output_serde: nil)
352
+ end
478
353
  end
479
354
 
480
355
  # Sends a one-way invocation using raw bytes (no serialization). Useful for proxying.
481
- sig do
482
- override.params(
483
- service: String,
484
- handler: String,
485
- arg: String,
486
- key: T.nilable(String),
487
- delay: T.nilable(Numeric),
488
- idempotency_key: T.nilable(String),
489
- headers: T.nilable(T::Hash[String, String])
490
- ).returns(SendHandle)
491
- end
492
356
  def generic_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil)
493
357
  delay_ms = delay ? (delay * 1000).to_i : nil
494
- invocation_id_handle = @vm.sys_send(
495
- service: service, handler: handler, parameter: arg.b,
496
- key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: headers
497
- )
498
- SendHandle.new(self, invocation_id_handle)
358
+ with_outbound_middleware(service, handler, headers) do |hdrs|
359
+ invocation_id_handle = @vm.sys_send(
360
+ service: service, handler: handler, parameter: arg,
361
+ key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs
362
+ )
363
+ SendHandle.new(self, invocation_id_handle)
364
+ end
499
365
  end
500
366
 
501
367
  # ── Request metadata ──
502
368
 
503
369
  # Returns metadata about the current invocation (id, headers, raw body).
504
- sig { override.returns(T.untyped) }
505
370
  def request
506
371
  @request ||= Request.new(
507
372
  id: @invocation.invocation_id,
@@ -512,7 +377,6 @@ module Restate
512
377
  end
513
378
 
514
379
  # Returns the key for this virtual object or workflow invocation.
515
- sig { override.returns(String) }
516
380
  def key
517
381
  @invocation.key
518
382
  end
@@ -522,18 +386,11 @@ module Restate
522
386
  # ── Progress loop ──
523
387
 
524
388
  # Polls until the given handle(s) complete, then takes the notification.
525
- sig do
526
- params(
527
- handle: Integer,
528
- block: T.nilable(T.proc.params(arg0: T.untyped).returns(T.untyped))
529
- ).returns(T.untyped)
530
- end
531
- def poll_and_take(handle, &block)
389
+ def poll_and_take(handle, &)
532
390
  poll_or_cancel([handle]) unless @vm.is_completed(handle)
533
- must_take_notification(handle, &block)
391
+ must_take_notification(handle, &)
534
392
  end
535
393
 
536
- sig { params(handles: T::Array[Integer]).void }
537
394
  def poll_or_cancel(handles)
538
395
  loop do
539
396
  flush_output
@@ -579,12 +436,6 @@ module Restate
579
436
  end
580
437
  end
581
438
 
582
- sig do
583
- params(
584
- handle: Integer,
585
- block: T.nilable(T.proc.params(arg0: T.untyped).returns(T.untyped))
586
- ).returns(T.untyped)
587
- end
588
439
  def must_take_notification(handle, &block)
589
440
  result = @vm.take_notification(handle)
590
441
 
@@ -607,7 +458,6 @@ module Restate
607
458
  end
608
459
  end
609
460
 
610
- sig { void }
611
461
  def flush_output
612
462
  loop do
613
463
  output = @vm.take_output
@@ -617,82 +467,72 @@ module Restate
617
467
  end
618
468
  end
619
469
 
470
+ # ── Outbound middleware ──
471
+
472
+ # Runs outbound middleware chain (Sidekiq client middleware pattern).
473
+ # Each middleware gets +call(service, handler, headers)+ and must +yield+
474
+ # to continue the chain. The block at the end performs the actual VM call.
475
+ def with_outbound_middleware(service, handler, headers, &action)
476
+ if @outbound_middleware.empty?
477
+ action.call(headers)
478
+ else
479
+ h = headers || {}
480
+ chain = ->(hdrs) { action.call(hdrs) }
481
+ @outbound_middleware.reverse_each do |mw|
482
+ prev = chain
483
+ chain = ->(hdrs) { mw.call(service, handler, hdrs) { prev.call(hdrs) } }
484
+ end
485
+ chain.call(h)
486
+ end
487
+ end
488
+
620
489
  # ── Call target resolution ──
621
490
 
622
491
  # Resolves a service+handler pair from class/symbol or string/string.
623
492
  # Returns [service_name, handler_name, handler_metadata_or_nil].
624
- sig do
625
- params(
626
- service: T.any(String, T::Class[T.anything]),
627
- handler: T.any(String, Symbol)
628
- ).returns([String, String, T.nilable(Handler)])
629
- end
630
493
  def resolve_call_target(service, handler)
494
+ handler_name = handler.is_a?(Symbol) ? handler.name : handler.to_s
631
495
  if service.is_a?(Class) && service.respond_to?(:service_name)
632
- svc_name = T.unsafe(service).service_name
633
- handler_name = handler.to_s
634
- handler_meta = service.respond_to?(:handlers) ? T.unsafe(service).handlers[handler_name] : nil
496
+ svc_name = service.service_name
497
+ handler_meta = service.respond_to?(:handlers) ? service.handlers[handler_name] : nil
635
498
  [svc_name, handler_name, handler_meta]
636
499
  else
637
- [service.to_s, handler.to_s, nil]
500
+ [service.to_s, handler_name, nil]
638
501
  end
639
502
  end
640
503
 
641
504
  # Resolves a serde value: if the caller passed NOT_SET, fall back to handler metadata, then JsonSerde.
642
- sig { params(caller_serde: T.untyped, handler_meta: T.nilable(Handler), field: Symbol).returns(T.untyped) }
643
505
  def resolve_serde(caller_serde, handler_meta, field)
644
506
  return caller_serde unless caller_serde.equal?(NOT_SET)
507
+ return JsonSerde unless handler_meta
645
508
 
646
- if handler_meta
647
- handler_meta.handler_io.public_send(field)
648
- else
649
- JsonSerde
509
+ io = handler_meta.handler_io
510
+ case field
511
+ when :input_serde then io.input_serde
512
+ when :output_serde then io.output_serde
513
+ else JsonSerde
650
514
  end
651
515
  end
652
516
 
653
517
  # ── Run execution ──
654
518
 
655
- sig do
656
- params(
657
- handle: Integer,
658
- action: T.proc.returns(T.untyped),
659
- serde: T.untyped,
660
- retry_policy: T.nilable(RunRetryPolicy)
661
- ).void
662
- end
663
519
  def execute_run(handle, action, serde, retry_policy)
664
520
  propose_run_result(handle, action, serde, retry_policy)
665
521
  end
666
522
 
667
523
  # Like execute_run, but offloads the action to a real OS Thread.
668
524
  # The fiber yields (via IO.pipe) while the thread runs, keeping the event loop responsive.
669
- sig do
670
- params(
671
- handle: Integer,
672
- action: T.proc.returns(T.untyped),
673
- serde: T.untyped,
674
- retry_policy: T.nilable(RunRetryPolicy)
675
- ).void
676
- end
677
525
  def execute_run_threaded(handle, action, serde, retry_policy)
678
526
  propose_run_result(handle, -> { offload_to_thread(action) }, serde, retry_policy)
679
527
  end
680
528
 
681
529
  # Runs the action and proposes the result (success/failure/transient) to the VM.
682
- sig do
683
- params(
684
- handle: Integer,
685
- action: T.proc.returns(T.untyped),
686
- serde: T.untyped,
687
- retry_policy: T.nilable(RunRetryPolicy)
688
- ).void
689
- end
690
530
  def propose_run_result(handle, action, serde, retry_policy)
691
531
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
692
532
  begin
693
533
  result = action.call
694
534
  buffer = serde.serialize(result)
695
- @vm.propose_run_completion_success(handle, buffer.b)
535
+ @vm.propose_run_completion_success(handle, buffer)
696
536
  rescue TerminalError => e
697
537
  failure = Failure.new(code: e.status_code, message: e.message)
698
538
  @vm.propose_run_completion_failure(handle, failure)
@@ -734,11 +574,10 @@ module Restate
734
574
  # most blocking I/O (Net::HTTP, TCPSocket, etc.) and yields the fiber
735
575
  # automatically. +background: true+ is only needed for CPU-heavy native
736
576
  # extensions that release the GVL (e.g., image processing, crypto).
737
- sig { params(action: T.proc.returns(T.untyped)).returns(T.untyped) }
738
577
  def offload_to_thread(action)
739
578
  read_io, write_io = IO.pipe
740
- result = T.let(nil, T.untyped)
741
- error = T.let(nil, T.nilable(Exception))
579
+ result = nil
580
+ error = nil
742
581
  event = @attempt_finished_event
743
582
 
744
583
  begin
@@ -772,25 +611,21 @@ module Restate
772
611
  # Avoids creating a new Thread per call (~1ms + ~1MB stack each).
773
612
  # Workers are daemon threads that do not prevent process exit.
774
613
  module BackgroundPool
775
- extend T::Sig
776
-
777
- @queue = T.let(Queue.new, Queue)
778
- @workers = T.let([], T::Array[Thread])
779
- @mutex = T.let(Mutex.new, Mutex)
780
- @size = T.let(0, Integer)
614
+ @queue = Queue.new
615
+ @workers = []
616
+ @mutex = Mutex.new
617
+ @size = 0
781
618
 
782
- POOL_SIZE = T.let(Integer(ENV.fetch('RESTATE_BACKGROUND_POOL_SIZE', 8)), Integer)
619
+ POOL_SIZE = Integer(ENV.fetch('RESTATE_BACKGROUND_POOL_SIZE', 8))
783
620
 
784
621
  module_function
785
622
 
786
623
  # Submit a block to be executed by a pool worker.
787
- sig { params(block: T.proc.void).void }
788
624
  def submit(&block)
789
625
  ensure_started
790
626
  @queue.push(block)
791
627
  end
792
628
 
793
- sig { void }
794
629
  def ensure_started
795
630
  return if @size >= POOL_SIZE
796
631