dust_dart
Dart-only runtime and annotations for Dust generated code.
You focus on product. We focus on performance.
Our Promise
- Stable Dart authoring APIs for data classes, JSON, validation, and HTTP client generation.
- Stable functional primitives used by generated code and app boundaries.
- DB APIs are beta and may still be refined before stabilization.
- Generated code and runtime helpers can improve without forcing app-code rewrites.
Public surfaces
package:dust_dart/fp.dart: functional primitives such asOption,None,Some,Result,Ok,Err,Unit, andunit.package:dust_dart/core.dart: compatibility export for functional primitives.package:dust_dart/derive.dart: derive annotations and marker traits.package:dust_dart/serde.dart: JSON/serde annotations and runtime helpers.package:dust_dart/http.dart: HTTP client annotations.package:dust_dart/db.dart: SQLx-style DB annotations and runtime contracts.package:dust_dart/dust_dart.dart: convenience export for all Dart-only APIs.
Stability
| Library | Status | Public contract |
|---|---|---|
fp.dart |
Stable for 0.1.x | Option, None, Some, Result, Ok, Err, Unit, and unit, including switch patterns, equality, map, andThen, match, and unwrap helpers. |
core.dart |
Stable for 0.1.x | Compatibility export for fp.dart. |
derive.dart |
Stable for 0.1.x | Derive, derive traits, validation annotations, validation result types, and documented generated-code imports. It re-exports fp.dart for generated-code compatibility. |
serde.dart |
Stable for 0.1.x | JSON derive annotations, SerDe, SerDeCodec, SerDeRename, and JsonHelper. |
http.dart |
Stable for 0.1.x | HTTP client annotations, parse-target enums, Dio runtime types, and dart:convert export used by generated clients. |
db.dart |
Beta | SQLx-style annotations and runtime contracts. Generated code may rely on them, but app-facing DB ergonomics can still be refined before DB stabilization. |
dust_dart.dart |
Stable barrel, mixed contents | Convenience export for Dart-only APIs. It includes the beta DB surface for generated-code compatibility. |
Stable surfaces avoid breaking app authoring APIs during 0.1.x. Generated
code and private helpers can still improve when the documented imports and
runtime contracts keep working. DB APIs remain beta until their stabilization
gates are closed.
Option
Option<T> is Rust-style: None() means no value, and Some(value) means a
value is present, even when that value is null.
import 'package:dust_dart/fp.dart';
String displayNickname(Option<String?> nickname) {
return switch (nickname) {
None<String?>() => 'Anonymous',
Some<String?>(:final value) => value ?? 'No nickname',
};
}
const missing = None<String?>();
const present = Some<String?>('John');
const presentNull = Some<String?>(null);
Pattern match to branch on absence or presence:
Option<int> parseCount(String text) {
final value = int.tryParse(text);
return value == null ? const None<int>() : Some(value);
}
final label = switch (parseCount('21')) {
None<int>() => 'missing',
Some<int>(:final value) => 'count=$value',
};
Use match, map, andThen, and unwrapping helpers at app boundaries:
final count = parseCount('21')
.andThen((value) => value > 0 ? Some<int>(value) : const None<int>())
.map((value) => value * 2)
.unwrapOr(0);
final message = parseCount('bad').match(
some: (value) => 'count=$value',
none: () => 'missing',
);
Generated copyWith
CopyWith API inspired by Freezed.
Generated copyWith uses typed callable ergonomics for IDE completion, supports
clearing nullable fields with null, and keeps normal copy operations shallow.
final renamed = profile.copyWith(name: 'John');
final cleared = profile.copyWith(nickname: null);
final moved = profile.copyWith.address(city: 'London');
final movedNullable = profile.copyWith.mailingAddress?.call(city: 'London');
Normal copyWith(...) replaces fields directly. Nested Dust model fields expose
chained helpers when the nested model is generated in the same library.
Collections are replaced by identity; Dust does not clone List, Map, or
Set values inside normal copyWith.
final tags = <String>['new'];
final updated = product.copyWith(tags: tags);
identical(updated.tags, tags); // true
Result
import 'package:dust_dart/fp.dart';
Result<int, String> parseCount(String text) {
final value = int.tryParse(text);
return value == null ? const Err('invalid count') : Ok(value);
}
final label = parseCount('42').match(
ok: (value) => 'count=$value',
err: (error) => error,
);
Chain fallible steps with andThen:
Result<int, String> requirePositive(int value) {
return value > 0 ? Ok(value) : const Err('count must be positive');
}
final parsed = parseCount('42').andThen(requirePositive);
Recover with orElse when the fallback can also fail:
Result<int, String> readCache() => const Err('cache miss');
Result<int, String> readRemote(String error) => const Ok(42);
final count = readCache().orElse(readRemote);
Use map, mapErr, and unwrapping helpers at app boundaries:
final doubled = parseCount('21').map((value) => value * 2);
final message = parseCount('bad').mapErr((error) => 'Parse failed: $error');
final fallback = parseCount('bad').unwrapOr(0);
final computed = parseCount('bad').unwrapOrElse((error) => error.length);
Use Unit when only success/failure matters:
Result<Unit, String> save() {
return const Ok(unit);
}
DB compatibility
package:dust_dart/db.dart re-exports Result, Ok, Err, Unit, and unit so generated DAO code can use:
Future<Result<UserRow?, SqlxError>> findById(int id);
Executor is the SQLx-style execution contract used by generated DAO code.
Pool, Connection, and Transaction implementations can all be passed to
DAO factories and query helpers.
Row is a driver-agnostic interface. Driver packages own concrete adapters
such as Sqlite3Row, while generated FromRow mappers stay driver-blind:
extension UserRowFromRow on UserRow {
static UserRow fromRow(Row row) {
return UserRow(id: row.read<int>('id'));
}
}
Use driver-specific escape hatches only when needed, for example
Sqlite3Executor.database from package:dust_db_sqlite3.
Validation
This package keeps its public runtime surface analyzer-clean, fully documented,
and fully covered. The package analysis options enable
public_member_api_docs, so every public annotation, runtime type, constructor,
field, and method needs Dartdoc.
Run the package gate after changing runtime code or annotations:
dart format --set-exit-if-changed packages/dust_dart/lib packages/dust_dart/test
dart analyze packages/dust_dart
dart --enable-asserts test --coverage=packages/dust_dart/coverage packages/dust_dart/test
dart run coverage:format_coverage --check-ignore \
--packages=.dart_tool/package_config.json \
--report-on=packages/dust_dart/lib \
--in=packages/dust_dart/coverage \
--out=packages/dust_dart/coverage/lcov.info \
--lcov
awk 'BEGIN{lf=lh=0} /^LF:/{v=$0; sub("LF:","",v); lf+=v} /^LH:/{v=$0; sub("LH:","",v); lh+=v} END{printf("TOTAL LH=%d LF=%d %.2f%%\n", lh, lf, (lf?100*lh/lf:100)); exit(lh==lf && lf>0 ? 0 : 1)}' packages/dust_dart/coverage/lcov.info
The SerDeCodec abstract interface constructor is excluded with
coverage:ignore-line because package users can implement the interface but
cannot extend it from outside the defining library.
Libraries
- core
- Stable compatibility export for Dart-only Dust functional primitives.
- db
- Beta SQLx-style DB annotations and runtime for Dust.
- derive
- Derive annotations for Dust model generation.
- dust_dart
- Unified Dart runtime and annotations for Dust code generation.
- fp
- Stable functional primitives for Dust runtimes and generated code.
- http
- HTTP client annotations and runtime exports for Dust.
- serde
- SerDe annotations for Dust JSON generation.