Build complex workflows with elegant, chainable TypeScript
Features β’ Quick Start β’ Examples β’ Documentation β’ API
Transform complex business logic into readable, maintainable workflows. No more spaghetti code or deeply nested conditionals.
// Before: Nested callbacks and hard-to-follow logic
async function processUser(userData) {
if (validateEmail(userData.email)) {
if (checkPasswordStrength(userData.password)) {
if (!await userExists(userData.email)) {
return await createUser(userData);
} else {
throw new Error('User exists');
}
} else {
throw new Error('Weak password');
}
} else {
throw new Error('Invalid email');
}
}
// After: Clean, declarative workflow
const userRegistrationFlow = Flow.create('user_registration')
.start(
ValidateEmailWidget.create('validate_email')
.moveTo(CheckPasswordStrengthWidget.create('check_password'))
.moveTo(CheckUserExistsWidget.create('check_exists'))
.moveTo(CreateUserWidget.create('create_user'))
)
.end();
await userRegistrationFlow(userData);Build workflows that read like sentences. Chain widgets together naturally with .moveTo() and .elseMoveTo().
Each widget is a node in a binary tree with success (left) and failure (right) paths. Automatic error routing included.
Use Split widgets with powerful comparison operators to route workflow execution based on data conditions.
Extract values from nested objects using template expressions: {{ payload.user.email }}. Variables are immutable by default.
Create custom widgets by extending CustomWidget. Implement your business logic while inheriting workflow orchestration.
Written in TypeScript with strict mode. Full type inference and IntelliSense support.
Core library has zero runtime dependencies. Optional integrations available (axios, winston).
Comprehensive test suite with Jest. 100% coverage on core functionality.
npm install declarative-based-flowyarn add declarative-based-flowpnpm add declarative-based-flowimport { Flow, SetVariable, Split, Compare, CustomWidget } from 'declarative-based-flow';
// 1. Extract variables from input
const extractData = SetVariable
.create('extract_data')
.variable('userId', '{{ payload.user.id }}')
.variable('userEmail', '{{ payload.user.email }}');
// 2. Create conditional branch
const checkUserType = Split
.create('check_user_type')
.case((data) => Compare.is(data.payload.user.type).equal('premium'));
// 3. Define success path (premium users)
const processPremium = SetVariable
.create('process_premium')
.variable('discount', '{{ payload.pricing.premium }}');
// 4. Define alternative path (standard users)
const processStandard = SetVariable
.create('process_standard')
.variable('discount', '{{ payload.pricing.standard }}');
// 5. Connect the workflow
extractData.moveTo(checkUserType);
checkUserType
.moveTo(processPremium)
.elseMoveTo(processStandard);
// 6. Create and execute the flow
const flow = Flow.create('user_discount_flow').start(extractData).end();
const result = await flow({
user: { id: 123, email: 'user@example.com', type: 'premium' },
pricing: { premium: 0.2, standard: 0.1 }
});
console.log(result.variables.discount); // 0.2Extract, transform, and load data through a multi-stage pipeline:
const etlFlow = Flow.create('customer_etl')
.start(
ExtractCustomersWidget.create('extract')
.moveTo(TransformDataWidget.create('transform'))
.moveTo(ValidateSchemaWidget.create('validate'))
.moveTo(LoadToDatabase.create('load'))
)
.end();Multi-step validation with error handling:
const validationFlow = Flow.create('user_validation')
.start(
ValidateEmailWidget.create('validate_email')
.moveTo(
Split.create('email_check')
.case((data) => Compare.is(data.payload.emailValid).equal(true))
.moveTo(ValidatePasswordWidget.create('validate_password'))
.elseMoveTo(EmailErrorWidget.create('email_error'))
)
)
.end();See full validation example β
E-commerce order fulfillment with inventory checks:
const orderFlow = Flow.create('order_processing')
.start(
CheckInventoryWidget.create('check_inventory')
.moveTo(
Split.create('inventory_available')
.case((data) => Compare.is(data.payload.inStock).equal(true))
.moveTo(ProcessPaymentWidget.create('payment'))
.elseMoveTo(OutOfStockWidget.create('out_of_stock'))
)
)
.end();Complex nested data transformation:
const transformFlow = Flow.create('data_transform')
.start(
SetVariable.create('extract_fields')
.variable('name', '{{ payload.customer.profile.name }}')
.variable('address', '{{ payload.customer.shipping.address }}')
.moveTo(NormalizeDataWidget.create('normalize'))
)
.end();See full transformation example β
The entry point of every workflow. Connects widgets and returns an executable function.
const flow = Flow.create('workflow_name')
.start(firstWidget)
.end();
// Execute with data
const result = await flow(inputData);Data Structure Convention:
{
payload: {
// Your input data (can be extended by widgets)
},
variables: {
// Extracted variables (immutable)
}
}Extract values from nested objects using template expressions.
const extractor = SetVariable
.create('extract_user_info')
.variable('email', '{{ payload.user.email }}')
.variable('fullName', '{{ payload.user.profile.name.first }}');Template Syntax:
- Pattern:
{{ path.to.value }} - Supports deep nesting:
{{ payload.user.profile.settings.theme }} - Variables are stored immutably using
Object.defineProperty
Conditional branching based on boolean functions.
const branch = Split
.create('check_age')
.case((data) => Compare.is(data.payload.age).greaterThan(18))
.moveTo(adultPathWidget)
.elseMoveTo(minorPathWidget);Factory pattern for comparison operations.
Compare.is(value).equal(expected) // ===
Compare.is(value).notEqual(expected) // !==
Compare.is(value).in(['a', 'b', 'c']) // includes
Compare.is(value).notIn(['x', 'y']) // !includes
Compare.is(value).greaterThan(10) // >
Compare.is(value).lesserThan(100) // <Extend with your own business logic.
class SendEmailWidget extends CustomWidget {
async process(data: WorkflowData): Promise<void> {
const email = data.payload.user?.email;
// Your business logic
await sendEmail(email, 'Welcome!');
// Log activity
this.register(`Email sent to ${email}`, 'info');
// IMPORTANT: Call super.process to continue workflow
await super.process(data);
}
}
// Usage
const emailWidget = SendEmailWidget.create('send_welcome_email');Custom Widget Requirements:
- Extend
CustomWidgetclass - Implement
async process(data): Promise<void> - Call
await super.process(data)at the end - Use
this.register(message, level)for logging
| Method | Parameters | Returns | Description |
|---|---|---|---|
create(name) |
name: string |
Flow |
Static factory method |
start(widget) |
widget: Widget |
Flow |
Set the first widget |
end() |
- | (data: any) => Promise<any> |
Returns executable function |
| Method | Parameters | Returns | Description |
|---|---|---|---|
create(name) |
name: string |
SetVariable |
Static factory method |
variable(key, template) |
key: string, template: string |
SetVariable |
Define variable extraction |
moveTo(widget) |
widget: Widget |
SetVariable |
Set success path |
| Method | Parameters | Returns | Description |
|---|---|---|---|
create(name) |
name: string |
Split |
Static factory method |
case(fn) |
fn: (data: any) => boolean |
Split |
Define condition function |
moveTo(widget) |
widget: Widget |
Split |
Set true path |
elseMoveTo(widget) |
widget: Widget |
Split |
Set false path |
| Method | Parameters | Returns | Description |
|---|---|---|---|
is(value) |
value: any |
Comparator |
Create comparator instance |
| Method | Parameters | Returns | Description |
|---|---|---|---|
equal(value) |
value: any |
boolean |
Strict equality (===) |
notEqual(value) |
value: any |
boolean |
Strict inequality (!==) |
in(array) |
array: any[] |
boolean |
Array includes |
notIn(array) |
array: any[] |
boolean |
Array not includes |
greaterThan(value) |
value: any |
boolean |
Greater than (>) |
lesserThan(value) |
value: any |
boolean |
Less than (<) |
| Method | Parameters | Returns | Description |
|---|---|---|---|
create(name) |
name: string |
CustomWidget |
Static factory method |
process(data) |
data: any |
Promise<void> |
Override to implement logic |
register(msg, level) |
msg: any, level: string |
void |
Log messages |
moveTo(widget) |
widget: Widget |
CustomWidget |
Set success path |
elseMoveTo(widget) |
widget: Widget |
CustomWidget |
Set failure path |
widgetA
.moveTo(widgetB)
.moveTo(widgetC)
.moveTo(widgetD);// Multiple paths converge to same widget
successPath.moveTo(finalWidget);
failurePath.moveTo(finalWidget);Split.create('outer_check')
.case(outerCondition)
.moveTo(
Split.create('inner_check')
.case(innerCondition)
.moveTo(deepSuccessWidget)
.elseMoveTo(deepFailureWidget)
);SetVariable.create('extract_1')
.variable('userId', '{{ payload.user.id }}')
.moveTo(
SetVariable.create('extract_2')
.variable('userName', '{{ payload.user.name }}')
.moveTo(ProcessWidget.create('process'))
);Run the test suite:
npm test # Run all tests
npm run test:dev # Watch mode
npm run test:coverage # Coverage reportRun specific tests:
npx jest test/flow.test.ts
npx jest test/split.test.ts
npx jest test/set-variable.test.tsnpm run build # Compile TypeScript to CommonJSnpm run lint # ESLint with auto-fix
npm run format # Prettier formattingdeclarative-based-flow/
βββ src/
β βββ flow.class.ts # Flow widget
β βββ set-variable.ts # Variable extraction
β βββ split.ts # Conditional branching
β βββ compare.ts # Comparison factory
β βββ comparator.ts # Comparison operators
β βββ custom-widget.ts # Base for custom widgets
β βββ widget.ts # Base widget class
β βββ index.ts # Public exports
βββ test/ # Jest test suite
βββ examples/ # Real-world examples
β βββ 01-etl-pipeline.ts
β βββ 02-user-validation-workflow.ts
β βββ 03-order-processing-workflow.ts
β βββ 04-data-transformation.ts
βββ lib/ # Compiled output
- β ETL Pipelines - Extract, transform, load data workflows
- β Business Process Automation - Order processing, approvals, notifications
- β Data Validation - Multi-step validation with error handling
- β API Orchestration - Chain multiple API calls with conditional logic
- β State Machines - Model complex state transitions
- β Workflow Engines - Build custom workflow execution engines
- β Data Transformation - Complex nested data mapping and transformation
- β Simple CRUD operations (use direct functions)
- β Real-time streaming (consider RxJS)
- β UI rendering logic (use React/Vue state management)
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Development Guidelines:
- Follow existing code style (run
npm run lintandnpm run format) - Add tests for new features
- Update documentation
- Ensure all tests pass (
npm test)
This project is licensed under the MIT License - see the LICENSE file for details.
Iago Calazans - iago.calazans@gmail.com
Contributors:
- Rafael Iunes - rafaeliunes97@gmail.com
- Inspired by functional programming and reactive patterns
- Built with TypeScript, Jest, and modern tooling
- Thanks to all contributors and users
- Parallel execution support
- Built-in retry and circuit breaker patterns
- Visual workflow builder/debugger
- Performance monitoring and metrics
- Plugin system for common integrations
- GraphQL workflow definitions
- π« Email: iago.calazans@gmail.com
- π Issues: GitHub Issues
- π‘ Discussions: GitHub Discussions
If this project helped you, please give it a β star!
Made with β€οΈ by Iago Calazans