UseCase Forge
English is the primary language of the ecosystem. README.ru.md is a
supplementary Russian translation.
UseCase Forge is a pure Dart, command-driven UseCase runtime. It accepts typed commands, resolves input and processing policies, runs exact-type handlers, publishes immutable snapshots, supports cooperative cancellation, and retains bounded terminal history.
Pre-release: the API may still change before 1.0. Pin the version and review the changelog when upgrading.
Core has no Flutter or RxDart dependency. Version 0 targets the Dart VM; Dart Web is not supported yet.
Features
- Exact runtime-type command handlers.
- Concurrent execution by default, with opt-in sequential lanes.
- Input conflict, debounce, throttle, and sliding rate-limit policies.
- Stable policy configuration through UseCase defaults and exact-type
registration; individual
addcall sites cannot change orchestration. - Processing policies: coexist, restart, and reject-new.
- Synchronous state publication with equality-based deduplication.
- Cooperative cancellation without pretending to abort a Dart
Future. - Awaited lifecycle hooks and explicit error stages.
- Replay-latest broadcast snapshot stream.
- Bounded terminal execution history.
- Optional injected clock for deterministic policy and timestamp tests.
- Automatic read-only Dart VM diagnostics for the separate DevTools package in supported debug runs, with application values hidden by default.
Installation
Add the package dependency:
dependencies:
usecase_forge: ^0.1.0-dev.5
dart pub add usecase_forge:^0.1.0-dev.5
Ecosystem packages
| Package | Purpose |
|---|---|
usecase_forge_flutter |
Provider, Builder, Listener, Selector, Consumer, ownership, and context lookup for Flutter |
usecase_forge_test |
Deterministic clocks, observers, matchers, and lifecycle test helpers |
usecase_forge_devtools |
Read-only VM diagnostics in a DevTools extension |
All companion packages are published independently. None of them is a core runtime dependency.
Minimal example
import 'package:usecase_forge/usecase_forge.dart';
final class CounterState {
const CounterState(this.value);
final int value;
@override
bool operator ==(Object other) =>
other is CounterState && other.value == value;
@override
int get hashCode => value.hashCode;
}
final class Increment extends UseCaseCommand {
const Increment();
}
final class CounterUseCase extends UseCase<CounterState> {
CounterUseCase() : super(initialState: const CounterState(0)) {
registerCommand<Increment>((command, context) async {
context.publish(CounterState(context.snapshot.state.value + 1));
});
}
}
Future<void> main() async {
final counter = CounterUseCase();
final finished = counter.stream.firstWhere(
(snapshot) => snapshot.phase == UseCaseExecutionPhase.finished,
);
counter.add(const Increment());
await finished;
print(counter.state.state.value); // 1
await counter.close();
}
Run the complete version:
dart run example/usecase_forge_example.dart
The examples index also includes catalog search, document synchronization, rate limiting and conflict policies, cooperative cancellation, command-specific error recovery, error propagation and hooks, and history diagnostics.
DevTools diagnostics
The separate usecase_forge_devtools package provides the developer-facing
DevTools tab. Core stays pure Dart: it has no Flutter or third-party runtime
dependency and exposes only a small read-only bridge through dart:developer
in supported non-product Dart VM runs.
The bridge reports registered command types, the current snapshot, Admission, Pending, Starting, Processing and Finalizing entries, terminal history, rejections, cancellation and error-routing events. It cannot add commands, cancel work, change State or replay executions.
State, commands, execution keys, reasons, errors and stack traces are hidden by
default. To reveal selected JSON-safe fields, import the secondary library and
mix in UseCaseDiagnosticsDataProvider:
import 'package:usecase_forge/diagnostics.dart';
final class CartUseCase extends UseCase<CartState>
with UseCaseDiagnosticsDataProvider {
// Normal UseCase implementation omitted.
@override
String get useCaseDiagnosticLabel => 'Shopping cart';
@override
Object? encodeUseCaseDiagnosticValue(
UseCaseDiagnosticValueKind kind,
Object? value,
) {
if (kind == UseCaseDiagnosticValueKind.state && value is CartState) {
return {'itemCount': value.itemCount};
}
return null; // Keep every other value hidden.
}
}
Returned data is validated, copied and bounded. Formatter failures are
contained by diagnostics and never enter UseCase.onError, the snapshot
stream or terminal history. See the
complete runnable example and the
diagnostics contract.
Execution model
UseCase.add returns void. The runtime creates an execution entry, passes
it through the input pipeline, schedules it, and exposes progress through
state, stream, and terminal history.
Instructions are configured on the UseCase instance and on exact command-type
registration. Defaults are frozen lazily for the instance; resolved
registration instructions are reused by every command of that type. If an
operation needs different execution semantics, model it as a different command
type or a different UseCase rather than changing an individual add call.
Execution is concurrent by default. Global state follows the order of synchronous context.publish calls, not command submission or future completion order. Use sequential lanes when operations must observe ordered state.
Policies that relate executions require a non-null UseCaseCommand.executionKey. Relation identity combines the exact command runtime type with that key.
Cancellation
context.cancel() immediately finalizes the execution as cancelled and frees its processing slot. Dart futures cannot be forcibly aborted, so the handler should observe context.isCancellationRequested or await context.whenCancellationRequested where appropriate. A late publish throws UseCaseExecutionAlreadyFinishedException and is routed through onError without rewriting history.
State equality
Snapshots are deduplicated with ==. State classes must implement meaningful value equality. Equality that is too broad can hide updates; identity-only equality can emit unnecessary updates.
Errors and hooks
Hooks return Future<void> and may observe a lifecycle step, but they cannot
replace its Entry or result. Handler and hook errors pass through onError.
The default implementation forwards the original error to the snapshot stream.
See the lifecycle contract and
public API contract.
An exact command registration can add a typed onError callback. It receives
the command, live execution context, original error, and original stack trace.
Return UseCaseCommandErrorAction.handled after command-specific recovery or
failure publication. Return UseCaseCommandErrorAction.useDefault to continue
through the global UseCase.onError flow unchanged. See the
command error handling example.
Public API
The main library exports the UseCase facade, commands and handlers, execution
context and value objects, snapshots, instructions, error context, rejection
reasons, and documented exceptions. Normal consumers should use
package:usecase_forge/usecase_forge.dart, not direct imports from lib/src/.
The only secondary public library in v0 is
package:usecase_forge/diagnostics.dart, used for explicit DevTools data
opt-in.
Testing and controlled time
Production UseCases normally call super(initialState: ...) and use the system
clock. A test may instead pass a public UseCaseClock implementation through
super(initialState: ..., clock: clock). The same clock controls debounce,
throttle and rate-limit timers plus createdAt, queuedAt, startedAt and
finishedAt. The separate usecase_forge_test package provides
TestUseCaseClock, an event-recording observer, semantic matchers and the
useCaseTest helper.
This clock is a testability boundary, not a public view of private queues. Admission, pending and processing internals remain encapsulated.
Documentation and project
- Runnable examples
- Runtime specification
- Package documentation
- UseCase Forge product site
- GitLab repository
- Issue tracker
- Generated API documentation: run
dart doc
UseCase Forge is created and maintained by Petr Orlov and published through the verified ArkTelos publisher. For package and ecosystem questions, contact packages@arktelos.dev or use the protected ArkTelos contact form.
Report suspected vulnerabilities through the private process described in SECURITY.md, not in a public issue.
License
Licensed under the Apache License, Version 2.0. Copyright 2026 Orlov Petr Petrovich. See LICENSE for the license terms and NOTICE for attribution.
Dependencies and development tools keep their own licenses; the Apache 2.0 license applies to UseCase Forge code and does not relicense third-party work.
Libraries
- diagnostics
- Lets a UseCase choose which application values DevTools may display.
- usecase_forge
- UseCase Forge — command-driven UseCase runtime.