Skip to content

Latest commit

 

History

History
729 lines (562 loc) · 18 KB

File metadata and controls

729 lines (562 loc) · 18 KB

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

npm install @renderinc/sdk

Or with yarn:

yarn add @renderinc/sdk

Or with pnpm:

pnpm add @renderinc/sdk

Or with Bun:

bun add @renderinc/sdk

Quick Start

REST API Client

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

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:

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:

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:

render workflows dev -- <start command>

For example:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

// 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:

await startTaskServer();

Types

TaskRunStatus

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

TaskRun

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

TaskRunDetails

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

TaskRunResult

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

RegisterTaskOptions

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:

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:

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

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

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

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

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

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

npm run build

Testing

npm test

Linting

npm run lint

Formatting

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:

Contributing

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