Skip to content

Spies & Observability

Spies are observer functions that monitor workflow execution in real time. Attach a spy to receive callbacks as steps execute, conditions evaluate, and workflows start or finish. This is useful for logging, timing, metrics, and audit trails.

Attaching a Spy

Call attachSpy on the MoostWf instance. It returns a detach function:

ts
import { MoostWf } from '@moostjs/event-wf'

const wf = new MoostWf()

const detach = wf.attachSpy((event, eventOutput, flowOutput, ms) => { 
  console.log(`[${event}] step=${flowOutput.stepId} (${ms}ms)`)
})

// ... run workflows ...

detach() // stop observing

Spy Callback Signature

The spy function receives four arguments:

ts
type TWorkflowSpy<T, I, IR> = (
  event: string,
  eventOutput: string | undefined | { fn: string | Function, result: boolean },
  flowOutput: TFlowOutput<T, I, IR>,
  ms?: number,
) => void
ArgumentDescription
eventEvent name (e.g., 'step', 'workflow-start', 'eval-condition-fn')
eventOutputEvent-specific data — step ID for steps, condition result for conditions
flowOutputCurrent workflow output snapshot (context, finished, stepId, etc.)
msElapsed time in milliseconds (from workflow start)

Spy Events

EventWhen it fireseventOutput
'workflow-start'Workflow beginsundefined
'subflow-start'Nested sub-workflow beginsundefined
'step'A step finishes executingStep ID (string)
'eval-condition-fn'A step condition is evaluated{ fn, result: boolean }
'eval-while-cond'A while loop condition is evaluated{ fn, result: boolean }
'eval-break-fn'A break condition is evaluated{ fn, result: boolean }
'eval-continue-fn'A continue condition is evaluated{ fn, result: boolean }
'workflow-end'Workflow completes normallyundefined
'workflow-interrupt'Workflow pauses (input required or retriable error)undefined
'subflow-end'Nested sub-workflow completesundefined

Practical Examples

Step Timing Logger

ts
wf.attachSpy((event, eventOutput, flowOutput, ms) => {
  if (event === 'step') {
    console.log(`Step "${flowOutput.stepId}" completed in ${ms}ms`)
  }
})

Audit Trail

ts
const auditLog: Array<{ event: string, stepId: string, timestamp: number }> = []

wf.attachSpy((event, _output, flowOutput) => {
  auditLog.push({
    event,
    stepId: flowOutput.stepId,
    timestamp: Date.now(),
  })
})

// After workflow completes:
await saveAuditLog(auditLog)

Condition Debugging

ts
wf.attachSpy((event, eventOutput) => {
  if (typeof eventOutput === 'object' && eventOutput && 'result' in eventOutput) {
    const condStr = typeof eventOutput.fn === 'string'
      ? eventOutput.fn
      : eventOutput.fn.toString()
    console.log(`${event}: ${condStr} => ${eventOutput.result}`)
  }
})

Detaching Spies

You can detach a spy in two ways:

ts
// Option 1: Use the returned detach function
const detach = wf.attachSpy(mySpy)
detach()

// Option 2: Pass the same function to detachSpy
wf.attachSpy(mySpy)
wf.detachSpy(mySpy)

INFO

Spies are called synchronously during workflow execution. Keep spy callbacks lightweight to avoid slowing down your workflows. For heavy processing (database writes, API calls), buffer events and process them asynchronously.

Released under the MIT License.