restate-sdk 0.5.1-aarch64-linux → 0.7.0-aarch64-linux

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.
@@ -52,14 +52,17 @@ module Restate
52
52
  # @param restate_image [String] Docker image for Restate server.
53
53
  # @param always_replay [Boolean] Force replay on every suspension point.
54
54
  # @param disable_retries [Boolean] Disable Restate retry policy.
55
+ # @yield [Endpoint] Optional block to configure the endpoint (e.g. add middleware).
55
56
  def initialize(*services,
56
57
  restate_image: 'docker.io/restatedev/restate:latest',
57
58
  always_replay: false,
58
- disable_retries: false)
59
+ disable_retries: false,
60
+ &configure)
59
61
  @services = services
60
62
  @restate_image = restate_image
61
63
  @always_replay = always_replay
62
64
  @disable_retries = disable_retries
65
+ @configure = configure
63
66
  @server_thread = nil
64
67
  @container = nil
65
68
  @port = nil
@@ -69,7 +72,9 @@ module Restate
69
72
 
70
73
  def start
71
74
  @port = find_free_port
72
- rack_app = Restate.endpoint(*@services).app
75
+ endpoint = Restate.endpoint(*@services)
76
+ @configure&.call(endpoint)
77
+ rack_app = endpoint.app
73
78
  start_sdk_server(rack_app)
74
79
  wait_for_tcp(@port)
75
80
  start_restate_container
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Restate
5
- VERSION = '0.5.1'
5
+ VERSION = '0.7.0'
6
6
  end
@@ -49,6 +49,30 @@ module Restate
49
49
  _register_handler(method_name, **T.unsafe({ kind: 'shared', **opts }))
50
50
  end
51
51
 
52
+ # Returns a call proxy for fluent durable calls to this virtual object.
53
+ #
54
+ # @example
55
+ # Counter.call("my-key").add(5).await
56
+ #
57
+ # @param key [String] the object key
58
+ # @return [ServiceCallProxy]
59
+ def self.call(key)
60
+ ServiceCallProxy.new(self, key: key, call_method: :object_call)
61
+ end
62
+
63
+ # Returns a send proxy for fluent fire-and-forget sends to this virtual object.
64
+ #
65
+ # @example
66
+ # Counter.send!("my-key").add(5)
67
+ # Counter.send!("my-key", delay: 60).add(5)
68
+ #
69
+ # @param key [String] the object key
70
+ # @param delay [Numeric, nil] optional delay in seconds
71
+ # @return [ServiceSendProxy]
72
+ def self.send!(key, delay: nil)
73
+ ServiceSendProxy.new(self, key: key, send_method: :object_send, delay: delay)
74
+ end
75
+
52
76
  def self._service_kind
53
77
  'object'
54
78
  end
@@ -49,6 +49,30 @@ module Restate
49
49
  _register_handler(method_name, **T.unsafe({ kind: 'shared', **opts }))
50
50
  end
51
51
 
52
+ # Returns a call proxy for fluent durable calls to this workflow.
53
+ #
54
+ # @example
55
+ # UserSignup.call("user42").run("user@example.com").await
56
+ #
57
+ # @param key [String] the workflow key
58
+ # @return [ServiceCallProxy]
59
+ def self.call(key)
60
+ ServiceCallProxy.new(self, key: key, call_method: :workflow_call)
61
+ end
62
+
63
+ # Returns a send proxy for fluent fire-and-forget sends to this workflow.
64
+ #
65
+ # @example
66
+ # UserSignup.send!("user42").run("user@example.com")
67
+ # UserSignup.send!("user42", delay: 60).run("user@example.com")
68
+ #
69
+ # @param key [String] the workflow key
70
+ # @param delay [Numeric, nil] optional delay in seconds
71
+ # @return [ServiceSendProxy]
72
+ def self.send!(key, delay: nil)
73
+ ServiceSendProxy.new(self, key: key, send_method: :workflow_send, delay: delay)
74
+ end
75
+
52
76
  def self._service_kind
53
77
  'workflow'
54
78
  end
data/lib/restate.rb CHANGED
@@ -16,9 +16,21 @@ require_relative 'restate/server_context'
16
16
  require_relative 'restate/durable_future'
17
17
  require_relative 'restate/discovery'
18
18
  require_relative 'restate/endpoint'
19
+ require_relative 'restate/service_proxy'
20
+ require_relative 'restate/config'
21
+ require_relative 'restate/client'
19
22
 
20
23
  # Restate Ruby SDK — build resilient applications with durable execution.
21
- module Restate
24
+ #
25
+ # All handler-facing operations are available as module methods on +Restate+.
26
+ # Inside a handler, call +Restate.run_sync+, +Restate.sleep+, +Restate.get+,
27
+ # etc. directly — no context parameter needed.
28
+ #
29
+ # The context is stored in +Thread.current[]+ (fiber-scoped in Ruby 3.0+).
30
+ # We intentionally use +Thread.current[]+ rather than +Fiber[]+ (Ruby 3.2+)
31
+ # because +Thread.current[]+ is NOT inherited by child fibers, which prevents
32
+ # accidental context leaks when Async spawns child tasks for run blocks.
33
+ module Restate # rubocop:disable Metrics/ModuleLength
22
34
  extend T::Sig
23
35
 
24
36
  module_function
@@ -44,61 +56,42 @@ module Restate
44
56
  ep
45
57
  end
46
58
 
47
- # ── Fiber-local context accessors ──
48
- #
49
- # The SDK passes the context as the first argument to every handler.
50
- # It is also stored in fiber-local storage (Thread.current[], which is
51
- # fiber-scoped in Ruby). These methods retrieve it with the appropriate
52
- # type for IDE completion.
53
- #
54
- # Use these from nested helper methods that don't have +ctx+ in scope.
55
-
56
- # Returns the current context for a Service handler.
57
- # Raises if called outside a Restate handler.
58
- #
59
- # @return [Context]
60
- sig { returns(Context) }
61
- def current_context
62
- fetch_context!
63
- end
59
+ # ── Global configuration ──
64
60
 
65
- # Returns the current context for a VirtualObject exclusive handler.
66
- # Raises if not inside a VirtualObject exclusive handler.
61
+ # Configure the SDK globally. Settings are used by +Restate.client+.
67
62
  #
68
- # @return [ObjectContext]
69
- sig { returns(ObjectContext) }
70
- def current_object_context
71
- fetch_context!(service_kind: 'object', handler_kind: 'exclusive')
63
+ # @example
64
+ # Restate.configure do |c|
65
+ # c.ingress_url = "http://localhost:8080"
66
+ # c.admin_url = "http://localhost:9070"
67
+ # end
68
+ sig { params(_block: T.proc.params(arg0: Config).void).void }
69
+ def configure(&_block)
70
+ yield config
72
71
  end
73
72
 
74
- # Returns the current context for a VirtualObject shared handler.
75
- # Read-only state: +get+ and +state_keys+ only, no +set+/+clear+.
76
- # Raises if not inside a VirtualObject shared handler.
77
- #
78
- # @return [ObjectSharedContext]
79
- sig { returns(ObjectSharedContext) }
80
- def current_shared_context
81
- fetch_context!(service_kind: 'object', handler_kind: 'shared')
73
+ # Returns the global configuration. Creates a default one on first access.
74
+ sig { returns(Config) }
75
+ def config
76
+ @config = T.let(@config, T.nilable(Config)) unless defined?(@config)
77
+ @config ||= Config.new
82
78
  end
83
79
 
84
- # Returns the current context for a Workflow main handler.
85
- # Raises if not inside a Workflow main handler.
80
+ # Returns a pre-configured Client using the global +config+.
81
+ # Creates a new Client on each call (stateless — safe to discard).
86
82
  #
87
- # @return [WorkflowContext]
88
- sig { returns(WorkflowContext) }
89
- def current_workflow_context
90
- fetch_context!(service_kind: 'workflow', handler_kind: 'workflow')
83
+ # @example
84
+ # Restate.client.service(Greeter).greet("World")
85
+ # Restate.client.resolve_awakeable(id, payload)
86
+ # Restate.client.create_deployment("http://localhost:9080")
87
+ sig { returns(Client) }
88
+ def client
89
+ cfg = config
90
+ Client.new(ingress_url: cfg.ingress_url, admin_url: cfg.admin_url,
91
+ ingress_headers: cfg.ingress_headers, admin_headers: cfg.admin_headers)
91
92
  end
92
93
 
93
- # Returns the current context for a Workflow shared handler.
94
- # Read-only state: +get+ and +state_keys+ only, no +set+/+clear+.
95
- # Raises if not inside a Workflow shared handler.
96
- #
97
- # @return [WorkflowSharedContext]
98
- sig { returns(WorkflowSharedContext) }
99
- def current_shared_workflow_context
100
- fetch_context!(service_kind: 'workflow', handler_kind: 'shared')
101
- end
94
+ # ── Context accessor (internal) ──
102
95
 
103
96
  # @!visibility private
104
97
  sig do
@@ -108,7 +101,7 @@ module Restate
108
101
  ctx = Thread.current[:restate_context]
109
102
  unless ctx
110
103
  Kernel.raise 'Not inside a Restate handler. ' \
111
- 'Context accessors can only be called during handler execution.'
104
+ 'Restate.* methods can only be called during handler execution.'
112
105
  end
113
106
 
114
107
  if service_kind
@@ -127,4 +120,260 @@ module Restate
127
120
 
128
121
  T.cast(ctx, ServerContext)
129
122
  end
123
+
124
+ # ── Durable execution ──
125
+
126
+ # Execute a durable side effect. The block runs at most once; the result
127
+ # is journaled and replayed on retries. Returns a DurableFuture.
128
+ sig do
129
+ params(name: String, serde: T.untyped, retry_policy: T.nilable(RunRetryPolicy),
130
+ background: T::Boolean, action: T.proc.returns(T.untyped)).returns(DurableFuture)
131
+ end
132
+ def run(name, serde: JsonSerde, retry_policy: nil, background: false, &action)
133
+ fetch_context!.run(name, serde: serde, retry_policy: retry_policy, background: background, &action)
134
+ end
135
+
136
+ # Convenience shortcut for +run(...).await+. Returns the result directly.
137
+ sig do
138
+ params(name: String, serde: T.untyped, retry_policy: T.nilable(RunRetryPolicy),
139
+ background: T::Boolean, action: T.proc.returns(T.untyped)).returns(T.untyped)
140
+ end
141
+ def run_sync(name, serde: JsonSerde, retry_policy: nil, background: false, &action)
142
+ fetch_context!.run_sync(name, serde: serde, retry_policy: retry_policy, background: background, &action)
143
+ end
144
+
145
+ # Durable timer that survives handler restarts.
146
+ sig { params(seconds: Numeric).returns(DurableFuture) }
147
+ def sleep(seconds)
148
+ fetch_context!.sleep(seconds)
149
+ end
150
+
151
+ # ── State operations (VirtualObject / Workflow) ──
152
+
153
+ # Durably retrieve a state entry. Returns nil if unset.
154
+ sig { params(name: String, serde: T.untyped).returns(T.untyped) }
155
+ def get(name, serde: JsonSerde)
156
+ fetch_context!.get(name, serde: serde)
157
+ end
158
+
159
+ # Durably retrieve a state entry, returning a DurableFuture instead of blocking.
160
+ sig { params(name: String, serde: T.untyped).returns(DurableFuture) }
161
+ def get_async(name, serde: JsonSerde)
162
+ fetch_context!.get_async(name, serde: serde)
163
+ end
164
+
165
+ # Durably set a state entry.
166
+ sig { params(name: String, value: T.untyped, serde: T.untyped).void }
167
+ def set(name, value, serde: JsonSerde)
168
+ fetch_context!.set(name, value, serde: serde)
169
+ end
170
+
171
+ # Durably remove a single state entry.
172
+ sig { params(name: String).void }
173
+ def clear(name)
174
+ fetch_context!.clear(name)
175
+ end
176
+
177
+ # Durably remove all state entries.
178
+ sig { void }
179
+ def clear_all
180
+ fetch_context!.clear_all
181
+ end
182
+
183
+ # List all state entry names.
184
+ sig { returns(T.untyped) }
185
+ def state_keys
186
+ fetch_context!.state_keys
187
+ end
188
+
189
+ # List all state entry names, returning a DurableFuture.
190
+ sig { returns(DurableFuture) }
191
+ def state_keys_async
192
+ fetch_context!.state_keys_async
193
+ end
194
+
195
+ # ── Service communication ──
196
+
197
+ # Durably call a handler on a Restate service.
198
+ sig do
199
+ params(service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
200
+ arg: T.untyped, key: T.nilable(String), idempotency_key: T.nilable(String),
201
+ headers: T.nilable(T::Hash[String, String]),
202
+ input_serde: T.untyped, output_serde: T.untyped).returns(DurableCallFuture)
203
+ end
204
+ def service_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil,
205
+ input_serde: NOT_SET, output_serde: NOT_SET)
206
+ ctx = fetch_context!
207
+ ctx.service_call(service, handler, arg, key: key, idempotency_key: idempotency_key,
208
+ headers: headers, input_serde: input_serde, output_serde: output_serde)
209
+ end
210
+
211
+ # Fire-and-forget send to a Restate service handler.
212
+ sig do
213
+ params(service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
214
+ arg: T.untyped, key: T.nilable(String), delay: T.nilable(Numeric),
215
+ idempotency_key: T.nilable(String), headers: T.nilable(T::Hash[String, String]),
216
+ input_serde: T.untyped).returns(SendHandle)
217
+ end
218
+ def service_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil,
219
+ headers: nil, input_serde: NOT_SET)
220
+ ctx = fetch_context!
221
+ ctx.service_send(service, handler, arg, key: key, delay: delay, idempotency_key: idempotency_key,
222
+ headers: headers, input_serde: input_serde)
223
+ end
224
+
225
+ # Durably call a handler on a Restate virtual object.
226
+ sig do
227
+ params(service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
228
+ key: String, arg: T.untyped, idempotency_key: T.nilable(String),
229
+ headers: T.nilable(T::Hash[String, String]),
230
+ input_serde: T.untyped, output_serde: T.untyped).returns(DurableCallFuture)
231
+ end
232
+ def object_call(service, handler, key, arg, idempotency_key: nil, headers: nil,
233
+ input_serde: NOT_SET, output_serde: NOT_SET)
234
+ ctx = fetch_context!
235
+ ctx.object_call(service, handler, key, arg, idempotency_key: idempotency_key,
236
+ headers: headers, input_serde: input_serde, output_serde: output_serde)
237
+ end
238
+
239
+ # Fire-and-forget send to a Restate virtual object handler.
240
+ sig do
241
+ params(service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
242
+ key: String, arg: T.untyped, delay: T.nilable(Numeric),
243
+ idempotency_key: T.nilable(String), headers: T.nilable(T::Hash[String, String]),
244
+ input_serde: T.untyped).returns(SendHandle)
245
+ end
246
+ def object_send(service, handler, key, arg, delay: nil, idempotency_key: nil,
247
+ headers: nil, input_serde: NOT_SET)
248
+ ctx = fetch_context!
249
+ ctx.object_send(service, handler, key, arg, delay: delay, idempotency_key: idempotency_key,
250
+ headers: headers, input_serde: input_serde)
251
+ end
252
+
253
+ # Durably call a handler on a Restate workflow.
254
+ sig do
255
+ params(service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
256
+ key: String, arg: T.untyped, idempotency_key: T.nilable(String),
257
+ headers: T.nilable(T::Hash[String, String]),
258
+ input_serde: T.untyped, output_serde: T.untyped).returns(DurableCallFuture)
259
+ end
260
+ def workflow_call(service, handler, key, arg, idempotency_key: nil, headers: nil,
261
+ input_serde: NOT_SET, output_serde: NOT_SET)
262
+ ctx = fetch_context!
263
+ ctx.workflow_call(service, handler, key, arg,
264
+ idempotency_key: idempotency_key, headers: headers,
265
+ input_serde: input_serde, output_serde: output_serde)
266
+ end
267
+
268
+ # Fire-and-forget send to a Restate workflow handler.
269
+ sig do
270
+ params(service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
271
+ key: String, arg: T.untyped, delay: T.nilable(Numeric),
272
+ idempotency_key: T.nilable(String), headers: T.nilable(T::Hash[String, String]),
273
+ input_serde: T.untyped).returns(SendHandle)
274
+ end
275
+ def workflow_send(service, handler, key, arg, delay: nil, idempotency_key: nil,
276
+ headers: nil, input_serde: NOT_SET)
277
+ ctx = fetch_context!
278
+ ctx.workflow_send(service, handler, key, arg, delay: delay, idempotency_key: idempotency_key,
279
+ headers: headers, input_serde: input_serde)
280
+ end
281
+
282
+ # Durably call a handler using raw bytes (no serialization).
283
+ sig do
284
+ params(service: String, handler: String, arg: String,
285
+ key: T.nilable(String), idempotency_key: T.nilable(String),
286
+ headers: T.nilable(T::Hash[String, String])).returns(DurableCallFuture)
287
+ end
288
+ def generic_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil)
289
+ fetch_context!.generic_call(service, handler, arg, key: key,
290
+ idempotency_key: idempotency_key, headers: headers)
291
+ end
292
+
293
+ # Fire-and-forget send using raw bytes (no serialization).
294
+ sig do
295
+ params(service: String, handler: String, arg: String,
296
+ key: T.nilable(String), delay: T.nilable(Numeric),
297
+ idempotency_key: T.nilable(String), headers: T.nilable(T::Hash[String, String])).returns(SendHandle)
298
+ end
299
+ def generic_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil)
300
+ fetch_context!.generic_send(service, handler, arg, key: key, delay: delay,
301
+ idempotency_key: idempotency_key, headers: headers)
302
+ end
303
+
304
+ # ── Awakeables ──
305
+
306
+ # Create an awakeable for external callbacks. Returns [awakeable_id, DurableFuture].
307
+ sig { params(serde: T.untyped).returns([String, DurableFuture]) }
308
+ def awakeable(serde: JsonSerde)
309
+ fetch_context!.awakeable(serde: serde)
310
+ end
311
+
312
+ # Resolve an awakeable with a success value.
313
+ sig { params(awakeable_id: String, payload: T.untyped, serde: T.untyped).void }
314
+ def resolve_awakeable(awakeable_id, payload, serde: JsonSerde)
315
+ fetch_context!.resolve_awakeable(awakeable_id, payload, serde: serde)
316
+ end
317
+
318
+ # Reject an awakeable with a terminal failure.
319
+ sig { params(awakeable_id: String, message: String, code: Integer).void }
320
+ def reject_awakeable(awakeable_id, message, code: 500)
321
+ fetch_context!.reject_awakeable(awakeable_id, message, code: code)
322
+ end
323
+
324
+ # ── Promises (Workflow only) ──
325
+
326
+ # Get a durable promise value, blocking until resolved.
327
+ sig { params(name: String, serde: T.untyped).returns(T.untyped) }
328
+ def promise(name, serde: JsonSerde)
329
+ fetch_context!.promise(name, serde: serde)
330
+ end
331
+
332
+ # Peek at a durable promise without blocking. Returns nil if not yet resolved.
333
+ sig { params(name: String, serde: T.untyped).returns(T.untyped) }
334
+ def peek_promise(name, serde: JsonSerde)
335
+ fetch_context!.peek_promise(name, serde: serde)
336
+ end
337
+
338
+ # Resolve a durable promise with a value.
339
+ sig { params(name: String, payload: T.untyped, serde: T.untyped).void }
340
+ def resolve_promise(name, payload, serde: JsonSerde)
341
+ fetch_context!.resolve_promise(name, payload, serde: serde)
342
+ end
343
+
344
+ # Reject a durable promise with a terminal failure.
345
+ sig { params(name: String, message: String, code: Integer).void }
346
+ def reject_promise(name, message, code: 500)
347
+ fetch_context!.reject_promise(name, message, code: code)
348
+ end
349
+
350
+ # ── Futures ──
351
+
352
+ # Wait until any of the given futures completes. Returns [completed, remaining].
353
+ sig { params(futures: T::Array[DurableFuture]).returns([T::Array[DurableFuture], T::Array[DurableFuture]]) }
354
+ def wait_any(*futures)
355
+ T.unsafe(fetch_context!).wait_any(*futures)
356
+ end
357
+
358
+ # ── Request metadata ──
359
+
360
+ # Returns metadata about the current invocation (id, headers, raw body).
361
+ sig { returns(T.untyped) }
362
+ def request
363
+ fetch_context!.request
364
+ end
365
+
366
+ # Returns the key for this virtual object or workflow invocation.
367
+ sig { returns(String) }
368
+ def key
369
+ fetch_context!.key
370
+ end
371
+
372
+ # ── Invocation control ──
373
+
374
+ # Request cancellation of another invocation.
375
+ sig { params(invocation_id: String).void }
376
+ def cancel_invocation(invocation_id)
377
+ fetch_context!.cancel_invocation(invocation_id)
378
+ end
130
379
  end
@@ -10,8 +10,8 @@ module Tapioca
10
10
  module Compilers
11
11
  # Generates Sorbet sigs for Restate handler methods.
12
12
  #
13
- # Every handler receives +ctx+ as its first parameter. Handlers that
14
- # accept input receive it as the second parameter (arity 2).
13
+ # Handlers take 0 or 1 parameters (the input). Context is implicit
14
+ # via +Restate.*+ module methods.
15
15
  #
16
16
  # Usage:
17
17
  # bundle exec tapioca dsl
@@ -58,9 +58,8 @@ module Tapioca
58
58
  def decorate # rubocop:disable Metrics/MethodLength
59
59
  root.create_path(constant) do |klass|
60
60
  constant.handlers.each do |name, handler|
61
- ctx_type = resolve_context_type(constant, handler)
62
- params = [create_param('ctx', type: ctx_type)]
63
- if handler.arity == 2
61
+ params = []
62
+ if handler.arity == 1
64
63
  input_type = resolve_input_type(handler)
65
64
  params << create_param('input', type: input_type)
66
65
  end