Documentation Index Fetch the complete documentation index at: https://mintlify.com/iii-hq/sdk/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Triggers are event sources that automatically invoke functions when specific conditions occur. The III Engine provides built-in trigger types (HTTP endpoints, scheduled jobs, system events) and supports custom trigger types implemented by workers.
Trigger Registration
Register a trigger to connect an event source to a function:
const trigger = iii . registerTrigger ({
type: 'http' , // Trigger type
function_id: 'api::users::list' , // Function to invoke
config: { // Type-specific configuration
api_path: '/users' ,
http_method: 'GET'
}
})
// Later: trigger.unregister()
Trigger registration message:
// Source: packages/node/iii/src/iii-types.ts:41-47
type RegisterTriggerMessage = {
message_type : MessageType . RegisterTrigger
id : string // Auto-generated UUID
type : string // Trigger type identifier
function_id : string // Target function
config : unknown // Type-specific configuration
}
Registration Flow
Implementation:
// Source: packages/node/iii/src/iii.ts:166-186
registerTrigger = ( trigger : Omit < RegisterTriggerMessage , 'message_type' | 'id' >) : Trigger => {
const id = crypto . randomUUID ()
const fullTrigger : RegisterTriggerMessage = {
... trigger ,
id ,
message_type: MessageType . RegisterTrigger
}
this . sendMessage ( MessageType . RegisterTrigger , fullTrigger , true )
this . triggers . set ( id , fullTrigger )
return {
unregister : () => {
this . sendMessage ( MessageType . UnregisterTrigger , {
id ,
message_type: MessageType . UnregisterTrigger ,
type: fullTrigger . type
})
this . triggers . delete ( id )
}
}
}
HTTP Triggers
HTTP triggers expose functions as REST API endpoints:
Basic HTTP Endpoint
import type { ApiRequest , ApiResponse } from 'iii-sdk'
const fn = iii . registerFunction (
{ id: 'api::products::list' },
async ( req : ApiRequest ) : Promise < ApiResponse > => {
const products = await db . products . findMany ()
return {
status_code: 200 ,
body: { products },
headers: { 'Content-Type' : 'application/json' }
}
}
)
iii . registerTrigger ({
type: 'http' ,
function_id: fn . id ,
config: {
api_path: '/products' ,
http_method: 'GET' ,
description: 'List all products'
}
})
HTTP request structure:
// Source: packages/node/iii/src/types.ts:222-230
type ApiRequest < TBody = unknown > = {
path_params : Record < string , string > // Route parameters
query_params : Record < string , string | string []> // Query string
body : TBody // Parsed JSON body
headers : Record < string , string | string []> // Request headers
method : string // HTTP method
request_body : ChannelReader // Raw body stream
}
Path Parameters
Capture dynamic segments from URL:
iii . registerFunction (
{ id: 'api::products::get' },
async ( req : ApiRequest ) : Promise < ApiResponse > => {
const { id } = req . path_params
const product = await db . products . findById ( id )
if ( ! product ) {
return { status_code: 404 , body: { error: 'Product not found' } }
}
return { status_code: 200 , body: product }
}
)
iii . registerTrigger ({
type: 'http' ,
function_id: 'api::products::get' ,
config: {
api_path: '/products/:id' , // :id becomes path parameter
http_method: 'GET'
}
})
Example from tests:
// Source: packages/node/iii/tests/api-triggers.test.ts:76-103
const fn = iii . registerFunction (
{ id: 'test.api.getById' },
async ( req : HttpRequest ) : Promise < ApiResponse > => ({
status_code: 200 ,
body: { id: req . path_params ?. id }
})
)
const trigger = iii . registerTrigger ({
type: 'http' ,
function_id: fn . id ,
config: {
api_path: 'test/items/:id' ,
http_method: 'GET'
}
})
const response = await httpRequest ( 'GET' , '/test/items/abc123' )
expect ( response . data ). toEqual ({ id: 'abc123' })
Query Parameters
iii . registerFunction (
{ id: 'api::search' },
async ( req : ApiRequest ) : Promise < ApiResponse > => {
const q = Array . isArray ( req . query_params . q )
? req . query_params . q [ 0 ]
: req . query_params . q
const limit = parseInt ( req . query_params . limit as string ) || 10
const results = await db . search ( q , limit )
return { status_code: 200 , body: { results , query: q } }
}
)
iii . registerTrigger ({
type: 'http' ,
function_id: 'api::search' ,
config: {
api_path: '/search' ,
http_method: 'GET'
}
})
// GET /search?q=widget&limit=20
Request Body
Parse JSON body automatically:
type CreateUserRequest = {
email : string
name : string
role : 'admin' | 'user'
}
iii . registerFunction (
{ id : 'api::users::create' },
async ( req : ApiRequest < CreateUserRequest >) : Promise < ApiResponse > => {
const { email , name , role } = req . body
// Validation
if (! email || ! name ) {
return {
status_code: 400 ,
body: { error: 'Email and name are required' }
}
}
const user = await db . users . create ({ email , name , role })
return { status_code : 201 , body : user }
}
)
iii . registerTrigger ({
type : 'http' ,
function_id : 'api::users::create' ,
config : {
api_path : '/users' ,
http_method : 'POST'
}
})
const fn = iii . registerFunction (
{ id: 'api::download' },
async ( req : ApiRequest ) : Promise < ApiResponse > => {
const file = await generateReport ()
return {
status_code: 200 ,
body: file ,
headers: {
'Content-Type' : 'application/pdf' ,
'Content-Disposition' : 'attachment; filename="report.pdf"' ,
'Cache-Control' : 'no-cache'
}
}
}
)
HTTP trigger handlers receive an ApiRequest and must return an ApiResponse with status_code, optional body, and optional headers.
Event Triggers
The Engine provides system event triggers:
Functions Available Event
Triggered when function registry changes (functions added/removed):
iii . onFunctionsAvailable (( functions ) => {
console . log ( ` ${ functions . length } functions available` )
// Check if required services are online
const requiredServices = [ 'payments::charge' , 'inventory::reserve' ]
const available = requiredServices . every ( id =>
functions . some ( f => f . function_id === id )
)
if ( available ) {
console . log ( 'All required services are online' )
}
})
Implementation detail:
// Source: packages/node/iii/src/iii-constants.ts:13-16
export const EngineTriggers = {
FUNCTIONS_AVAILABLE: 'engine::functions-available' ,
LOG: 'log'
} as const
The SDK creates an internal function and trigger automatically. See packages/node/iii/src/iii.ts:393-428.
Log Events
Receive OpenTelemetry log events from the Engine:
iii . onLog (( log ) => {
console . log ( `[ ${ log . severity_text } ] ${ log . body } ` )
console . log ( ' Service:' , log . service_name )
console . log ( ' Trace ID:' , log . trace_id )
console . log ( ' Attributes:' , log . attributes )
}, { level: 'warn' }) // Only WARN and above
OtelLogEvent structure:
// Source: packages/node/iii/src/types.ts:18-43
type OtelLogEvent = {
timestamp_unix_nano : number
observed_timestamp_unix_nano : number
severity_number : number // OTEL severity (1-24)
severity_text : string // "INFO", "WARN", "ERROR", etc.
body : string // Log message
attributes : Record < string , unknown > // Structured attributes
trace_id ?: string // For correlation
span_id ?: string
resource : Record < string , string > // Resource attributes
service_name : string // Emitting service
instrumentation_scope_name ?: string
instrumentation_scope_version ?: string
}
Severity levels:
// Source: packages/node/iii/src/types.ts:46
type LogSeverityLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'all'
Schedule Triggers
Schedule triggers are handled by the Engine’s scheduler. Configuration format depends on Engine implementation (cron expressions, intervals, etc.).
Example pattern:
iii . registerFunction (
{ id: 'jobs::cleanup' },
async () => {
await db . sessions . deleteExpired ()
return { deleted: 42 }
}
)
iii . registerTrigger ({
type: 'schedule' ,
function_id: 'jobs::cleanup' ,
config: {
cron: '0 * * * *' // Every hour
}
})
Custom Trigger Types
Workers can implement custom trigger types for any event source:
Implementing a Trigger Handler
import type { TriggerHandler } from 'iii-sdk'
type WebhookTriggerConfig = {
webhook_url : string
secret : string
}
const webhookHandler : TriggerHandler < WebhookTriggerConfig > = {
async registerTrigger ( config ) {
const { id , function_id , config : { webhook_url , secret } } = config
// Set up webhook listener
const listener = await createWebhookListener ( webhook_url , secret )
listener . on ( 'event' , async ( data ) => {
// Invoke the registered function
await iii . call ( function_id , data )
})
// Store listener for cleanup
webhookListeners . set ( id , listener )
},
async unregisterTrigger ( config ) {
const listener = webhookListeners . get ( config . id )
if ( listener ) {
await listener . close ()
webhookListeners . delete ( config . id )
}
}
}
TriggerHandler interface:
// Source: packages/node/iii/src/triggers.ts:1-11
type TriggerConfig < TConfig > = {
id : string
function_id : string
config : TConfig
}
type TriggerHandler < TConfig > = {
registerTrigger ( config : TriggerConfig < TConfig >) : Promise < void >
unregisterTrigger ( config : TriggerConfig < TConfig >) : Promise < void >
}
Registering Trigger Types
Register your custom trigger type with the Engine:
iii . registerTriggerType (
{
id: 'webhook' ,
description: 'External webhook events'
},
webhookHandler
)
// Now any worker can use this trigger type
iii . registerTrigger ({
type: 'webhook' ,
function_id: 'notifications::process' ,
config: {
webhook_url: 'https://example.com/hook' ,
secret: process . env . WEBHOOK_SECRET
}
})
Implementation:
// Source: packages/node/iii/src/iii.ts:146-155
registerTriggerType = < TConfig >(
triggerType : Omit < RegisterTriggerTypeMessage , 'message_type' >,
handler : TriggerHandler < TConfig >
) : void => {
this . sendMessage ( MessageType . RegisterTriggerType , triggerType , true )
this . triggerTypes . set ( triggerType . id , {
message: { ... triggerType , message_type: MessageType . RegisterTriggerType },
handler
})
}
Trigger Registration Result
The Engine sends confirmation after trigger registration:
// Source: packages/node/iii/src/iii-types.ts:32-39
type TriggerRegistrationResultMessage = {
message_type : MessageType . TriggerRegistrationResult
id : string
type : string
function_id : string
result ?: unknown
error ?: unknown // { code: string, message: string }
}
Possible error codes:
trigger_type_not_found - Trigger type not registered
trigger_registration_failed - Handler threw exception
Real-World Examples
API Helper Hook
Simplify HTTP trigger registration:
// Source: packages/node/iii-example/src/hooks.ts:1-30
import { type ApiRequest , type ApiResponse , getContext } from 'iii-sdk'
import { iii } from './iii'
export const useApi = < TBody = any >(
config : {
api_path : string
http_method : string
description ?: string
metadata ?: Record < string , unknown >
},
handler : ( req : ApiRequest < TBody >, context : Context ) => Promise < ApiResponse >
) => {
const function_id = `api:: ${ config . http_method . toLowerCase () } :: ${ config . api_path } `
iii . registerFunction (
{ id: function_id , metadata: config . metadata },
req => handler ( req , getContext ())
)
iii . registerTrigger ({
type: 'http' ,
function_id ,
config: {
api_path: config . api_path ,
http_method: config . http_method ,
description: config . description ,
metadata: config . metadata
}
})
}
Usage:
// Source: packages/node/iii-example/src/index.ts:7-36
useApi (
{
api_path: '/todo' ,
http_method: 'POST' ,
description: 'Create a new todo' ,
metadata: { tags: [ 'todo' ] }
},
async ( req , ctx ) => {
ctx . logger . info ( 'Creating new todo' , { body: req . body })
const { description , dueDate } = req . body
const todoId = `todo- ${ Date . now () } - ${ Math . random (). toString ( 36 ). substring ( 2 , 9 ) } `
if ( ! description ) {
return { status_code: 400 , body: { error: 'Description is required' } }
}
const newTodo : Todo = {
id: todoId ,
description ,
groupId: 'inbox' ,
createdAt: new Date (). toISOString (),
dueDate ,
completedAt: null
}
const todo = await streams . set < Todo >( 'todo' , 'inbox' , todoId , newTodo )
return { status_code: 201 , body: todo }
}
)
Database Change Trigger
Custom trigger for database changes:
type DbChangeTriggerConfig = {
table : string
operation : 'insert' | 'update' | 'delete'
}
const dbChangeHandler : TriggerHandler < DbChangeTriggerConfig > = {
async registerTrigger ( config ) {
const { id , function_id , config : { table , operation } } = config
// Set up database listener (e.g., PostgreSQL LISTEN/NOTIFY)
const listener = await db . listen ( ` ${ table } _ ${ operation } ` )
listener . on ( 'notification' , async ( payload ) => {
await iii . callVoid ( function_id , {
table ,
operation ,
data: payload
})
})
dbListeners . set ( id , listener )
},
async unregisterTrigger ( config ) {
const listener = dbListeners . get ( config . id )
await listener ?. unlisten ()
dbListeners . delete ( config . id )
}
}
iii . registerTriggerType (
{ id: 'db-change' , description: 'Database change events' },
dbChangeHandler
)
iii . registerTrigger ({
type: 'db-change' ,
function_id: 'cache::invalidate' ,
config: { table: 'products' , operation: 'update' }
})
Trigger Lifecycle
Registration
Worker sends RegisterTrigger message to Engine with type, function_id, and config
Handler Invocation
Engine invokes the trigger type’s registerTrigger handler (if custom type)
Event Source Setup
Handler sets up event listener, webhook server, scheduler, etc.
Active
Trigger invokes function when events occur
Unregistration
Worker calls trigger.unregister() or disconnects, Handler’s unregisterTrigger is called
Multi-Language Support
const trigger = iii . registerTrigger ({
type: 'http' ,
function_id: 'api::hello' ,
config: { api_path: '/hello' , http_method: 'GET' }
})
trigger . unregister ()
trigger = iii.register_trigger(
type = 'http' ,
function_id = 'api::hello' ,
config = { 'api_path' : '/hello' , 'http_method' : 'GET' }
)
trigger.unregister()
use iii :: RegisterTriggerMessage ;
use serde_json :: json;
let trigger = iii . register_trigger ( RegisterTriggerMessage {
r#type : "http" . into (),
function_id : "api::hello" . into (),
config : json! ({ "api_path" : "/hello" , "http_method" : "GET" }),
.. Default :: default ()
}) ? ;
trigger . unregister ();
Next Steps
Functions Learn about function registration and invocation
Channels Stream data between functions