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.
import { CronRoom, buildCronContext } from 'deepspace/worker'
import type { CronTask, CronExecution, CronRoomConfig, CronContext } from 'deepspace/worker'
CronRoom<E>
See Rooms reference → CronRoom for the class signature. The scaffolded AppCronRoom extends this and overrides onTask.
CronTask
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
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.
function buildCronContext(
env: {
RECORD_ROOMS: DurableObjectNamespace
INTERNAL_STORAGE_HMAC_SECRET?: string
API_BASE_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.
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.
Properties
ctx.records.* - RBAC-bypassing record operations (results already unwrapped from the ActionResult envelope).
ctx.integrations.call(endpoint, params) - proxies through the api-worker via signed internal HMAC, billed to the app owner (uses INTERNAL_STORAGE_HMAC_SECRET).
ctx.ownerUserId - convenience accessor for the owner’s user ID.
Pattern
// 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
// 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):
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:
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