diff --git a/hello-world/README.md b/hello-world/README.md index 036c26f..ed11810 100644 --- a/hello-world/README.md +++ b/hello-world/README.md @@ -1,37 +1,117 @@ -# Hello World - Getting Started with Render Workflows +# Hello World - Render Workflows (TypeScript) -The simplest possible workflow example to help you understand the basics of Render Workflows. +This hello-world example demonstrates three foundational workflow patterns: -## What you'll learn +- A minimal task definition (`calculateSquare`) +- A task that chains runs of another task (`sumSquares`) +- A task with custom retry behavior (`flipCoin`) -- **What is a task?** A function that can be executed as a workflow -- **What is a subtask?** A task called by another task using `await` -- **How to orchestrate:** Combining multiple tasks to create workflows +## What You'll Learn -## Workflow structure +- How to define tasks with `task(...)` +- How to chain task runs using `await` and `Promise.all` +- How to customize retry behavior with `retry` -``` -calculateAndProcess (multi-step orchestrator) - ├── addDoubledNumbers - │ ├── double (subtask #1) - │ └── double (subtask #2) - └── processNumbers - ├── double (subtask for item 1) - ├── double (subtask for item 2) - └── double (subtask for item N) -``` +## Example Tasks + +### `calculateSquare(a: number): number` + +The smallest possible task: takes one number and returns its square. + +### `sumSquares(a: number, b: number): Promise` -## Run locally +Chains two runs of `calculateSquare` and sums the results. -```bash -npm install -npm run build -npm start +It uses `Promise.all(...)` to chain the two runs in parallel: + +```ts +const [result1, result2] = await Promise.all([ + calculateSquare(a), + calculateSquare(b), +]); ``` -## Deploy to Render +### `flipCoin(): string` + +Simulates a coin flip: + +- Heads: Returns success +- Tails: Raises an error to trigger retry + +Retry policy in this example: + +- max retries: `3` +- wait duration: `1000ms` +- backoff scaling: `1.5` + +## Local Development + +### Prerequisites + +- Node.js 18+ + +### Run locally + +> Make sure you've installed the latest version of the [Render CLI](https://render.com/docs/cli). + +1. From this template's root, start the local task server: + + ```bash + npm install + render workflows dev -- npm start + ``` + +2. In a separate terminal, trigger task runs: + + ```bash + render workflows tasks start calculateSquare --local --input='[5]' + render workflows tasks start sumSquares --local --input='[3,4]' + render workflows tasks start flipCoin --local --input='[]' + ``` + +Expected behavior: + +- `calculateSquare` with `5` returns `25` +- `sumSquares` with `3,4` returns `25` +- `flipCoin` may fail and retry before succeeding + +## Deploying to Render + +Configure your Workflow service with: + +| Option | Value | +| --- | --- | +| Build command | `npm install` | +| Start command | `npm start` | + +## Key Concepts + +### Task registration + +Any call to `task({ name: ... }, handler)` registers a runnable workflow task. + +### Chaining runs + +Inside an async task, calling `await anotherTask(...)` chains a run of that task. + +### Retries + +Use the `retry` option in task config when transient failures should be retried automatically. + +## Troubleshooting + +### "Task not found" + +- Confirm the service is running +- Verify task names exactly match: `calculateSquare`, `sumSquares`, `flipCoin` + +### Import or dependency issues + +- Confirm dependency install completed from `package.json` +- Confirm Node.js version is 18+ -Create a new **Workflow** service on Render: +## Resources -- **Build command:** `npm install && npm run build` -- **Start command:** `npm start` +- [Render Workflows documentation](https://render.com/docs/workflows) +- [Workflows tutorial](https://render.com/docs/workflows-tutorial) +- [Local development guide](https://render.com/docs/workflows-local-development) diff --git a/hello-world/package.json b/hello-world/package.json index 9b3dd68..048e8f5 100644 --- a/hello-world/package.json +++ b/hello-world/package.json @@ -1,19 +1,15 @@ { "name": "hello-world-workflow", "version": "1.0.0", - "description": "Hello World - Getting Started with Render Workflows (TypeScript)", + "description": "Hello World - Render Workflows (TypeScript)", "type": "module", "scripts": { - "build": "tsc", - "start": "node dist/main.js", - "dev": "tsx src/main.ts" + "start": "tsx src/main.ts" }, "dependencies": { - "@renderinc/sdk": "latest", - "dotenv": "^16.4.7" + "@renderinc/sdk": "^0.5.0" }, "devDependencies": { - "tsx": "^4.19.0", - "typescript": "^5.7.0" + "tsx": "^4.20.2" } } diff --git a/hello-world/src/main.ts b/hello-world/src/main.ts index 01bedee..6c00007 100644 --- a/hello-world/src/main.ts +++ b/hello-world/src/main.ts @@ -1,89 +1,36 @@ -import "dotenv/config"; import { task } from "@renderinc/sdk/workflows"; -// Subtask: doubles a number -const double = task({ name: "double" }, function double(x: number): number { - console.log(`[TASK] Doubling ${x}`); - const result = x * 2; - console.log(`[TASK] Result: ${result}`); - return result; -}); - -// Subtask (also callable as root): doubles two numbers and sums them -const addDoubledNumbers = task( - { name: "addDoubledNumbers" }, - async function addDoubledNumbers(a: number, b: number) { - console.log(`[WORKFLOW] Starting: addDoubledNumbers(${a}, ${b})`); - - const doubledA = await double(a); - const doubledB = await double(b); - const total = doubledA + doubledB; - - const result = { - original_numbers: [a, b], - doubled_numbers: [doubledA, doubledB], - sum_of_doubled: total, - explanation: `${a} doubled is ${doubledA}, ${b} doubled is ${doubledB}, sum is ${total}`, - }; - - console.log("[WORKFLOW] Complete:", result); - return result; +const calculateSquare = task( + { name: "calculateSquare" }, + function calculateSquare(a: number): number { + return a * a; }, ); -// Subtask (also callable as root): doubles each number in a list -const processNumbers = task( - { name: "processNumbers" }, - async function processNumbers(...numbers: number[]) { - console.log(`[WORKFLOW] Starting: processNumbers(${numbers})`); - - const doubledResults: number[] = []; - - for (let i = 0; i < numbers.length; i++) { - console.log( - `[WORKFLOW] Processing item ${i + 1}/${numbers.length}: ${numbers[i]}`, - ); - const doubled = await double(numbers[i]); - doubledResults.push(doubled); - } - - const result = { - original_numbers: numbers, - doubled_numbers: doubledResults, - count: numbers.length, - explanation: `Processed ${numbers.length} numbers through the double subtask`, - }; - - console.log("[WORKFLOW] Complete:", result); - return result; +const sumSquares = task( + { name: "sumSquares" }, + async function sumSquares(a: number, b: number): Promise { + const [result1, result2] = await Promise.all([ + calculateSquare(a), + calculateSquare(b), + ]); + return result1 + result2; }, ); -// Root task: chains addDoubledNumbers and processNumbers task( - { name: "calculateAndProcess" }, - async function calculateAndProcess( - a: number, - b: number, - ...moreNumbers: number[] - ) { - console.log("[WORKFLOW] Starting multi-step workflow"); - - console.log("[WORKFLOW] Step 1: Adding doubled numbers"); - const step1Result = await addDoubledNumbers(a, b); - - console.log("[WORKFLOW] Step 2: Processing number list"); - const step2Result = await processNumbers(...moreNumbers); - - console.log("[WORKFLOW] Step 3: Combining results"); - const finalResult = { - step1_sum: step1Result.sum_of_doubled, - step2_doubled: step2Result.doubled_numbers, - total_operations: 2 + moreNumbers.length, - summary: `Added doubled ${a} and ${b}, then doubled ${moreNumbers.length} more numbers`, - }; - - console.log("[WORKFLOW] Multi-step workflow complete"); - return finalResult; + { + name: "flipCoin", + retry: { + maxRetries: 3, + waitDurationMs: 1000, + backoffScaling: 1.5, + }, + }, + function flipCoin(): string { + if (Math.random() < 0.5) { + throw new Error("Flipped tails! Retrying."); + } + return "Flipped heads!"; }, ); diff --git a/hello-world/template.yaml b/hello-world/template.yaml index 0093ad2..7a0254c 100644 --- a/hello-world/template.yaml +++ b/hello-world/template.yaml @@ -3,13 +3,13 @@ # after scaffolding. You can safely ignore it when using this example directly. name: Hello World -description: Simple task and subtask basics +description: Minimal Render Workflows starter template default: true workflowsRoot: . -renderBuildCommand: npm install && npm run build -renderStartCommand: node dist/main.js +renderBuildCommand: npm install +renderStartCommand: npm start nextSteps: - label: Enter your project directory @@ -18,8 +18,8 @@ nextSteps: command: render workflows dev -- {{startCommand}} hint: This runs your workflow service locally, allowing you to view and run tasks without deploying to Render. - label: Run a task locally (in another terminal) - command: "render workflows tasks start double --local --input='[5]'" - hint: If you see 10, your tasks are working! + command: "render workflows tasks start calculateSquare --local --input='[5]'" + hint: If you see 25, your tasks are working! - label: Deploy your workflow service to Render hint: | To deploy this workflow to Render, first push this code to a remote repository. Then, create a workflow service from your Render Dashboard, connect it to your repository, and provide the following build and start commands: