restate-sdk 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0916e5aeaaa8b06b50750be1b126fadecd9b26b63437fa8da07eaa66de351f5
4
- data.tar.gz: 3723bd3f868ce6b8c9f05f3fe070b6f0ffaa3290130dd83baf83bd36390b716f
3
+ metadata.gz: bc7f07e2b3bd62dbf82cb815a5b66443586e339bb3ae896e6b94594fdce9fab4
4
+ data.tar.gz: 9066dc59318b6e14f249c249c59b03b8e075df6986cf2b2d8a3d9b93ca56614b
5
5
  SHA512:
6
- metadata.gz: e8ff04264f631fa443395eb05493f30a6d88b0835e7df0aa1469ddc1e22ca4091cbd12d1f5a85d5dfef0339ed52b3db6664ccfc67e749de714fc4dffcddadcb7
7
- data.tar.gz: bb01d499eb5a8cd9d2279343ff5d41a61bd7beddfca5c8db86864be4b5e989c83e24f20d33ab44f7e0021ea0fedb0a6861278ca2e8d028b3871c8e9eada81f29
6
+ metadata.gz: 79b2cee29852e255f9745665f44b8d4450badc22319c59120a2988c9ccd9dfa88723529ffb5059dbf4c94d929722368e00a5a1887c0e32b48663196513768860
7
+ data.tar.gz: 8faf5c53ca4b7bc538ff348a3ccb99a0cdbac8a418d9263073f67a8476353d8c01fb714784c93237f644b35ea7f1ff08e89074a1e07a2761f9c0d59f892643d5
data/Cargo.lock CHANGED
@@ -569,7 +569,7 @@ dependencies = [
569
569
 
570
570
  [[package]]
571
571
  name = "restate_internal"
572
- version = "0.5.0"
572
+ version = "0.6.0"
573
573
  dependencies = [
574
574
  "magnus",
575
575
  "rb-sys",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "restate_internal"
3
- version = "0.5.0"
3
+ version = "0.6.0"
4
4
  edition = "2021"
5
5
  publish = false
6
6
 
@@ -15,11 +15,15 @@ module Restate
15
15
  sig { returns(T.nilable(String)) }
16
16
  attr_accessor :protocol
17
17
 
18
+ sig { returns(T::Array[T.untyped]) }
19
+ attr_reader :middleware
20
+
18
21
  sig { void }
19
22
  def initialize
20
23
  @services = T.let({}, T::Hash[String, T.untyped])
21
24
  @protocol = T.let(nil, T.nilable(String))
22
25
  @identity_keys = T.let([], T::Array[String])
26
+ @middleware = T.let([], T::Array[T.untyped])
23
27
  end
24
28
 
25
29
  # Bind one or more services to this endpoint.
@@ -59,6 +63,70 @@ module Restate
59
63
  self
60
64
  end
61
65
 
66
+ # Add handler-level middleware.
67
+ #
68
+ # Middleware wraps every handler invocation with access to the handler metadata
69
+ # and context. Use it for tracing, metrics, logging, error reporting, etc.
70
+ #
71
+ # A middleware is a class whose instances respond to +call(handler, ctx)+.
72
+ # Use +yield+ inside +call+ to invoke the next middleware or the handler.
73
+ # The return value of +yield+ is the handler's return value.
74
+ #
75
+ # This follows the same pattern as {https://github.com/sidekiq/sidekiq/wiki/Middleware Sidekiq middleware}.
76
+ #
77
+ # @example OpenTelemetry tracing
78
+ # class OpenTelemetryMiddleware
79
+ # def call(handler, ctx)
80
+ # tracer.in_span(handler.name, attributes: {
81
+ # 'restate.service' => handler.service_tag.name,
82
+ # 'restate.invocation_id' => ctx.request.id
83
+ # }) do
84
+ # yield
85
+ # end
86
+ # end
87
+ # end
88
+ # endpoint.use(OpenTelemetryMiddleware)
89
+ #
90
+ # @example Metrics
91
+ # class MetricsMiddleware
92
+ # def call(handler, ctx)
93
+ # start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
94
+ # result = yield
95
+ # duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
96
+ # StatsD.timing("restate.handler.#{handler.name}", duration)
97
+ # result
98
+ # end
99
+ # end
100
+ # endpoint.use(MetricsMiddleware)
101
+ #
102
+ # @example Middleware with configuration
103
+ # class AuthMiddleware
104
+ # def initialize(api_key:)
105
+ # @api_key = api_key
106
+ # end
107
+ #
108
+ # def call(handler, ctx)
109
+ # raise Restate::TerminalError.new('unauthorized', status_code: 401) unless valid?(ctx)
110
+ # yield
111
+ # end
112
+ # end
113
+ # endpoint.use(AuthMiddleware, api_key: 'secret')
114
+ #
115
+ # @param klass [Class] middleware class (will be instantiated by the SDK)
116
+ # @param args [Array] positional arguments for the middleware constructor
117
+ # @param kwargs [Hash] keyword arguments for the middleware constructor
118
+ # @return [self]
119
+ sig { params(klass: T.untyped, args: T.untyped, kwargs: T.untyped).returns(T.self_type) }
120
+ def use(klass, *args, **kwargs)
121
+ instance = if kwargs.empty?
122
+ klass.new(*args)
123
+ else
124
+ klass.new(*args, **kwargs)
125
+ end
126
+ @middleware << instance
127
+ self
128
+ end
129
+
62
130
  # Build and return the Rack-compatible application.
63
131
  sig { returns(T.untyped) }
64
132
  def app
@@ -33,19 +33,32 @@ module Restate
33
33
 
34
34
  # Invoke a handler with the context and raw input bytes.
35
35
  # The context is passed as the first argument to every handler.
36
+ # Middleware (if any) wraps the handler call.
36
37
  # Returns raw output bytes.
37
- sig { params(handler: T.untyped, ctx: T.untyped, in_buffer: String).returns(String) }
38
- def invoke_handler(handler:, ctx:, in_buffer:)
39
- if handler.arity == 2
40
- begin
41
- in_arg = handler.handler_io.input_serde.deserialize(in_buffer)
42
- rescue StandardError => e
43
- Kernel.raise TerminalError, "Unable to parse input argument: #{e.message}"
38
+ sig do
39
+ params(handler: T.untyped, ctx: T.untyped, in_buffer: String,
40
+ middleware: T::Array[T.untyped]).returns(String)
41
+ end
42
+ def invoke_handler(handler:, ctx:, in_buffer:, middleware: []) # rubocop:disable Metrics/AbcSize
43
+ call_handler = Kernel.proc do
44
+ if handler.arity == 2
45
+ begin
46
+ in_arg = handler.handler_io.input_serde.deserialize(in_buffer)
47
+ rescue StandardError => e
48
+ Kernel.raise TerminalError, "Unable to parse input argument: #{e.message}"
49
+ end
50
+ handler.callable.call(ctx, in_arg)
51
+ else
52
+ handler.callable.call(ctx)
44
53
  end
45
- out_arg = handler.callable.call(ctx, in_arg)
46
- else
47
- out_arg = handler.callable.call(ctx)
48
54
  end
55
+
56
+ # Build the middleware chain so each middleware can use `yield` to call the next.
57
+ chain = middleware.reverse.reduce(call_handler) do |nxt, mw|
58
+ Kernel.proc { mw.call(handler, ctx, &nxt) }
59
+ end
60
+
61
+ out_arg = chain.call
49
62
  handler.handler_io.output_serde.serialize(out_arg)
50
63
  end
51
64
  end
@@ -204,7 +204,8 @@ module Restate
204
204
  handler: handler,
205
205
  invocation: invocation,
206
206
  send_output: send_output,
207
- input_queue: input_queue
207
+ input_queue: input_queue,
208
+ middleware: @endpoint.middleware
208
209
  )
209
210
 
210
211
  # Spawn the handler as an async task so the response body can stream
@@ -28,8 +28,11 @@ module Restate
28
28
  sig { returns(T.untyped) }
29
29
  attr_reader :invocation
30
30
 
31
- sig { params(vm: VMWrapper, handler: T.untyped, invocation: T.untyped, send_output: T.untyped, input_queue: Async::Queue).void }
32
- def initialize(vm:, handler:, invocation:, send_output:, input_queue:)
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: [])
33
36
  @vm = T.let(vm, VMWrapper)
