npm package: @renderinc/sdk
Description: Render SDK for TypeScript
Author: Render
Latest version: 0.5.1

# Render SDK for TypeScript

The official TypeScript SDK for Render. Define Workflow tasks, manage task runs, and access experimental platform features like object storage.

## Features

- **REST API Client**: Run, monitor, and manage task runs
- **Task Definition SDK**: Define and register tasks with decorators
- **Server-Sent Events**: Real-time streaming of task run events
- **Async/Await Support**: Modern Promise-based API
- **TypeScript First**: Full type safety and IntelliSense support
- **Retry Logic**: Configurable retry behavior for tasks
- **Subtask Execution**: Execute tasks from within other tasks
- **Object Storage**: Experimental object storage API with upload, download, and list

## Installation

```bash
npm install @renderinc/sdk
```

Or with yarn:

```bash
yarn add @renderinc/sdk
```

Or with pnpm:

```bash
pnpm add @renderinc/sdk
```

Or with Bun:

```bash
bun add @renderinc/sdk
```

## Quick Start

### REST API Client

Use the Render SDK to run tasks and monitor their execution:

```typescript
import { Render } from '@renderinc/sdk';

// Create a Render SDK instance (uses RENDER_API_KEY from environment)
const render = new Render();

// Run a task and wait for completion
const result = await render.workflows.runTask('my-workflow/my-task', [42, 'hello']);
console.log('Status:', result.status);
console.log('Results:', result.results);

// Or start a task and decide when to await the result
const run = await render.workflows.startTask('my-workflow/my-task', [42, 'hello']);
console.log('Task run ID:', run.taskRunId);
const details = await run.get();

// List recent task runs
const taskRuns = await render.workflows.listTaskRuns({ limit: 10 });
```

Alternatively, you can create a workflows client directly:

```typescript
import { createWorkflowsClient } from '@renderinc/sdk/workflows';

const client = createWorkflowsClient();
const result = await client.runTask('my-workflow/my-task', [42, 'hello']);
```

### Task Definition

Define tasks that can be executed by the workflow system:

```typescript
import { task, startTaskServer } from '@renderinc/sdk/workflows';

// Simple task
const square = task(
  { name: 'square' },
  function square(a: number): number {
    return a * a;
  }
);

// Async task with subtask execution
task(
  { name: 'addSquares' },
  async function addSquares(a: number, b: number): Promise<number> {
    const result1 = await square(a);
    const result2 = await square(b);
    return result1 + result2;
  }
);

// Task with custom options
task(
  {
    name: 'retryableTask',
    retry: {
      maxRetries: 3,
      waitDurationMs: 1000,
      backoffScaling: 1.5,
    },
    timeoutSeconds: 86400, // 24h
    plan: 'starter',
  },
  async function retryableTask(input: string): Promise<string> {
    // Task implementation
    return input.toUpperCase();
  }
);

// The task server starts automatically when running in a workflow environment
// (when RENDER_SDK_SOCKET_PATH is set). No need to call startTaskServer() explicitly.
//
// To disable auto-start, set RENDER_SDK_AUTO_START=false in your environment.
```

### Run the Local Task Server

For local development, use the Render CLI:

```bash
render workflows dev -- <start command>
```

For example:

```bash
render workflows dev -- npm start
```

To interact with tasks registered to the local task server, run CLI commands with the `--local` flag in another terminal. For example:

```bash
render workflows tasks start <task name> --local
```

## API Reference

### Render SDK

#### `new Render(options?)`

Creates a new Render SDK instance with access to all Render products.

**Options:**
- `token?: string` - API token (defaults to `RENDER_API_KEY` env var)
- `baseUrl?: string` - Base URL (defaults to `https://api.render.com`)
- `useLocalDev?: boolean` - Use local development mode
- `localDevUrl?: string` - Local development URL
- `ownerId?: string` - Default owner ID for object storage (falls back to `RENDER_WORKSPACE_ID` env var)
- `region?: string` - Default region for object storage (falls back to `RENDER_REGION` env var)

**Properties:**
- `workflows` - WorkflowsClient instance for managing workflow tasks
- `experimental` - ExperimentalClient instance for object storage and other experimental APIs

**Example:**
```typescript
import { Render } from '@renderinc/sdk';

const render = new Render({
  token: 'your-api-token',
  baseUrl: 'https://api.render.com',
});

// Access workflows client
const result = await render.workflows.runTask('my-workflow/task', [42]);
```

### Workflows Client API

The workflows client is accessible via `render.workflows` or can be created directly using `createWorkflowsClient`:

