restate-sdk 0.4.3
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 +7 -0
- data/Cargo.lock +1040 -0
- data/Cargo.toml +8 -0
- data/LICENSE +21 -0
- data/README.md +133 -0
- data/ext/restate_internal/Cargo.toml +16 -0
- data/ext/restate_internal/extconf.rb +4 -0
- data/ext/restate_internal/src/lib.rs +1094 -0
- data/lib/restate/context.rb +336 -0
- data/lib/restate/discovery.rb +150 -0
- data/lib/restate/durable_future.rb +131 -0
- data/lib/restate/endpoint.rb +69 -0
- data/lib/restate/errors.rb +60 -0
- data/lib/restate/handler.rb +51 -0
- data/lib/restate/serde.rb +313 -0
- data/lib/restate/server.rb +280 -0
- data/lib/restate/server_context.rb +812 -0
- data/lib/restate/service.rb +37 -0
- data/lib/restate/service_dsl.rb +243 -0
- data/lib/restate/testing.rb +197 -0
- data/lib/restate/version.rb +6 -0
- data/lib/restate/virtual_object.rb +58 -0
- data/lib/restate/vm.rb +325 -0
- data/lib/restate/workflow.rb +57 -0
- data/lib/restate.rb +130 -0
- data/lib/tapioca/dsl/compilers/restate.rb +45 -0
- metadata +127 -0
data/lib/restate/vm.rb
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative 'restate_internal'
|
|
5
|
+
|
|
6
|
+
module Restate
|
|
7
|
+
# Ruby-side data types for VM results
|
|
8
|
+
Invocation = Struct.new(:invocation_id, :random_seed, :headers, :input_buffer, :key, keyword_init: true)
|
|
9
|
+
Failure = Struct.new(:code, :message, :stacktrace, keyword_init: true)
|
|
10
|
+
|
|
11
|
+
class NotReady; end
|
|
12
|
+
class Suspended; end
|
|
13
|
+
|
|
14
|
+
NOT_READY = T.let(NotReady.new.freeze, NotReady)
|
|
15
|
+
SUSPENDED = T.let(Suspended.new.freeze, Suspended)
|
|
16
|
+
CANCEL_HANDLE = T.let(Internal::CANCEL_NOTIFICATION_HANDLE, Integer)
|
|
17
|
+
|
|
18
|
+
# Progress loop result types
|
|
19
|
+
class DoProgressAnyCompleted; end
|
|
20
|
+
class DoProgressReadFromInput; end
|
|
21
|
+
class DoProgressCancelSignalReceived; end
|
|
22
|
+
class DoWaitPendingRun; end
|
|
23
|
+
|
|
24
|
+
DO_PROGRESS_ANY_COMPLETED = T.let(DoProgressAnyCompleted.new.freeze, DoProgressAnyCompleted)
|
|
25
|
+
DO_PROGRESS_READ_FROM_INPUT = T.let(DoProgressReadFromInput.new.freeze, DoProgressReadFromInput)
|
|
26
|
+
DO_PROGRESS_CANCEL_SIGNAL_RECEIVED = T.let(DoProgressCancelSignalReceived.new.freeze, DoProgressCancelSignalReceived)
|
|
27
|
+
DO_WAIT_PENDING_RUN = T.let(DoWaitPendingRun.new.freeze, DoWaitPendingRun)
|
|
28
|
+
|
|
29
|
+
DoProgressExecuteRun = Struct.new(:handle, keyword_init: true)
|
|
30
|
+
|
|
31
|
+
# User-facing retry policy for ctx.run
|
|
32
|
+
RunRetryPolicy = Struct.new(
|
|
33
|
+
:initial_interval, :max_attempts, :max_duration,
|
|
34
|
+
:max_interval, :interval_factor,
|
|
35
|
+
keyword_init: true
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Exponential retry configuration for run
|
|
39
|
+
RunRetryConfig = Struct.new(
|
|
40
|
+
:initial_interval, :max_attempts, :max_duration,
|
|
41
|
+
:max_interval, :interval_factor,
|
|
42
|
+
keyword_init: true
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Wraps the native Restate::Internal::VM, mapping native types to Ruby types.
|
|
46
|
+
class VMWrapper
|
|
47
|
+
extend T::Sig
|
|
48
|
+
|
|
49
|
+
sig { params(headers: T.untyped).void }
|
|
50
|
+
def initialize(headers)
|
|
51
|
+
@vm = T.let(Internal::VM.new(headers), Internal::VM)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
sig { returns([Integer, T.untyped]) }
|
|
55
|
+
def get_response_head
|
|
56
|
+
result = @vm.get_response_head
|
|
57
|
+
[result.status_code, result.headers]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
sig { params(buf: String).void }
|
|
61
|
+
def notify_input(buf)
|
|
62
|
+
@vm.notify_input(buf)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
sig { void }
|
|
66
|
+
def notify_input_closed
|
|
67
|
+
@vm.notify_input_closed
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
sig { params(error: String, stacktrace: T.nilable(String)).void }
|
|
71
|
+
def notify_error(error, stacktrace = nil)
|
|
72
|
+
@vm.notify_error(error, stacktrace)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
sig { returns(T.nilable(String)) }
|
|
76
|
+
def take_output
|
|
77
|
+
@vm.take_output
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
sig { returns(T::Boolean) }
|
|
81
|
+
def is_ready_to_execute
|
|
82
|
+
@vm.is_ready_to_execute
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
sig { params(handle: Integer).returns(T::Boolean) }
|
|
86
|
+
def is_completed(handle)
|
|
87
|
+
@vm.is_completed(handle)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
sig { params(handles: T::Array[Integer]).returns(T.untyped) }
|
|
91
|
+
def do_progress(handles)
|
|
92
|
+
result = @vm.do_progress(handles)
|
|
93
|
+
map_do_progress(result)
|
|
94
|
+
rescue Internal::VMError => e
|
|
95
|
+
e
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
sig { params(handle: Integer).returns(T.untyped) }
|
|
99
|
+
def take_notification(handle)
|
|
100
|
+
result = @vm.take_notification(handle)
|
|
101
|
+
map_notification(result)
|
|
102
|
+
rescue Internal::VMError => e
|
|
103
|
+
e
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
sig { returns(T.untyped) }
|
|
107
|
+
def sys_input
|
|
108
|
+
inp = @vm.sys_input
|
|
109
|
+
headers = inp.headers.map { |h| [h.key, h.value] }
|
|
110
|
+
Invocation.new(
|
|
111
|
+
invocation_id: inp.invocation_id,
|
|
112
|
+
random_seed: inp.random_seed,
|
|
113
|
+
headers: headers,
|
|
114
|
+
input_buffer: inp.input.b,
|
|
115
|
+
key: inp.key
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
sig { params(name: String).returns(Integer) }
|
|
120
|
+
def sys_get_state(name)
|
|
121
|
+
@vm.sys_get_state(name)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
sig { returns(Integer) }
|
|
125
|
+
def sys_get_state_keys
|
|
126
|
+
@vm.sys_get_state_keys
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
sig { params(name: String, value: String).void }
|
|
130
|
+
def sys_set_state(name, value)
|
|
131
|
+
@vm.sys_set_state(name, value)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
sig { params(name: String).void }
|
|
135
|
+
def sys_clear_state(name)
|
|
136
|
+
@vm.sys_clear_state(name)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
sig { void }
|
|
140
|
+
def sys_clear_all_state
|
|
141
|
+
@vm.sys_clear_all_state
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
sig { params(millis: Integer, name: T.nilable(String)).returns(Integer) }
|
|
145
|
+
def sys_sleep(millis, name = nil)
|
|
146
|
+
# Rust side always expects 2 args: (millis, name_or_nil)
|
|
147
|
+
@vm.sys_sleep(millis, name)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
sig do
|
|
151
|
+
params(
|
|
152
|
+
service: String,
|
|
153
|
+
handler: String,
|
|
154
|
+
parameter: String,
|
|
155
|
+
key: T.nilable(String),
|
|
156
|
+
idempotency_key: T.nilable(String),
|
|
157
|
+
headers: T.nilable(T::Hash[String, String])
|
|
158
|
+
).returns(Internal::CallHandle)
|
|
159
|
+
end
|
|
160
|
+
def sys_call(service:, handler:, parameter:, key: nil, idempotency_key: nil, headers: nil)
|
|
161
|
+
# Rust side expects 6 args: (service, handler, buffer, key_or_nil, idem_key_or_nil, headers_or_nil)
|
|
162
|
+
hdr_array = headers&.map { |k, v| [k, v] }
|
|
163
|
+
@vm.sys_call(service, handler, parameter, key, idempotency_key, hdr_array)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
sig do
|
|
167
|
+
params(
|
|
168
|
+
service: String,
|
|
169
|
+
handler: String,
|
|
170
|
+
parameter: String,
|
|
171
|
+
key: T.nilable(String),
|
|
172
|
+
delay: T.nilable(Integer),
|
|
173
|
+
idempotency_key: T.nilable(String),
|
|
174
|
+
headers: T.nilable(T::Hash[String, String])
|
|
175
|
+
).returns(Integer)
|
|
176
|
+
end
|
|
177
|
+
def sys_send(service:, handler:, parameter:, key: nil, delay: nil, idempotency_key: nil, headers: nil)
|
|
178
|
+
# Rust side expects 7 args
|
|
179
|
+
hdr_array = headers&.map { |k, v| [k, v] }
|
|
180
|
+
@vm.sys_send(service, handler, parameter, key, delay, idempotency_key, hdr_array)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
sig { params(name: String).returns(Integer) }
|
|
184
|
+
def sys_run(name)
|
|
185
|
+
@vm.sys_run(name)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
sig { params(handle: Integer, output: String).void }
|
|
189
|
+
def propose_run_completion_success(handle, output)
|
|
190
|
+
@vm.propose_run_completion_success(handle, output)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
sig { params(handle: Integer, failure: T.untyped).void }
|
|
194
|
+
def propose_run_completion_failure(handle, failure)
|
|
195
|
+
native_failure = Internal::Failure.new(failure.code, failure.message, nil)
|
|
196
|
+
@vm.propose_run_completion_failure(handle, native_failure)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
sig do
|
|
200
|
+
params(
|
|
201
|
+
handle: Integer,
|
|
202
|
+
failure: T.untyped,
|
|
203
|
+
attempt_duration_ms: Integer,
|
|
204
|
+
config: T.untyped
|
|
205
|
+
).void
|
|
206
|
+
end
|
|
207
|
+
def propose_run_completion_transient(handle, failure:, attempt_duration_ms:, config:)
|
|
208
|
+
native_failure = Internal::Failure.new(failure.code, failure.message, failure.stacktrace)
|
|
209
|
+
native_config = Internal::ExponentialRetryConfig.new(
|
|
210
|
+
config.initial_interval, config.max_attempts,
|
|
211
|
+
config.max_duration, config.max_interval,
|
|
212
|
+
config.interval_factor
|
|
213
|
+
)
|
|
214
|
+
@vm.propose_run_completion_failure_transient(handle, native_failure, attempt_duration_ms, native_config)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
sig { params(output: String).void }
|
|
218
|
+
def sys_write_output_success(output)
|
|
219
|
+
@vm.sys_write_output_success(output)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
sig { params(failure: T.untyped).void }
|
|
223
|
+
def sys_write_output_failure(failure)
|
|
224
|
+
native_failure = Internal::Failure.new(failure.code, failure.message, nil)
|
|
225
|
+
@vm.sys_write_output_failure(native_failure)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
sig { void }
|
|
229
|
+
def sys_end
|
|
230
|
+
@vm.sys_end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
sig { returns(T::Boolean) }
|
|
234
|
+
def is_replaying
|
|
235
|
+
@vm.is_replaying
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Returns [awakeable_id (String), notification_handle (Integer)]
|
|
239
|
+
sig { returns([String, Integer]) }
|
|
240
|
+
def sys_awakeable
|
|
241
|
+
@vm.sys_awakeable
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
sig { params(awakeable_id: String, value: String).void }
|
|
245
|
+
def sys_complete_awakeable_success(awakeable_id, value)
|
|
246
|
+
@vm.sys_complete_awakeable_success(awakeable_id, value)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
sig { params(awakeable_id: String, failure: T.untyped).void }
|
|
250
|
+
def sys_complete_awakeable_failure(awakeable_id, failure)
|
|
251
|
+
native_failure = Internal::Failure.new(failure.code, failure.message, nil)
|
|
252
|
+
@vm.sys_complete_awakeable_failure(awakeable_id, native_failure)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
sig { params(key: String).returns(Integer) }
|
|
256
|
+
def sys_get_promise(key)
|
|
257
|
+
@vm.sys_get_promise(key)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
sig { params(key: String).returns(Integer) }
|
|
261
|
+
def sys_peek_promise(key)
|
|
262
|
+
@vm.sys_peek_promise(key)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
sig { params(key: String, value: String).returns(Integer) }
|
|
266
|
+
def sys_complete_promise_success(key, value)
|
|
267
|
+
@vm.sys_complete_promise_success(key, value)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
sig { params(key: String, failure: T.untyped).returns(Integer) }
|
|
271
|
+
def sys_complete_promise_failure(key, failure)
|
|
272
|
+
native_failure = Internal::Failure.new(failure.code, failure.message, nil)
|
|
273
|
+
@vm.sys_complete_promise_failure(key, native_failure)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
sig { params(invocation_id: String).void }
|
|
277
|
+
def sys_cancel_invocation(invocation_id)
|
|
278
|
+
@vm.sys_cancel_invocation(invocation_id)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
private
|
|
282
|
+
|
|
283
|
+
sig { params(result: T.untyped).returns(T.untyped) }
|
|
284
|
+
def map_do_progress(result)
|
|
285
|
+
case result
|
|
286
|
+
when Internal::Suspended
|
|
287
|
+
SUSPENDED
|
|
288
|
+
when Internal::DoProgressAnyCompleted
|
|
289
|
+
DO_PROGRESS_ANY_COMPLETED
|
|
290
|
+
when Internal::DoProgressReadFromInput
|
|
291
|
+
DO_PROGRESS_READ_FROM_INPUT
|
|
292
|
+
when Internal::DoProgressExecuteRun
|
|
293
|
+
DoProgressExecuteRun.new(handle: result.handle)
|
|
294
|
+
when Internal::DoProgressCancelSignalReceived
|
|
295
|
+
DO_PROGRESS_CANCEL_SIGNAL_RECEIVED
|
|
296
|
+
when Internal::DoWaitForPendingRun
|
|
297
|
+
DO_WAIT_PENDING_RUN
|
|
298
|
+
else
|
|
299
|
+
raise "Unknown progress type: #{result.class}"
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
sig { params(result: T.untyped).returns(T.untyped) }
|
|
304
|
+
def map_notification(result)
|
|
305
|
+
case result
|
|
306
|
+
when Internal::Suspended
|
|
307
|
+
SUSPENDED
|
|
308
|
+
when NilClass
|
|
309
|
+
NOT_READY
|
|
310
|
+
when Internal::Void
|
|
311
|
+
nil
|
|
312
|
+
when String
|
|
313
|
+
# Could be bytes (success) or invocation_id string.
|
|
314
|
+
# The native layer returns RString for both.
|
|
315
|
+
result
|
|
316
|
+
when Internal::Failure
|
|
317
|
+
Failure.new(code: result.code, message: result.message)
|
|
318
|
+
when Internal::StateKeys
|
|
319
|
+
result.keys
|
|
320
|
+
else
|
|
321
|
+
raise "Unknown notification type: #{result.class}"
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Restate
|
|
5
|
+
# A durable workflow with a main entry point and shared handlers.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# class Signup < Restate::Workflow
|
|
9
|
+
# main def run(email)
|
|
10
|
+
# # workflow logic
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# handler def status
|
|
14
|
+
# ctx = Restate.current_workflow_context
|
|
15
|
+
# ctx.get("status")
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
class Workflow
|
|
19
|
+
extend T::Sig
|
|
20
|
+
extend ServiceDSL
|
|
21
|
+
|
|
22
|
+
# Register the main workflow entry point.
|
|
23
|
+
# Use as: +main def run(arg)+ or +main :run, input: String+
|
|
24
|
+
#
|
|
25
|
+
# @param method_name [Symbol] name of the method to register
|
|
26
|
+
# @param opts [Hash] handler options (+input:+, +output:+, +accept:+, +content_type:+)
|
|
27
|
+
# @return [Symbol] the method name
|
|
28
|
+
def self.main(method_name = nil, **opts)
|
|
29
|
+
if method_name.is_a?(String)
|
|
30
|
+
raise ArgumentError,
|
|
31
|
+
"handler expects a Symbol (use `main def #{method_name}(...)` or `main :#{method_name}`)"
|
|
32
|
+
end
|
|
33
|
+
return method_name unless method_name.is_a?(Symbol)
|
|
34
|
+
|
|
35
|
+
_register_handler(method_name, **T.unsafe({ kind: 'workflow', **opts }))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Register a shared handler on this workflow.
|
|
39
|
+
#
|
|
40
|
+
# @param method_name [Symbol] name of the method to register
|
|
41
|
+
# @param opts [Hash] handler options (+input:+, +output:+, +accept:+, +content_type:+)
|
|
42
|
+
# @return [Symbol] the method name
|
|
43
|
+
def self.handler(method_name = nil, **opts)
|
|
44
|
+
if method_name.is_a?(String)
|
|
45
|
+
raise ArgumentError,
|
|
46
|
+
"handler expects a Symbol (use `handler def #{method_name}(...)` or `handler :#{method_name}`)"
|
|
47
|
+
end
|
|
48
|
+
return method_name unless method_name.is_a?(Symbol)
|
|
49
|
+
|
|
50
|
+
_register_handler(method_name, **T.unsafe({ kind: 'shared', **opts }))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self._service_kind
|
|
54
|
+
'workflow'
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/restate.rb
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
|
+
require_relative 'restate/version'
|
|
6
|
+
require_relative 'restate/errors'
|
|
7
|
+
require_relative 'restate/serde'
|
|
8
|
+
require_relative 'restate/vm'
|
|
9
|
+
require_relative 'restate/context'
|
|
10
|
+
require_relative 'restate/handler'
|
|
11
|
+
require_relative 'restate/service_dsl'
|
|
12
|
+
require_relative 'restate/service'
|
|
13
|
+
require_relative 'restate/virtual_object'
|
|
14
|
+
require_relative 'restate/workflow'
|
|
15
|
+
require_relative 'restate/server_context'
|
|
16
|
+
require_relative 'restate/durable_future'
|
|
17
|
+
require_relative 'restate/discovery'
|
|
18
|
+
require_relative 'restate/endpoint'
|
|
19
|
+
|
|
20
|
+
# Restate Ruby SDK — build resilient applications with durable execution.
|
|
21
|
+
module Restate
|
|
22
|
+
extend T::Sig
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# Create an endpoint, optionally binding services.
|
|
27
|
+
# Returns an Endpoint that can be further configured before calling +.app+.
|
|
28
|
+
#
|
|
29
|
+
# @param services [Array<Class>] service classes or instances to bind
|
|
30
|
+
# @return [Endpoint]
|
|
31
|
+
sig do
|
|
32
|
+
params(
|
|
33
|
+
services: T.untyped,
|
|
34
|
+
protocol: T.nilable(String),
|
|
35
|
+
identity_keys: T.nilable(T::Array[String])
|
|
36
|
+
).returns(Endpoint)
|
|
37
|
+
end
|
|
38
|
+
def endpoint(*services, protocol: nil, identity_keys: nil)
|
|
39
|
+
ep = Endpoint.new
|
|
40
|
+
ep.streaming_protocol if protocol == 'bidi'
|
|
41
|
+
ep.request_response_protocol if protocol == 'request_response'
|
|
42
|
+
services.each { |s| ep.bind(s) }
|
|
43
|
+
identity_keys&.each { |k| ep.identity_key(k) }
|
|
44
|
+
ep
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# ── Fiber-local context accessors ──
|
|
48
|
+
#
|
|
49
|
+
# The SDK stores the current handler context in fiber-local storage
|
|
50
|
+
# (Thread.current[], which is fiber-scoped in Ruby). These methods
|
|
51
|
+
# retrieve it with the appropriate type for IDE completion.
|
|
52
|
+
#
|
|
53
|
+
# Use these instead of the +ctx+ parameter when you need the context
|
|
54
|
+
# from a nested method that doesn'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
|
|
64
|
+
|
|
65
|
+
# Returns the current context for a VirtualObject exclusive handler.
|
|
66
|
+
# Raises if not inside a VirtualObject exclusive handler.
|
|
67
|
+
#
|
|
68
|
+
# @return [ObjectContext]
|
|
69
|
+
sig { returns(ObjectContext) }
|
|
70
|
+
def current_object_context
|
|
71
|
+
fetch_context!(service_kind: 'object', handler_kind: 'exclusive')
|
|
72
|
+
end
|
|
73
|
+
|
|
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')
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Returns the current context for a Workflow main handler.
|
|
85
|
+
# Raises if not inside a Workflow main handler.
|
|
86
|
+
#
|
|
87
|
+
# @return [WorkflowContext]
|
|
88
|
+
sig { returns(WorkflowContext) }
|
|
89
|
+
def current_workflow_context
|
|
90
|
+
fetch_context!(service_kind: 'workflow', handler_kind: 'workflow')
|
|
91
|
+
end
|
|
92
|
+
|
|
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
|
|
102
|
+
|
|
103
|
+
# @!visibility private
|
|
104
|
+
sig do
|
|
105
|
+
params(service_kind: T.nilable(String), handler_kind: T.nilable(String)).returns(ServerContext)
|
|
106
|
+
end
|
|
107
|
+
def fetch_context!(service_kind: nil, handler_kind: nil) # rubocop:disable Metrics
|
|
108
|
+
ctx = Thread.current[:restate_context]
|
|
109
|
+
unless ctx
|
|
110
|
+
Kernel.raise 'Not inside a Restate handler. ' \
|
|
111
|
+
'Context accessors can only be called during handler execution.'
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if service_kind
|
|
115
|
+
actual_service = Thread.current[:restate_service_kind]
|
|
116
|
+
unless actual_service == service_kind
|
|
117
|
+
Kernel.raise "Expected a #{service_kind} handler, but current handler is #{actual_service || 'unknown'}."
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
if handler_kind
|
|
122
|
+
actual_handler = Thread.current[:restate_handler_kind]
|
|
123
|
+
unless actual_handler == handler_kind
|
|
124
|
+
Kernel.raise "Expected a #{handler_kind} handler, but current handler kind is #{actual_handler || 'unknown'}."
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
T.cast(ctx, ServerContext)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# typed: false
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
return unless defined?(Tapioca::Dsl::Compiler)
|
|
5
|
+
|
|
6
|
+
module Tapioca
|
|
7
|
+
module Dsl
|
|
8
|
+
module Compilers
|
|
9
|
+
# Generates Sorbet sigs for Restate handler methods.
|
|
10
|
+
#
|
|
11
|
+
# Handlers no longer receive +ctx+ as a parameter — context is accessed
|
|
12
|
+
# via fiber-local +Restate.current_context+ (or typed variants). This
|
|
13
|
+
# compiler generates sigs reflecting the actual handler arity (0 or 1).
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# bundle exec tapioca dsl
|
|
17
|
+
class Restate < Compiler
|
|
18
|
+
ConstantType = type_member { { fixed: Module } }
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def gather_constants
|
|
22
|
+
all_classes.select do |klass|
|
|
23
|
+
klass.is_a?(Class) && (
|
|
24
|
+
klass < ::Restate::Service ||
|
|
25
|
+
klass < ::Restate::VirtualObject ||
|
|
26
|
+
klass < ::Restate::Workflow
|
|
27
|
+
)
|
|
28
|
+
rescue TypeError
|
|
29
|
+
false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def decorate
|
|
35
|
+
root.create_path(constant) do |klass|
|
|
36
|
+
constant.handlers.each do |name, handler|
|
|
37
|
+
params = handler.arity == 1 ? [create_param('input', type: 'T.untyped')] : []
|
|
38
|
+
klass.create_method(name, parameters: params, return_type: 'T.untyped')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: restate-sdk
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.4.3
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Restate Developers
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-17 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: async
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: falcon
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.47'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.47'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rack
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '2.0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '2.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: sorbet-runtime
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
description: Build resilient applications with distributed durable async/await using
|
|
70
|
+
Restate
|
|
71
|
+
email:
|
|
72
|
+
- code@restate.dev
|
|
73
|
+
executables: []
|
|
74
|
+
extensions:
|
|
75
|
+
- ext/restate_internal/extconf.rb
|
|
76
|
+
extra_rdoc_files: []
|
|
77
|
+
files:
|
|
78
|
+
- Cargo.lock
|
|
79
|
+
- Cargo.toml
|
|
80
|
+
- LICENSE
|
|
81
|
+
- README.md
|
|
82
|
+
- ext/restate_internal/Cargo.toml
|
|
83
|
+
- ext/restate_internal/extconf.rb
|
|
84
|
+
- ext/restate_internal/src/lib.rs
|
|
85
|
+
- lib/restate.rb
|
|
86
|
+
- lib/restate/context.rb
|
|
87
|
+
- lib/restate/discovery.rb
|
|
88
|
+
- lib/restate/durable_future.rb
|
|
89
|
+
- lib/restate/endpoint.rb
|
|
90
|
+
- lib/restate/errors.rb
|
|
91
|
+
- lib/restate/handler.rb
|
|
92
|
+
- lib/restate/serde.rb
|
|
93
|
+
- lib/restate/server.rb
|
|
94
|
+
- lib/restate/server_context.rb
|
|
95
|
+
- lib/restate/service.rb
|
|
96
|
+
- lib/restate/service_dsl.rb
|
|
97
|
+
- lib/restate/testing.rb
|
|
98
|
+
- lib/restate/version.rb
|
|
99
|
+
- lib/restate/virtual_object.rb
|
|
100
|
+
- lib/restate/vm.rb
|
|
101
|
+
- lib/restate/workflow.rb
|
|
102
|
+
- lib/tapioca/dsl/compilers/restate.rb
|
|
103
|
+
homepage: https://github.com/restatedev/sdk-ruby
|
|
104
|
+
licenses:
|
|
105
|
+
- MIT
|
|
106
|
+
metadata:
|
|
107
|
+
rubygems_mfa_required: 'true'
|
|
108
|
+
post_install_message:
|
|
109
|
+
rdoc_options: []
|
|
110
|
+
require_paths:
|
|
111
|
+
- lib
|
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '3.1'
|
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
|
+
requirements:
|
|
119
|
+
- - ">="
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '0'
|
|
122
|
+
requirements: []
|
|
123
|
+
rubygems_version: 3.5.22
|
|
124
|
+
signing_key:
|
|
125
|
+
specification_version: 4
|
|
126
|
+
summary: Restate SDK for Ruby
|
|
127
|
+
test_files: []
|