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

The Node SDK establishes the WebSocket connection when you call registerWorker(). There is no separate connect() method. Keep the process alive so the worker stays registered.
// Node SDK connects on registerWorker() — keep the process alive
await new Promise(() => {})

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.