Trough

Dart License: MIT

Trough is a middleware pipeline library for Dart, inspired by similar libraries in the JavaScript ecosystem. It allows you to create composable function pipelines for processing data in sequence.

Features

  • πŸ”„ Middleware Pipeline: Create composable function chains
  • ⚑ Sync & Async Support: Support for synchronous return values and asynchronous Futures
  • πŸ›‘οΈ Error Handling: Built-in error propagation and handling mechanisms
  • πŸ”— Method Chaining: Fluent API design
  • πŸ“ Strong Typing: TypeScript-style type definitions
  • πŸ§ͺ Full Test Coverage: Comprehensive test suite included

Installation

Add the dependency to your pubspec.yaml file:

dependencies:
  trough: ^1.0.0

Then run:

dart pub get

Quick Start

Basic Usage

import 'package:trough/trough.dart';

void main() {
  // Create a new pipeline
  final pipeline = trough();

  // Add middleware - multiply input by 2
  pipeline.use((List<dynamic> input, [Function? next]) {
    final value = input.isNotEmpty ? input[0] as int : 0;
    next?.call(null, [value * 2]);
    return null;
  });

  // Add another middleware - add 10
  pipeline.use((List<dynamic> input, [Function? next]) {
    final value = input.isNotEmpty ? input[0] as int : 0;
    next?.call(null, [value + 10]);
    return null;
  });

  // Run the pipeline
  pipeline.run([5], (Object? error, [List<dynamic>? output]) {
    if (error != null) {
      print('Error: $error');
    } else {
      print('Result: ${output?.first}'); // Output: Result: 20 (5 * 2 + 10)
    }
  });
}

Synchronous Middleware

void main() {
  final pipeline = trough();
  
  // Synchronous middleware that returns a value directly
  pipeline.use((List<dynamic> input, [Function? next]) {
    final text = input.isNotEmpty ? input[0] as String : '';
    return '$text World!';
  });

  pipeline.run(['Hello'], (Object? error, [List<dynamic>? output]) {
    if (error != null) {
      print('Error: $error');
    } else {
      print('Result: ${output?.first}'); // Output: Result: Hello World!
    }
  });
}

Asynchronous Middleware

import 'dart:async';

void main() async {
  final pipeline = trough();
  
  // Asynchronous middleware
  pipeline.use((List<dynamic> input, [Function? next]) {
    return Future.delayed(Duration(milliseconds: 100), () {
      final value = input.isNotEmpty ? input[0] as int : 0;
      return value * 3;
    });
  });

  pipeline.run([7], (Object? error, [List<dynamic>? output]) {
    if (error != null) {
      print('Error: $error');
    } else {
      print('Async result: ${output?.first}'); // Output: Async result: 21
    }
  });
}

Error Handling

void main() {
  final pipeline = trough();
  
  // Middleware that throws an error
  pipeline.use((List<dynamic> input, [Function? next]) {
    if (input.isEmpty) {
      throw Exception('Input cannot be empty');
    }
    next?.call(null, [input[0]]);
    return null;
  });

  pipeline.run([], (Object? error, [List<dynamic>? output]) {
    if (error != null) {
      print('Caught error: $error'); // Output: Caught error: Exception: Input cannot be empty
    } else {
      print('Result: ${output?.first}');
    }
  });
}

API Documentation

Type Definitions

Callback

typedef Callback = void Function(Object? error, [List<dynamic>? output]);

Callback function called when the pipeline completes.

Middleware

typedef Middleware = dynamic Function(List<dynamic> input, [Function? next]);

Middleware function signature. Can accept input and an optional next callback function.

Main API

trough()

Creates a new pipeline instance.

Returns: Pipeline - A new pipeline instance

Pipeline.use(Middleware middleware)

Adds middleware to the pipeline.

Parameters:

  • middleware: The middleware function to add

Returns: Pipeline - Returns the pipeline instance for method chaining

Pipeline.run(List<dynamic> values, Callback callback)

Runs all middleware in the pipeline.

Parameters:

  • values: Input values passed to the first middleware
  • callback: Callback function called when the pipeline completes

wrap(Middleware middleware, Callback callback)

Helper function to wrap middleware with a uniform interface.

Parameters:

  • middleware: The middleware function to wrap
  • callback: Callback function called on completion

Returns: Function - The wrapped function

Middleware Patterns

1. Using next Callback

pipeline.use((List<dynamic> input, [Function? next]) {
  // Process input
  final processed = processInput(input);
  
  // Call next to continue the pipeline
  next?.call(null, [processed]);
  return null;
});

2. Direct Return Value (Synchronous)

pipeline.use((List<dynamic> input, [Function? next]) {
  final processed = processInput(input);
  return processed; // Return processed value directly
});

3. Return Future (Asynchronous)

pipeline.use((List<dynamic> input, [Function? next]) {
  return processInputAsync(input); // Return Future
});

4. Error Handling

pipeline.use((List<dynamic> input, [Function? next]) {
  try {
    final processed = processInput(input);
    next?.call(null, [processed]);
  } catch (error) {
    next?.call(error); // Pass error along
  }
  return null;
});

Real-World Examples

Text Processing Pipeline

final textProcessor = trough()
  ..use((List<dynamic> input, [Function? next]) {
    // Trim whitespace
    final text = (input[0] as String).trim();
    next?.call(null, [text]);
    return null;
  })
  ..use((List<dynamic> input, [Function? next]) {
    // Convert to lowercase
    final text = (input[0] as String).toLowerCase();
    next?.call(null, [text]);
    return null;
  })
  ..use((List<dynamic> input, [Function? next]) {
    // Replace special characters
    final text = (input[0] as String).replaceAll(RegExp(r'[^\w\s]'), '');
    next?.call(null, [text]);
    return null;
  });

textProcessor.run(['  Hello, World!  '], (error, output) {
  print(output?.first); // Output: "hello world"
});

Data Validation Pipeline

final validator = trough()
  ..use((List<dynamic> input, [Function? next]) {
    // Check if input is empty
    if (input.isEmpty || input[0] == null) {
      throw Exception('Input cannot be empty');
    }
    next?.call(null, input);
    return null;
  })
  ..use((List<dynamic> input, [Function? next]) {
    // Check type
    if (input[0] is! String) {
      throw Exception('Input must be a string');
    }
    next?.call(null, input);
    return null;
  })
  ..use((List<dynamic> input, [Function? next]) {
    // Check length
    final text = input[0] as String;
    if (text.length < 3) {
      throw Exception('Input must be at least 3 characters long');
    }
    next?.call(null, ['Validation passed: $text']);
    return null;
  });

Development

Run Tests

dart test

Run Example

dart run example/trough_example.dart

Code Formatting

dart format .

Static Analysis

dart analyze

License

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

Contributing

Contributions are welcome! Please feel free to submit Pull Requests or create Issues.


Note: This library's design is inspired by similar libraries in the JavaScript ecosystem, particularly wooorm/trough.

Libraries

trough