34
37
  @handler = T.let(handler, T.untyped)
35
38
  @invocation = T.let(invocation, T.untyped)
@@ -37,6 +40,7 @@ module Restate
37
40
  @input_queue = T.let(input_queue, Async::Queue)
38
41
  @run_coros_to_execute = T.let({}, T::Hash[Integer, T.untyped])
39
42
  @attempt_finished_event = T.let(AttemptFinishedEvent.new, AttemptFinishedEvent)
43
+ @middleware = T.let(middleware, T::Array[T.untyped])
40
44
  end
41
45
 
42
46
  # ── Main entry point ──
@@ -48,7 +52,8 @@ module Restate
48
52
  Thread.current[:restate_service_kind] = @handler.service_tag.kind
49
53
  Thread.current[:restate_handler_kind] = @handler.kind
50
54
  in_buffer = @invocation.input_buffer
51
- out_buffer = Restate.invoke_handler(handler: @handler, ctx: self, in_buffer: in_buffer)
55
+ out_buffer = Restate.invoke_handler(handler: @handler, ctx: self, in_buffer: in_buffer,
56
+ middleware: @middleware)
52
57
  @vm.sys_write_output_success(out_buffer.b)
53
58
  @vm.sys_end
54
59
  rescue TerminalError => e
@@ -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.0'
5
+ VERSION = '0.6.0'
6
6
  end
