restate-sdk 1.1.0 → 1.2.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 +3 -3
- data/ext/restate_internal/Cargo.toml +2 -2
- data/ext/restate_internal/src/lib.rs +54 -8
- data/lib/restate/context.rb +37 -3
- data/lib/restate/server/context.rb +154 -20
- data/lib/restate/service.rb +10 -4
- data/lib/restate/service_proxy.rb +16 -2
- data/lib/restate/version.rb +1 -1
- data/lib/restate/virtual_object.rb +10 -4
- data/lib/restate/vm.rb +15 -8
- data/lib/restate/workflow.rb +14 -4
- data/lib/restate.rb +17 -5
- data/sig/restate.rbs +15 -4
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: af00581289bac17d418aea72014305bdd1fd64e3a8d02a0ca6a5194ac1497443
|
|
4
|
+
data.tar.gz: 3f9dc77d320b092cd074b7e97317893cee9332b0e2a9014c4020b50500ad8701
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 471fd2f7996b2a03ad7f77d9ed27a0ab5fa03f4f65350292845893b5727165277fc2c7d6b767a5e775dfa26a6b0cd7911bda317b25d7ed19cd35cfd524b3259a
|
|
7
|
+
data.tar.gz: a0f4f51c113aa455e649a2194bc0b4095110e746122750687017397911dcd3e107eb81f2f8fe1a7feb893fd83c894666157cca2b8c1f487c760ed22d737a05b0
|
data/Cargo.lock
CHANGED
|
@@ -830,9 +830,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
|
|
830
830
|
|
|
831
831
|
[[package]]
|
|
832
832
|
name = "restate-sdk-shared-core"
|
|
833
|
-
version = "7.0.
|
|
833
|
+
version = "7.0.1"
|
|
834
834
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
835
|
-
checksum = "
|
|
835
|
+
checksum = "8bd1f1d1961ac32aea52923f089e668a07630ca9c4edcadf18d38bc24e5f14c0"
|
|
836
836
|
dependencies = [
|
|
837
837
|
"base64",
|
|
838
838
|
"bs58",
|
|
@@ -850,7 +850,7 @@ dependencies = [
|
|
|
850
850
|
|
|
851
851
|
[[package]]
|
|
852
852
|
name = "restate_internal"
|
|
853
|
-
version = "1.
|
|
853
|
+
version = "1.2.0"
|
|
854
854
|
dependencies = [
|
|
855
855
|
"magnus",
|
|
856
856
|
"rb-sys",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "restate_internal"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.2.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
publish = false
|
|
6
6
|
|
|
@@ -12,5 +12,5 @@ doc = false
|
|
|
12
12
|
[dependencies]
|
|
13
13
|
magnus = { version = "0.8", features = ["rb-sys"] }
|
|
14
14
|
rb-sys = { version = "0.9", features = ["stable-api-compiled-fallback"] }
|
|
15
|
-
restate-sdk-shared-core = { version = "7.0.
|
|
15
|
+
restate-sdk-shared-core = { version = "7.0.1" , features = ["request_identity", "sha2_random_seed", "rust_crypto"] }
|
|
16
16
|
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
|
|
@@ -215,6 +215,9 @@ struct RbInput {
|
|
|
215
215
|
key: String,
|
|
216
216
|
headers: Vec<RbHeader>,
|
|
217
217
|
input: Vec<u8>,
|
|
218
|
+
scope: Option<String>,
|
|
219
|
+
limit_key: Option<String>,
|
|
220
|
+
idempotency_key: Option<String>,
|
|
218
221
|
}
|
|
219
222
|
|
|
220
223
|
impl RbInput {
|
|
@@ -227,6 +230,15 @@ impl RbInput {
|
|
|
227
230
|
fn key(&self) -> &str {
|
|
228
231
|
&self.key
|
|
229
232
|
}
|
|
233
|
+
fn scope(&self) -> Option<&str> {
|
|
234
|
+
self.scope.as_deref()
|
|
235
|
+
}
|
|
236
|
+
fn limit_key(&self) -> Option<&str> {
|
|
237
|
+
self.limit_key.as_deref()
|
|
238
|
+
}
|
|
239
|
+
fn idempotency_key(&self) -> Option<&str> {
|
|
240
|
+
self.idempotency_key.as_deref()
|
|
241
|
+
}
|
|
230
242
|
fn headers_array(&self) -> Result<RArray, Error> {
|
|
231
243
|
let ruby = Ruby::get().map_err(|_| Error::new(vm_error_class(), "Ruby not available"))?;
|
|
232
244
|
let ary = ruby.ary_new_capa(self.headers.len());
|
|
@@ -249,6 +261,9 @@ impl From<Input> for RbInput {
|
|
|
249
261
|
key: i.key,
|
|
250
262
|
headers: i.headers.into_iter().map(Into::into).collect(),
|
|
251
263
|
input: i.input.into(),
|
|
264
|
+
scope: i.scope,
|
|
265
|
+
limit_key: i.limit_key,
|
|
266
|
+
idempotency_key: i.idempotency_key,
|
|
252
267
|
}
|
|
253
268
|
}
|
|
254
269
|
}
|
|
@@ -584,7 +599,9 @@ impl RbVM {
|
|
|
584
599
|
.map_err(core_error_to_magnus)
|
|
585
600
|
}
|
|
586
601
|
|
|
587
|
-
// sys_call(service, handler, buffer, key_or_nil, idempotency_key_or_nil, headers_or_nil
|
|
602
|
+
// sys_call(service, handler, buffer, key_or_nil, idempotency_key_or_nil, headers_or_nil,
|
|
603
|
+
// scope_or_nil, limit_key_or_nil)
|
|
604
|
+
#[allow(clippy::too_many_arguments)]
|
|
588
605
|
fn sys_call(
|
|
589
606
|
&self,
|
|
590
607
|
service: String,
|
|
@@ -593,6 +610,8 @@ impl RbVM {
|
|
|
593
610
|
key: Value,
|
|
594
611
|
idempotency_key: Value,
|
|
595
612
|
headers: Value,
|
|
613
|
+
scope: Value,
|
|
614
|
+
limit_key: Value,
|
|
596
615
|
) -> Result<RbCallHandle, Error> {
|
|
597
616
|
let bytes: Vec<u8> = unsafe { buffer.as_slice().to_vec() };
|
|
598
617
|
let key_opt: Option<String> = if key.is_nil() {
|
|
@@ -610,6 +629,16 @@ impl RbVM {
|
|
|
610
629
|
} else {
|
|
611
630
|
parse_headers_array(RArray::try_convert(headers)?)?
|
|
612
631
|
};
|
|
632
|
+
let scope_opt: Option<String> = if scope.is_nil() {
|
|
633
|
+
None
|
|
634
|
+
} else {
|
|
635
|
+
Some(String::try_convert(scope)?)
|
|
636
|
+
};
|
|
637
|
+
let limit_key_opt: Option<String> = if limit_key.is_nil() {
|
|
638
|
+
None
|
|
639
|
+
} else {
|
|
640
|
+
Some(String::try_convert(limit_key)?)
|
|
641
|
+
};
|
|
613
642
|
self.vm
|
|
614
643
|
.borrow_mut()
|
|
615
644
|
.sys_call(
|
|
@@ -618,8 +647,8 @@ impl RbVM {
|
|
|
618
647
|
handler,
|
|
619
648
|
key: key_opt,
|
|
620
649
|
idempotency_key: idem_opt,
|
|
621
|
-
scope:
|
|
622
|
-
limit_key:
|
|
650
|
+
scope: scope_opt,
|
|
651
|
+
limit_key: limit_key_opt,
|
|
623
652
|
headers: hdr_vec,
|
|
624
653
|
},
|
|
625
654
|
bytes.into(),
|
|
@@ -630,7 +659,9 @@ impl RbVM {
|
|
|
630
659
|
.map_err(core_error_to_magnus)
|
|
631
660
|
}
|
|
632
661
|
|
|
633
|
-
// sys_send(service, handler, buffer, key_or_nil, delay_or_nil, idempotency_key_or_nil,
|
|
662
|
+
// sys_send(service, handler, buffer, key_or_nil, delay_or_nil, idempotency_key_or_nil,
|
|
663
|
+
// headers_or_nil, scope_or_nil, limit_key_or_nil)
|
|
664
|
+
#[allow(clippy::too_many_arguments)]
|
|
634
665
|
fn sys_send(
|
|
635
666
|
&self,
|
|
636
667
|
service: String,
|
|
@@ -640,6 +671,8 @@ impl RbVM {
|
|
|
640
671
|
delay: Value,
|
|
641
672
|
idempotency_key: Value,
|
|
642
673
|
headers: Value,
|
|
674
|
+
scope: Value,
|
|
675
|
+
limit_key: Value,
|
|
643
676
|
) -> Result<u32, Error> {
|
|
644
677
|
let bytes: Vec<u8> = unsafe { buffer.as_slice().to_vec() };
|
|
645
678
|
let key_opt: Option<String> = if key.is_nil() {
|
|
@@ -662,6 +695,16 @@ impl RbVM {
|
|
|
662
695
|
} else {
|
|
663
696
|
parse_headers_array(RArray::try_convert(headers)?)?
|
|
664
697
|
};
|
|
698
|
+
let scope_opt: Option<String> = if scope.is_nil() {
|
|
699
|
+
None
|
|
700
|
+
} else {
|
|
701
|
+
Some(String::try_convert(scope)?)
|
|
702
|
+
};
|
|
703
|
+
let limit_key_opt: Option<String> = if limit_key.is_nil() {
|
|
704
|
+
None
|
|
705
|
+
} else {
|
|
706
|
+
Some(String::try_convert(limit_key)?)
|
|
707
|
+
};
|
|
665
708
|
self.vm
|
|
666
709
|
.borrow_mut()
|
|
667
710
|
.sys_send(
|
|
@@ -670,8 +713,8 @@ impl RbVM {
|
|
|
670
713
|
handler,
|
|
671
714
|
key: key_opt,
|
|
672
715
|
idempotency_key: idem_opt,
|
|
673
|
-
scope:
|
|
674
|
-
limit_key:
|
|
716
|
+
scope: scope_opt,
|
|
717
|
+
limit_key: limit_key_opt,
|
|
675
718
|
headers: hdr_vec,
|
|
676
719
|
},
|
|
677
720
|
bytes.into(),
|
|
@@ -1135,6 +1178,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
1135
1178
|
input_class.define_method("invocation_id", method!(RbInput::invocation_id, 0))?;
|
|
1136
1179
|
input_class.define_method("random_seed", method!(RbInput::random_seed, 0))?;
|
|
1137
1180
|
input_class.define_method("key", method!(RbInput::key, 0))?;
|
|
1181
|
+
input_class.define_method("scope", method!(RbInput::scope, 0))?;
|
|
1182
|
+
input_class.define_method("limit_key", method!(RbInput::limit_key, 0))?;
|
|
1183
|
+
input_class.define_method("idempotency_key", method!(RbInput::idempotency_key, 0))?;
|
|
1138
1184
|
input_class.define_method("headers", method!(RbInput::headers_array, 0))?;
|
|
1139
1185
|
input_class.define_method("input", method!(RbInput::input_bytes, 0))?;
|
|
1140
1186
|
|
|
@@ -1189,8 +1235,8 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
1189
1235
|
vm_class.define_method("sys_clear_state", method!(RbVM::sys_clear_state, 1))?;
|
|
1190
1236
|
vm_class.define_method("sys_clear_all_state", method!(RbVM::sys_clear_all_state, 0))?;
|
|
1191
1237
|
vm_class.define_method("sys_sleep", method!(RbVM::sys_sleep, 2))?;
|
|
1192
|
-
vm_class.define_method("sys_call", method!(RbVM::sys_call,
|
|
1193
|
-
vm_class.define_method("sys_send", method!(RbVM::sys_send,
|
|
1238
|
+
vm_class.define_method("sys_call", method!(RbVM::sys_call, 8))?;
|
|
1239
|
+
vm_class.define_method("sys_send", method!(RbVM::sys_send, 9))?;
|
|
1194
1240
|
vm_class.define_method("sys_run", method!(RbVM::sys_run, 1))?;
|
|
1195
1241
|
vm_class.define_method(
|
|
1196
1242
|
"propose_run_completion_success",
|
data/lib/restate/context.rb
CHANGED
|
@@ -63,7 +63,14 @@ module Restate
|
|
|
63
63
|
# @return [String] raw input bytes
|
|
64
64
|
# @!attribute [r] attempt_finished_event
|
|
65
65
|
# @return [AttemptFinishedEvent] signaled when this attempt ends
|
|
66
|
-
|
|
66
|
+
# @!attribute [r] scope
|
|
67
|
+
# @return [String, nil] the scope this invocation was routed within, if any
|
|
68
|
+
# @!attribute [r] limit_key
|
|
69
|
+
# @return [String, nil] the concurrency limit key this invocation was made with, if any
|
|
70
|
+
# @!attribute [r] idempotency_key
|
|
71
|
+
# @return [String, nil] the idempotency key this invocation was made with, if any
|
|
72
|
+
Request = Struct.new(:id, :headers, :body, :attempt_finished_event,
|
|
73
|
+
:scope, :limit_key, :idempotency_key, keyword_init: true)
|
|
67
74
|
|
|
68
75
|
# Base context interface for all Restate handlers.
|
|
69
76
|
#
|
|
@@ -120,10 +127,37 @@ module Restate
|
|
|
120
127
|
end
|
|
121
128
|
|
|
122
129
|
# Durably call a handler using raw bytes (no serialization).
|
|
123
|
-
def generic_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil
|
|
130
|
+
def generic_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil,
|
|
131
|
+
scope: nil, limit_key: nil)
|
|
132
|
+
end
|
|
124
133
|
|
|
125
134
|
# Fire-and-forget send using raw bytes (no serialization).
|
|
126
|
-
def generic_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil
|
|
135
|
+
def generic_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil,
|
|
136
|
+
scope: nil, limit_key: nil)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Returns a +ScopedContext+ that routes all outgoing calls within the given scope.
|
|
140
|
+
#
|
|
141
|
+
# NOTE: This API is in preview and is not enabled by default. To use it in
|
|
142
|
+
# restate-server 1.7, enable the flow control and protocol v7 experimental
|
|
143
|
+
# features via +RESTATE_EXPERIMENTAL_ENABLE_PROTOCOL_V7=true+ and
|
|
144
|
+
# +RESTATE_EXPERIMENTAL_ENABLE_VQUEUES=true+. These can be enabled only on new
|
|
145
|
+
# clusters. If they aren't enabled, the call fails with a retryable error and
|
|
146
|
+
# keeps retrying until they are.
|
|
147
|
+
#
|
|
148
|
+
# A scope is a sub-grouping of resources (invocations, workflow instances,
|
|
149
|
+
# concurrency limits) within the Restate cluster. It becomes part of the target
|
|
150
|
+
# identity tuple and contributes to the partition key, so all resources in a
|
|
151
|
+
# scope get co-located by the restate-server. Omitting the scope (i.e. using the
|
|
152
|
+
# regular +service_call+ / +workflow_call+ methods) is equivalent to calling
|
|
153
|
+
# with no scope, which is the existing behavior.
|
|
154
|
+
#
|
|
155
|
+
# The scope must consist only of +[a-zA-Z0-9_.-]+ characters, 1 <= length <= 36.
|
|
156
|
+
#
|
|
157
|
+
# @param scope [String] the scope identifier
|
|
158
|
+
# @return [ScopedContext]
|
|
159
|
+
# @see https://docs.restate.dev/services/flow-control
|
|
160
|
+
def scope(scope); end
|
|
127
161
|
|
|
128
162
|
# Create an awakeable for external callbacks.
|
|
129
163
|
# Returns [awakeable_id, DurableFuture].
|
|
@@ -243,8 +243,11 @@ module Restate
|
|
|
243
243
|
# ── Service calls ──
|
|
244
244
|
|
|
245
245
|
# Durably calls a handler on a Restate service and returns a future for its result.
|
|
246
|
+
#
|
|
247
|
+
# +scope+ and +limit_key+ are internal parameters set by +ScopedContext+
|
|
248
|
+
# (via +ctx.scope(...)+); prefer that API over passing them directly.
|
|
246
249
|
def service_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil,
|
|
247
|
-
input_serde: NOT_SET, output_serde: NOT_SET)
|
|
250
|
+
input_serde: NOT_SET, output_serde: NOT_SET, scope: nil, limit_key: nil)
|
|
248
251
|
svc_name, handler_name, handler_meta = resolve_call_target(service, handler)
|
|
249
252
|
in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
|
|
250
253
|
out_serde = resolve_serde(output_serde, handler_meta, :output_serde)
|
|
@@ -252,7 +255,7 @@ module Restate
|
|
|
252
255
|
with_outbound_middleware(svc_name, handler_name, headers, handler_meta: handler_meta) do |hdrs|
|
|
253
256
|
call_handle = @vm.sys_call(
|
|
254
257
|
service: svc_name, handler: handler_name, parameter: parameter,
|
|
255
|
-
key: key, idempotency_key: idempotency_key, headers: hdrs
|
|
258
|
+
key: key, idempotency_key: idempotency_key, headers: hdrs, scope: scope, limit_key: limit_key
|
|
256
259
|
)
|
|
257
260
|
DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
|
|
258
261
|
output_serde: out_serde)
|
|
@@ -260,8 +263,11 @@ module Restate
|
|
|
260
263
|
end
|
|
261
264
|
|
|
262
265
|
# Sends a one-way invocation to a Restate service handler (fire-and-forget).
|
|
266
|
+
#
|
|
267
|
+
# +scope+ and +limit_key+ are internal parameters set by +ScopedContext+
|
|
268
|
+
# (via +ctx.scope(...)+); prefer that API over passing them directly.
|
|
263
269
|
def service_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil,
|
|
264
|
-
input_serde: NOT_SET)
|
|
270
|
+
input_serde: NOT_SET, scope: nil, limit_key: nil)
|
|
265
271
|
svc_name, handler_name, handler_meta = resolve_call_target(service, handler)
|
|
266
272
|
in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
|
|
267
273
|
parameter = in_serde.serialize(arg)
|
|
@@ -269,15 +275,19 @@ module Restate
|
|
|
269
275
|
with_outbound_middleware(svc_name, handler_name, headers, handler_meta: handler_meta) do |hdrs|
|
|
270
276
|
invocation_id_handle = @vm.sys_send(
|
|
271
277
|
service: svc_name, handler: handler_name, parameter: parameter,
|
|
272
|
-
key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs
|
|
278
|
+
key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs,
|
|
279
|
+
scope: scope, limit_key: limit_key
|
|
273
280
|
)
|
|
274
281
|
SendHandle.new(self, invocation_id_handle)
|
|
275
282
|
end
|
|
276
283
|
end
|
|
277
284
|
|
|
278
285
|
# Durably calls a handler on a Restate virtual object, keyed by +key+.
|
|
286
|
+
#
|
|
287
|
+
# +scope+ and +limit_key+ are internal parameters set by +ScopedContext+
|
|
288
|
+
# (via +ctx.scope(...)+); prefer that API over passing them directly.
|
|
279
289
|
def object_call(service, handler, key, arg, idempotency_key: nil, headers: nil,
|
|
280
|
-
input_serde: NOT_SET, output_serde: NOT_SET)
|
|
290
|
+
input_serde: NOT_SET, output_serde: NOT_SET, scope: nil, limit_key: nil)
|
|
281
291
|
svc_name, handler_name, handler_meta = resolve_call_target(service, handler)
|
|
282
292
|
in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
|
|
283
293
|
out_serde = resolve_serde(output_serde, handler_meta, :output_serde)
|
|
@@ -285,7 +295,7 @@ module Restate
|
|
|
285
295
|
with_outbound_middleware(svc_name, handler_name, headers, handler_meta: handler_meta) do |hdrs|
|
|
286
296
|
call_handle = @vm.sys_call(
|
|
287
297
|
service: svc_name, handler: handler_name, parameter: parameter,
|
|
288
|
-
key: key, idempotency_key: idempotency_key, headers: hdrs
|
|
298
|
+
key: key, idempotency_key: idempotency_key, headers: hdrs, scope: scope, limit_key: limit_key
|
|
289
299
|
)
|
|
290
300
|
DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
|
|
291
301
|
output_serde: out_serde)
|
|
@@ -293,8 +303,11 @@ module Restate
|
|
|
293
303
|
end
|
|
294
304
|
|
|
295
305
|
# Sends a one-way invocation to a Restate virtual object handler (fire-and-forget).
|
|
306
|
+
#
|
|
307
|
+
# +scope+ and +limit_key+ are internal parameters set by +ScopedContext+
|
|
308
|
+
# (via +ctx.scope(...)+); prefer that API over passing them directly.
|
|
296
309
|
def object_send(service, handler, key, arg, delay: nil, idempotency_key: nil, headers: nil,
|
|
297
|
-
input_serde: NOT_SET)
|
|
310
|
+
input_serde: NOT_SET, scope: nil, limit_key: nil)
|
|
298
311
|
svc_name, handler_name, handler_meta = resolve_call_target(service, handler)
|
|
299
312
|
in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
|
|
300
313
|
parameter = in_serde.serialize(arg)
|
|
@@ -302,24 +315,38 @@ module Restate
|
|
|
302
315
|
with_outbound_middleware(svc_name, handler_name, headers, handler_meta: handler_meta) do |hdrs|
|
|
303
316
|
invocation_id_handle = @vm.sys_send(
|
|
304
317
|
service: svc_name, handler: handler_name, parameter: parameter,
|
|
305
|
-
key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs
|
|
318
|
+
key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs,
|
|
319
|
+
scope: scope, limit_key: limit_key
|
|
306
320
|
)
|
|
307
321
|
SendHandle.new(self, invocation_id_handle)
|
|
308
322
|
end
|
|
309
323
|
end
|
|
310
324
|
|
|
311
325
|
# Durably calls a handler on a Restate workflow, keyed by +key+.
|
|
326
|
+
#
|
|
327
|
+
# +scope+ and +limit_key+ are internal parameters set by +ScopedContext+
|
|
328
|
+
# (via +ctx.scope(...)+); prefer that API over passing them directly.
|
|
312
329
|
def workflow_call(service, handler, key, arg, idempotency_key: nil, headers: nil,
|
|
313
|
-
input_serde: NOT_SET, output_serde: NOT_SET)
|
|
314
|
-
object_call(
|
|
315
|
-
|
|
330
|
+
input_serde: NOT_SET, output_serde: NOT_SET, scope: nil, limit_key: nil)
|
|
331
|
+
object_call(
|
|
332
|
+
service, handler, key, arg,
|
|
333
|
+
idempotency_key: idempotency_key, headers: headers,
|
|
334
|
+
input_serde: input_serde, output_serde: output_serde,
|
|
335
|
+
scope: scope, limit_key: limit_key
|
|
336
|
+
)
|
|
316
337
|
end
|
|
317
338
|
|
|
318
339
|
# Sends a one-way invocation to a Restate workflow handler (fire-and-forget).
|
|
340
|
+
#
|
|
341
|
+
# +scope+ and +limit_key+ are internal parameters set by +ScopedContext+
|
|
342
|
+
# (via +ctx.scope(...)+); prefer that API over passing them directly.
|
|
319
343
|
def workflow_send(service, handler, key, arg, delay: nil, idempotency_key: nil, headers: nil,
|
|
320
|
-
input_serde: NOT_SET)
|
|
321
|
-
object_send(
|
|
322
|
-
|
|
344
|
+
input_serde: NOT_SET, scope: nil, limit_key: nil)
|
|
345
|
+
object_send(
|
|
346
|
+
service, handler, key, arg,
|
|
347
|
+
delay: delay, idempotency_key: idempotency_key, headers: headers,
|
|
348
|
+
input_serde: input_serde, scope: scope, limit_key: limit_key
|
|
349
|
+
)
|
|
323
350
|
end
|
|
324
351
|
|
|
325
352
|
# ── Awakeables ──
|
|
@@ -403,11 +430,15 @@ module Restate
|
|
|
403
430
|
# ── Generic calls (raw bytes, no serde) ──
|
|
404
431
|
|
|
405
432
|
# Durably calls a handler using raw bytes (no serialization). Useful for proxying.
|
|
406
|
-
|
|
433
|
+
#
|
|
434
|
+
# Optionally accepts a +scope+ to route the call within (see +#scope+) and a
|
|
435
|
+
# +limit_key+ to enforce hierarchical concurrency limits within that scope.
|
|
436
|
+
def generic_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil,
|
|
437
|
+
scope: nil, limit_key: nil)
|
|
407
438
|
with_outbound_middleware(service, handler, headers) do |hdrs|
|
|
408
439
|
call_handle = @vm.sys_call(
|
|
409
440
|
service: service, handler: handler, parameter: arg,
|
|
410
|
-
key: key, idempotency_key: idempotency_key, headers: hdrs
|
|
441
|
+
key: key, idempotency_key: idempotency_key, headers: hdrs, scope: scope, limit_key: limit_key
|
|
411
442
|
)
|
|
412
443
|
DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
|
|
413
444
|
output_serde: nil)
|
|
@@ -415,26 +446,42 @@ module Restate
|
|
|
415
446
|
end
|
|
416
447
|
|
|
417
448
|
# Sends a one-way invocation using raw bytes (no serialization). Useful for proxying.
|
|
418
|
-
|
|
449
|
+
#
|
|
450
|
+
# Optionally accepts a +scope+ to route the send within (see +#scope+) and a
|
|
451
|
+
# +limit_key+ to enforce hierarchical concurrency limits within that scope.
|
|
452
|
+
def generic_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil,
|
|
453
|
+
scope: nil, limit_key: nil)
|
|
419
454
|
delay_ms = delay ? (delay * 1000).to_i : nil
|
|
420
455
|
with_outbound_middleware(service, handler, headers) do |hdrs|
|
|
421
456
|
invocation_id_handle = @vm.sys_send(
|
|
422
457
|
service: service, handler: handler, parameter: arg,
|
|
423
|
-
key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs
|
|
458
|
+
key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs,
|
|
459
|
+
scope: scope, limit_key: limit_key
|
|
424
460
|
)
|
|
425
461
|
SendHandle.new(self, invocation_id_handle)
|
|
426
462
|
end
|
|
427
463
|
end
|
|
428
464
|
|
|
465
|
+
# Returns a +ScopedContext+ that routes all outgoing calls within the given scope.
|
|
466
|
+
# See {Restate::Context#scope} for the full semantics and constraints.
|
|
467
|
+
def scope(scope)
|
|
468
|
+
ScopedContext.new(self, scope)
|
|
469
|
+
end
|
|
470
|
+
|
|
429
471
|
# ── Request metadata ──
|
|
430
472
|
|
|
431
|
-
# Returns metadata about the current invocation (id, headers, raw body
|
|
473
|
+
# Returns metadata about the current invocation (id, headers, raw body,
|
|
474
|
+
# and — when present — the scope, limit_key, and idempotency_key it was
|
|
475
|
+
# invoked with).
|
|
432
476
|
def request
|
|
433
477
|
@request ||= Request.new(
|
|
434
478
|
id: @invocation.invocation_id,
|
|
435
479
|
headers: @invocation.headers.to_h,
|
|
436
480
|
body: @invocation.input_buffer,
|
|
437
|
-
attempt_finished_event: @attempt_finished_event
|
|
481
|
+
attempt_finished_event: @attempt_finished_event,
|
|
482
|
+
scope: @invocation.scope,
|
|
483
|
+
limit_key: @invocation.limit_key,
|
|
484
|
+
idempotency_key: @invocation.idempotency_key
|
|
438
485
|
)
|
|
439
486
|
end
|
|
440
487
|
|
|
@@ -737,5 +784,92 @@ module Restate
|
|
|
737
784
|
end
|
|
738
785
|
end
|
|
739
786
|
end
|
|
787
|
+
|
|
788
|
+
# A context for making RPC calls within a specific scope.
|
|
789
|
+
#
|
|
790
|
+
# NOTE: This API is in preview and is not enabled by default. See
|
|
791
|
+
# {Restate::Context#scope} for how to enable it and for the full semantics.
|
|
792
|
+
#
|
|
793
|
+
# Returned by +ctx.scope(scope)+: calls and sends made through this context
|
|
794
|
+
# carry the captured scope, and each method additionally accepts an optional
|
|
795
|
+
# +limit_key+.
|
|
796
|
+
#
|
|
797
|
+
# The limit key enforces hierarchical concurrency limits on invocations sharing
|
|
798
|
+
# the same scope. It can have one or two levels separated by +/+ (e.g. +"tenant1"+
|
|
799
|
+
# or +"tenant1/user42"+). Each level must consist only of +[a-zA-Z0-9_.-]+
|
|
800
|
+
# characters, 1 <= length <= 36.
|
|
801
|
+
#
|
|
802
|
+
# The limit key is *not* part of the request identity: two calls to the same
|
|
803
|
+
# target with the same scope and object key but different limit keys refer to the
|
|
804
|
+
# *same* resource instance. The limit key only affects concurrency limits.
|
|
805
|
+
class ScopedContext
|
|
806
|
+
def initialize(ctx, scope)
|
|
807
|
+
@ctx = ctx
|
|
808
|
+
@scope = scope
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
# Durably calls a handler on a Restate service, within this scope.
|
|
812
|
+
def service_call(service, handler, arg, key: nil, limit_key: nil, idempotency_key: nil,
|
|
813
|
+
headers: nil, input_serde: NOT_SET, output_serde: NOT_SET)
|
|
814
|
+
@ctx.service_call(
|
|
815
|
+
service, handler, arg,
|
|
816
|
+
key: key, idempotency_key: idempotency_key, headers: headers,
|
|
817
|
+
input_serde: input_serde, output_serde: output_serde,
|
|
818
|
+
scope: @scope, limit_key: limit_key
|
|
819
|
+
)
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
# Fire-and-forget send to a Restate service handler, within this scope.
|
|
823
|
+
def service_send(service, handler, arg, key: nil, delay: nil, limit_key: nil,
|
|
824
|
+
idempotency_key: nil, headers: nil, input_serde: NOT_SET)
|
|
825
|
+
@ctx.service_send(
|
|
826
|
+
service, handler, arg,
|
|
827
|
+
key: key, delay: delay, idempotency_key: idempotency_key, headers: headers,
|
|
828
|
+
input_serde: input_serde, scope: @scope, limit_key: limit_key
|
|
829
|
+
)
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
# Durably calls a handler on a Restate virtual object, within this scope.
|
|
833
|
+
def object_call(service, handler, key, arg, limit_key: nil, idempotency_key: nil,
|
|
834
|
+
headers: nil, input_serde: NOT_SET, output_serde: NOT_SET)
|
|
835
|
+
@ctx.object_call(
|
|
836
|
+
service, handler, key, arg,
|
|
837
|
+
idempotency_key: idempotency_key, headers: headers,
|
|
838
|
+
input_serde: input_serde, output_serde: output_serde,
|
|
839
|
+
scope: @scope, limit_key: limit_key
|
|
840
|
+
)
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
# Fire-and-forget send to a Restate virtual object handler, within this scope.
|
|
844
|
+
def object_send(service, handler, key, arg, delay: nil, limit_key: nil,
|
|
845
|
+
idempotency_key: nil, headers: nil, input_serde: NOT_SET)
|
|
846
|
+
@ctx.object_send(
|
|
847
|
+
service, handler, key, arg,
|
|
848
|
+
delay: delay, idempotency_key: idempotency_key, headers: headers,
|
|
849
|
+
input_serde: input_serde, scope: @scope, limit_key: limit_key
|
|
850
|
+
)
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
# Durably calls a handler on a Restate workflow, within this scope.
|
|
854
|
+
def workflow_call(service, handler, key, arg, limit_key: nil, idempotency_key: nil,
|
|
855
|
+
headers: nil, input_serde: NOT_SET, output_serde: NOT_SET)
|
|
856
|
+
@ctx.workflow_call(
|
|
857
|
+
service, handler, key, arg,
|
|
858
|
+
idempotency_key: idempotency_key, headers: headers,
|
|
859
|
+
input_serde: input_serde, output_serde: output_serde,
|
|
860
|
+
scope: @scope, limit_key: limit_key
|
|
861
|
+
)
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
# Fire-and-forget send to a Restate workflow handler, within this scope.
|
|
865
|
+
def workflow_send(service, handler, key, arg, delay: nil, limit_key: nil,
|
|
866
|
+
idempotency_key: nil, headers: nil, input_serde: NOT_SET)
|
|
867
|
+
@ctx.workflow_send(
|
|
868
|
+
service, handler, key, arg,
|
|
869
|
+
delay: delay, idempotency_key: idempotency_key, headers: headers,
|
|
870
|
+
input_serde: input_serde, scope: @scope, limit_key: limit_key
|
|
871
|
+
)
|
|
872
|
+
end
|
|
873
|
+
end
|
|
740
874
|
end
|
|
741
875
|
end
|
data/lib/restate/service.rb
CHANGED
|
@@ -33,10 +33,13 @@ module Restate
|
|
|
33
33
|
#
|
|
34
34
|
# @example
|
|
35
35
|
# Greeter.call.greet("World").await
|
|
36
|
+
# Greeter.call(scope: "tenant1", limit_key: "tenant1/user42").greet("World").await
|
|
36
37
|
#
|
|
38
|
+
# @param scope [String, nil] optional scope to route the call within (see {Restate.scope})
|
|
39
|
+
# @param limit_key [String, nil] optional concurrency limit key within the scope
|
|
37
40
|
# @return [ServiceCallProxy]
|
|
38
|
-
def self.call
|
|
39
|
-
ServiceCallProxy.new(self, call_method: :service_call)
|
|
41
|
+
def self.call(scope: nil, limit_key: nil)
|
|
42
|
+
ServiceCallProxy.new(self, call_method: :service_call, scope: scope, limit_key: limit_key)
|
|
40
43
|
end
|
|
41
44
|
|
|
42
45
|
# Returns a send proxy for fluent fire-and-forget sends to this service.
|
|
@@ -44,11 +47,14 @@ module Restate
|
|
|
44
47
|
# @example
|
|
45
48
|
# Greeter.send!.greet("World")
|
|
46
49
|
# Greeter.send!(delay: 60).greet("World")
|
|
50
|
+
# Greeter.send!(scope: "tenant1", limit_key: "tenant1/user42").greet("World")
|
|
47
51
|
#
|
|
48
52
|
# @param delay [Numeric, nil] optional delay in seconds
|
|
53
|
+
# @param scope [String, nil] optional scope to route the send within (see {Restate.scope})
|
|
54
|
+
# @param limit_key [String, nil] optional concurrency limit key within the scope
|
|
49
55
|
# @return [ServiceSendProxy]
|
|
50
|
-
def self.send!(delay: nil)
|
|
51
|
-
ServiceSendProxy.new(self, send_method: :service_send, delay: delay)
|
|
56
|
+
def self.send!(delay: nil, scope: nil, limit_key: nil)
|
|
57
|
+
ServiceSendProxy.new(self, send_method: :service_send, delay: delay, scope: scope, limit_key: limit_key)
|
|
52
58
|
end
|
|
53
59
|
|
|
54
60
|
def self._service_kind
|
|
@@ -14,16 +14,23 @@ module Restate
|
|
|
14
14
|
# # Instead of: ctx.object_call(Counter, :add, "key", 5)
|
|
15
15
|
# Counter.call("key").add(5)
|
|
16
16
|
#
|
|
17
|
+
# # Scoped call (preview): route within a scope with an optional limit key
|
|
18
|
+
# Greeter.call(scope: "tenant1", limit_key: "tenant1/user42").greet("World")
|
|
19
|
+
#
|
|
17
20
|
# @!visibility private
|
|
18
21
|
class ServiceCallProxy
|
|
19
|
-
def initialize(service_class, key: nil, call_method: :service_call)
|
|
22
|
+
def initialize(service_class, key: nil, call_method: :service_call, scope: nil, limit_key: nil)
|
|
20
23
|
@service_class = service_class
|
|
21
24
|
@key = key
|
|
22
25
|
@call_method = call_method
|
|
26
|
+
@scope = scope
|
|
27
|
+
@limit_key = limit_key
|
|
23
28
|
end
|
|
24
29
|
|
|
25
30
|
def method_missing(handler_name, arg = nil, **opts)
|
|
26
31
|
ctx = Restate.fetch_context!
|
|
32
|
+
opts[:scope] = @scope unless @scope.nil? || opts.key?(:scope)
|
|
33
|
+
opts[:limit_key] = @limit_key unless @limit_key.nil? || opts.key?(:limit_key)
|
|
27
34
|
if @key
|
|
28
35
|
ctx.public_send(@call_method, @service_class, handler_name, @key, arg, **opts)
|
|
29
36
|
else
|
|
@@ -47,18 +54,25 @@ module Restate
|
|
|
47
54
|
# # Instead of: ctx.object_send(Counter, :add, "key", 5, delay: 60)
|
|
48
55
|
# Counter.send!("key", delay: 60).add(5)
|
|
49
56
|
#
|
|
57
|
+
# # Scoped send (preview): route within a scope with an optional limit key
|
|
58
|
+
# Greeter.send!(scope: "tenant1", limit_key: "tenant1/user42").greet("World")
|
|
59
|
+
#
|
|
50
60
|
# @!visibility private
|
|
51
61
|
class ServiceSendProxy
|
|
52
|
-
def initialize(service_class, key: nil, send_method: :service_send, delay: nil)
|
|
62
|
+
def initialize(service_class, key: nil, send_method: :service_send, delay: nil, scope: nil, limit_key: nil)
|
|
53
63
|
@service_class = service_class
|
|
54
64
|
@key = key
|
|
55
65
|
@send_method = send_method
|
|
56
66
|
@delay = delay
|
|
67
|
+
@scope = scope
|
|
68
|
+
@limit_key = limit_key
|
|
57
69
|
end
|
|
58
70
|
|
|
59
71
|
def method_missing(handler_name, arg = nil, **opts)
|
|
60
72
|
ctx = Restate.fetch_context!
|
|
61
73
|
opts[:delay] = @delay if @delay
|
|
74
|
+
opts[:scope] = @scope unless @scope.nil? || opts.key?(:scope)
|
|
75
|
+
opts[:limit_key] = @limit_key unless @limit_key.nil? || opts.key?(:limit_key)
|
|
62
76
|
if @key
|
|
63
77
|
ctx.public_send(@send_method, @service_class, handler_name, @key, arg, **opts)
|
|
64
78
|
else
|
data/lib/restate/version.rb
CHANGED
|
@@ -52,11 +52,14 @@ module Restate
|
|
|
52
52
|
#
|
|
53
53
|
# @example
|
|
54
54
|
# Counter.call("my-key").add(5).await
|
|
55
|
+
# Counter.call("my-key", scope: "tenant1", limit_key: "tenant1/user42").add(5).await
|
|
55
56
|
#
|
|
56
57
|
# @param key [String] the object key
|
|
58
|
+
# @param scope [String, nil] optional scope to route the call within (see {Restate.scope})
|
|
59
|
+
# @param limit_key [String, nil] optional concurrency limit key within the scope
|
|
57
60
|
# @return [ServiceCallProxy]
|
|
58
|
-
def self.call(key)
|
|
59
|
-
ServiceCallProxy.new(self, key: key, call_method: :object_call)
|
|
61
|
+
def self.call(key, scope: nil, limit_key: nil)
|
|
62
|
+
ServiceCallProxy.new(self, key: key, call_method: :object_call, scope: scope, limit_key: limit_key)
|
|
60
63
|
end
|
|
61
64
|
|
|
62
65
|
# Returns a send proxy for fluent fire-and-forget sends to this virtual object.
|
|
@@ -64,12 +67,15 @@ module Restate
|
|
|
64
67
|
# @example
|
|
65
68
|
# Counter.send!("my-key").add(5)
|
|
66
69
|
# Counter.send!("my-key", delay: 60).add(5)
|
|
70
|
+
# Counter.send!("my-key", scope: "tenant1", limit_key: "tenant1/user42").add(5)
|
|
67
71
|
#
|
|
68
72
|
# @param key [String] the object key
|
|
69
73
|
# @param delay [Numeric, nil] optional delay in seconds
|
|
74
|
+
# @param scope [String, nil] optional scope to route the send within (see {Restate.scope})
|
|
75
|
+
# @param limit_key [String, nil] optional concurrency limit key within the scope
|
|
70
76
|
# @return [ServiceSendProxy]
|
|
71
|
-
def self.send!(key, delay: nil)
|
|
72
|
-
ServiceSendProxy.new(self, key: key, send_method: :object_send, delay: delay)
|
|
77
|
+
def self.send!(key, delay: nil, scope: nil, limit_key: nil)
|
|
78
|
+
ServiceSendProxy.new(self, key: key, send_method: :object_send, delay: delay, scope: scope, limit_key: limit_key)
|
|
73
79
|
end
|
|
74
80
|
|
|
75
81
|
def self._service_kind
|
data/lib/restate/vm.rb
CHANGED
|
@@ -18,7 +18,8 @@ end
|
|
|
18
18
|
|
|
19
19
|
module Restate
|
|
20
20
|
# Ruby-side data types for VM results
|
|
21
|
-
Invocation = Struct.new(:invocation_id, :random_seed, :headers, :input_buffer, :key,
|
|
21
|
+
Invocation = Struct.new(:invocation_id, :random_seed, :headers, :input_buffer, :key,
|
|
22
|
+
:scope, :limit_key, :idempotency_key, keyword_init: true)
|
|
22
23
|
Failure = Struct.new(:code, :message, :stacktrace, :metadata, keyword_init: true)
|
|
23
24
|
|
|
24
25
|
class NotReady; end
|
|
@@ -116,7 +117,10 @@ module Restate
|
|
|
116
117
|
random_seed: inp.random_seed,
|
|
117
118
|
headers: headers,
|
|
118
119
|
input_buffer: inp.input.b,
|
|
119
|
-
key: inp.key
|
|
120
|
+
key: inp.key,
|
|
121
|
+
scope: inp.scope,
|
|
122
|
+
limit_key: inp.limit_key,
|
|
123
|
+
idempotency_key: inp.idempotency_key
|
|
120
124
|
)
|
|
121
125
|
end
|
|
122
126
|
|
|
@@ -145,16 +149,19 @@ module Restate
|
|
|
145
149
|
@vm.sys_sleep(millis, name)
|
|
146
150
|
end
|
|
147
151
|
|
|
148
|
-
def sys_call(service:, handler:, parameter:, key: nil, idempotency_key: nil, headers: nil
|
|
149
|
-
|
|
152
|
+
def sys_call(service:, handler:, parameter:, key: nil, idempotency_key: nil, headers: nil,
|
|
153
|
+
scope: nil, limit_key: nil)
|
|
154
|
+
# Rust side expects 8 args:
|
|
155
|
+
# (service, handler, buffer, key_or_nil, idem_key_or_nil, headers_or_nil, scope_or_nil, limit_key_or_nil)
|
|
150
156
|
hdr_array = headers&.map { |k, v| [k, v] }
|
|
151
|
-
@vm.sys_call(service, handler, parameter, key, idempotency_key, hdr_array)
|
|
157
|
+
@vm.sys_call(service, handler, parameter, key, idempotency_key, hdr_array, scope, limit_key)
|
|
152
158
|
end
|
|
153
159
|
|
|
154
|
-
def sys_send(service:, handler:, parameter:, key: nil, delay: nil, idempotency_key: nil, headers: nil
|
|
155
|
-
|
|
160
|
+
def sys_send(service:, handler:, parameter:, key: nil, delay: nil, idempotency_key: nil, headers: nil,
|
|
161
|
+
scope: nil, limit_key: nil)
|
|
162
|
+
# Rust side expects 9 args
|
|
156
163
|
hdr_array = headers&.map { |k, v| [k, v] }
|
|
157
|
-
@vm.sys_send(service, handler, parameter, key, delay, idempotency_key, hdr_array)
|
|
164
|
+
@vm.sys_send(service, handler, parameter, key, delay, idempotency_key, hdr_array, scope, limit_key)
|
|
158
165
|
end
|
|
159
166
|
|
|
160
167
|
def sys_run(name)
|
data/lib/restate/workflow.rb
CHANGED
|
@@ -52,11 +52,14 @@ module Restate
|
|
|
52
52
|
#
|
|
53
53
|
# @example
|
|
54
54
|
# UserSignup.call("user42").run("user@example.com").await
|
|
55
|
+
# UserSignup.call("user42", scope: "tenant1", limit_key: "tenant1/user42").run(email).await
|
|
55
56
|
#
|
|
56
57
|
# @param key [String] the workflow key
|
|
58
|
+
# @param scope [String, nil] optional scope to route the call within (see {Restate.scope})
|
|
59
|
+
# @param limit_key [String, nil] optional concurrency limit key within the scope
|
|
57
60
|
# @return [ServiceCallProxy]
|
|
58
|
-
def self.call(key)
|
|
59
|
-
ServiceCallProxy.new(self, key: key, call_method: :workflow_call)
|
|
61
|
+
def self.call(key, scope: nil, limit_key: nil)
|
|
62
|
+
ServiceCallProxy.new(self, key: key, call_method: :workflow_call, scope: scope, limit_key: limit_key)
|
|
60
63
|
end
|
|
61
64
|
|
|
62
65
|
# Returns a send proxy for fluent fire-and-forget sends to this workflow.
|
|
@@ -64,12 +67,19 @@ module Restate
|
|
|
64
67
|
# @example
|
|
65
68
|
# UserSignup.send!("user42").run("user@example.com")
|
|
66
69
|
# UserSignup.send!("user42", delay: 60).run("user@example.com")
|
|
70
|
+
# UserSignup.send!("user42", scope: "tenant1", limit_key: "tenant1/user42").run(email)
|
|
67
71
|
#
|
|
68
72
|
# @param key [String] the workflow key
|
|
69
73
|
# @param delay [Numeric, nil] optional delay in seconds
|
|
74
|
+
# @param scope [String, nil] optional scope to route the send within (see {Restate.scope})
|
|
75
|
+
# @param limit_key [String, nil] optional concurrency limit key within the scope
|
|
70
76
|
# @return [ServiceSendProxy]
|
|
71
|
-
def self.send!(key, delay: nil)
|
|
72
|
-
ServiceSendProxy.new(
|
|
77
|
+
def self.send!(key, delay: nil, scope: nil, limit_key: nil)
|
|
78
|
+
ServiceSendProxy.new(
|
|
79
|
+
self,
|
|
80
|
+
key: key, send_method: :workflow_send,
|
|
81
|
+
delay: delay, scope: scope, limit_key: limit_key
|
|
82
|
+
)
|
|
73
83
|
end
|
|
74
84
|
|
|
75
85
|
def self._service_kind
|
data/lib/restate.rb
CHANGED
|
@@ -219,15 +219,27 @@ module Restate # rubocop:disable Metrics/ModuleLength
|
|
|
219
219
|
end
|
|
220
220
|
|
|
221
221
|
# Durably call a handler using raw bytes (no serialization).
|
|
222
|
-
def generic_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
def generic_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil,
|
|
223
|
+
scope: nil, limit_key: nil)
|
|
224
|
+
fetch_context!.generic_call(service, handler, arg, key: key, idempotency_key: idempotency_key,
|
|
225
|
+
headers: headers, scope: scope, limit_key: limit_key)
|
|
225
226
|
end
|
|
226
227
|
|
|
227
228
|
# Fire-and-forget send using raw bytes (no serialization).
|
|
228
|
-
def generic_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil
|
|
229
|
+
def generic_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil,
|
|
230
|
+
scope: nil, limit_key: nil)
|
|
229
231
|
fetch_context!.generic_send(service, handler, arg, key: key, delay: delay,
|
|
230
|
-
idempotency_key: idempotency_key, headers: headers
|
|
232
|
+
idempotency_key: idempotency_key, headers: headers,
|
|
233
|
+
scope: scope, limit_key: limit_key)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Returns a +ScopedContext+ that routes all outgoing calls within the given scope.
|
|
237
|
+
# See {Restate::Context#scope} for the full semantics and constraints.
|
|
238
|
+
#
|
|
239
|
+
# @example
|
|
240
|
+
# Restate.scope("tenant1").service_call(Greeter, :greet, "World", limit_key: "tenant1/user42")
|
|
241
|
+
def scope(scope)
|
|
242
|
+
fetch_context!.scope(scope)
|
|
231
243
|
end
|
|
232
244
|
|
|
233
245
|
# ── Awakeables ──
|
data/sig/restate.rbs
CHANGED
|
@@ -35,8 +35,9 @@ module Restate
|
|
|
35
35
|
def self.object_send: (untyped service, (String | Symbol) handler, String key, untyped arg, ?delay: Numeric?, ?idempotency_key: String?, ?headers: Hash[String, String]?, ?input_serde: untyped) -> SendHandle
|
|
36
36
|
def self.workflow_call: (untyped service, (String | Symbol) handler, String key, untyped arg, ?idempotency_key: String?, ?headers: Hash[String, String]?, ?input_serde: untyped, ?output_serde: untyped) -> DurableCallFuture
|
|
37
37
|
def self.workflow_send: (untyped service, (String | Symbol) handler, String key, untyped arg, ?delay: Numeric?, ?idempotency_key: String?, ?headers: Hash[String, String]?, ?input_serde: untyped) -> SendHandle
|
|
38
|
-
def self.generic_call: (String service, String handler, String arg, ?key: String?, ?idempotency_key: String?, ?headers: Hash[String, String]?) -> DurableCallFuture
|
|
39
|
-
def self.generic_send: (String service, String handler, String arg, ?key: String?, ?delay: Numeric?, ?idempotency_key: String?, ?headers: Hash[String, String]?) -> SendHandle
|
|
38
|
+
def self.generic_call: (String service, String handler, String arg, ?key: String?, ?idempotency_key: String?, ?headers: Hash[String, String]?, ?scope: String?, ?limit_key: String?) -> DurableCallFuture
|
|
39
|
+
def self.generic_send: (String service, String handler, String arg, ?key: String?, ?delay: Numeric?, ?idempotency_key: String?, ?headers: Hash[String, String]?, ?scope: String?, ?limit_key: String?) -> SendHandle
|
|
40
|
+
def self.scope: (String scope) -> Server::ScopedContext
|
|
40
41
|
|
|
41
42
|
# ── Awakeables ──
|
|
42
43
|
|
|
@@ -197,11 +198,11 @@ module Restate
|
|
|
197
198
|
# ── Service proxies ──
|
|
198
199
|
|
|
199
200
|
class ServiceCallProxy
|
|
200
|
-
def initialize: (untyped service_class, ?key: String?, ?call_method: Symbol) -> void
|
|
201
|
+
def initialize: (untyped service_class, ?key: String?, ?call_method: Symbol, ?scope: String?, ?limit_key: String?) -> void
|
|
201
202
|
end
|
|
202
203
|
|
|
203
204
|
class ServiceSendProxy
|
|
204
|
-
def initialize: (untyped service_class, ?key: String?, ?send_method: Symbol, ?delay: Numeric?) -> void
|
|
205
|
+
def initialize: (untyped service_class, ?key: String?, ?send_method: Symbol, ?delay: Numeric?, ?scope: String?, ?limit_key: String?) -> void
|
|
205
206
|
end
|
|
206
207
|
|
|
207
208
|
# ── Service classes (declared but not checked — heavy metaprogramming) ──
|
|
@@ -225,6 +226,16 @@ module Restate
|
|
|
225
226
|
|
|
226
227
|
class Context
|
|
227
228
|
end
|
|
229
|
+
|
|
230
|
+
class ScopedContext
|
|
231
|
+
def initialize: (untyped ctx, String scope) -> void
|
|
232
|
+
def service_call: (untyped service, (String | Symbol) handler, untyped arg, ?key: String?, ?limit_key: String?, ?idempotency_key: String?, ?headers: Hash[String, String]?, ?input_serde: untyped, ?output_serde: untyped) -> DurableCallFuture
|
|
233
|
+
def service_send: (untyped service, (String | Symbol) handler, untyped arg, ?key: String?, ?delay: Numeric?, ?limit_key: String?, ?idempotency_key: String?, ?headers: Hash[String, String]?, ?input_serde: untyped) -> SendHandle
|
|
234
|
+
def object_call: (untyped service, (String | Symbol) handler, String key, untyped arg, ?limit_key: String?, ?idempotency_key: String?, ?headers: Hash[String, String]?, ?input_serde: untyped, ?output_serde: untyped) -> DurableCallFuture
|
|
235
|
+
def object_send: (untyped service, (String | Symbol) handler, String key, untyped arg, ?delay: Numeric?, ?limit_key: String?, ?idempotency_key: String?, ?headers: Hash[String, String]?, ?input_serde: untyped) -> SendHandle
|
|
236
|
+
def workflow_call: (untyped service, (String | Symbol) handler, String key, untyped arg, ?limit_key: String?, ?idempotency_key: String?, ?headers: Hash[String, String]?, ?input_serde: untyped, ?output_serde: untyped) -> DurableCallFuture
|
|
237
|
+
def workflow_send: (untyped service, (String | Symbol) handler, String key, untyped arg, ?delay: Numeric?, ?limit_key: String?, ?idempotency_key: String?, ?headers: Hash[String, String]?, ?input_serde: untyped) -> SendHandle
|
|
238
|
+
end
|
|
228
239
|
end
|
|
229
240
|
|
|
230
241
|
module JsonSerde
|
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: 1.
|
|
4
|
+
version: 1.2.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-07-
|
|
11
|
+
date: 2026-07-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: async
|