diff --git a/code_snippets/ts/src/develop/awakeable.ts b/code_snippets/ts/src/develop/awakeable.ts index 444798ff..74e29a58 100644 --- a/code_snippets/ts/src/develop/awakeable.ts +++ b/code_snippets/ts/src/develop/awakeable.ts @@ -4,14 +4,20 @@ const service = restate.service({ name: "Awakeable", handlers: { greet: async (ctx: restate.Context, name: string) => { - // + // + // const awakeable = ctx.awakeable(); const awakeableId = awakeable.id + // + // await ctx.run(() => triggerTaskAndDeliverId(awakeableId)); + // + // const payload = await awakeable.promise; - // + // + // // ctx.resolveAwakeable(awakeableId, "hello"); diff --git a/code_snippets/ts/src/develop/my_service.ts b/code_snippets/ts/src/develop/my_service.ts index 57a7e768..79c85b03 100644 --- a/code_snippets/ts/src/develop/my_service.ts +++ b/code_snippets/ts/src/develop/my_service.ts @@ -1,15 +1,18 @@ import * as restate from "@restatedev/restate-sdk"; +import {Context} from "@restatedev/restate-sdk"; const myService = restate.service({ name: "MyService", handlers: { - myHandler: async (ctx: restate.Context, greeting: string) => { + myHandler: async (ctx: Context, greeting: string) => { return `${greeting}!`; }, } }) +// export const MyService: typeof myService = { name: "MyService" }; +// restate .endpoint() diff --git a/code_snippets/ts/src/develop/my_virtual_object.ts b/code_snippets/ts/src/develop/my_virtual_object.ts index 69a03ad5..ef9ca83b 100644 --- a/code_snippets/ts/src/develop/my_virtual_object.ts +++ b/code_snippets/ts/src/develop/my_virtual_object.ts @@ -1,15 +1,17 @@ import * as restate from "@restatedev/restate-sdk"; -import {handlers} from "@restatedev/restate-sdk"; +import {handlers, ObjectContext, ObjectSharedContext} from "@restatedev/restate-sdk"; const myVirtualObject = restate.object({ name: "MyVirtualObject", handlers: { - myHandler: async (ctx: restate.ObjectContext, greeting: string) => { + myHandler: async (ctx: ObjectContext, greeting: string) => { return `${greeting} ${ctx.key}!`; }, - myConcurrentHandler: handlers.object.shared(async (ctx: restate.ObjectSharedContext, greeting: string) => { - return `${greeting} ${ctx.key}!`; - }), + myConcurrentHandler: handlers.object.shared( + async (ctx: ObjectSharedContext, greeting: string) => { + return `${greeting} ${ctx.key}!`; + } + ), } }) diff --git a/code_snippets/ts/src/develop/service_communication.ts b/code_snippets/ts/src/develop/service_communication.ts index 05ffed62..5797f6c5 100644 --- a/code_snippets/ts/src/develop/service_communication.ts +++ b/code_snippets/ts/src/develop/service_communication.ts @@ -33,8 +33,8 @@ const service = restate.service({ // // - ctx.objectSendClient(MyVirtualObject, "Mary").myHandler("Hi!"); - ctx.objectSendClient(MyVirtualObject, "Mary").myHandler("Hi again!"); + ctx.objectSendClient(MyVirtualObject, "Mary").myHandler("I'm call A"); + ctx.objectSendClient(MyVirtualObject, "Mary").myHandler("I'm call B"); // }, callWorkflows: async (ctx: restate.Context, name: string) => { diff --git a/code_snippets/ts/src/develop/workflow.ts b/code_snippets/ts/src/develop/workflow.ts index 2d0bea32..d98598b6 100644 --- a/code_snippets/ts/src/develop/workflow.ts +++ b/code_snippets/ts/src/develop/workflow.ts @@ -11,18 +11,19 @@ // import * as restate from "@restatedev/restate-sdk"; +import {WorkflowContext, WorkflowSharedContext} from "@restatedev/restate-sdk"; const myWorkflow = restate.workflow({ name: "MyWorkflow", handlers: { - run: async (ctx: restate.WorkflowContext, req: string) => { + run: async (ctx: WorkflowContext, req: string) => { // implement workflow logic here return "success"; }, - interactWithWorkflow: async (ctx: restate.WorkflowSharedContext) => { + interactWithWorkflow: async (ctx: WorkflowSharedContext) => { // implement interaction logic here }, }, diff --git a/docs/develop/java/awakeables.mdx b/docs/develop/java/awakeables.mdx index 798129d6..400214a2 100644 --- a/docs/develop/java/awakeables.mdx +++ b/docs/develop/java/awakeables.mdx @@ -28,7 +28,7 @@ This pattern is also known as the callback (task token) pattern. 2. The handler **triggers a task/process** and attaches the awakeable ID (e.g. over Kafka, via HTTP,...). For example, send an HTTP request to a service that executes the task, and attach the ID to the payload. - You use `ctx.run` to avoid retriggering the task this on retries. + You use `ctx.run` to avoid re-triggering the task on retries. ```java CODE_LOAD::java/src/main/java/develop/Awakeables.java?2 @@ -59,7 +59,7 @@ This pattern is also known as the callback (task token) pattern. 2. The handler **triggers a task/process** and attaches the awakeable ID (e.g. over Kafka, via HTTP,...). For example, send an HTTP request to a service that executes the task, and attach the ID to the payload. - You use `ctx.runBlock` to avoid retriggering the task this on retries. + You use `ctx.runBlock` to avoid re-triggering the task on retries. ```kotlin CODE_LOAD::kotlin/src/main/kotlin/develop/Awakeables.kt?2 @@ -77,6 +77,7 @@ This pattern is also known as the callback (task token) pattern. + ## Completing awakeables The external process completes the awakeable by either resolving it with an optional payload or by rejecting it diff --git a/docs/develop/ts/awakeables.mdx b/docs/develop/ts/awakeables.mdx index 149152f5..3656c956 100644 --- a/docs/develop/ts/awakeables.mdx +++ b/docs/develop/ts/awakeables.mdx @@ -12,51 +12,74 @@ Awakeables pause an invocation while waiting for another process to complete a t You can use this pattern to let a handler execute a task somewhere else and retrieve the result. This pattern is also known as the callback (task token) pattern. - - - ```typescript my_handler.ts - CODE_LOAD::ts/src/develop/awakeable.ts#create - ``` - - - 1. [The handler **creates an awakeable**](focus://1,2). This contains a [String identifier](focus://2) and a Promise/Awaitable. - 2. [The handler **triggers a task/process** and attaches the awakeable ID](focus://4) (e.g. over Kafka, via HTTP,...). - For example, send an HTTP request to a service that executes the task, and attach the ID to the payload. - You use `ctx.run` to avoid retriggering the task this on retries. - 3. [The handler **waits** until the other process has executed the task.](focus://6) - - - 4. **The external process completes the awakeable** when the task is finished by: - - - ```shell resolve_curl.sh - curl localhost:8080/restate/awakeables/prom_1PePOqp/resolve -H 'content-type: application/json' - -d '{"hello": "world"}' - ``` - - ```shell reject_curl.sh - curl localhost:8080/restate/awakeables/prom_1PePOqp/reject -H 'content-type: text/plain' \ - -d 'Very bad error!' - ``` - - ```typescript external_process_resolve.ts - CODE_LOAD::ts/src/develop/awakeable.ts#resolve - ``` - - ```typescript external_proces_reject.ts - CODE_LOAD::ts/src/develop/awakeable.ts#reject - ``` - - - - **Resolving** the awakeable - - [Over HTTP](focus://resolve_curl.sh#1:2) with [its ID](focus://resolve_curl.sh#1[40:52]) and [an optional payload](focus://resolve_curl.sh#2) - - [Via the SDK with its ID and an optional payload](focus://external_process_resolve.ts#1) - - **Rejecting** the awakeable. This throws [a terminal error](/develop/java/error-handling) in the waiting handler. - - [Over HTTP](focus://reject_curl.sh#1:2) with [its ID](focus://reject_curl.sh#1[40:52]) and [a failure reason](focus://reject_curl.sh#2) - - [Via the SDK with its ID and a failure reason](focus://external_proces_reject.ts#1) - - 5. The handler **receives the payload and resumes**. +## Creating awakeables + + + + 1. The handler **creates an awakeable**. This contains a String identifier and a Promise. + ```ts + CODE_LOAD::ts/src/develop/awakeable.ts?1 + ``` + + --- + + 2. The handler **triggers a task/process** and attaches the awakeable ID (e.g. over Kafka, via HTTP,...). + For example, send an HTTP request to a service that executes the task, and attach the ID to the payload. + You use `ctx.run` to avoid re-triggering the task on retries. + + ```ts + CODE_LOAD::ts/src/develop/awakeable.ts?2 + ``` + + --- + + 3. The handler **waits** until the other process has executed the task. + The handler **receives the payload and resumes**. + + ```ts + CODE_LOAD::ts/src/develop/awakeable.ts?3 + ``` + + + +## Completing awakeables + +The external process completes the awakeable by either resolving it with an optional payload or by rejecting it +with its ID and a reason for the failure. This throws [a terminal error](/develop/java/error-handling) in the waiting handler. + + + - Resolving over HTTP with its ID and an optional payload: + + ```shell + curl localhost:8080/restate/awakeables/prom_1PePOqp/resolve + -H 'content-type: application/json' + -d '{"hello": "world"}' + ``` + + + - Rejecting over HTTP with its ID and a reason: + + ```shell + curl localhost:8080/restate/awakeables/prom_1PePOqp/reject + -H 'content-type: text/plain' \ + -d 'Very bad error!' + ``` + + + - Resolving via the SDK with its ID and an optional payload: + + ```ts + CODE_LOAD::ts/src/develop/awakeable.ts#resolve + ``` + + + - Rejecting via the SDK with its ID and a reason: + + ```ts + CODE_LOAD::ts/src/develop/awakeable.ts#reject + ``` + diff --git a/docs/develop/ts/overview.mdx b/docs/develop/ts/overview.mdx index 6038601c..e88845d7 100644 --- a/docs/develop/ts/overview.mdx +++ b/docs/develop/ts/overview.mdx @@ -21,69 +21,53 @@ Handlers can either be part of a [Service](/concepts/services#services-1), a [Vi ## Services [Services](/concepts/services#services-1) and their handlers are defined as follows: - - ```typescript CODE_LOAD::ts/src/develop/my_service.ts ``` - - -- [Create the service](focus://3,10) -- Specify [the service name.](focus://4) The service can then be called at `/MyService/myHandler`. -- [List the handlers.](focus://5:8) - [Each handler has a name (`myHandler`) and a function that implements the handler logic.](focus://6) +- Specify that you want to create a Service via _`restate.service`_. +- Specify the service name. The service can then be called at `/MyService/myHandler`. +- The service definition contains a list of handlers. + Each handler has a name (`myHandler`) and a function that implements the handler logic. The function has the Restate Context as its first argument. Within the handler, you use the `Context` to interact with Restate. The SDK stores the actions you do on the context in the Restate journal to make them durable. - The handler input parameters and return type are optional and can be of any type, as long as they can be serialized as a Buffer with _`Buffer.from(JSON.stringify(yourObject))`_ and deserialized with _`JSON.parse(result.toString()) as T`_. -- [Export the service definition](focus://12) so that it can be used by other handlers to call the service. (See [Service Communication docs](/develop/ts/service-communication).) -- [Create an endpoint](focus://14,15) and [bind the service(s)](focus://16) to the Restate endpoint. [Listen](focus://17) on the specified port (default `9080`) for connections and requests. - - +- Export the service definition `MyService` so that it can be used by other handlers to call the service. (See [Service Communication docs](/develop/ts/service-communication).) +- Finally, create an endpoint and bind the service(s) to the Restate endpoint. Listen on the specified port (default `9080`) for connections and requests. ## Virtual Objects [Virtual Objects](/concepts/services#virtual-objects) and their handlers are defined similarly to services, with the following differences: - - - ```typescript CODE_LOAD::ts/src/develop/my_virtual_object.ts ``` - - -- [Create the virtual object](focus://4) -- [The first argument of the handler must be the `ObjectContext` parameter](focus://7). +- Specify that you want to create a Virtual Object via _`restate.object`_. +- The first argument of each handler must be the `ObjectContext` parameter. Handlers with the `ObjectContext` parameter can write to the K/V state store. Only one handler can be active at a time, to ensure consistency. -- [If you want to have a handler that executes concurrently to the others and doesn't have write access to the K/V state, wrap the handler in `handlers.object.shared` and use the `ObjectSharedContext`.](focus://10) +- If you want to have a handler that executes concurrently to the others and doesn't have write access to the K/V state, wrap the handler in `handlers.object.shared` and use the `ObjectSharedContext`. For example, you can use these handlers to read K/V state, or interact with the blocking handler. - ## Workflows [Workflows](/concepts/services#workflows) are a special type of Virtual Objects, their definition is similar but with the following differences: - - - - ```typescript - CODE_LOAD::ts/src/develop/workflow.ts - ``` - - - - - [Create the workflow](focus://3,17) - - [Every workflow implementation needs to have a handler called `run` that implements the workflow logic. This handler uses the `WorkflowContext` to interact with the SDK. The `run` handler executes exactly one time per workflow execution/object.](focus://6:11) - - [The other handlers of the workflow are used to interact with the workflow: either query it, or signal it. They use the `WorkflowSharedContext` to interact with the SDK. These handlers can run concurrently with the `run` handler and can still be called after the `run` handler has finished.](focus://13:15) - - [Have a look at the workflow docs to learn more.](/develop/ts/workflows) - - +```typescript +CODE_LOAD::ts/src/develop/workflow.ts +``` +- Create the workflow with _`restate.workflow`_. +- Every workflow implementation needs to have a handler called `run` that implements the workflow logic. +This handler uses the `WorkflowContext` to interact with the SDK. +The `run` handler executes exactly one time per workflow execution/object. +- The other handlers of the workflow are used to interact with the workflow: either query it, or signal it. +They use the `WorkflowSharedContext` to interact with the SDK. +These handlers can run concurrently with the `run` handler and can still be called after the `run` handler has finished. +- [Have a look at the workflow docs to learn more.](/develop/ts/workflows) Now that you have a high-level idea of what a Restate service might look like, let's have a look at what the Restate Context allows you to do. diff --git a/docs/develop/ts/service-communication.mdx b/docs/develop/ts/service-communication.mdx index 220eb162..4d7973cb 100644 --- a/docs/develop/ts/service-communication.mdx +++ b/docs/develop/ts/service-communication.mdx @@ -12,13 +12,9 @@ A handler can call another handler and wait for the response (request-response), Make sure you export the service definition of the service you want to call, as we did in the [service example on the overview page](/develop/ts/overview#services): - - - ```typescript focus=12 - CODE_LOAD::ts/src/develop/my_service.ts - ``` - - +```typescript +CODE_LOAD::ts/src/develop/my_service.ts#api_export +``` Import this service definition in the service handler that wants to call it. @@ -149,19 +145,14 @@ To schedule a delayed call, send a message with a delay parameter, as follows: - Invocations to a Virtual Object are executed serially. Invocations will execute in the same order in which they arrive at Restate. For example, assume a handler calls the same Virtual Object twice: - - ```typescript - CODE_LOAD::ts/src/develop/service_communication.ts#ordering - ``` - - - It is guaranteed that [the invocation on line `1`](focus://1) will execute before [the invocation on line `2`](focus://2). - It is not guaranteed though that [invocation `2`](focus://2) will be executed immediately after [invocation `1`](focus://1), as invocations coming from other handlers/sources, could interleave these two calls. - + ```typescript + CODE_LOAD::ts/src/develop/service_communication.ts#ordering + ``` + It is guaranteed that call A will execute before call B. + It is not guaranteed though that call B will be executed immediately after call A, as invocations coming from other handlers/sources, could interleave these two calls. diff --git a/docs/develop/ts/serving.mdx b/docs/develop/ts/serving.mdx index 9b898fa3..9df6bad9 100644 --- a/docs/develop/ts/serving.mdx +++ b/docs/develop/ts/serving.mdx @@ -10,18 +10,14 @@ import Admonition from '@theme/Admonition'; Restate services can run in two ways: as an HTTP endpoint or as AWS Lambda functions. ## Creating an HTTP endpoint - +1. Create the endpoint +2. Bind one or multiple services to it. +3. Listen on the specified port (default `9080`) for connections and requests. + ```typescript CODE_LOAD::ts/src/develop/serving.ts#endpoint ``` - -1. [Create the endpoint](focus://1,2) -2. [Bind one or multiple services to it.](focus://3,4,5) -3. [Listen on the specified port (default `9080`) for connections and requests.](focus://6) - - -
Customizing the HTTP2 server @@ -42,7 +38,6 @@ CODE_LOAD::ts/src/develop/serving.ts#lambda Have a look at the [deployment section](/category/aws--aws-lambda) for guidance on how to deploy your services on AWS Lambda. - The implementation of your services and handlers remains the same for both deployment options. diff --git a/docs/develop/ts/state.mdx b/docs/develop/ts/state.mdx index c910ae2c..0e03a003 100644 --- a/docs/develop/ts/state.mdx +++ b/docs/develop/ts/state.mdx @@ -30,14 +30,13 @@ CODE_LOAD::ts/src/develop/state.ts#statekeys ``` ### Retrieving state - -Use `ctx.get` to retrieve the state for [a key](focus://1[40:53],2[40:53]): +Use `ctx.get` to retrieve the state for a key: + ```typescript CODE_LOAD::ts/src/develop/state.ts#get ``` -The return value is `null` if no value was stored. - +The return value is `null` if no value was stored. ### Setting state Use `ctx.set` to set a new value for a key: diff --git a/docs/invoke/http.mdx b/docs/invoke/http.mdx index e594b535..9747b319 100644 --- a/docs/invoke/http.mdx +++ b/docs/invoke/http.mdx @@ -91,15 +91,15 @@ You can **delay the message** by adding a delay request parameter in ISO8601 not ```shell humantime # focus[40:56] curl localhost:8080/MyService/myHandler/send?delay=10s \ - -H 'content-type: application/json' \ - -d '{"name": "Mary", "age": 25}' + -H 'content-type: application/json' \ + -d '{"name": "Mary", "age": 25}' ``` ```shell ISO8601 # focus[40:56] curl localhost:8080/MyService/myHandler/send?delay=PT10S \ - -H 'content-type: application/json' \ - -d '{"name": "Mary", "age": 25}' + -H 'content-type: application/json' \ + -d '{"name": "Mary", "age": 25}' ``` @@ -137,8 +137,8 @@ If you re-invoke the service with the same idempotency key within 24 hours, Rest You can tune the [retention time](focus://3) on a service-level by using the [Admin API](focus://1[15:28]) ([docs](/references/admin-api#tag/service/operation/modify_service)): ```shell curl -X PATCH localhost:9070/services/MyService \ - -H 'content-type: application/json' \ - -d '{"idempotency_retention": "2days"}' + -H 'content-type: application/json' \ + -d '{"idempotency_retention": "2days"}' ``` The [retention time](focus://3) is in [humantime format](https://docs.rs/humantime/latest/humantime/). diff --git a/docs/use-cases/event-processing.mdx b/docs/use-cases/event-processing.mdx index c7e60dd9..2facbbb0 100644 --- a/docs/use-cases/event-processing.mdx +++ b/docs/use-cases/event-processing.mdx @@ -60,7 +60,7 @@ Restate pushes the events to your function. Let Restate subscribe to a Kafka topic and specify to which function to push the events. Restate will take care of the event plumbing: polling for records, committing offsets, recovering... - + ```ts user_updates.ts CODE_LOAD::ts/src/use_cases/event_processing.ts?1