Trough
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 middlewarecallback
: 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 wrapcallback
: 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.
Related Projects
- trough (JavaScript) - The original JavaScript version
- unified - Text processing ecosystem that uses trough
Note: This library's design is inspired by similar libraries in the JavaScript ecosystem, particularly wooorm/trough.