```typescript
import { createWorkflowsClient } from '@renderinc/sdk/workflows';

const client = createWorkflowsClient({
  token: 'your-api-token',
  baseUrl: 'https://api.render.com',
});
```

### Workflows Client Methods

#### `render.workflows.runTask(taskSlug, inputData, signal?)`

Runs a task and waits for completion.

**Parameters:**
- `taskSlug: string` - Task slug in format "workflow-slug/task-name"
- `inputData: any[]` - Input data as array of parameters
- `signal?: AbortSignal` - Optional abort signal for cancellation

**Returns:** `Promise<TaskRunDetails>`

**Example:**
```typescript
const render = new Render();
const result = await render.workflows.runTask('my-workflow/square', [5]);
console.log('Results:', result.results);
```

#### `render.workflows.startTask(taskSlug, inputData, signal?)`

Starts a task run and returns a `TaskRunResult`. Results are not streamed until you call `.get()` on the returned result. Use this when you need the task run ID, want to defer awaiting, or want fire-and-forget.

**Parameters:**
- `taskSlug: string` - Task slug in format "workflow-slug/task-name"
- `inputData: any[]` - Input data as array of parameters
- `signal?: AbortSignal` - Optional abort signal for cancellation

**Returns:** `Promise<TaskRunResult>`

**Example:**
```typescript
const render = new Render();

// Start a task and grab its ID
const run = await render.workflows.startTask('my-workflow/square', [5]);
console.log('Task run ID:', run.taskRunId);

// Await the result when you're ready
const result = await run.get();
console.log('Results:', result.results);
```

#### `render.workflows.taskRunEvents(taskRunIds, signal?)`

Streams task run events as an async iterable. Yields a `TaskRunDetails` for each terminal event (completed, failed, or canceled) received on the stream.

**Parameters:**
- `taskRunIds: string[]` - One or more task run IDs to subscribe to
- `signal?: AbortSignal` - Optional abort signal for cancellation

**Returns:** `AsyncGenerator<TaskRunDetails>`

**Example:**
```typescript
const render = new Render();

const run1 = await render.workflows.startTask('my-workflow/square', [3]);
const run2 = await render.workflows.startTask('my-workflow/square', [6]);

// The stream stays open until you break or abort.
const pending = new Set([run1.taskRunId, run2.taskRunId]);
for await (const event of render.workflows.taskRunEvents([...pending])) {
  console.log('Event:', event.status, event.id, event.results);
  pending.delete(event.id);
  if (pending.size === 0) break;
}
```

#### `render.workflows.getTaskRun(taskRunId)`

Gets task run details by ID.

**Parameters:**
- `taskRunId: string` - Task run ID

**Returns:** `Promise<TaskRunDetails>`

**Example:**
```typescript
const render = new Render();
const details = await render.workflows.getTaskRun('task-run-id');
```

#### `render.workflows.cancelTaskRun(taskRunId)`

Cancels a running task.

**Parameters:**
- `taskRunId: string` - Task run ID to cancel

**Returns:** `Promise<void>`

**Example:**
```typescript
const render = new Render();
const run = await render.workflows.startTask('my-workflow/square', [5]);
await render.workflows.cancelTaskRun(run.taskRunId);
```

#### `render.workflows.listTaskRuns(params)`

Lists task runs with optional filters.

**Parameters:**
- `params.limit?: number` - Maximum number of results
- `params.cursor?: string` - Pagination cursor
- `params.ownerId?: string[]` - Filter by owner IDs

**Returns:** `Promise<TaskRun[]>`

**Example:**
```typescript
const render = new Render();
const taskRuns = await render.workflows.listTaskRuns({ limit: 10 });
```

### Task API

#### `task(options, func)`

Registers a function as a task.

**Parameters:**
- `options: RegisterTaskOptions` - Task configuration
  - `name: string` - Task name (required)
  - `retry?: RetryOptions` - Optional retry configuration
    - `maxRetries: number` - Maximum number of retries
    - `waitDurationMs: number` - Wait duration between retries in milliseconds
    - `backoffScaling?: number` - Backoff multiplier (default: 1.5)
  - `timeoutSeconds?: number` - Maximum execution time in seconds
  - `plan?: string` - Resource plan for task execution (e.g., `"starter"`, `"standard"`, `"pro"`)
- `func: TaskFunction` - The task function to register

**Returns:** The registered function with the same signature

