@@ -12,6 +12,7 @@ import dev.restate.client.Client
1212import dev.restate.client.RequestOptions
1313import dev.restate.client.Response
1414import dev.restate.client.ResponseHead
15+ import dev.restate.client.ScopedClient
1516import dev.restate.client.SendResponse
1617import dev.restate.common.InvocationOptions
1718import dev.restate.common.Output
@@ -272,51 +273,148 @@ val <Res> SendResponse<Res>.sendStatus: SendResponse.SendStatus
272273 get() = this .sendStatus()
273274
274275/* *
275- * **PREVIEW:** Returns a [ScopedKotlinClient] that routes all outgoing calls within the given
276- * scope.
276+ * **PREVIEW:** Create a proxy client for a Restate service within the given [scope][Client.scope].
277277 *
278- * **NOTE:** This API is in preview and is not enabled by default. To use it in restate-server 1.7,
279- * enable the flow control and protocol v7 experimental features, via
280- * `RESTATE_EXPERIMENTAL_ENABLE_PROTOCOL_V7=true` and `RESTATE_EXPERIMENTAL_ENABLE_VQUEUES=true`.
281- * These can be enabled only on **new clusters**, for more info check out
282- * https://docs.restate.dev/services/flow-control#enabling-flow-control. If these experimental
283- * features aren't enabled, the call fails with a retryable error and keeps retrying until they are.
278+ * Example usage:
279+ * ```kotlin
280+ * // Route a call into a named scope
281+ * client.scope("tenant-123").service<MyService>().process(payload)
282+ * ```
284283 *
285- * A scope is a sub-grouping of resources (invocations, virtual object instances, workflow
286- * instances, concurrency limits) within the Restate cluster. It becomes part of the target identity
287- * tuple:
288- * - `scope, service, handler, idempotencyKey?`
289- * - `scope, virtualObject, objectKey, handler, idempotencyKey?`
290- * - `scope, workflow, workflowKey, handler`
284+ * @param SVC the service class annotated with @Service
285+ * @return a proxy client to invoke the service
286+ * @see Client.scope
287+ */
288+ @org.jetbrains.annotations.ApiStatus .Experimental
289+ inline fun <reified SVC : Any > ScopedClient.service (): SVC {
290+ return service(this .innerClient, SVC ::class .java, this .scope)
291+ }
292+
293+ /* *
294+ * **PREVIEW:** Create a proxy client for a Restate virtual object within the given
295+ * [scope][Client.scope].
291296 *
292- * Under the hood, the scope contributes to the partition key, so all resources in a scope get
293- * co-located by the restate-server.
297+ * *NOTE:* To use scopes with virtual objects you must enable the additional experimental feature in
298+ * restate-server, via `RESTATE_EXPERIMENTAL_ENABLE_SCOPED_VIRTUAL_OBJECTS=true` .
294299 *
295- * Omitting the scope (i.e. using the regular `service` / `workflow` methods) is equivalent to
296- * calling with no scope, which is the existing behavior.
300+ * @param SVC the virtual object class annotated with @VirtualObject
301+ * @param key the key identifying the specific virtual object instance
302+ * @return a proxy client to invoke the virtual object
303+ * @see Client.scope
304+ */
305+ @org.jetbrains.annotations.ApiStatus .Experimental
306+ inline fun <reified SVC : Any > ScopedClient.virtualObject (key : String ): SVC {
307+ return virtualObject(this .innerClient, SVC ::class .java, key, this .scope)
308+ }
309+
310+ /* *
311+ * **PREVIEW:** Create a proxy client for a Restate workflow within the given [scope][Client.scope].
297312 *
298- * The scope key must consist only of `[a-zA-Z0-9_.-]` characters, with `1 <= length <= 36` chars.
313+ * @param SVC the workflow class annotated with @Workflow
314+ * @param key the key identifying the specific workflow instance
315+ * @return a proxy client to invoke the workflow
316+ * @see Client.scope
317+ */
318+ @org.jetbrains.annotations.ApiStatus .Experimental
319+ inline fun <reified SVC : Any > ScopedClient.workflow (key : String ): SVC {
320+ return workflow(this .innerClient, SVC ::class .java, key, this .scope)
321+ }
322+
323+ /* *
324+ * **PREVIEW:** Create a builder for invoking a Restate service within the given
325+ * [scope][Client.scope].
299326 *
300327 * Example usage:
301328 * ```kotlin
302- * // Route a call into a named scope
303- * client.scope("tenant-123").service<MyService>().process(payload)
329+ * client.scope("tenant-123").toService<MyService>()
330+ * .request { process(payload) }
331+ * .options { idempotencyKey = "req-1" }
332+ * .call()
304333 * ```
305334 *
335+ * @param SVC the service class annotated with @Service
336+ * @return a builder for creating typed requests
337+ * @see Client.scope
338+ */
339+ @org.jetbrains.annotations.ApiStatus .Experimental
340+ inline fun <reified SVC : Any > ScopedClient.toService (): KClientRequestBuilder <SVC > {
341+ ReflectionUtils .mustHaveServiceAnnotation(SVC ::class .java)
342+ require(ReflectionUtils .isKotlinClass(SVC ::class .java)) {
343+ " Using Java classes with Kotlin's API is not supported"
344+ }
345+ return KClientRequestBuilder (this .innerClient, SVC ::class .java, null , this .scope)
346+ }
347+
348+ /* *
349+ * **PREVIEW:** Create a builder for invoking a Restate virtual object within the given
350+ * [scope][Client.scope].
351+ *
352+ * *NOTE:* To use scopes with virtual objects you must enable the additional experimental feature in
353+ * restate-server, via `RESTATE_EXPERIMENTAL_ENABLE_SCOPED_VIRTUAL_OBJECTS=true`.
354+ *
355+ * @param SVC the virtual object class annotated with @VirtualObject
356+ * @param key the key identifying the specific virtual object instance
357+ * @return a builder for creating typed requests
358+ * @see Client.scope
359+ */
360+ @org.jetbrains.annotations.ApiStatus .Experimental
361+ inline fun <reified SVC : Any > ScopedClient.toVirtualObject (
362+ key : String
363+ ): KClientRequestBuilder <SVC > {
364+ ReflectionUtils .mustHaveVirtualObjectAnnotation(SVC ::class .java)
365+ require(ReflectionUtils .isKotlinClass(SVC ::class .java)) {
366+ " Using Java classes with Kotlin's API is not supported"
367+ }
368+ return KClientRequestBuilder (this .innerClient, SVC ::class .java, key, this .scope)
369+ }
370+
371+ /* *
372+ * **PREVIEW:** Create a builder for invoking a Restate workflow within the given
373+ * [scope][Client.scope].
374+ *
375+ * @param SVC the workflow class annotated with @Workflow
376+ * @param key the key identifying the specific workflow instance
377+ * @return a builder for creating typed requests
378+ * @see Client.scope
379+ */
380+ @org.jetbrains.annotations.ApiStatus .Experimental
381+ inline fun <reified SVC : Any > ScopedClient.toWorkflow (key : String ): KClientRequestBuilder <SVC > {
382+ ReflectionUtils .mustHaveWorkflowAnnotation(SVC ::class .java)
383+ require(ReflectionUtils .isKotlinClass(SVC ::class .java)) {
384+ " Using Java classes with Kotlin's API is not supported"
385+ }
386+ return KClientRequestBuilder (this .innerClient, SVC ::class .java, key, this .scope)
387+ }
388+
389+ /* *
390+ * **PREVIEW:** Returns a [ScopedKotlinClient] that routes all outgoing calls within the given
391+ * scope.
392+ *
393+ * This extension has the same signature as the [Client.scope] member on the Java `Client` interface
394+ * (which returns the platform [ScopedClient]), so it is shadowed by the member and is only
395+ * reachable via an aliased import. Prefer `client.scope(scopeKey)`, which returns [ScopedClient]
396+ * and now exposes the Kotlin `service`/`virtualObject`/`workflow`/`toService`/... extensions
397+ * directly.
398+ *
306399 * @param scopeKey the scope identifier
307- * @see <a
308- * href="https://docs.restate.dev/services/flow-control">https://docs.restate.dev/services/flow-control</a>
309400 */
401+ @Deprecated(
402+ " Use client.scope(scopeKey), which returns a ScopedClient exposing the Kotlin extension methods. " +
403+ " This extension is shadowed by the Client.scope member and only reachable via an aliased import."
404+ )
405+ @Suppress(" DEPRECATION" )
310406@org.jetbrains.annotations.ApiStatus .Experimental
311407fun Client.scope (scopeKey : String ): ScopedKotlinClient = ScopedKotlinClient (this , scopeKey)
312408
313409/* *
314410 * **PREVIEW:** A client for making RPC calls within a specific scope.
315411 *
316- * Obtain an instance via [Client.scope].
317- *
318- * @see Client.scope
412+ * Use the extension methods on the platform [ScopedClient] instead, obtained via
413+ * `client.scope(scopeKey)`.
319414 */
415+ @Deprecated(
416+ " Use the extension methods on ScopedClient, obtained via client.scope(scopeKey)." ,
417+ )
320418@org.jetbrains.annotations.ApiStatus .Experimental
321419class ScopedKotlinClient
322420@PublishedApi
@@ -673,7 +771,11 @@ private class KClientRequestImpl<Req, Res>(
673771 builder.block()
674772 return KClientRequestImpl (
675773 client,
676- this .toBuilder().headers(builder.headers).idempotencyKey(builder.idempotencyKey).build(),
774+ this .toBuilder()
775+ .headers(builder.headers)
776+ .idempotencyKey(builder.idempotencyKey)
777+ .limitKey(builder.limitKey)
778+ .build(),
677779 )
678780 }
679781
0 commit comments