ctx) that provide Restate’s core capabilities.
These actions enable durable execution, state management, service communication, and timing control.
Durable steps
Userun to safely wrap any non-deterministic operation, like HTTP calls or database responses, and have Restate persist its result.
// External API call
const apiResult = await ctx.run("fetch-data", async () => {
const response = await fetch("https://api.example.com/data");
return response.json();
});
// Database operation
const dbResult = await ctx.run("update-user", () => {
return updateUserDatabase(userId, { name: "John" });
});
// Idempotency key generation
const id = ctx.rand.uuidv4();
// External API call
String apiResult =
Restate.run("fetchData", String.class, () -> fetchData("https://api.example.com/data"));
// Database operation
boolean dbResult =
Restate.run("updateUserDatabase", Boolean.class, () -> updateUserDatabase(userId, user));
// Idempotency key generation
String id = Restate.random().nextUUID().toString();
# External API call
api_result = await ctx.run_typed(
"fetch-data", fetch_url, url="https://api.example.com/data"
)
# Database operation
db_result = await ctx.run_typed(
"update-user", update_user_database, id=user_id, data={"name": "John"}
)
# Idempotency key generation
id = ctx.uuid()
// External API call
apiResult, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) {
return fetchData("https://api.example.com/data")
})
if err != nil {
return err
}
// Database operation
success, err := restate.Run(ctx, func(ctx restate.RunContext) (bool, error) {
return updateUserDatabase(userId, user)
})
if err != nil {
return err
}
// Idempotency key generation
id := restate.UUID(ctx).String()
run(), these operations would produce different results during replay, breaking deterministic recovery.
State management
Available in Virtual Object and Workflow handlers for persistent key-value storage.Get
Retrieve stored state by key.// Get with type and default value
const profile = await ctx.get<UserProfile>("profile");
const count = (await ctx.get<number>("count")) ?? 0;
const cart = (await ctx.get<ShoppingCart>("cart")) ?? [];
// Get with type and default value
UserProfile profile = Restate.state().get(PROFILE).orElse(null);
int count = Restate.state().get(COUNT).orElse(0);
ShoppingCart cart = Restate.state().get(CART).orElse(new ShoppingCart());
# Get with type and default value
profile = await ctx.get("profile", type_hint=UserProfile)
count = await ctx.get("count", type_hint=int) or 0
cart = await ctx.get("cart", type_hint=ShoppingCart) or ShoppingCart()
// Get with type and default value
profile, err := restate.Get[UserProfile](ctx, "profile")
if err != nil {
return err
}
count, err := restate.Get[int](ctx, "count")
if err != nil {
return err
}
cart, err := restate.Get[ShoppingCart](ctx, "cart")
if err != nil {
return err
}
Set
Store state that persists across function invocations.// Store simple values
ctx.set("lastLogin", request.date);
ctx.set("count", count + 1);
// Store complex objects
ctx.set("profile", {
name: "John Doe",
email: "[email protected]",
});
// Store simple values
Restate.state().set(COUNT, count + 1);
Restate.state().set(LAST_LOGIN, request.date());
// Store complex objects
Restate.state().set(PROFILE, new UserProfile("John Doe", "[email protected]"));
# Store simple values
ctx.set("lastLogin", request["date"])
ctx.set("count", count + 1)
# Store complex objects
ctx.set("profile", UserProfile(name="John Doe", email="[email protected]"))
// Store simple values
restate.Set(ctx, "lastLogin", request.Date)
restate.Set(ctx, "count", count+1)
// Store complex objects
restate.Set(ctx, "profile", UserProfile{
Name: "John Doe",
Email: "[email protected]",
})
Clear
State is retained indefinitely for Virtual Objects, or for the configured retention period for Workflows. To clear state:// Clear specific keys
ctx.clear("shoppingCart");
ctx.clear("sessionToken");
// Clear all user data
ctx.clearAll();
// Clear specific keys
Restate.state().clear(StateKey.of("shoppingCart", ShoppingCart.class));
Restate.state().clear(StateKey.of("sessionToken", String.class));
// Clear all user data
Restate.state().clearAll();
# Clear specific keys
ctx.clear("shoppingCart")
ctx.clear("sessionToken")
# Clear all user data
ctx.clear_all()
Service communication
Request-response calls
Make request-response calls to other services. Your function waits for the result.// Call another service
const validation = await ctx
.serviceClient(ValidationService)
.validateOrder(order);
// Call Virtual Object function
const profile = await ctx.objectClient(UserAccount, userId).getProfile();
// Submit Workflow
const result = await ctx
.workflowClient(OrderWorkflow, orderId)
.run(order);
// Call another service
var validation =
Restate.serviceHandle(ValidationService.class)
.call(ValidationService::validateOrder, req.order())
.await();
// Call Virtual Object function
var profile =
Restate.virtualObjectHandle(UserAccount.class, req.userId())
.call(UserAccount::getProfile)
.await();
// Submit Workflow
var result =
Restate.workflowHandle(OrderWorkflow.class, req.orderId())
.call(OrderWorkflow::run, req.order())
.await();
# Call another service
from .validation_service import validate_order
validation = await ctx.service_call(validate_order, order)
# Call Virtual Object function
from .user_account import get_profile
profile: UserProfile = await ctx.object_call(get_profile, key=user_id, arg=None)
# Submit Workflow
from .order_workflow import run
result = await ctx.workflow_call(run, key=order_id, arg=order)
// Call another service
validation, err := restate.Service[bool](ctx, "ValidationService", "ValidateOrder").Request(order)
if err != nil {
return err
}
// Call Virtual Object function
profile, err := restate.Object[UserProfile](ctx, "UserAccount", userId, "GetProfile").Request(restate.Void{})
if err != nil {
return err
}
// Submit Workflow
_, err = restate.Workflow[restate.Void](ctx, "OrderWorkflow", orderId, "Run").Request(order)
if err != nil {
return err
}
Sending messages
Make one-way calls that don’t return results. Your function continues immediately.// Fire-and-forget notification
ctx
.serviceSendClient(NotificationService)
.sendEmail({ userId, message: "Welcome!" });
// Background analytics
ctx
.serviceSendClient(AnalyticsService)
.recordEvent({ kind: "user_signup", userId });
// Cleanup task
ctx.objectSendClient(ShoppingCartObject, userId).emtpyExpiredCart();
// Fire-and-forget notification
Restate.serviceHandle(NotificationService.class)
.send(NotificationService::sendEmail, new EmailRequest(userId, "Welcome!"));
// Background analytics
Restate.serviceHandle(AnalyticsService.class).send(AnalyticsService::recordEvent, event);
// Cleanup task
Restate.virtualObjectHandle(ShoppingCartObject.class, userId)
.send(ShoppingCartObject::emptyExpiredCart);
# Fire-and-forget notification
from .notification_service import send_email
ctx.service_send(send_email, {"userId": user_id, "message": "Welcome!"})
# Background analytics
from .analytics_service import record_event
ctx.service_send(record_event, {"kind": "user_signup", "userId": user_id})
# Cleanup task
from .shopping_cart_object import empty_expired_cart
ctx.object_send(empty_expired_cart, key=user_id, arg=None)
// Fire-and-forget notification
restate.ServiceSend(ctx, "NotificationService", "SendEmail").Send(message)
// Background analytics
restate.ServiceSend(ctx, "AnalyticsService", "RecordEvent").Send(event)
// Cleanup task
restate.ObjectSend(ctx, "ShoppingCartObject", userId, "EmptyExpiredCart").Send(restate.Void{})
Delayed messages
Schedule handlers to run in the future.// Schedule reminder for tomorrow
ctx.serviceSendClient(ReminderService).sendReminder(
{ userId, message },
sendOpts({
delay: { days: 1 },
})
);
// Schedule reminder for tomorrow
Restate.serviceHandle(ReminderService.class)
.send(ReminderService::sendReminder, reminderRequest, Duration.ofDays(1));
# Schedule reminder for tomorrow
from .notification_service import send_reminder
ctx.service_send(
send_reminder,
{"userId": user_id, "message": message},
send_delay=timedelta(days=1),
)
// Schedule reminder for tomorrow
restate.ServiceSend(ctx, "ReminderService", "SendReminder").Send(
message,
restate.WithDelay(24*time.Hour),
)
Durable timers and timeouts
Pause function execution for a specific duration.// Sleep for specific duration
await ctx.sleep({ minutes: 5 }); // 5 minutes
// Wait for action or timeout
const result = await ctx
.workflowClient(OrderWorkflow, orderId)
.run(order)
.orTimeout({ minutes: 5 });
// Sleep for specific duration
Restate.sleep(Duration.ofMinutes(5)); // 5 minutes
// Wait for action or timeout
try {
Restate.workflowHandle(OrderWorkflow.class, req.orderId())
.call(OrderWorkflow::run, req.order())
.await(Duration.ofMinutes(5));
} catch (TimeoutException e) {
// Handle timeout
}
# Sleep for specific duration
await ctx.sleep(timedelta(minutes=5)) # 5 minutes
# Wait for action or timeout
from .order_workflow import run
match await restate.select(
result=ctx.workflow_call(run, key=order_id, arg=order),
timeout=ctx.sleep(timedelta(minutes=5)),
):
case ["result", result]:
return result
case _:
print("Order processing timed out")
// Sleep for specific duration
if err := restate.Sleep(ctx, 5*time.Minute); err != nil {
return err
}
// Wait for action or timeout
sleepFuture := restate.After(ctx, 5*time.Minute)
callFuture := restate.Workflow[restate.Void](ctx, "OrderWorkflow", orderId, "Run").RequestFuture(order)
fut, err := restate.WaitFirst(ctx, sleepFuture, callFuture)
if err != nil {
return err
}
switch fut {
case sleepFuture:
if err := sleepFuture.Done(); err != nil {
return err
}
// Timeout occurred
case callFuture:
if _, err := callFuture.Response(); err != nil {
return err
}
// Call completed
}
Workflow events
Use durable promises to wait for external events or human input in your workflows. Create promises that external systems can resolve to send data to your workflow.// Wait for external event
const paymentResult = await ctx.promise<PaymentResult>(
"payment-completed"
);
// Wait for human approval
const approved = await ctx.promise<boolean>("manager-approval");
// Wait for multiple events
const [payment, inventory] = await RestatePromise.all([
ctx.promise<PaymentResult>("payment").get(),
ctx.promise<InventoryResult>("inventory").get(),
]);
// Wait for external event
PaymentResult paymentResult = Restate.promise(PAYMENT_COMPLETED).future().await();
// Wait for human approval
Boolean approved = Restate.promise(MANAGER_APPROVAL).future().await();
// Wait for multiple events
var paymentFuture = Restate.promise(PAYMENT).future();
var inventoryFuture = Restate.promise(INVENTORY).future();
PaymentResult payment = paymentFuture.await();
InventoryResult inventory = inventoryFuture.await();
# Wait for external event
payment_result = await ctx.promise(
"payment-completed", type_hint=PaymentResult
).value()
# Wait for human approval
approved = await ctx.promise("manager-approval", type_hint=bool).value()
# Wait for multiple events using gather
payment_promise = ctx.promise("payment", type_hint=PaymentResult)
inventory_promise = ctx.promise("inventory", type_hint=InventoryConfirmation)
payment, inventory = await restate.gather(
payment_promise.value(), inventory_promise.value()
)
// Wait for external event
paymentResult, err := restate.Promise[PaymentResult](ctx, "payment-completed").Result()
if err != nil {
return err
}
// Wait for human approval
approved, err := restate.Promise[bool](ctx, "manager-approval").Result()
if err != nil {
return err
}
// Wait for multiple events
paymentFuture := restate.Promise[PaymentResult](ctx, "payment")
inventoryFuture := restate.Promise[InventoryResult](ctx, "inventory")
payment, err := paymentFuture.Result()
if err != nil {
return err
}
inventory, err := inventoryFuture.Result()
if err != nil {
return err
}
// In a signal function
confirmPayment: async (
ctx: WorkflowSharedContext,
result: PaymentResult
) => {
await ctx.promise("payment-completed").resolve(result);
},
// In a signal function
approveRequest: async (ctx: WorkflowSharedContext, approved: boolean) => {
await ctx.promise("manager-approval").resolve(approved);
},
// In a signal function
@Shared
public void confirmPayment(PaymentResult result) {
Restate.promiseHandle(PAYMENT_COMPLETED).resolve(result);
}
// In a signal function
@Shared
public void approveRequest(Boolean approved) {
Restate.promiseHandle(MANAGER_APPROVAL).resolve(approved);
}
# In a signal function
@workflow_example_workflow.handler()
async def confirm_payment(ctx: WorkflowSharedContext, result: PaymentResult) -> None:
await ctx.promise("payment-completed", type_hint=PaymentResult).resolve(result)
# In a signal function
@workflow_example_workflow.handler()
async def approve_request(ctx: WorkflowSharedContext, approved: bool) -> None:
await ctx.promise("manager-approval", type_hint=bool).resolve(approved)
// In a signal function
func (WorkflowExample) ConfirmPayment(ctx restate.WorkflowSharedContext, result PaymentResult) error {
if err := restate.Promise[PaymentResult](ctx, "payment-completed").Resolve(result); err != nil {
return err
}
return nil
}
// In a signal function
func (WorkflowExample) ApproveRequest(ctx restate.WorkflowSharedContext, approved bool) error {
if err := restate.Promise[bool](ctx, "manager-approval").Resolve(approved); err != nil {
return err
}
return nil
}
To implement a similar pattern in Basic Services or Virtual Objects, have a look at awakeables (TS/Java/Kotlin/Go/Python).