**Usage:**
```typescript
// Basic usage
const myTask = task(
  { name: 'myTask' },
  function myTask(arg: string): string {
    return arg.toUpperCase();
  }
);

// With retry, timeout, and plan options
task(
  {
    name: 'retryableTask',
    retry: {
      maxRetries: 3,
      waitDurationMs: 1000,
      backoffScaling: 1.5,
    },
    timeoutSeconds: 300,
    plan: 'starter',
  },
  function retryableTask(arg: string): string {
    return arg.toUpperCase();
  }
);

// Async task with subtasks
const square = task(
  { name: 'square' },
  function square(a: number): number {
    return a * a;
  }
);

task(
  { name: 'addSquares' },
  async function addSquares(a: number, b: number): Promise<number> {
    const result1 = await square(a);
    const result2 = await square(b);
    return result1 + result2;
  }
);
```

#### `startTaskServer()`

Starts the task server and listens for task execution requests.

**Returns:** `Promise<void>`

**Example:**
```typescript
await startTaskServer();
```

### Types

#### `TaskRunStatus`

```typescript
enum TaskRunStatus {
  PENDING = 'pending',
  RUNNING = 'running',
  COMPLETED = 'completed',
  FAILED = 'failed',
  CANCELED = 'canceled',
}
```

#### `TaskRun`

```typescript
interface TaskRun {
  id: string;
  taskId: string;
  status: TaskRunStatus;
  startedAt?: string;
  completedAt?: string;
  parentTaskRunId: string;
  rootTaskRunId: string;
  retries: number;
}
```

#### `TaskRunDetails`

```typescript
interface TaskRunDetails {
  id: string;
  taskId: string;
  status: TaskRunStatus;
  results?: any;
  error?: string;
  startedAt?: string;
  completedAt?: string;
}
```

#### `TaskRunResult`

```typescript
class TaskRunResult {
  readonly taskRunId: string;
  get(): Promise<TaskRunDetails>;
}
```

#### `RegisterTaskOptions`

```typescript
interface RegisterTaskOptions {
  name: string;
  retry?: {
    maxRetries: number;
    waitDurationMs: number;
    backoffScaling?: number; // default 1.5
  };
  timeoutSeconds?: number;
  plan?: string; // e.g., "starter", "standard", "pro"
}
```

### Error Handling

The SDK provides several error classes:

```typescript
import { Render } from '@renderinc/sdk';
import {
  RenderError,
  TaskRunError,
  ClientError,
  ServerError,
  AbortError,
} from '@renderinc/sdk';

const render = new Render();

try {
  const result = await render.workflows.runTask('my-workflow/task', [42]);
} catch (error) {
  if (error instanceof TaskRunError) {
    console.error('Task failed:', error.taskRunId, error.message);
  } else if (error instanceof ClientError) {
    console.error('Client error:', error.statusCode, error.cause);
  } else if (error instanceof ServerError) {
    console.error('Server error:', error.statusCode, error.cause);
  } else if (error instanceof AbortError) {
    console.error('Request was aborted');
  } else if (error instanceof RenderError) {
    console.error('General SDK error:', error.message);
  }
}
```

## Environment Variables

- `RENDER_API_KEY` - Your Render API key (required)
- `RENDER_WORKSPACE_ID` - Default owner ID for object storage (workspace team ID, e.g. `tea-xxxxx`)
- `RENDER_REGION` - Default region for object storage (e.g. `oregon`, `frankfurt`)
- `RENDER_USE_LOCAL_DEV` - Enable local development mode (`true`/`false`)
- `RENDER_LOCAL_DEV_URL` - Local development URL (default: `http://localhost:8120`)
- `RENDER_SDK_MODE` - Task execution mode (`run` or `register`)
- `RENDER_SDK_SOCKET_PATH` - Unix socket path for task communication

### Object Storage

When running on Render, `RENDER_WORKSPACE_ID` and `RENDER_REGION` are set automatically. You can also pass them as constructor options:

```typescript
import { Render } from '@renderinc/sdk';

const render = new Render();  // Uses env vars for auth + object storage defaults

// Upload (no need to pass ownerId/region when env vars are set)
await render.experimental.storage.objects.put({
  key: 'path/to/file.png',
  data: Buffer.from('binary content'),
  contentType: 'image/png',
});

// Download
const obj = await render.experimental.storage.objects.get({ key: 'path/to/file.png' });

// List
const response = await render.experimental.storage.objects.list();
```

## Examples

### Example 1: Running a Task

```typescript
import { Render } from '@renderinc/sdk';

const render = new Render();

const result = await render.workflows.runTask('my-workflow/square', [5]);
console.log('Square of 5 is:', result.results[0]); // 25
```

