Skip to content

A powerful and intuitive package designed to simplify the construction of complex, structured workflows using a declarative and fluent syntax.

License

Notifications You must be signed in to change notification settings

iagocalazans/declarative-based-flow

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

52 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🌊 Declarative Based Flow

Build complex workflows with elegant, chainable TypeScript

Node.js Jest npm version License: MIT TypeScript PRs Welcome

Features β€’ Quick Start β€’ Examples β€’ Documentation β€’ API


🎯 Why Declarative Based Flow?

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);

✨ Features

πŸ”— Fluent API & Method Chaining

Build workflows that read like sentences. Chain widgets together naturally with .moveTo() and .elseMoveTo().

🌳 Widget Tree Architecture

Each widget is a node in a binary tree with success (left) and failure (right) paths. Automatic error routing included.

πŸ”€ Conditional Branching

Use Split widgets with powerful comparison operators to route workflow execution based on data conditions.

πŸ“¦ Variable Extraction

Extract values from nested objects using template expressions: {{ payload.user.email }}. Variables are immutable by default.

🎨 Fully Extensible

Create custom widgets by extending CustomWidget. Implement your business logic while inheriting workflow orchestration.

πŸ›‘οΈ Type-Safe

Written in TypeScript with strict mode. Full type inference and IntelliSense support.

⚑ Zero Dependencies (Runtime)

Core library has zero runtime dependencies. Optional integrations available (axios, winston).

πŸ§ͺ Fully Tested

Comprehensive test suite with Jest. 100% coverage on core functionality.


πŸ“¦ Installation

npm install declarative-based-flow
yarn add declarative-based-flow
pnpm add declarative-based-flow

πŸš€ Quick Start

Basic Example: Data Transformation Pipeline

import { 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.2

πŸ’‘ Examples

ETL Pipeline

Extract, 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();

See full ETL example β†’

User Validation Workflow

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 β†’

Order Processing

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();

See full order example β†’

Data Transformation

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 β†’


πŸ“š Documentation

Core Concepts

🌊 Flow

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)
  }
}

πŸ”§ SetVariable

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

πŸ”€ Split

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);

βš–οΈ Compare & Comparator

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)           // <

🎨 CustomWidget

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:

  1. Extend CustomWidget class
  2. Implement async process(data): Promise<void>
  3. Call await super.process(data) at the end
  4. Use this.register(message, level) for logging

πŸ”§ API Reference

Flow

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

SetVariable

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

Split

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

Compare

Method Parameters Returns Description
is(value) value: any Comparator Create comparator instance

Comparator

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 (<)

CustomWidget

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

πŸŽ“ Advanced Patterns

Sequential Processing

widgetA
  .moveTo(widgetB)
  .moveTo(widgetC)
  .moveTo(widgetD);

Convergent Paths

// Multiple paths converge to same widget
successPath.moveTo(finalWidget);
failurePath.moveTo(finalWidget);

Nested Conditionals

Split.create('outer_check')
  .case(outerCondition)
  .moveTo(
    Split.create('inner_check')
      .case(innerCondition)
      .moveTo(deepSuccessWidget)
      .elseMoveTo(deepFailureWidget)
  );

Variable Chain Extraction

SetVariable.create('extract_1')
  .variable('userId', '{{ payload.user.id }}')
  .moveTo(
    SetVariable.create('extract_2')
      .variable('userName', '{{ payload.user.name }}')
      .moveTo(ProcessWidget.create('process'))
  );

πŸ§ͺ Testing

Run the test suite:

npm test                 # Run all tests
npm run test:dev         # Watch mode
npm run test:coverage    # Coverage report

Run specific tests:

npx jest test/flow.test.ts
npx jest test/split.test.ts
npx jest test/set-variable.test.ts

πŸ› οΈ Development

Build

npm run build            # Compile TypeScript to CommonJS

Code Quality

npm run lint             # ESLint with auto-fix
npm run format           # Prettier formatting

Project Structure

declarative-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

🌟 Use Cases

Perfect For:

  • βœ… 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

Not Ideal For:

  • ❌ Simple CRUD operations (use direct functions)
  • ❌ Real-time streaming (consider RxJS)
  • ❌ UI rendering logic (use React/Vue state management)

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Development Guidelines:

  • Follow existing code style (run npm run lint and npm run format)
  • Add tests for new features
  • Update documentation
  • Ensure all tests pass (npm test)

πŸ“ License

This project is licensed under the MIT License - see the LICENSE file for details.


πŸ‘¨β€πŸ’» Authors

Iago Calazans - iago.calazans@gmail.com

Contributors:


πŸ™ Acknowledgments

  • Inspired by functional programming and reactive patterns
  • Built with TypeScript, Jest, and modern tooling
  • Thanks to all contributors and users

πŸ“Š Roadmap

  • 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

πŸ’¬ Support


If this project helped you, please give it a ⭐ star!

Made with ❀️ by Iago Calazans

About

A powerful and intuitive package designed to simplify the construction of complex, structured workflows using a declarative and fluent syntax.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •