Skip to main content
The cron trigger type fires a registered function on a schedule. The handler receives {trigger, job_id, scheduled_time, actual_time} as its payload. Work is typically fanned out by calling enqueue to downstream queue handlers.

Minimal cron function

import { registerWorker, Logger, TriggerAction } from 'iii-sdk'

const iii = registerWorker(process.env.III_URL ?? 'ws://localhost:49134')

iii.registerFunction(
  { id: 'cron.periodic_job', description: 'Fires on schedule and enqueues work' },
  async () => {
    const logger = new Logger()
    logger.info('Periodic job fired')

    await iii.trigger({
      function_id: 'enqueue',
      payload: {
        topic: 'job.tick',
        data: { firedAt: new Date().toISOString(), message: 'Periodic job executed' },
      },
      action: TriggerAction.Void(),
    })
  },
)

iii.registerTrigger({
  type: 'cron',
  function_id: 'cron.periodic_job',
  config: { expression: '* * * * *' }, // every minute
})

Downstream queue handler

The function that consumes the cron’s emitted event:
iii.registerFunction(
  { id: 'job.handle_tick', description: 'Processes the periodic job event' },
  async (data: { firedAt: string; message: string }) => {
    const logger = new Logger()
    logger.info('Periodic job processed', { firedAt: data.firedAt, message: data.message })
  },
)

iii.registerTrigger({
  type: 'queue',
  function_id: 'job.handle_tick',
  config: { topic: 'job.tick' },
})

State sweep cron

A common pattern: read all records in a state scope, filter them by a business rule, and emit events for those that need action.
iii.registerFunction(
  { id: 'cron.orders_sweep', description: 'Checks for overdue orders every 5 minutes' },
  async () => {
    const logger = new Logger()

    const orders = await iii.trigger<{
      id: string
      shipDate: string
      complete: boolean
      status: string
    }[]>({ function_id: 'state::list', payload: { scope: 'orders' } })

    let swept = 0

    for (const order of orders ?? []) {
      if (!order.complete && new Date() > new Date(order.shipDate)) {
        logger.warn('Order overdue', { orderId: order.id, shipDate: order.shipDate })

        await iii.trigger({
          function_id: 'enqueue',
          payload: {
            topic: 'notification',
            data: {
              orderId: order.id,
              templateId: 'order-audit-warning',
              status: order.status,
              shipDate: order.shipDate,
            },
          },
          action: TriggerAction.Void(),
        })

        swept++
      }
    }

    logger.info('Sweep complete', { checked: orders?.length ?? 0, swept })
  },
)

iii.registerTrigger({
  type: 'cron',
  function_id: 'cron.orders_sweep',
  config: { expression: '*/5 * * * *' },
})

Cron expression format

iii uses a six-field extended cron format. The optional leading field is seconds.
┌──────────── second (0-59, optional)
│ ┌────────── minute (0-59)
│ │ ┌──────── hour (0-23)
│ │ │ ┌────── day of month (1-31)
│ │ │ │ ┌──── month (1-12)
│ │ │ │ │ ┌── day of week (0-7)
│ │ │ │ │ │
* * * * * *
ExpressionMeaning
* * * * *Every minute
0 * * * *Every hour
0 9 * * 1-509:00 on weekdays
*/5 * * * *Every 5 minutes
0/5 * * * * *Every 5 seconds (with seconds field)

Key concepts

  • Cron handlers receive {trigger, job_id, scheduled_time, actual_time} as their payload.
  • Keep cron handlers lightweight. Use enqueue to fan out work to queue handlers that can run in parallel.
  • Combine state::list with cron for periodic sweeps over accumulated data — the state sweep pattern above is the idiomatic approach.