### Example 2: Defining Tasks with Subtasks

```typescript
import { task } from '@renderinc/sdk/workflows';

const square = task(
  { name: 'square' },
  function square(a: number): number {
    return a * a;
  }
);

task(
  { name: 'pythagorean' },
  async function pythagorean(a: number, b: number): Promise<number> {
    const aSquared = await square(a);
    const bSquared = await square(b);
    return Math.sqrt(aSquared + bSquared);
  }
);
```

### Example 3: Error Handling in Tasks

```typescript
import { task } from '@renderinc/sdk/workflows';

const divide = task(
  { name: 'divide' },
  async function divide(a: number, b: number): Promise<number> {
    if (b === 0) {
      throw new Error('Cannot divide by zero');
    }
    return a / b;
  }
);

task(
  {
    name: 'safeDivide',
    retry: {
      maxRetries: 3,
      waitDurationMs: 1000,
    },
  },
  async function safeDivide(a: number, b: number): Promise<number> {
    try {
      return await divide(a, b);
    } catch (error) {
      console.error('Division failed:', error);
      return 0; // Return default value
    }
  }
);
```

### Example 4: Using AbortSignal for Cancellation

```typescript
import { Render, AbortError } from '@renderinc/sdk';

const render = new Render();

async function runTaskWithCancellation() {
  const abortController = new AbortController();

  // Cancel the task after 5 seconds
  setTimeout(() => abortController.abort(), 5000);

  try {
    const result = await render.workflows.runTask(
      'my-workflow/long-running-task',
      [42],
      abortController.signal
    );
    console.log('Task completed:', result.results);
  } catch (error) {
    if (error instanceof AbortError) {
      console.log('Task was cancelled');
    } else {
      console.error('Task failed:', error);
    }
  }
}

runTaskWithCancellation();
```

### Example 5: Using the Unified Render SDK

```typescript
import { Render } from '@renderinc/sdk';

// Single entry point for all Render products
const render = new Render({
  token: process.env.RENDER_API_KEY,
});

async function workflowExample() {
  try {
    // Run a workflow task
    const result = await render.workflows.runTask('my-workflow/process-data', [
      { userId: 123, data: 'example' },
    ]);

    console.log('Workflow completed:', result.status);
    console.log('Results:', result.results);

    // List and monitor recent task runs
    const recentRuns = await render.workflows.listTaskRuns({ limit: 5 });
    console.log(`\nRecent task runs: ${recentRuns.length}`);

    for (const run of recentRuns) {
      console.log(`- ${run.id}: ${run.status} (${run.taskId})`);
    }
  } catch (error) {
    console.error('Error:', error);
  }
}

workflowExample();
```

## Development

### Building

```bash
npm run build
```

### Testing

```bash
npm test
```

### Linting

```bash
npm run lint
```

### Formatting

```bash
npm run format
```

## Project Structure

```
typescript/
├── src/
│   ├── render.ts              # Main Render SDK class
│   ├── errors.ts              # Error classes
│   ├── index.ts               # Main exports
│   ├── version.ts             # SDK version and user-agent
│   ├── workflows/             # Workflows functionality
│   │   ├── task.ts            # task() function
│   │   ├── runner.ts          # startTaskServer() and run()
│   │   ├── executor.ts        # TaskExecutor
│   │   ├── registry.ts        # TaskRegistry
│   │   ├── uds.ts             # Unix socket client
│   │   ├── types.ts           # Type definitions
│   │   ├── client/            # REST API client
│   │   │   ├── client.ts      # WorkflowsClient class
│   │   │   ├── create-client.ts # createWorkflowsClient() factory
│   │   │   ├── task-run-result.ts # TaskRunResult class
│   │   │   ├── sse.ts         # SSE event types
│   │   │   ├── types.ts       # Client type definitions
│   │   │   └── index.ts       # Exports
│   │   └── index.ts           # Workflows exports
│   ├── experimental/          # Experimental features
│   │   └── object/            # Object storage API
│   └── utils/                 # Shared utilities
├── examples/
│   ├── client/                # Client example
│   │   ├── main.ts
│   │   └── package.json
│   └── task/                  # Task example
│       ├── main.ts
│       └── package.json
├── package.json
├── tsconfig.json
└── README.md
```

## License

MIT

## Support

For issues and questions, please visit:
- GitHub Issues: https://github.com/renderinc/workflow-sdk/issues
- Documentation: https://render.com/docs/workflows

## Contributing

Contributions are welcome! Please read our contributing guidelines before submitting pull requests.