dey_dev_utils
Small, focused utilities for Dart development, with explicit APIs for common functional programming and control-flow patterns.
This package is also a place to explore ideas with AI assistance (many of things here came up from there). AI can help with development concepts, suggest classes and utilities, explain trade-offs, and help implement or test those ideas. The package itself has no AI runtime dependency: the AI guidance lives in the repository's skills and documentation.
Features
The package's tested core includes five libraries:
- Result: represent success and typed failure explicitly with
OkandErr. - Scope helpers: run blocks, manage cleanup, observe values, transform values, and filter them fluently.
- Type-safe comparison: use
Comparison.less,Comparison.equal, andComparison.greaterinstead of unexplained comparison integers. - Trampoline: evaluate synchronous bounce chains iteratively to avoid call stack growth in deep computations.
- Specification pattern: express and compose Boolean business rules.
It also includes experimental libraries for options, validation, lenses, isomorphisms, middleware, sagas, tracing, rate limiting, contracts, monoids, and lazy initialization.
Getting started
Requires Dart SDK 3.13.7 or newer.
Add the package to your project:
dart pub add dey_dev_utils
Then run:
dart pub get
There is currently no umbrella library. Import the utility you need directly:
import 'package:dey_dev_utils/result.dart';
Usage
Explicit results
Use Result<T, E> when an operation can fail and the failure should remain
visible in its return type:
import 'package:dey_dev_utils/result.dart';
Result<int, FormatException> parseNumber(String input) {
final value = int.tryParse(input);
return value == null
? Result.err(FormatException('Not a number: $input'), StackTrace.current)
: Result.ok(value);
}
final result = parseNumber('30');
final number = result.unwrapOr(0);
Result.guardSync / Result.guardAsync convert a matching exception type into
Ok/Err while allowing unrelated errors to propagate, and the
guardExceptionSync / guardExceptionAsync variants keep Error values
uncaught.
Scope helpers
Scope utilities support readable transformations and resource cleanup:
import 'package:dey_dev_utils/scope.dart';
final label = 30
.let((value) => value * 2)
.also((value) => print('Computed: $value'))
.toString();
final content = use(
'temporary resource',
(resource) => resource.length,
dispose: (resource) => print('Releasing $resource'),
);
Use applyIf for type-preserving conditional changes and takeIf or
takeUnless when a value should be kept or discarded.
Readable comparisons
import 'package:dey_dev_utils/comparison.dart';
final ordering = 3.compareWith(5);
if (ordering == Comparison.less) {
print('3 is smaller than 5');
}
final isReady = 10.isGreaterThan(5);
Stack-safe synchronous steps
Build each next step lazily with Bounce.more, then evaluate the chain with
trampoline:
import 'package:dey_dev_utils/trampoline.dart';
Bounce<int> sumDown(int value, int total) {
if (value == 0) return Bounce.done(total);
return Bounce.more(() => sumDown(value - 1, total + value));
}
final result = trampoline(sumDown(3, 0)); // 6
The trampoline is synchronous. It does not schedule asynchronous work or automatically make arbitrary recursive code stack-safe.
Composable specifications
Represent each business rule as a reusable Boolean specification. Create rules
with predicate, always, or never, then combine them with &, |, ^,
nand, nor, xnor, implies, iff, or ~/negated():
import 'package:dey_dev_utils/spec.dart';
final isPositive = Spec<int>.predicate((value) => value > 0);
final isUnderOneHundred = Spec<int>.predicate((value) => value < 100);
final accepted = Spec<int>.allOf([isPositive, isUnderOneHundred]);
// .call for tear-offs, instead of using .isSatisfiedBy
final acceptedValues = [12, -3, 100].where(accepted.call).toList(); // [12]
For a named rule, use a final, base, or sealed subclass because Spec<T>
is an abstract base class:
final class IsAdult extends Spec<int> {
const IsAdult();
@override
bool isSatisfiedBy(int age) => age >= 18;
}
final canVote = IsAdult() & Spec<int>.predicate((age) => age.isEven);
contramap adapts a specification to another candidate type through a
projection. Aggregate factories snapshot their input when created; allOf and
noneOf of an empty iterable are satisfied, while anyOf is not. AND, OR,
NAND, and NOR short-circuit in evaluation order. XOR, XNOR, and iff evaluate
both operands. Predicate callbacks run on every evaluation and are not cached.
Experimental APIs
The following libraries are not exported and may change or be removed in future releases without the same compatibility guarantees as the tested core:
iso.dartfor reversible transformations between typeslens.dartfor composable access to nested immutable datamiddleware.dartfor asynchronous handlers and pipelinesmonoid.dartfor identity and associative combinationonce.dartfor lazy one-time initializationrate_limit.dartfor debouncing and throttlingsaga.dartfor compensating asynchronous workflowstrace.dartfor timing and logging wrappersvalidate.dartfor accumulating validation results
The experimental option.dart for Some and None values.
Contract checks
runWithChecking and runWithCheckingAsync evaluate a precondition, run an
operation, and check its result. They default to
ContractCheckingMode.assertionsOnly, where failed checks use Dart assertions
and callbacks are not evaluated when assertions are disabled. Pass
checkingMode: ContractCheckingMode.alwaysThrow to enforce checks with
ContractViolationError regardless of assertion settings. The Invariant
mixin provides callback-based pre and post checks, explicit
verifyInvariant, and sync/async runWithInvariant helpers. Those helpers
evaluate the invariant even when the body throws; if both the body and
invariant check fail, the invariant failure takes precedence.
import 'package:dey_dev_utils/contract.dart';
final input = ' value ';
final result = runWithChecking(
preCondition: () => input.isNotEmpty,
operation: () => input.trim(),
postCondition: (value) => value.isNotEmpty,
checkingMode: ContractCheckingMode.alwaysThrow,
);
Use runWithCheckingAsync for an asynchronous operation:
final result = await runWithCheckingAsync(
preCondition: () => input.isNotEmpty,
operation: () async => input.trim(),
postCondition: (value) => value.isNotEmpty,
checkingMode: ContractCheckingMode.alwaysThrow,
);
AI skills for stable libraries
The repository includes reusable AI skills for the stable libraries. They give an AI coding assistant the API model, important semantics, examples, testing guidance, and review checklists for:
To use them, open the relevant SKILL.md file in your AI coding assistant or
copy its path into the assistant's context. Then ask for the task while naming
the library. For example:
Use the dev-utils Result skill to review this function and add focused tests for its success, typed failure, and stack-trace behavior.
These skills are useful for implementation, refactoring, documentation, testing, and code review. They are guidance for development tools, not code that is loaded by your Dart application.
Development
Run the test suite and static analysis locally:
dart analyze
dart test
GitHub Actions runs formatting, analysis, and tests on every push and pull request. Dedupe, cognitive complexity, and undead reports are published as workflow artifacts and summaries for review; they are advisory while the APIs are evolving. Mutation testing runs weekly and on manual workflow dispatch, and its report is advisory as well. Package contents are validated before release publishing.
Contributions are welcome. Please open an issue for bugs or ideas, and include the smallest reproducible example when reporting a problem. See the repository for source code and project discussions.
The tested core is released as version 1.0.0.
License
dey_dev_utils is available under the MIT License. Copyright (c) 2026
ElanDeyan.