@@ -0,0 +1,307 @@
1
+ # typed: true
2
+
3
+ # RBI shipped with the restate-sdk gem.
4
+ # Tapioca merges this automatically when users run `tapioca gems`.
5
+
6
+ module Restate
7
+ # Create an endpoint, optionally binding services.
8
+ sig do
9
+ params(
10
+ services: T.untyped,
11
+ protocol: T.nilable(String),
12
+ identity_keys: T.nilable(T::Array[String])
13
+ ).returns(Restate::Endpoint)
14
+ end
15
+ def self.endpoint(*services, protocol: nil, identity_keys: nil); end
16
+
17
+ # Returns the current context (any handler).
18
+ sig { returns(Restate::Context) }
19
+ def self.current_context; end
20
+
21
+ # Returns the current context for a VirtualObject exclusive handler.
22
+ sig { returns(Restate::ObjectContext) }
23
+ def self.current_object_context; end
24
+
25
+ # Returns the current context for a VirtualObject shared handler.
26
+ sig { returns(Restate::ObjectSharedContext) }
27
+ def self.current_shared_context; end
28
+
29
+ # Returns the current context for a Workflow main handler.
30
+ sig { returns(Restate::WorkflowContext) }
31
+ def self.current_workflow_context; end
32
+
33
+ # Returns the current context for a Workflow shared handler.
34
+ sig { returns(Restate::WorkflowSharedContext) }
35
+ def self.current_shared_workflow_context; end
36
+
37
+ class TerminalError < StandardError
38
+ sig { returns(Integer) }
39
+ def status_code; end
40
+
41
+ sig { params(message: String, status_code: Integer).void }
42
+ def initialize(message = '', status_code: 500); end
43
+ end
44
+
45
+ class AttemptFinishedEvent
46
+ sig { returns(T::Boolean) }
47
+ def set?; end
48
+
49
+ sig { void }
50
+ def wait; end
51
+ end
52
+
53
+ Request = T.type_alias { T.untyped }
54
+
55
+ class RunRetryPolicy < T::Struct
56
+ const :initial_interval, T.nilable(Integer)
57
+ const :max_attempts, T.nilable(Integer)
58
+ const :max_duration, T.nilable(Integer)
59
+ const :max_interval, T.nilable(Integer)
60
+ const :interval_factor, T.nilable(Float)
61
+ end
62
+
63
+ class DurableFuture
64
+ sig { returns(T.untyped) }
65
+ def await; end
66
+
67
+ sig { returns(T::Boolean) }
68
+ def completed?; end
69
+
70
+ sig { returns(Integer) }
71
+ def handle; end
72
+ end
73
+
74
+ class DurableCallFuture < DurableFuture
75
+ sig { returns(String) }
76
+ def invocation_id; end
77
+
78
+ sig { void }
79
+ def cancel; end
80
+ end
81
+
82
+ class SendHandle
83
+ sig { returns(String) }
84
+ def invocation_id; end
85
+
86
+ sig { void }
87
+ def cancel; end
88
+ end
89
+
90
+ # Base context interface for all Restate handlers.
91
+ module Context
92
+ sig do
93
+ params(
94
+ name: String, serde: T.untyped, retry_policy: T.nilable(RunRetryPolicy),
95
+ background: T::Boolean, action: T.proc.returns(T.untyped)
96
+ ).returns(DurableFuture)
97
+ end
98
+ def run(name, serde: Restate::JsonSerde, retry_policy: nil, background: false, &action); end
99
+
100
+ sig do
101
+ params(
102
+ name: String, serde: T.untyped, retry_policy: T.nilable(RunRetryPolicy),
103
+ background: T::Boolean, action: T.proc.returns(T.untyped)
104
+ ).returns(T.untyped)
105
+ end
106
+ def run_sync(name, serde: Restate::JsonSerde, retry_policy: nil, background: false, &action); end
107
+
108
+ sig { params(seconds: Numeric).returns(DurableFuture) }
109
+ def sleep(seconds); end
110
+
111
+ sig do
112
+ params(
113
+ service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
114
+ arg: T.untyped, key: T.nilable(String), idempotency_key: T.nilable(String),
115
+ headers: T.nilable(T::Hash[String, String]), input_serde: T.untyped, output_serde: T.untyped
116
+ ).returns(DurableCallFuture)
117
+ end
118
+ def service_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil,
119
+ input_serde: T.unsafe(nil), output_serde: T.unsafe(nil)); end
120
+
121
+ sig do
122
+ params(
123
+ service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
124
+ arg: T.untyped, key: T.nilable(String), delay: T.nilable(Numeric),
125
+ idempotency_key: T.nilable(String), headers: T.nilable(T::Hash[String, String]),
126
+ input_serde: T.untyped
127
+ ).returns(SendHandle)
128
+ end
129
+ def service_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil,
130
+ headers: nil, input_serde: T.unsafe(nil)); end
131
+
132
+ sig do
133
+ params(
134
+ service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
135
+ key: String, arg: T.untyped, idempotency_key: T.nilable(String),
136
+ headers: T.nilable(T::Hash[String, String]), input_serde: T.untyped, output_serde: T.untyped
137
+ ).returns(DurableCallFuture)
138
+ end
139
+ def object_call(service, handler, key, arg, idempotency_key: nil, headers: nil,
140
+ input_serde: T.unsafe(nil), output_serde: T.unsafe(nil)); end
141
+
142
+ sig do
143
+ params(
144
+ service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
145
+ key: String, arg: T.untyped, delay: T.nilable(Numeric),
146
+ idempotency_key: T.nilable(String), headers: T.nilable(T::Hash[String, String]),
147
+ input_serde: T.untyped
148
+ ).returns(SendHandle)
149
+ end
150
+ def object_send(service, handler, key, arg, delay: nil, idempotency_key: nil,
151
+ headers: nil, input_serde: T.unsafe(nil)); end
152
+
153
+ sig do
154
+ params(
155
+ service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
156
+ key: String, arg: T.untyped, idempotency_key: T.nilable(String),
157
+ headers: T.nilable(T::Hash[String, String]), input_serde: T.untyped, output_serde: T.untyped
158
+ ).returns(DurableCallFuture)
159
+ end
160
+ def workflow_call(service, handler, key, arg, idempotency_key: nil, headers: nil,
161
+ input_serde: T.unsafe(nil), output_serde: T.unsafe(nil)); end
162
+
163
+ sig do
164
+ params(
165
+ service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
166
+ key: String, arg: T.untyped, delay: T.nilable(Numeric),
167
+ idempotency_key: T.nilable(String), headers: T.nilable(T::Hash[String, String]),
168
+ input_serde: T.untyped
169
+ ).returns(SendHandle)
170
+ end
171
+ def workflow_send(service, handler, key, arg, delay: nil, idempotency_key: nil,
172
+ headers: nil, input_serde: T.unsafe(nil)); end
173
+
174
+ sig do
175
+ params(
176
+ service: String, handler: String, arg: String,
177
+ key: T.nilable(String), idempotency_key: T.nilable(String),
178
+ headers: T.nilable(T::Hash[String, String])
179
+ ).returns(DurableCallFuture)
180
+ end
181
+ def generic_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil); end
182
+
183
+ sig do
184
+ params(
185
+ service: String, handler: String, arg: String,
186
+ key: T.nilable(String), delay: T.nilable(Numeric),
187
+ idempotency_key: T.nilable(String), headers: T.nilable(T::Hash[String, String])
188
+ ).returns(SendHandle)
189
+ end
190
+ def generic_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil); end
191
+
192
+ sig { params(serde: T.untyped).returns([String, DurableFuture]) }
193
+ def awakeable(serde: Restate::JsonSerde); end
194
+
195
+ sig { params(awakeable_id: String, payload: T.untyped, serde: T.untyped).void }
196
+ def resolve_awakeable(awakeable_id, payload, serde: Restate::JsonSerde); end
197
+
198
+ sig { params(awakeable_id: String, message: String, code: Integer).void }
199
+ def reject_awakeable(awakeable_id, message, code: 500); end
200
+
201
+ sig { params(invocation_id: String).void }
202
+ def cancel_invocation(invocation_id); end
203
+
204
+ sig { params(futures: DurableFuture).returns([T::Array[DurableFuture], T::Array[DurableFuture]]) }
205
+ def wait_any(*futures); end
206
+
207
+ sig { returns(T.untyped) }
208
+ def request; end
209
+
210
+ sig { returns(String) }
211
+ def key; end
212
+ end
213
+
214
+ # VirtualObject shared handler context (read-only state).
215
+ module ObjectSharedContext
216
+ include Context
217
+
218
+ sig { params(name: String, serde: T.untyped).returns(T.untyped) }
219
+ def get(name, serde: Restate::JsonSerde); end
220
+
221
+ sig { params(name: String, serde: T.untyped).returns(DurableFuture) }
222
+ def get_async(name, serde: Restate::JsonSerde); end
223
+
224
+ sig { returns(T.untyped) }
225
+ def state_keys; end
226
+
227
+ sig { returns(DurableFuture) }
228
+ def state_keys_async; end
229
+ end
230
+
231
+ # VirtualObject exclusive handler context (full state access).
232
+ module ObjectContext
233
+ include ObjectSharedContext
234
+
235
+ sig { params(name: String, value: T.untyped, serde: T.untyped).void }
236
+ def set(name, value, serde: Restate::JsonSerde); end
237
+
238
+ sig { params(name: String).void }
239
+ def clear(name); end
240
+
241
+ sig { void }
242
+ def clear_all; end
243
+ end
244
+
245
+ # Workflow shared handler context (read-only state + promises).
246
+ module WorkflowSharedContext
247
+ include ObjectSharedContext
248
+
249
+ sig { params(name: String, serde: T.untyped).returns(T.untyped) }
250
+ def promise(name, serde: Restate::JsonSerde); end
251
+
252
+ sig { params(name: String, serde: T.untyped).returns(T.untyped) }
253
+ def peek_promise(name, serde: Restate::JsonSerde); end
254
+
255
+ sig { params(name: String, payload: T.untyped, serde: T.untyped).void }
256
+ def resolve_promise(name, payload, serde: Restate::JsonSerde); end
257
+
258
+ sig { params(name: String, message: String, code: Integer).void }
259
+ def reject_promise(name, message, code: 500); end
260
+ end
261
+
262
+ # Workflow main handler context (full state + promises).
263
+ module WorkflowContext
264
+ include ObjectContext
265
+
266
+ sig { params(name: String, serde: T.untyped).returns(T.untyped) }
267
+ def promise(name, serde: Restate::JsonSerde); end
268
+
269
+ sig { params(name: String, serde: T.untyped).returns(T.untyped) }
270
+ def peek_promise(name, serde: Restate::JsonSerde); end
271
+
272
+ sig { params(name: String, payload: T.untyped, serde: T.untyped).void }
273
+ def resolve_promise(name, payload, serde: Restate::JsonSerde); end
274
+
275
+ sig { params(name: String, message: String, code: Integer).void }
276
+ def reject_promise(name, message, code: 500); end
277
+ end
278
+
279
+ # Stateless service base class.
280
+ class Service; end
281
+
282
+ # Keyed virtual object base class.
283
+ class VirtualObject; end
284
+
285
+ # Durable workflow base class.
286
+ class Workflow; end
287
+
288
+ class Endpoint
289
+ sig { params(services: T.untyped).void }
290
+ def bind(*services); end
291
+
292
+ sig { void }
293
+ def streaming_protocol; end
294
+
295
+ sig { void }
296
+ def request_response_protocol; end
297
+
298
+ sig { params(key: String).void }
299
+ def identity_key(key); end
300
+
301
+ sig { returns(T.untyped) }
302
+ def app; end
303
+ end
304
+
305
+ module JsonSerde; end
306
+ module BytesSerde; end
307
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restate-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Restate Developers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-18 00:00:00.000000000 Z
11
+ date: 2026-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -86,6 +86,7 @@ files:
86
86
  - lib/restate/vm.rb
87
87
  - lib/restate/workflow.rb
88
88
  - lib/tapioca/dsl/compilers/restate.rb
89
+ - rbi/restate-sdk.rbi
89
90
  homepage: https://github.com/restatedev/sdk-ruby
90
91
  licenses:
91
92
  - MIT