Loop Engine

Getting Started

Quick Start

Prerequisites

Node.js 18+ and npm or pnpm.

Install

1npm install @loop-engine/sdk

Define a loop

1import { LoopBuilder } from '@loop-engine/sdk'
2 
3const approval = LoopBuilder
4 .create('expense.approval', 'finance')
5 .description('Expense report approval')
6 .state('SUBMITTED')
7 .state('UNDER_REVIEW')
8 .state('APPROVED', { isTerminal: true })
9 .state('REJECTED', { isTerminal: true })
10 .initialState('SUBMITTED')
11 .transition({
12 id: 'start_review',
13 from: 'SUBMITTED',
14 to: 'UNDER_REVIEW',
15 actors: ['automation']
16 })
17 .transition({
18 id: 'approve',
19 from: 'UNDER_REVIEW',
20 to: 'APPROVED',
21 actors: ['human']
22 })
23 .transition({
24 id: 'reject',
25 from: 'UNDER_REVIEW',
26 to: 'REJECTED',
27 actors: ['human']
28 })
29 .outcome({
30 id: 'expense_approved',
31 description: 'Expense report approved',
32 valueUnit: 'expense_approved'
33 })
34 .build()

Run it

1import { aggregateId, transitionId } from '@loop-engine/core'
2import { createLoopSystem } from '@loop-engine/sdk'
3 
4const { engine, eventBus } = createLoopSystem({ loops: [approval] })
5eventBus.subscribe(async (event) => console.log(event.type))
6 
7await engine.start({
8 loopId: 'expense.approval',
9 aggregateId: aggregateId('EXP-2026-001'),
10 orgId: 'acme',
11 actor: { type: 'system', id: 'system:intake' }
12})
13 
14await engine.transition({
15 aggregateId: aggregateId('EXP-2026-001'),
16 transitionId: transitionId('start_review'),
17 actor: { type: 'automation', id: 'system:router' }
18})
19 
20await engine.transition({
21 aggregateId: aggregateId('EXP-2026-001'),
22 transitionId: transitionId('approve'),
23 actor: { type: 'human', id: 'manager@acme.com' },
24 evidence: { comment: 'Approved for Q1 budget' }
25})
26 
27const state = await engine.getState(aggregateId('EXP-2026-001'))
28console.log(state?.currentState) "cmt">// APPROVED
29console.log(state?.status) "cmt">// CLOSED

What just happened

Next steps