user-flow-engine
v1.0.0
Published
An Engine for Customizable Scenarios
Maintainers
Readme
User Flow Engine
User Flow Engine is a lightweight runtime engine for executing user flows as ordered steps with deterministic transitions.
It is framework-agnostic and works in both browser and Node.js environments.
Features
- Deterministic step execution by index
- Goto-style transitions with
next('stepShort') - Works with sync and async handlers
- Extensible flow lifecycle hooks:
onCreate,onBeforeFinish,onError - Zero framework coupling
Quick Start
import { BaseFlow, createEngine } from 'user-flow-engine';
class OnboardingFlow extends BaseFlow {
short = 'onboarding';
abstract = false;
onCreate() {
this.addStep({
short: 'welcome',
async handle(_from, _to, next) {
console.log('Welcome step');
return next('profile');
},
});
this.addStep({
short: 'profile',
handle() {
console.log('Profile step');
// return undefined/null -> engine moves to next step by index
},
});
}
}
const engine = createEngine({
short: 'app',
context: { isAuthorized: true },
flows: [new OnboardingFlow()],
});
engine.handle('onboarding');Installation
npm i user-flow-engineExample
flows.js:
import { BaseFlow } from 'user-flow-engine';
export class SignedInFlow extends BaseFlow {
abstract = true;
}
export class UsersWithCompaniesFlow extends SignedInFlow {
abstract = true;
}
export class JustRegisteredFlow extends SignedInFlow {
short = 'justRegistered';
abstract = false;
onCreate() {
this.addStep({
short: 'todoReplaceByTheRealStepShort',
handle() {
// TODO: Replace by the real step handler
},
});
}
}
export class LandingToPaymentFlow extends UsersWithCompaniesFlow {
short = 'landingToPayment';
abstract = false;
todoRemoveThisCounter = 0;
onCreate() {
this.addStep({
short: 'todoReplaceByTheRealStepShort1',
async handle() {
console.log('TEST FLOW #' + this.todoRemoveThisCounter);
this.todoRemoveThisCounter += 1;
},
});
this.addStep({
short: 'todoReplaceByTheRealStepShort2',
handle(_from, _to, next) {
console.log('------------');
return next('todoReplaceByTheRealStepShort1');
},
});
}
}engine.js:
import { createEngine } from 'user-flow-engine';
import {
JustRegisteredFlow,
LandingToPaymentFlow,
} from './flows';
export const engine = createEngine({
short: 'legacyFrontend',
context: {
isAuthorized: true,
isJustSignedIn: true,
isJustSignedUp: true,
isActivePlan: false,
isTrial: false,
},
flows: [
new JustRegisteredFlow(),
new LandingToPaymentFlow(),
],
});
export default engine;API
Exports
createEngine(options)FlowEngine(default export)BaseFlowFlowControllerFlowStepBaseErrortypes(TypeScript contracts)FLOW_SUCCESSDEFAULT_TICK
createEngine({/* options */})
| Option | Type | Required | Description |
|-----------| --- | --- | --- |
| short | string | yes | Engine identifier |
| context | Record<string, any> \| null | no | Default context |
| flows | FlowInterface[] \| null | no | Registered flows |
FlowEngine
Key methods:
setShort(short: string): thissetContext(context: Obj): thissetFlows(flows: FlowInterface[]): thisaddFlow(flow: FlowInterface, _default?: boolean | null): thisgetFlow(short: string): FlowInterface | nullhandle(val?: FlowInterface | string, context?: Obj | null): FlowControllerInterface
handle(...) modes:
engine.handle(flowObj)-> starts provided flow object.engine.handle('flowShort')-> starts registered flow byshort.engine.handle()-> starts first flow in engine list.
BaseFlow
Fields:
short: string(default:'baseFlow')abstract: boolean(default:true)steps: StepInterface[]
Hooks:
onCreate(): void | Promise<void>onBeforeFinish(val?: any): void | Promise<void>onError(error: Error): void
Key methods:
addStep(step, _default?)setSteps(steps)handle(context?)
Important:
abstract = trueflows are not runnable and throwBaseErroronCreate()runs on eachhandle()call
FlowController
Fields:
tickstepNumstepsintervalisInProgressdoStopstopVal
Methods:
handle(): Promise<any>stop(val?: any): voidgetStep(...),getStepIndex(...),setSteps(...),addStep(...)
FlowStep
handle(from, to, next)from-> previous step ornullto-> next linear step ornullnext('stepShort')-> returns target step by short
Execution Semantics
- Execution is sequential by
stepNum - If current step is missing, flow resolves with
FLOW_SUCCESS - If step handler returns
undefinedornull, controller moves to next step by index - If handler returns
next('stepShort'), controller jumps to that step - If step throws, flow fails and
onErroris called controller.stop(value)stops execution and resolves with that value
Notes
- The engine does not provide routing/UI/state/network/storage/analytics
- No built-in automatic "best flow" selection (
priority, scoring, tie-breakers, etc.) onCreate()should register steps predictably to avoid accidental accumulation across re-runs
TypeScript Contracts
Main contracts are defined in src/types.ts:
StepInterfaceFlowInterfaceFlowControllerInterfaceStepHandler,StepNextHandlerCreateEngineOptions,AddStepOptions
Development
Build library bundles and declarations:
npm run buildUser Flow Engine by Kenny Romanov
TrustMe
