iii / worker
$worker

configuration

v0.16.0-next.5

Configuration worker for the iii engine.

engine module
baked into the iii engine; no separate install required.

install

install
$iii worker add configuration@0.16.0-next.5

configuration

iii-config.yaml
- adapter:
    config:
      directory: ./data/configuration
    name: fs
  ttl_seconds: 0
README.md

configuration

Schema-validated, reactive registry of named configuration entries. Workers register their configuration ids with a JSON Schema, publish values that are validated against it, and emit configuration triggers on every change so other workers react without polling.

The default fs adapter stores one YAML file per id under a configurable directory and watches it for external edits — manual edits surface as configuration:updated events the same way SDK calls do. The bridge adapter delegates to a remote III Engine and re-broadcasts its events into the local fan-out. Reads expand ${VAR:default} placeholders against the live process env on every call, so env changes propagate without a worker restart.

Sample Configuration

- name: configuration
  config:
    adapter:
      name: fs
      config:
        directory: ./data/configuration
    ttl_seconds: 0

Configuration

Field Type Description
adapter Adapter Adapter for configuration persistence. Defaults to fs.
ttl_seconds integer Per-id cleanup countdown in seconds. When >0, an entry whose last subscriber trigger has been unregistered for this long is deleted. Defaults to 0 (no cleanup).

Adapters

fs

File-system adapter that stores one YAML file per configuration id and watches the directory for external edits.

name: fs
config:
  directory: ./data/configuration
Field Type Description
directory string Directory holding .yaml files. Created on boot. Defaults to ./data/configuration.

External writes / edits / removals to the watched directory are debounced (500 ms) and replayed as configuration:registered, configuration:updated, or configuration:deleted events through the same trigger fan-out used by SDK calls.

bridge

Forwards every configuration::* call to a remote III Engine via the iii-sdk and registers a remote configuration trigger so changes on the source engine are mirrored into the local trigger fan-out.

name: bridge
config:
  bridge_url: ${REMOTE_III_URL:ws://localhost:49134}
Field Type Description
bridge_url string WebSocket URL of the remote III Engine. Defaults to ws://localhost:49134.

The bridge adapter cannot delete configurations on the remote engine; cleanup happens via the remote's TTL or by operating on the remote engine directly.

Functions

configuration::register

Declare a configuration id with a JSON Schema, name, description, and optional initial value. Idempotent — re-registering replaces the schema, name, description, and metadata; the stored value is kept unless initial_value is supplied. Validates initial_value against schema before persisting. Fires configuration:registered on first registration or configuration:updated on subsequent calls.

Parameters: id (string), name (string), description (string), schema (object — JSON Schema), initial_value (any, optional), metadata (any, optional)

Returns: the stored entry { id, name, description, schema, value, metadata }. Templates inside value are stored verbatim; expansion happens on read.

configuration::set

Replace the stored value for a registered id. Validates value against the registered schema. Fires configuration:updated.

Parameters: id (string), value (any)

Returns: old_value (any, or null if the entry had no prior value), new_value (any)

configuration::get

Read one configuration by id.

Parameters: id (string), raw (boolean, optional — defaults to false)

Returns: id (string), value (any). When raw is false, ${VAR:default} placeholders inside string fields are expanded against the live process env. When raw is true, the stored value is returned verbatim.

configuration::list

Enumerate every registered configuration. Never returns the stored value — pair with configuration::get once you have the id you want.

Returns: configurations (array of { id, name, description, schema, metadata }), sorted lexicographically by id.

configuration::schema

Read the schema, name, description, and metadata for one id without exposing the value.

Parameters: id (string)

Returns: id (string), name (string), description (string), schema (object), metadata (any, optional)

Error Codes

Code Meaning
NOT_REGISTERED set was called against an id that has not been passed to register yet.
INVALID_ID The id does not match [a-z0-9_-]{1,64} — the constraint applied so ids are safe filenames for the fs adapter.
SCHEMA_INVALID The supplied value does not satisfy the registered JSON Schema. The error message lists each violation.
NOT_FOUND get or schema was called against an id that is not registered.
ADAPTER_ERROR The adapter failed to persist the change (disk error, bridge unreachable, etc.).

Trigger Type: configuration

Fires when a configuration entry is registered, updated, or deleted — including external fs file edits and bridge-forwarded events from a remote engine.

Config Field Type Description
configuration_id string Only fire for changes to this id. When omitted, fires for every id.
event_types string[] Subset of configuration:registered, configuration:updated, configuration:deleted. When omitted, fires for every event type.
condition_function_id string Function ID for conditional execution. If it returns false, the handler is skipped.

Configuration Event Payload

Field Type Description
type string Always "configuration".
event_type string "configuration:registered", "configuration:updated", or "configuration:deleted".
id string The configuration id that changed.
name string The registered name at the time of the event.
description string The registered description.
schema object The registered JSON Schema.
old_value any Previous value with ${VAR:default} placeholders expanded. null on configuration:registered.
new_value any New value with ${VAR:default} placeholders expanded. null on configuration:deleted.
metadata any Echoes the registered metadata; omitted when none was supplied.

Sample Code

const fn = iii.registerFunction(
  { id: 'stream::onConfigChange' },
  async (event) => {
    console.log('Configuration changed:', event.event_type, event.id)
    console.log('New value:', event.new_value)
    return {}
  },
)

iii.registerTrigger({
  type: 'configuration',
  function_id: fn.id,
  config: {
    configuration_id: 'iii-stream',
    event_types: ['configuration:updated'],
  },
})

Mutations that fire triggers: configuration::register (:registered on first call, :updated on re-registration), configuration::set (:updated), TTL-driven cleanup (:deleted), and external fs create / edit / delete events. Reads (configuration::get, configuration::list, configuration::schema) do not fire triggers.

TTL Cleanup

The worker tracks the number of triggers currently bound to each configuration id. When ttl_seconds > 0 is configured AND the last trigger for an id is unregistered, a countdown deletes the entry after the TTL elapses. A new trigger registration before the countdown fires aborts the cleanup; while at least one trigger is bound to an id, that entry never expires.

ttl_seconds = 0 (the default) disables expiry entirely — entries persist until they are explicitly deleted via the adapter or, for the fs adapter, by removing the underlying file.

This is the cleanup story for ephemeral workers that come and go (sandboxes, short-lived consumers) and should not leave stale configuration behind when no one is left subscribing.

Usage Example: Migrating a Worker Off config.yaml

import { registerWorker } from 'iii-sdk'

const iii = registerWorker('ws://localhost:49134')

// 1. Declare the schema once at startup.
await iii.trigger({
  function_id: 'configuration::register',
  payload: {
    id: 'iii-stream',
    name: 'Stream worker',
    description: 'Connection settings for the stream worker.',
    schema: {
      type: 'object',
      required: ['port'],
      properties: {
        port: { type: 'integer', minimum: 1, maximum: 65535 },
        host: { type: 'string' },
      },
    },
    initial_value: { port: 3112, host: '127.0.0.1' },
  },
})

// 2. Read the live value (placeholders expanded) anywhere you need it.
const { value } = await iii.trigger({
  function_id: 'configuration::get',
  payload: { id: 'iii-stream' },
})

// 3. Subscribe to changes elsewhere — set/external-edit/bridge events all
//    arrive on the same trigger.
const onChange = iii.registerFunction('stream::onConfigChange', async (event) => {
  console.log('Stream config now:', event.new_value)
  return {}
})

iii.registerTrigger({
  type: 'configuration',
  function_id: onChange.id,
  config: { configuration_id: 'iii-stream' },
})