restate-sdk 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +1 -1
- data/README.md +15 -10
- data/ext/restate_internal/Cargo.toml +1 -1
- data/lib/restate/client.rb +5 -29
- data/lib/restate/config.rb +4 -11
- data/lib/restate/context.rb +13 -128
- data/lib/restate/discovery.rb +6 -15
- data/lib/restate/durable_future.rb +15 -39
- data/lib/restate/endpoint.rb +5 -22
- data/lib/restate/errors.rb +2 -15
- data/lib/restate/handler.rb +21 -21
- data/lib/restate/serde.rb +14 -130
- data/lib/restate/server.rb +10 -26
- data/lib/restate/server_context.rb +50 -248
- data/lib/restate/service.rb +2 -3
- data/lib/restate/service_dsl.rb +8 -8
- data/lib/restate/service_proxy.rb +2 -13
- data/lib/restate/testing.rb +1 -1
- data/lib/restate/version.rb +2 -2
- data/lib/restate/virtual_object.rb +3 -4
- data/lib/restate/vm.rb +9 -74
- data/lib/restate/workflow.rb +3 -4
- data/lib/restate.rb +4 -93
- data/sig/restate.rbs +196 -0
- metadata +3 -18
- data/lib/tapioca/dsl/compilers/restate.rb +0 -115
- data/rbi/restate-sdk.rbi +0 -582
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc64f400fa1b48565474c9cc26c8bde6742e58b0c02f5090c0f633e16fd2b354
|
|
4
|
+
data.tar.gz: 7d72ad638b4dd6742c149d5314e8c67de919d198362d4a29e65d78da24146898
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6180be88c64766e8537a59ce16c22ff591d9a0b6620301668b0c7759b981f2344a9d04f9e6d896b08a569168cd517e29e9202c69947f1311410911c1c2190f85
|
|
7
|
+
data.tar.gz: bfaa1a28cbfdbeafa95a10a428e7e0b7f17ebc033e0c28659ffeec059c980c4f1635827f40a799e517ccf31c6d143bd342fb6fdfa4b70f6f8dd9709d383af7a9
|
data/Cargo.lock
CHANGED
data/README.md
CHANGED
|
@@ -58,23 +58,28 @@ Or add the gem to an existing project:
|
|
|
58
58
|
gem install restate-sdk
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
### Typed handlers with
|
|
61
|
+
### Typed handlers with Dry::Struct
|
|
62
62
|
|
|
63
|
-
Use
|
|
63
|
+
Use [dry-struct](https://dry-rb.org/gems/dry-struct/) for typed input/output with automatic JSON Schema generation:
|
|
64
64
|
|
|
65
65
|
```ruby
|
|
66
66
|
require 'restate'
|
|
67
|
+
require 'dry-struct'
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const :attendee, String
|
|
71
|
-
const :num_guests, Integer
|
|
72
|
-
const :note, T.nilable(String)
|
|
69
|
+
module Types
|
|
70
|
+
include Dry.Types()
|
|
73
71
|
end
|
|
74
72
|
|
|
75
|
-
class
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
class RegistrationRequest < Dry::Struct
|
|
74
|
+
attribute :event_name, Types::String
|
|
75
|
+
attribute :attendee, Types::String
|
|
76
|
+
attribute :num_guests, Types::Integer
|
|
77
|
+
attribute? :note, Types::String # optional attribute
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class RegistrationResponse < Dry::Struct
|
|
81
|
+
attribute :registration_id, Types::String
|
|
82
|
+
attribute :status, Types::String
|
|
78
83
|
end
|
|
79
84
|
|
|
80
85
|
class EventService < Restate::Service
|
data/lib/restate/client.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: false
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require 'net/http'
|
|
@@ -31,13 +31,6 @@ module Restate
|
|
|
31
31
|
# client.cancel_invocation(invocation_id)
|
|
32
32
|
# client.create_deployment("http://localhost:9080")
|
|
33
33
|
class Client
|
|
34
|
-
extend T::Sig
|
|
35
|
-
|
|
36
|
-
sig do
|
|
37
|
-
params(ingress_url: String, admin_url: String,
|
|
38
|
-
ingress_headers: T::Hash[String, String],
|
|
39
|
-
admin_headers: T::Hash[String, String]).void
|
|
40
|
-
end
|
|
41
34
|
def initialize(ingress_url: 'http://localhost:8080', admin_url: 'http://localhost:9070',
|
|
42
35
|
ingress_headers: {}, admin_headers: {})
|
|
43
36
|
@ingress_url = ingress_url.chomp('/')
|
|
@@ -49,19 +42,16 @@ module Restate
|
|
|
49
42
|
# ── Service invocation proxies ──
|
|
50
43
|
|
|
51
44
|
# Returns a proxy for calling a stateless service.
|
|
52
|
-
sig { params(service: T.any(String, T::Class[T.anything])).returns(ClientServiceProxy) }
|
|
53
45
|
def service(service)
|
|
54
46
|
ClientServiceProxy.new(@ingress_url, resolve_name(service), nil, @ingress_headers)
|
|
55
47
|
end
|
|
56
48
|
|
|
57
49
|
# Returns a proxy for calling a keyed virtual object.
|
|
58
|
-
sig { params(service: T.any(String, T::Class[T.anything]), key: String).returns(ClientServiceProxy) }
|
|
59
50
|
def object(service, key)
|
|
60
51
|
ClientServiceProxy.new(@ingress_url, resolve_name(service), key, @ingress_headers)
|
|
61
52
|
end
|
|
62
53
|
|
|
63
54
|
# Returns a proxy for calling a workflow.
|
|
64
|
-
sig { params(service: T.any(String, T::Class[T.anything]), key: String).returns(ClientServiceProxy) }
|
|
65
55
|
def workflow(service, key)
|
|
66
56
|
ClientServiceProxy.new(@ingress_url, resolve_name(service), key, @ingress_headers)
|
|
67
57
|
end
|
|
@@ -69,13 +59,11 @@ module Restate
|
|
|
69
59
|
# ── Awakeable operations ──
|
|
70
60
|
|
|
71
61
|
# Resolve an awakeable from outside the Restate runtime.
|
|
72
|
-
sig { params(awakeable_id: String, payload: T.untyped).void }
|
|
73
62
|
def resolve_awakeable(awakeable_id, payload)
|
|
74
63
|
post_ingress("/restate/awakeables/#{awakeable_id}/resolve", payload)
|
|
75
64
|
end
|
|
76
65
|
|
|
77
66
|
# Reject an awakeable from outside the Restate runtime.
|
|
78
|
-
sig { params(awakeable_id: String, message: String, code: Integer).void }
|
|
79
67
|
def reject_awakeable(awakeable_id, message, code: 500)
|
|
80
68
|
post_ingress("/restate/awakeables/#{awakeable_id}/reject",
|
|
81
69
|
{ 'message' => message, 'code' => code })
|
|
@@ -84,57 +72,51 @@ module Restate
|
|
|
84
72
|
# ── Invocation management ──
|
|
85
73
|
|
|
86
74
|
# Cancel a running invocation.
|
|
87
|
-
sig { params(invocation_id: String).void }
|
|
88
75
|
def cancel_invocation(invocation_id)
|
|
89
76
|
post_admin("/restate/invocations/#{invocation_id}/cancel", nil)
|
|
90
77
|
end
|
|
91
78
|
|
|
92
79
|
# Kill a running invocation (immediate termination, no cleanup).
|
|
93
|
-
sig { params(invocation_id: String).void }
|
|
94
80
|
def kill_invocation(invocation_id)
|
|
95
81
|
post_admin("/restate/invocations/#{invocation_id}/kill", nil)
|
|
96
82
|
end
|
|
97
83
|
|
|
98
84
|
private
|
|
99
85
|
|
|
100
|
-
sig { params(service: T.any(String, T::Class[T.anything])).returns(String) }
|
|
101
86
|
def resolve_name(service)
|
|
102
87
|
if service.is_a?(Class) && service.respond_to?(:service_name)
|
|
103
|
-
|
|
88
|
+
service.service_name # steep:ignore NoMethod
|
|
104
89
|
else
|
|
105
90
|
service.to_s
|
|
106
91
|
end
|
|
107
92
|
end
|
|
108
93
|
|
|
109
|
-
sig { params(path: String, body: T.untyped).returns(T.untyped) }
|
|
110
94
|
def post_ingress(path, body) # rubocop:disable Metrics/AbcSize
|
|
111
95
|
uri = URI("#{@ingress_url}#{path}")
|
|
112
96
|
request = Net::HTTP::Post.new(uri)
|
|
113
97
|
request['Content-Type'] = 'application/json'
|
|
114
98
|
@ingress_headers.each { |k, v| request[k] = v }
|
|
115
99
|
request.body = JSON.generate(body) if body
|
|
116
|
-
response = Net::HTTP.start(uri.hostname, uri.port,
|
|
100
|
+
response = Net::HTTP.start(uri.hostname, uri.port, # steep:ignore ArgumentTypeMismatch
|
|
117
101
|
use_ssl: uri.scheme == 'https',
|
|
118
102
|
read_timeout: 30) { |http| http.request(request) }
|
|
119
103
|
Kernel.raise "Restate ingress error: #{response.code} #{response.body}" unless response.is_a?(Net::HTTPSuccess)
|
|
120
104
|
parse_response(response)
|
|
121
105
|
end
|
|
122
106
|
|
|
123
|
-
sig { params(path: String, body: T.untyped).returns(T.untyped) }
|
|
124
107
|
def post_admin(path, body) # rubocop:disable Metrics/AbcSize
|
|
125
108
|
uri = URI("#{@admin_url}#{path}")
|
|
126
109
|
request = Net::HTTP::Post.new(uri)
|
|
127
110
|
request['Content-Type'] = 'application/json'
|
|
128
111
|
@admin_headers.each { |k, v| request[k] = v }
|
|
129
112
|
request.body = JSON.generate(body) if body
|
|
130
|
-
response = Net::HTTP.start(uri.hostname, uri.port,
|
|
113
|
+
response = Net::HTTP.start(uri.hostname, uri.port, # steep:ignore ArgumentTypeMismatch
|
|
131
114
|
use_ssl: uri.scheme == 'https',
|
|
132
115
|
read_timeout: 30) { |http| http.request(request) }
|
|
133
116
|
Kernel.raise "Restate admin error: #{response.code} #{response.body}" unless response.is_a?(Net::HTTPSuccess)
|
|
134
117
|
parse_response(response)
|
|
135
118
|
end
|
|
136
119
|
|
|
137
|
-
sig { params(response: Net::HTTPResponse).returns(T.untyped) }
|
|
138
120
|
def parse_response(response)
|
|
139
121
|
body = response.body
|
|
140
122
|
body && !body.empty? ? JSON.parse(body) : nil
|
|
@@ -146,12 +128,6 @@ module Restate
|
|
|
146
128
|
#
|
|
147
129
|
# @!visibility private
|
|
148
130
|
class ClientServiceProxy
|
|
149
|
-
extend T::Sig
|
|
150
|
-
|
|
151
|
-
sig do
|
|
152
|
-
params(base_url: String, service_name: String, key: T.nilable(String),
|
|
153
|
-
headers: T::Hash[String, String]).void
|
|
154
|
-
end
|
|
155
131
|
def initialize(base_url, service_name, key, headers)
|
|
156
132
|
@base_url = base_url
|
|
157
133
|
@service_name = service_name
|
|
@@ -166,7 +142,7 @@ module Restate
|
|
|
166
142
|
request['Content-Type'] = 'application/json'
|
|
167
143
|
@headers.each { |k, v| request[k] = v }
|
|
168
144
|
request.body = JSON.generate(arg)
|
|
169
|
-
response = Net::HTTP.start(uri.hostname, uri.port,
|
|
145
|
+
response = Net::HTTP.start(uri.hostname, uri.port, # steep:ignore ArgumentTypeMismatch
|
|
170
146
|
use_ssl: uri.scheme == 'https',
|
|
171
147
|
read_timeout: 30) { |http| http.request(request) }
|
|
172
148
|
Kernel.raise "Restate ingress error: #{response.code} #{response.body}" unless response.is_a?(Net::HTTPSuccess)
|
data/lib/restate/config.rb
CHANGED
|
@@ -13,30 +13,23 @@ module Restate
|
|
|
13
13
|
# # Then use the pre-configured client:
|
|
14
14
|
# Restate.client.service(Greeter).greet("World")
|
|
15
15
|
class Config
|
|
16
|
-
extend T::Sig
|
|
17
|
-
|
|
18
16
|
# Restate ingress URL (for invoking services).
|
|
19
|
-
sig { returns(String) }
|
|
20
17
|
attr_accessor :ingress_url
|
|
21
18
|
|
|
22
19
|
# Restate admin URL (for deployments, invocation management).
|
|
23
|
-
sig { returns(String) }
|
|
24
20
|
attr_accessor :admin_url
|
|
25
21
|
|
|
26
22
|
# Default headers sent with every ingress request.
|
|
27
|
-
sig { returns(T::Hash[String, String]) }
|
|
28
23
|
attr_accessor :ingress_headers
|
|
29
24
|
|
|
30
25
|
# Default headers sent with every admin request.
|
|
31
|
-
sig { returns(T::Hash[String, String]) }
|
|
32
26
|
attr_accessor :admin_headers
|
|
33
27
|
|
|
34
|
-
sig { void }
|
|
35
28
|
def initialize
|
|
36
|
-
@ingress_url =
|
|
37
|
-
@admin_url =
|
|
38
|
-
@ingress_headers =
|
|
39
|
-
@admin_headers =
|
|
29
|
+
@ingress_url = 'http://localhost:8080'
|
|
30
|
+
@admin_url = 'http://localhost:9070'
|
|
31
|
+
@ingress_headers = {}
|
|
32
|
+
@admin_headers = {}
|
|
40
33
|
end
|
|
41
34
|
end
|
|
42
35
|
end
|
data/lib/restate/context.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: false
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
# rubocop:disable
|
|
4
|
+
# rubocop:disable Style/EmptyMethod
|
|
5
5
|
module Restate
|
|
6
6
|
# Signals when the current invocation attempt has finished — either the handler
|
|
7
7
|
# completed, the connection was lost, or a transient error occurred.
|
|
@@ -17,27 +17,22 @@ module Restate
|
|
|
17
17
|
# # poll event.set? periodically, or pass it to your HTTP client
|
|
18
18
|
# end
|
|
19
19
|
class AttemptFinishedEvent
|
|
20
|
-
extend T::Sig
|
|
21
|
-
|
|
22
|
-
sig { void }
|
|
23
20
|
def initialize
|
|
24
|
-
@mutex =
|
|
25
|
-
@set =
|
|
26
|
-
@waiters =
|
|
21
|
+
@mutex = Mutex.new
|
|
22
|
+
@set = false
|
|
23
|
+
@waiters = []
|
|
27
24
|
end
|
|
28
25
|
|
|
29
26
|
# Returns true if the attempt has finished.
|
|
30
|
-
sig { returns(T::Boolean) }
|
|
31
27
|
def set?
|
|
32
28
|
@set
|
|
33
29
|
end
|
|
34
30
|
|
|
35
31
|
# Blocks the current fiber/thread until the attempt finishes.
|
|
36
|
-
sig { void }
|
|
37
32
|
def wait
|
|
38
33
|
return if @set
|
|
39
34
|
|
|
40
|
-
waiter =
|
|
35
|
+
waiter = nil
|
|
41
36
|
@mutex.synchronize do
|
|
42
37
|
unless @set
|
|
43
38
|
waiter = Thread::Queue.new
|
|
@@ -49,7 +44,6 @@ module Restate
|
|
|
49
44
|
|
|
50
45
|
# Marks the event as set and wakes all waiters.
|
|
51
46
|
# Called internally by the SDK when the attempt ends.
|
|
52
|
-
sig { void }
|
|
53
47
|
def set!
|
|
54
48
|
@mutex.synchronize do
|
|
55
49
|
@set = true
|
|
@@ -79,258 +73,149 @@ module Restate
|
|
|
79
73
|
# @see ObjectContext for VirtualObject handlers (adds state operations)
|
|
80
74
|
# @see WorkflowContext for Workflow handlers (adds promise operations)
|
|
81
75
|
module Context
|
|
82
|
-
extend T::Sig
|
|
83
|
-
extend T::Helpers
|
|
84
|
-
|
|
85
|
-
abstract!
|
|
86
|
-
|
|
87
76
|
# Execute a durable side effect. The block runs at most once; the result
|
|
88
77
|
# is journaled and replayed on retries.
|
|
89
78
|
#
|
|
90
79
|
# Pass +background: true+ to offload the block to a real OS Thread,
|
|
91
80
|
# keeping the fiber event loop responsive for CPU-intensive work.
|
|
92
|
-
sig do
|
|
93
|
-
abstract.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
81
|
def run(name, serde: JsonSerde, retry_policy: nil, background: false, &action); end
|
|
99
82
|
|
|
100
83
|
# Convenience shortcut for +run(...).await+. Returns the result directly.
|
|
101
84
|
# Accepts all the same options as +run+, including +background: true+.
|
|
102
|
-
sig do
|
|
103
|
-
abstract.params(
|
|
104
|
-
name: String, serde: T.untyped, retry_policy: T.nilable(RunRetryPolicy),
|
|
105
|
-
background: T::Boolean, action: T.proc.returns(T.untyped)
|
|
106
|
-
).returns(T.untyped)
|
|
107
|
-
end
|
|
108
85
|
def run_sync(name, serde: JsonSerde, retry_policy: nil, background: false, &action); end
|
|
109
86
|
|
|
110
87
|
# Durable timer that survives handler restarts.
|
|
111
|
-
sig { params(seconds: Numeric).returns(DurableFuture) }
|
|
112
88
|
def sleep(seconds) # rubocop:disable Lint/UnusedMethodArgument
|
|
113
89
|
Kernel.raise NotImplementedError
|
|
114
90
|
end
|
|
115
91
|
|
|
116
92
|
# Durably call a handler on a Restate service.
|
|
117
|
-
sig do
|
|
118
|
-
abstract.params(
|
|
119
|
-
service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
|
|
120
|
-
arg: T.untyped, key: T.nilable(String), idempotency_key: T.nilable(String),
|
|
121
|
-
headers: T.nilable(T::Hash[String, String]), input_serde: T.untyped, output_serde: T.untyped
|
|
122
|
-
).returns(DurableCallFuture)
|
|
123
|
-
end
|
|
124
93
|
def service_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil,
|
|
125
94
|
input_serde: NOT_SET, output_serde: NOT_SET)
|
|
126
95
|
end
|
|
96
|
+
|
|
127
97
|
# Fire-and-forget send to a Restate service handler.
|
|
128
|
-
sig do
|
|
129
|
-
abstract.params(
|
|
130
|
-
service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
|
|
131
|
-
arg: T.untyped, key: T.nilable(String), delay: T.nilable(Numeric),
|
|
132
|
-
idempotency_key: T.nilable(String), headers: T.nilable(T::Hash[String, String]),
|
|
133
|
-
input_serde: T.untyped
|
|
134
|
-
).returns(SendHandle)
|
|
135
|
-
end
|
|
136
98
|
def service_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil,
|
|
137
99
|
headers: nil, input_serde: NOT_SET)
|
|
138
100
|
end
|
|
101
|
+
|
|
139
102
|
# Durably call a handler on a Restate virtual object.
|
|
140
|
-
sig do
|
|
141
|
-
abstract.params(
|
|
142
|
-
service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
|
|
143
|
-
key: String, arg: T.untyped, idempotency_key: T.nilable(String),
|
|
144
|
-
headers: T.nilable(T::Hash[String, String]), input_serde: T.untyped, output_serde: T.untyped
|
|
145
|
-
).returns(DurableCallFuture)
|
|
146
|
-
end
|
|
147
103
|
def object_call(service, handler, key, arg, idempotency_key: nil, headers: nil,
|
|
148
104
|
input_serde: NOT_SET, output_serde: NOT_SET)
|
|
149
105
|
end
|
|
106
|
+
|
|
150
107
|
# Fire-and-forget send to a Restate virtual object handler.
|
|
151
|
-
sig do
|
|
152
|
-
abstract.params(
|
|
153
|
-
service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
|
|
154
|
-
key: String, arg: T.untyped, delay: T.nilable(Numeric),
|
|
155
|
-
idempotency_key: T.nilable(String), headers: T.nilable(T::Hash[String, String]),
|
|
156
|
-
input_serde: T.untyped
|
|
157
|
-
).returns(SendHandle)
|
|
158
|
-
end
|
|
159
108
|
def object_send(service, handler, key, arg, delay: nil, idempotency_key: nil,
|
|
160
109
|
headers: nil, input_serde: NOT_SET)
|
|
161
110
|
end
|
|
111
|
+
|
|
162
112
|
# Durably call a handler on a Restate workflow.
|
|
163
|
-
sig do
|
|
164
|
-
abstract.params(
|
|
165
|
-
service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
|
|
166
|
-
key: String, arg: T.untyped, idempotency_key: T.nilable(String),
|
|
167
|
-
headers: T.nilable(T::Hash[String, String]), input_serde: T.untyped, output_serde: T.untyped
|
|
168
|
-
).returns(DurableCallFuture)
|
|
169
|
-
end
|
|
170
113
|
def workflow_call(service, handler, key, arg, idempotency_key: nil, headers: nil,
|
|
171
114
|
input_serde: NOT_SET, output_serde: NOT_SET)
|
|
172
115
|
end
|
|
116
|
+
|
|
173
117
|
# Fire-and-forget send to a Restate workflow handler.
|
|
174
|
-
sig do
|
|
175
|
-
abstract.params(
|
|
176
|
-
service: T.any(String, T::Class[T.anything]), handler: T.any(String, Symbol),
|
|
177
|
-
key: String, arg: T.untyped, delay: T.nilable(Numeric),
|
|
178
|
-
idempotency_key: T.nilable(String), headers: T.nilable(T::Hash[String, String]),
|
|
179
|
-
input_serde: T.untyped
|
|
180
|
-
).returns(SendHandle)
|
|
181
|
-
end
|
|
182
118
|
def workflow_send(service, handler, key, arg, delay: nil, idempotency_key: nil,
|
|
183
119
|
headers: nil, input_serde: NOT_SET)
|
|
184
120
|
end
|
|
121
|
+
|
|
185
122
|
# Durably call a handler using raw bytes (no serialization).
|
|
186
|
-
sig do
|
|
187
|
-
abstract.params(
|
|
188
|
-
service: String, handler: String, arg: String,
|
|
189
|
-
key: T.nilable(String), idempotency_key: T.nilable(String),
|
|
190
|
-
headers: T.nilable(T::Hash[String, String])
|
|
191
|
-
).returns(DurableCallFuture)
|
|
192
|
-
end
|
|
193
123
|
def generic_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil); end
|
|
194
124
|
|
|
195
125
|
# Fire-and-forget send using raw bytes (no serialization).
|
|
196
|
-
sig do
|
|
197
|
-
abstract.params(
|
|
198
|
-
service: String, handler: String, arg: String,
|
|
199
|
-
key: T.nilable(String), delay: T.nilable(Numeric),
|
|
200
|
-
idempotency_key: T.nilable(String), headers: T.nilable(T::Hash[String, String])
|
|
201
|
-
).returns(SendHandle)
|
|
202
|
-
end
|
|
203
126
|
def generic_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil); end
|
|
204
127
|
|
|
205
128
|
# Create an awakeable for external callbacks.
|
|
206
129
|
# Returns [awakeable_id, DurableFuture].
|
|
207
|
-
sig { abstract.params(serde: T.untyped).returns([String, DurableFuture]) }
|
|
208
130
|
def awakeable(serde: JsonSerde); end
|
|
209
131
|
|
|
210
132
|
# Resolve an awakeable with a success value.
|
|
211
|
-
sig { abstract.params(awakeable_id: String, payload: T.untyped, serde: T.untyped).void }
|
|
212
133
|
def resolve_awakeable(awakeable_id, payload, serde: JsonSerde); end
|
|
213
134
|
|
|
214
135
|
# Reject an awakeable with a terminal failure.
|
|
215
|
-
sig { abstract.params(awakeable_id: String, message: String, code: Integer).void }
|
|
216
136
|
def reject_awakeable(awakeable_id, message, code: 500); end
|
|
217
137
|
|
|
218
138
|
# Request cancellation of another invocation.
|
|
219
|
-
sig { abstract.params(invocation_id: String).void }
|
|
220
139
|
def cancel_invocation(invocation_id); end
|
|
221
140
|
|
|
222
141
|
# Wait until any of the given futures completes.
|
|
223
142
|
# Returns [completed, remaining].
|
|
224
|
-
sig { abstract.params(futures: DurableFuture).returns([T::Array[DurableFuture], T::Array[DurableFuture]]) }
|
|
225
143
|
def wait_any(*futures); end
|
|
226
144
|
|
|
227
145
|
# Returns metadata about the current invocation.
|
|
228
|
-
sig { abstract.returns(Request) }
|
|
229
146
|
def request; end
|
|
230
147
|
|
|
231
148
|
# Returns the key for this virtual object or workflow invocation.
|
|
232
|
-
sig { abstract.returns(String) }
|
|
233
149
|
def key; end
|
|
234
150
|
end
|
|
235
151
|
|
|
236
152
|
# Context interface for VirtualObject shared handlers (read-only state).
|
|
237
153
|
# Extends {Context} with +get+, +state_keys+, and +key+ — but no mutations.
|
|
238
154
|
module ObjectSharedContext
|
|
239
|
-
extend T::Sig
|
|
240
|
-
extend T::Helpers
|
|
241
|
-
|
|
242
|
-
abstract!
|
|
243
155
|
include Context
|
|
244
156
|
|
|
245
157
|
# Durably retrieve a state entry. Returns nil if unset.
|
|
246
|
-
sig { abstract.params(name: String, serde: T.untyped).returns(T.untyped) }
|
|
247
158
|
def get(name, serde: JsonSerde); end
|
|
248
159
|
|
|
249
160
|
# Durably retrieve a state entry, returning a DurableFuture instead of blocking.
|
|
250
|
-
sig { abstract.params(name: String, serde: T.untyped).returns(DurableFuture) }
|
|
251
161
|
def get_async(name, serde: JsonSerde); end
|
|
252
162
|
|
|
253
163
|
# List all state entry names.
|
|
254
|
-
sig { abstract.returns(T.untyped) }
|
|
255
164
|
def state_keys; end
|
|
256
165
|
|
|
257
166
|
# List all state entry names, returning a DurableFuture instead of blocking.
|
|
258
|
-
sig { abstract.returns(DurableFuture) }
|
|
259
167
|
def state_keys_async; end
|
|
260
168
|
end
|
|
261
169
|
|
|
262
170
|
# Context interface for VirtualObject exclusive handlers (full state access).
|
|
263
171
|
# Extends {ObjectSharedContext} with mutating state operations.
|
|
264
172
|
module ObjectContext
|
|
265
|
-
extend T::Sig
|
|
266
|
-
extend T::Helpers
|
|
267
|
-
|
|
268
|
-
abstract!
|
|
269
173
|
include ObjectSharedContext
|
|
270
174
|
|
|
271
175
|
# Durably set a state entry.
|
|
272
|
-
sig { abstract.params(name: String, value: T.untyped, serde: T.untyped).void }
|
|
273
176
|
def set(name, value, serde: JsonSerde); end
|
|
274
177
|
|
|
275
178
|
# Durably remove a single state entry.
|
|
276
|
-
sig { abstract.params(name: String).void }
|
|
277
179
|
def clear(name); end
|
|
278
180
|
|
|
279
181
|
# Durably remove all state entries.
|
|
280
|
-
sig { abstract.void }
|
|
281
182
|
def clear_all; end
|
|
282
183
|
end
|
|
283
184
|
|
|
284
185
|
# Context interface for Workflow shared handlers (read-only state + promises).
|
|
285
186
|
# Extends {ObjectSharedContext} with durable promise operations.
|
|
286
187
|
module WorkflowSharedContext
|
|
287
|
-
extend T::Sig
|
|
288
|
-
extend T::Helpers
|
|
289
|
-
|
|
290
|
-
abstract!
|
|
291
188
|
include ObjectSharedContext
|
|
292
189
|
|
|
293
190
|
# Get a durable promise value, blocking until resolved.
|
|
294
|
-
sig { abstract.params(name: String, serde: T.untyped).returns(T.untyped) }
|
|
295
191
|
def promise(name, serde: JsonSerde); end
|
|
296
192
|
|
|
297
193
|
# Peek at a durable promise without blocking. Returns nil if not yet resolved.
|
|
298
|
-
sig { abstract.params(name: String, serde: T.untyped).returns(T.untyped) }
|
|
299
194
|
def peek_promise(name, serde: JsonSerde); end
|
|
300
195
|
|
|
301
196
|
# Resolve a durable promise with a value.
|
|
302
|
-
sig { abstract.params(name: String, payload: T.untyped, serde: T.untyped).void }
|
|
303
197
|
def resolve_promise(name, payload, serde: JsonSerde); end
|
|
304
198
|
|
|
305
199
|
# Reject a durable promise with a terminal failure.
|
|
306
|
-
sig { abstract.params(name: String, message: String, code: Integer).void }
|
|
307
200
|
def reject_promise(name, message, code: 500); end
|
|
308
201
|
end
|
|
309
202
|
|
|
310
203
|
# Context interface for Workflow main handler (full state + promises).
|
|
311
204
|
# Extends {ObjectContext} with durable promise operations.
|
|
312
205
|
module WorkflowContext
|
|
313
|
-
extend T::Sig
|
|
314
|
-
extend T::Helpers
|
|
315
|
-
|
|
316
|
-
abstract!
|
|
317
206
|
include ObjectContext
|
|
318
207
|
|
|
319
208
|
# Get a durable promise value, blocking until resolved.
|
|
320
|
-
sig { abstract.params(name: String, serde: T.untyped).returns(T.untyped) }
|
|
321
209
|
def promise(name, serde: JsonSerde); end
|
|
322
210
|
|
|
323
211
|
# Peek at a durable promise without blocking. Returns nil if not yet resolved.
|
|
324
|
-
sig { abstract.params(name: String, serde: T.untyped).returns(T.untyped) }
|
|
325
212
|
def peek_promise(name, serde: JsonSerde); end
|
|
326
213
|
|
|
327
214
|
# Resolve a durable promise with a value.
|
|
328
|
-
sig { abstract.params(name: String, payload: T.untyped, serde: T.untyped).void }
|
|
329
215
|
def resolve_promise(name, payload, serde: JsonSerde); end
|
|
330
216
|
|
|
331
217
|
# Reject a durable promise with a terminal failure.
|
|
332
|
-
sig { abstract.params(name: String, message: String, code: Integer).void }
|
|
333
218
|
def reject_promise(name, message, code: 500); end
|
|
334
219
|
end
|
|
335
220
|
end
|
|
336
|
-
# rubocop:enable
|
|
221
|
+
# rubocop:enable Style/EmptyMethod
|
data/lib/restate/discovery.rb
CHANGED
|
@@ -5,36 +5,32 @@ require 'json'
|
|
|
5
5
|
|
|
6
6
|
module Restate
|
|
7
7
|
module Discovery # rubocop:disable Metrics/ModuleLength
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
PROTOCOL_MODES = T.let({
|
|
8
|
+
PROTOCOL_MODES = {
|
|
11
9
|
'bidi' => 'BIDI_STREAM',
|
|
12
10
|
'request_response' => 'REQUEST_RESPONSE'
|
|
13
|
-
}.freeze
|
|
11
|
+
}.freeze
|
|
14
12
|
|
|
15
|
-
SERVICE_TYPES =
|
|
13
|
+
SERVICE_TYPES = {
|
|
16
14
|
'service' => 'SERVICE',
|
|
17
15
|
'object' => 'VIRTUAL_OBJECT',
|
|
18
16
|
'workflow' => 'WORKFLOW'
|
|
19
|
-
}.freeze
|
|
17
|
+
}.freeze
|
|
20
18
|
|
|
21
|
-
HANDLER_TYPES =
|
|
19
|
+
HANDLER_TYPES = {
|
|
22
20
|
'exclusive' => 'EXCLUSIVE',
|
|
23
21
|
'shared' => 'SHARED',
|
|
24
22
|
'workflow' => 'WORKFLOW'
|
|
25
|
-
}.freeze
|
|
23
|
+
}.freeze
|
|
26
24
|
|
|
27
25
|
module_function
|
|
28
26
|
|
|
29
27
|
# Generate the discovery JSON for the given endpoint.
|
|
30
|
-
sig { params(endpoint: Endpoint, _version: Integer, discovered_as: String).returns(String) }
|
|
31
28
|
def compute_discovery_json(endpoint, _version, discovered_as)
|
|
32
29
|
ep = compute_discovery(endpoint, discovered_as)
|
|
33
30
|
JSON.generate(ep, allow_nan: false)
|
|
34
31
|
end
|
|
35
32
|
|
|
36
33
|
# Build the discovery hash for the endpoint.
|
|
37
|
-
sig { params(endpoint: Endpoint, discovered_as: String).returns(T::Hash[Symbol, T.untyped]) }
|
|
38
34
|
def compute_discovery(endpoint, discovered_as)
|
|
39
35
|
services = endpoint.services.values.map do |service|
|
|
40
36
|
build_service(service)
|
|
@@ -50,7 +46,6 @@ module Restate
|
|
|
50
46
|
)
|
|
51
47
|
end
|
|
52
48
|
|
|
53
|
-
sig { params(service: T.untyped).returns(T::Hash[Symbol, T.untyped]) }
|
|
54
49
|
def build_service(service) # rubocop:disable Metrics/AbcSize
|
|
55
50
|
service_type = SERVICE_TYPES.fetch(service.service_tag.kind)
|
|
56
51
|
|
|
@@ -80,7 +75,6 @@ module Restate
|
|
|
80
75
|
result
|
|
81
76
|
end
|
|
82
77
|
|
|
83
|
-
sig { params(handler: T.untyped).returns(T::Hash[Symbol, T.untyped]) }
|
|
84
78
|
def build_handler(handler) # rubocop:disable Metrics/AbcSize
|
|
85
79
|
ty = handler.kind ? HANDLER_TYPES.fetch(handler.kind) : nil
|
|
86
80
|
|
|
@@ -118,7 +112,6 @@ module Restate
|
|
|
118
112
|
end
|
|
119
113
|
|
|
120
114
|
# Convert seconds to milliseconds (integer). Returns nil if input is nil.
|
|
121
|
-
sig { params(seconds: T.nilable(Numeric)).returns(T.nilable(Integer)) }
|
|
122
115
|
def seconds_to_ms(seconds)
|
|
123
116
|
return nil if seconds.nil?
|
|
124
117
|
|
|
@@ -126,7 +119,6 @@ module Restate
|
|
|
126
119
|
end
|
|
127
120
|
|
|
128
121
|
# Merge retry policy fields (flattened) into the target hash.
|
|
129
|
-
sig { params(target: T::Hash[Symbol, T.untyped], policy: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
|
130
122
|
def merge_retry_policy!(target, policy) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
|
|
131
123
|
return if policy.nil? || policy.empty?
|
|
132
124
|
|
|
@@ -138,7 +130,6 @@ module Restate
|
|
|
138
130
|
end
|
|
139
131
|
|
|
140
132
|
# Remove nil values from a hash (non-recursive for top level, recursive for nested).
|
|
141
|
-
sig { params(kwargs: T.untyped).returns(T::Hash[Symbol, T.untyped]) }
|
|
142
133
|
def compact(**kwargs)
|
|
143
134
|
kwargs.each_with_object({}) do |(k, v), result|
|
|
144
135
|
next if v.nil?
|