Skip to main content
This example shows the two-step pattern at the heart of most iii workflows: an HTTP handler accepts a request and publishes an event to the queue, then a queue handler processes that event in the background and persists the result to state.

Worker setup

Every iii worker starts by initialising the SDK and connecting to the engine.
// worker.ts
import { registerWorker, Logger } from 'iii-sdk'

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

Step 1 — HTTP handler

Registers a function and binds it to an HTTP trigger. Returns immediately after publishing to the queue.
await iii.registerFunction(
  { id: 'hello::api', description: 'Receives hello request' },
  async (req: ApiRequest) => {
    const logger = new Logger()
    const appName = 'III App'
    const requestId = Math.random().toString(36).substring(7)

    logger.info('Hello API called', { appName, requestId })

    await iii.trigger({
      function_id: 'greet::process',
      payload: {
        requestId,
        appName,
        greetingPrefix: process.env.GREETING_PREFIX ?? 'Hello',
        timestamp: new Date().toISOString(),
      },
      action: TriggerAction.Enqueue({ queue: 'default' }),
    })

    return {
      status_code: 200,
      body: {
        message: 'Hello request received! Processing in background.',
        status: 'processing',
        appName,
      },
    } satisfies ApiResponse
  },
)

await iii.registerTrigger({
  type: 'http',
  function_id: 'hello::api',
  config: { api_path: 'hello', http_method: 'GET' },
})

Step 2 — Queue handler

Consumes the event, builds the greeting, and persists it to state.
await iii.registerFunction(
  { id: 'greet::process', description: 'Processes greeting in background' },
  async (data) => {
    const logger = new Logger()
    const { requestId, appName, greetingPrefix, timestamp } = data as {
      requestId: string
      appName: string
      greetingPrefix: string
      timestamp: string
    }

    logger.info('Processing greeting', { requestId, appName })

    const greeting = `${greetingPrefix} ${appName}!`

    await iii.trigger({
      function_id: 'state::set',
      payload: {
        scope: 'greetings',
        key: requestId,
        value: {
          greeting,
          processedAt: new Date().toISOString(),
          originalTimestamp: timestamp,
        },
      },
    })

    logger.info('Greeting processed', { requestId, greeting })
  },
)

Connect and run

Every SDK establishes the WebSocket connection when you call registerWorker(). The process stays alive automatically while connected. Call shutdown() when you want to stop the worker.

Test it

curl http://localhost:3111/hello
# {"message":"Hello request received! Processing in background.","status":"processing","appName":"III App"}

Key concepts

  • iii.registerFunction pairs a string ID with an async handler. The ID is referenced by all triggers bound to that function.
  • iii.registerTrigger binds a trigger type + config to a function ID. A function can have multiple triggers.
  • iii.trigger({ function_id, payload, action: TriggerAction.Enqueue({ queue }) }) enqueues work to a named queue. The target function receives the payload as its input.
  • iii.trigger({ function_id: 'state::set', payload: { scope, key, value }, action: TriggerAction.Void() }) persists data to the engine’s key-value store.