diff --git a/frontend/docs/pages/home/go-sdk/_meta.json b/frontend/docs/pages/home/go-sdk/_meta.json index 34b88f5c3..ee923adbe 100644 --- a/frontend/docs/pages/home/go-sdk/_meta.json +++ b/frontend/docs/pages/home/go-sdk/_meta.json @@ -1,7 +1,7 @@ { - "setup": "Setup", - "creating-a-worker": "Creating a Worker", - "creating-a-workflow": "Creating a Workflow", - "pushing-events": "Pushing Events", - "scheduling-workflows": "Scheduling Workflows" -} \ No newline at end of file + "setup": "Setup", + "creating-a-workflow": "Creating a Workflow", + "creating-a-worker": "Creating a Worker", + "pushing-events": "Pushing Events", + "scheduling-workflows": "Scheduling Workflows" +} diff --git a/frontend/docs/pages/home/python-sdk/_meta.json b/frontend/docs/pages/home/python-sdk/_meta.json index c1988f0a5..04be9ab47 100644 --- a/frontend/docs/pages/home/python-sdk/_meta.json +++ b/frontend/docs/pages/home/python-sdk/_meta.json @@ -1,6 +1,6 @@ { - "setup": "Setup", - "creating-a-worker": "Creating a Worker", - "creating-a-workflow": "Creating a Workflow", - "pushing-events": "Pushing Events" -} \ No newline at end of file + "setup": "Setup", + "creating-a-workflow": "Creating a Workflow", + "creating-a-worker": "Creating a Worker", + "pushing-events": "Pushing Events" +} diff --git a/frontend/docs/pages/home/typescript-sdk/_meta.json b/frontend/docs/pages/home/typescript-sdk/_meta.json index c1988f0a5..04be9ab47 100644 --- a/frontend/docs/pages/home/typescript-sdk/_meta.json +++ b/frontend/docs/pages/home/typescript-sdk/_meta.json @@ -1,6 +1,6 @@ { - "setup": "Setup", - "creating-a-worker": "Creating a Worker", - "creating-a-workflow": "Creating a Workflow", - "pushing-events": "Pushing Events" -} \ No newline at end of file + "setup": "Setup", + "creating-a-workflow": "Creating a Workflow", + "creating-a-worker": "Creating a Worker", + "pushing-events": "Pushing Events" +} diff --git a/frontend/docs/pages/home/typescript-sdk/creating-a-worker.mdx b/frontend/docs/pages/home/typescript-sdk/creating-a-worker.mdx index 778bfa7ba..c6f2828f6 100644 --- a/frontend/docs/pages/home/typescript-sdk/creating-a-worker.mdx +++ b/frontend/docs/pages/home/typescript-sdk/creating-a-worker.mdx @@ -6,30 +6,21 @@ It will automatically read in any `HATCHET_CLIENT` environment variables, which For example: ```ts -import Hatchet from "@hatchet/sdk"; -import { Workflow } from "@hatchet/sdk"; +import Hatchet, { Workflow } from "@hatchet-dev/typescript-sdk"; const hatchet = Hatchet.init(); // workflow code... -hatchet.run(workflow); -``` - -## Advanced Usage - -The `hatchet.run(workflow)` method will return a reference to the `Worker` object. - -You could then stop the worker: +async function main() { + const worker = await hatchet.worker("example-worker"); + await worker.registerWorkflow(workflow); + worker.start(); +} -```ts -const worker = hatchet.run(workflow); -worker.stop(); +main(); ``` -Alternatively, you can create a worker object with `hatchet.worker()` and manually start the service. +## Options -```ts -const worker = hatchet.worker(workflow); -worker.start(); -``` +The `hatchet.worker()` method takes a simple name parameter which can be used to identify the worker on the Hatchet dashboard. diff --git a/frontend/docs/pages/home/typescript-sdk/creating-a-workflow.mdx b/frontend/docs/pages/home/typescript-sdk/creating-a-workflow.mdx index f365bf7dc..982f5458b 100644 --- a/frontend/docs/pages/home/typescript-sdk/creating-a-workflow.mdx +++ b/frontend/docs/pages/home/typescript-sdk/creating-a-workflow.mdx @@ -4,8 +4,7 @@ To create a workflow, simply create a new `Workflow` object. For example, a simple 2-step workflow would look like: ```ts -import Hatchet from "@hatchet/sdk"; -import { Workflow } from "@hatchet/workflow"; +import Hatchet, { Workflow } from "@hatchet-dev/typescript-sdk"; const hatchet = Hatchet.init(); @@ -18,7 +17,7 @@ const workflow: Workflow = { steps: [ { name: "step1", - run: (input, ctx) => { + run: (ctx) => { console.log("executed step1!"); return { step1: "step1" }; }, @@ -26,7 +25,7 @@ const workflow: Workflow = { { name: "step2", parents: ["step1"] - run: (input, ctx) => { + run: (ctx) => { console.log("executed step2!"); return { step2: "step2" }; }, @@ -35,19 +34,14 @@ const workflow: Workflow = { }; ``` -You'll notice that the workflow defines a workflow trigger (in this case, `on_events`), and the workflow definition. The workflow definition includes a series of steps which is simply an array of `Step` objects. Each step has a `run` prop which is a function that takes an `input` and a `context`. The `context` argument is a `Context` object, which contains information about the workflow, such as the input data and the output data of previous steps. +You'll notice that the workflow defines a workflow trigger (in this case, `on_events`), and the workflow definition. The workflow definition includes a series of steps which is simply an array of `Step` objects. +Each step has a `run` prop which is a function that with a `context` augment. The `context` argument is a `Context` object, which contains information about the workflow, such as the input data and the output data of previous steps. To create multi-step workflows, you can use `parents` to define the steps which the current step depends on. In the example, `step2` will not invoke until after `step1` completes. -## Getting Access to the Input Data +## Getting Access to the Workflow Input Data -You can get access to the workflow's input data simply by accessing the `input` parameter. You can . For example, given the following event: - -```json -{ - "name": "John" -} -``` +You can get access to the workflow's input data simply by calling `ctx.workflowInput()`. Here's an example `Step` which accesses the workflow input: @@ -55,17 +49,25 @@ Here's an example `Step` which accesses the workflow input: const stepPrintsInput: Step = { name: "step2", parents: ["step1"], - run: (input, ctx) => { - console.log("executed step2!", input["name"]); + run: (ctx) => { + console.log("executed step2!", ctx.workflowInput["name"]); }, }; ``` +Given the following event: + +```json +{ + "name": "John" +} +``` + The console will log: -```` +``` executed step2! John -`` +``` ## Step Outputs @@ -74,46 +76,166 @@ Step outputs should be a of type `Record`, should be `JSON` seriali ```ts const stepReturnsData: Step = { name: "step2", - run: (input, ctx) => { + run: (ctx) => { return { awesome: "data" }; }, }; -```` +``` + +Future steps can access this output through the context (`ctx`) parameter `ctx.stepOutput("")`. In this example, a future step could access this data via `context.stepOutput("step2")`: + +```ts +const futureStep: Step = { + name: "step3", + run: (ctx) => { + const uppercaseStep2 = ctx.stepOutput("step2")["awesome"].toUpperCase(); + return { uppercase: uppercaseStep2 }; + }, +}; +``` -Future steps can access this output through the context (`ctx`) parameter `ctx.stepOutput("")`. In this example, a future step could access this data via `context.stepOutput("step2")`. Remember, a step that depends on previous step data should include this dependency in the `parents` array. +Remember, a step that depends on previous step data should include this dependency in the `parents` array. ## Cron Schedules You can declare a cron schedule by defining `on_crons` in the `Workflow` object. For example, to trigger a workflow every 5 minutes, you can do the following: -```go -import Hatchet from '@hatchet/sdk'; -import { Workflow } from '@hatchet/workflow'; +```ts +import Hatchet from "@hatchet-dev/typescript-sdk"; +import { Workflow } from "@hatchet/workflow"; const hatchet = Hatchet.init(); const workflow: Workflow = { - id: 'example', - description: 'test', + id: "example", + description: "test", on: { cron: "*/5 * * * *", }, steps: [ { - name: 'step1', + name: "step1", run: (input, ctx) => { - console.log('executed step1!'); - return { step1: 'step1' }; + console.log("executed step1!"); + return { step1: "step1" }; }, }, { - name: 'step2', - parents: ['step1'], + name: "step2", + parents: ["step1"], run: (input, ctx) => { - console.log('executed step2!', input); - return { step2: 'step2' }; + console.log("executed step2!", input); + return { step2: "step2" }; }, }, ], }; ``` + +## Concurrency Limits and Fairness + +> \***\*Note:** this feature is currently in beta, and currently only supports a concurrency strategy which terminates the oldest running workflow run to make room for the new one. This will be expanded in the future to support other strategies.\*\* + +By default, there are no concurrency limits for Hatchet workflows. Workflow runs are immediately executed as soon as they are triggered (by an event, cron, or schedule). However, you can enforce a concurrency limit by adding a `concurrency` configuration to your workflow declaration. This configuration includes a key which takes a function that returns a **concurrency group key**, which is a string that is used to group concurrent executions. **Note that this function should not also be used as a `hatchet.step`.** For example, the following workflow will only allow 5 concurrent executions for any workflow execution of `ConcurrencyDemoWorkflow`, since the key is statically set to `concurrency-key`: + +```ts +const workflow: Workflow = { + id: "concurrency-example", + description: "test", + on: { + event: "concurrency:create", + }, + concurrency: { + name: "basic-concurrency", + key: (ctx) => "concurrency-key", + }, + steps: [ + { + name: "step1", + run: async (ctx) => { + const { data } = ctx.workflowInput(); + const { signal } = ctx.controller; + + if (signal.aborted) throw new Error("step1 was aborted"); + + console.log("starting step1 and waiting 5 seconds...", data); + await sleep(5000); + + if (signal.aborted) throw new Error("step1 was aborted"); + + // NOTE: the AbortController signal can be passed to many http libraries to cancel active requests + // fetch(url, { signal }) + // axios.get(url, { signal }) + + console.log("executed step1!"); + return { step1: `step1 results for ${data}!` }; + }, + }, + { + name: "step2", + parents: ["step1"], + run: (ctx) => { + console.log( + "executed step2 after step1 returned ", + ctx.stepOutput("step1") + ); + return { step2: "step2 results!" }; + }, + }, + ], +}; +``` + +### Cancellation Signalling + +When a concurrent workflow is already running, Hatchet will send a cancellation signal to the step via it's context. For now, you must handle this signal to exit the step at a logical point: + +```ts +{ + name: "step1", + run: async (ctx) => { + const { data } = ctx.workflowInput(); + const { signal } = ctx.controller; + + if (signal.aborted) throw new Error("step1 was aborted"); + + console.log("starting step1 and waiting 5 seconds...", data); + await sleep(5000); + + if (signal.aborted) throw new Error("step1 was aborted"); + + // NOTE: the AbortController signal can be passed to many http libraries to cancel active requests + // fetch(url, { signal }) + // axios.get(url, { signal }) + + console.log("executed step1!"); + return { step1: `step1 results for ${data}!` }; + }, + }, +``` + +### Use-Case: Enforcing Per-User Concurrency Limits + +You can use the custom concurrency function to enforce per-user concurrency limits. For example, the following workflow will only allow 1 concurrent execution per user: + +```py +const workflow: Workflow = { + id: "concurrency-example", + description: "test", + on: { + event: "concurrency:create", + }, + concurrency: { + name: "basic-concurrency", + maxRuns: 1, + key: (ctx) => ctx.workflowInput().userId, + }, + // Rest of the workflow configuration +} +``` + +This same approach can be used for: + +- Setting concurrency for a specific user session by `session_id` (i.e. multiple chat messages sent) +- Limiting data or document ingestion by setting an input hash or on-file key. +- Rudimentary fairness rules by limiting groups per tenant to a certain number of concurrent executions. diff --git a/frontend/docs/pages/home/typescript-sdk/pushing-events.mdx b/frontend/docs/pages/home/typescript-sdk/pushing-events.mdx index 99dc82829..ee7620d3b 100644 --- a/frontend/docs/pages/home/typescript-sdk/pushing-events.mdx +++ b/frontend/docs/pages/home/typescript-sdk/pushing-events.mdx @@ -3,7 +3,7 @@ Events can be pushed via the client's `hatchet.event.push()` method: ```ts -import Hatchet from "@hatchet/sdk"; +import Hatchet from "@hatchet-dev/typescript-sdk"; const hatchet = Hatchet.init();