# Durable Function APIs

Durable functions are Rippling functions that receive a `DurableFunctionContext`. They let you split work into named, durable steps; pause execution; wait for external events; and fail runs with structured metadata.

## Function signature

```ts
import RipplingSDK, { DurableFunctionContext, FunctionEvent, FunctionResponse } from '@rippling/rippling-sdk';

export async function onRipplingEvent(
  event: FunctionEvent,
  context: DurableFunctionContext,
): Promise<FunctionResponse> {
  return new FunctionResponse({ statusCode: 200 });
}
```

Use a narrower event type when the function is tied to a known manifest surface:

```ts
import {
  DetailViewActionFunctionEvent,
  FunctionContext,
  FunctionResponse,
  SduiPageFunctionEvent,
} from '@rippling/rippling-sdk';

export async function onSduiPage(
  event: SduiPageFunctionEvent<{ action: 'init' | 'refresh' }>,
  context: FunctionContext,
): Promise<FunctionResponse> {
  const action = event.parameters.action;
  return new FunctionResponse({ body: { action } });
}

export async function onRecordAction(
  event: DetailViewActionFunctionEvent<{}, 'stuffed_animal__c', 'tester'>,
  context: FunctionContext,
): Promise<FunctionResponse> {
  return new FunctionResponse({ body: { recordId: event.record_id } });
}
```

## Context APIs

```ts
// context.step()
const queryResult = await context.step('run employee query', async () => {
  return await client.workflowActionExecutions.create({
    action_type: 'run_query',
    payload: {
      query: employeeQuery,
    },
    workflow_definition_id: event['workflow_definition_id'],
  });
});

// context.sleep()
await context.sleep('wait before retry', '1 hour');
await context.sleep('wait 5 seconds', 5000); // milliseconds

// context.sleepUntil()
await context.sleepUntil('wait until launch', new Date('2025-01-01T00:00:00Z'));
await context.sleepUntil('wait until timestamp', Date.parse('2025-01-01T00:00:00Z'));

// context.waitForEvent()
const approval = await context.waitForEvent<{ approved: boolean }>('wait for approval', {
  type: 'approval',
  timeout: '24 hours',
});

if (approval.isTimeout()) {
  context.fail('Approval timed out', {
    code: 'approval_timeout',
    details: approval,
  });
}

if (!approval.payload.approved) {
  context.fail('Approval was rejected', {
    code: 'approval_rejected',
    details: approval.payload,
  });
}
```

## `DurableFunctionContext`

`DurableFunctionContext` extends the standard `FunctionContext` with durable execution methods.

```ts
interface DurableFunctionContext extends FunctionContext {
  step<T>(name: string, fn: () => Promise<T> | T, opts?: unknown): Promise<T>;
  sleep(name: string, duration: DurableDuration): Promise<void>;
  sleepUntil(name: string, timestamp: DurableTimestamp): Promise<void>;
  waitForEvent<T = unknown>(
    name: string,
    opts: DurableWaitForEventOptions,
  ): Promise<DurableWaitForEventResult<T>>;
  fail(message: string, opts?: DurableFailureOptions): never;
}

interface DurableWaitForEventEventResult<T = unknown> {
  kind: 'event';
  waitName: string;
  eventType: string;
  payload: T;
}

interface DurableWaitForEventTimeoutResult {
  kind: 'timeout';
  waitName: string;
  eventType: string;
}

interface DurableWaitForEventResultHelpers<T = unknown> {
  isEvent(): this is DurableWaitForEventEventResult<T> & DurableWaitForEventResultHelpers<T>;
  isTimeout(): this is DurableWaitForEventTimeoutResult & DurableWaitForEventResultHelpers<T>;
  throwTimeout(this: DurableWaitForEventTimeoutResult & DurableWaitForEventResultHelpers<T>): never;
}

type DurableWaitForEventResult<T = unknown> =
  | (DurableWaitForEventEventResult<T> & DurableWaitForEventResultHelpers<T>)
  | (DurableWaitForEventTimeoutResult & DurableWaitForEventResultHelpers<T>);
```

The inherited `FunctionContext` includes environment, function metadata, and settings.

```ts
interface FunctionContext {
  env: {
    rippling_user_bearer_token: string | undefined;
  };
  function: {
    company_id: string;
    function_id: string;
    function_api_name: string;
    function_version_id: string;
    run_id: string;
    role_id?: string;
  };
  settings: Record<string, string | boolean | number>;
}
```

## Step API

Use `context.step()` for work that should be durably tracked. Each step should have a stable, descriptive name.

```ts
const initialQuery = event['initial_query'] as string;
const followUpQuery = event['follow_up_query'] as string;

await context.step('run initial query', async () => {
  await client.workflowActionExecutions.create({
    action_type: 'run_query',
    payload: {
      query: initialQuery,
    },
    workflow_definition_id: event['workflow_definition_id'],
  });
});

await context.step('run follow-up query', async () => {
  await client.workflowActionExecutions.create({
    action_type: 'run_query',
    payload: {
      query: followUpQuery,
    },
    workflow_definition_id: event['workflow_definition_id'],
  });
});
```

### Idempotency

Code inside `context.step()` may be replayed or retried by the durable runtime. Design side-effecting steps to be idempotent when possible.

```ts
await context.step('run employee query', async () => {
  await client.workflowActionExecutions.create({
    action_type: 'run_query',
    payload: {
      query: employeeQuery,
    },
    workflow_definition_id: event['workflow_definition_id'],
  });
});
```

## Sleep and scheduling

Use `context.sleep()` for relative delays and `context.sleepUntil()` for absolute wake-up times.

```ts
// Relative delays
await context.sleep('wait 15 minutes', '15 minutes');
await context.sleep('wait 1 day', '1 day');
await context.sleep('wait 5 seconds', 5000); // milliseconds

// Absolute times
await context.sleepUntil('wait until deadline', new Date('2025-12-31T23:59:59Z'));
await context.sleepUntil('wait until epoch timestamp', 1767225599000);
await context.sleepUntil('wait until ISO timestamp', '2025-12-31T23:59:59Z');
```

`DurableDuration` accepts a `number` or `string`. `DurableTimestamp` accepts a `number`, `Date`, or `string`.

## Waiting for external events

Use `context.waitForEvent()` when a function should pause until another system sends an event back to the run. The method returns a structured result with `kind: 'event'` when the event arrives, or `kind: 'timeout'` when the timeout expires.

```ts
type ApprovalEvent = {
  approved: boolean;
  reviewerId: string;
  reason?: string;
};

const approval = await context.waitForEvent<ApprovalEvent>('wait for manager approval', {
  type: 'manager_approval',
  timeout: '3 days',
});

if (approval.isTimeout()) {
  context.fail('Manager approval timed out', {
    code: 'approval_timeout',
    details: approval,
  });
}

if (!approval.payload.approved) {
  context.fail('Manager rejected the request', {
    code: 'approval_rejected',
    details: approval.payload,
  });
}
```

## Failing a durable function

Use `context.fail()` to stop the run with a human-readable message and optional structured failure metadata.

```ts
context.fail('The requested employee could not be found', {
  code: 'employee_not_found',
  details: {
    employeeId,
    runId: context.function.run_id,
  },
});
```

Because `fail()` returns `never`, TypeScript understands that code after it is unreachable.
