> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deep.space/llms.txt
> Use this file to discover all available pages before exploring further.

# Worker cron reference

> `CronRoom`, `CronTask`, and `buildCronContext`.

```ts theme={null}
import { CronRoom, buildCronContext } from 'deepspace/worker'
import type { CronTask, CronExecution, CronRoomConfig, CronContext } from 'deepspace/worker'
```

## `CronRoom<E>`

See [Rooms reference → CronRoom](/sdk-reference/worker/rooms#cronroom-e) for the class signature. The scaffolded `AppCronRoom` extends this and overrides `onTask`.

## `CronTask`

```ts theme={null}
interface CronTask {
  name: string
  intervalMinutes?: number     // mutually exclusive with schedule
  schedule?: string            // 5-field cron expression
  timezone?: string            // IANA timezone (required with schedule)
  paused?: boolean             // start disabled
}
```

Each task declares **either** `intervalMinutes` OR `schedule` + `timezone`. Declaring both or neither throws at DO construction.

Cron mode is DST-aware - the wall-clock comparison happens after the timezone shift.

## `CronExecution`

```ts theme={null}
interface CronExecution {
  taskName: string
  startedAt: string
  /** Null while the task is still running. */
  completedAt: string | null
  success: boolean
  durationMs: number
  error?: string
}
```

Stored in the DO's history and streamed to `useCronMonitor` subscribers. The `lastRunAt` field on a task state row is similarly nullable until the task fires at least once.

## `buildCronContext(env, ownerUserId, roomId?)`

Returns a context for use inside `runTask`. Operations run as the app owner, bypassing RBAC.

```ts theme={null}
function buildCronContext(
  env: {
    RECORD_ROOMS: DurableObjectNamespace
    /** Optional in the type, but `ctx.integrations.call(...)` throws at runtime if missing. */
    APP_OWNER_JWT?: string
    API_WORKER?: Fetcher
    API_WORKER_URL?: string
  },
  ownerUserId: string,
  roomId?: string,
): CronContext

interface CronContext {
  records: {
    query(
      collection: string,
      opts?: { where?: Record<string, unknown>; limit?: number },
    ): Promise<any[]>
    create(collection: string, data: Record<string, unknown>): Promise<any>
    update(collection: string, recordId: string, data: Record<string, unknown>): Promise<any>
    delete(collection: string, recordId: string): Promise<any>
  }
  integrations: {
    call(endpoint: string, params?: Record<string, unknown>): Promise<any>
  }
  ownerUserId: string
}
```

`roomId` defaults to `'default'`. Pass `app:${env.APP_NAME}` to target the per-app RecordRoom (the scaffold convention).

The `records.*` methods are intentionally typed loosely (`Promise<any>` / `Promise<any[]>`) - they call the underlying RecordRoom tools API and surface its `data` field directly. At runtime:

* `query` resolves to an array of record envelopes (`{ recordId, data, createdAt, updatedAt, ... }`).
* `create` / `update` resolve to `{ recordId, record }` (the envelope of the row that was written).
* `delete` resolves to a delete confirmation payload.

There is **no `records.get` method** on `CronContext` - use `records.query` with a `where` clause when you need a single row.

<Warning>
  **Shapes differ from server actions.** `ctx.records.query` returns the unwrapped array directly (no `ActionResult` envelope). The methods throw on failure rather than returning a `{ success: false }` envelope - wrap in `try/catch` if you need to handle denied writes inline.
</Warning>

## Properties

* `ctx.records.*` - RBAC-bypassing record operations (results already unwrapped from the `ActionResult` envelope).
* `ctx.integrations.call(endpoint, params)` - proxies through the api-worker as the app owner (signed with `APP_OWNER_JWT`), billed to the app owner. Returns the unwrapped `data` field; throws on non-2xx or `success: false`.
* `ctx.ownerUserId` - convenience accessor for the owner's user ID.

## Pattern

```ts theme={null}
// src/cron.ts
import type { CronTask } from 'deepspace/worker'
import { buildCronContext } from 'deepspace/worker'

export const tasks: CronTask[] = [
  { name: 'heartbeat', intervalMinutes: 1 },
  { name: 'daily-digest', schedule: '0 9 * * *', timezone: 'America/New_York' },
]

export async function runTask(name: string, env: Env): Promise<void> {
  const ctx = buildCronContext(env, env.OWNER_USER_ID, `app:${env.APP_NAME}`)

  if (name === 'heartbeat') {
    const settings = await ctx.records.query('settings', { where: { key: 'lastHeartbeat' } })
    if (settings.length > 0) {
      await ctx.records.update('settings', settings[0].recordId, {
        value: new Date().toISOString(),
      })
    }
  }
}
```

## Worker wiring

```ts theme={null}
// worker.ts
export class AppCronRoom extends CronRoom<Env> {
  constructor(state: DurableObjectState, env: Env) {
    super(state, env, { tasks: cronTasks })
  }
  protected async onTask(name: string): Promise<void> {
    await runCronTask(name, this.env)
  }
}
```

Don't edit the WebSocket route or DO binding wiring - add tasks in `src/cron.ts` and the DO picks them up at construction.

## Outbound calls

Use `ctx.integrations.call(...)` for third-party APIs (billed to the owner):

```ts theme={null}
const data = await ctx.integrations.call('resend/send-email', {
  to: user.data.email,
  subject: 'Your digest',
  text: '...',
})
```

For autonomous LLM calls via the AI SDK, use `createDeepSpaceAI` without `authToken`:

```ts theme={null}
import { createDeepSpaceAI } from 'deepspace/worker'
import { generateText } from 'ai'

const ai = createDeepSpaceAI(env, 'anthropic')   // owner pays
const { text } = await generateText({ model: ai('claude-haiku-4-5'), prompt: '...' })
```

## See also

* [Scheduled jobs guide](/guides/scheduled-jobs) - patterns and worked examples
* [Rooms reference](/sdk-reference/worker/rooms#cronroom-e) - class signature
* [Real-time reference](/sdk-reference/client/realtime#usecronmonitor-roomid) - client-side monitor
