gmana 0.2.0 copy "gmana: ^0.2.0" to clipboard
gmana: ^0.2.0 copied to clipboard

Convenience facade re-exporting gmana_functional, gmana_predicates, gmana_validation, gmana_utils, and gmana_extensions.

gmana #

Core Dart utilities for production apps: functional result types, focused extensions, validation primitives, ID generation, debounce/throttle helpers, and stream helpers.

gmana is pure Dart, so it works in CLI tools, server apps, packages, and Flutter apps.

For a function-by-function guide with examples, see doc/api.md.

Installation #

dart pub add gmana

For Flutter projects:

flutter pub add gmana

Manual pubspec.yaml setup:

dependencies:
  gmana: ^0.2.0

Imports #

Use the umbrella import when you want the curated public API:

import 'package:gmana/gmana.dart';

Or import only the area you need:

import 'package:gmana/extensions.dart';
import 'package:gmana/functional.dart';
import 'package:gmana/utilities.dart';
import 'package:gmana/validation.dart';

What You Can Use #

Area APIs
Functional results Either, Result, FutureResult, Failure, Unit, UseCase
String extensions casing, parsing, blank handling, slugs, duration parsing, truncation
Number and duration extensions 5.seconds, 2.hours, rounding, normalization, time formatting
Iterable and list extensions sum, average, median, chunked, groupBy, flatten, whereNotNull
Stream extensions debounce, throttle, scan, pairwise, whereNotNull, onErrorReturn
Validation email, password, number, text validators, and predicate functions
Utilities IdGenerator, Debouncer, Throttler

Functional Results #

Use Either<L, R> when a function can fail and you want explicit success and failure handling instead of exceptions.

import 'package:gmana/functional.dart';

Either<String, int> divide(int a, int b) {
  if (b == 0) return const Left('Cannot divide by zero');
  return Right(a ~/ b);
}

void main() {
  final result = divide(10, 2);

  final message = result.fold(
    (error) => 'Error: $error',
    (value) => 'Result: $value',
  );

  print(message); // Result: 5
}

Common helpers:

final result = Right<String, int>(10);

final doubled = result.map((value) => value * 2);
final value = doubled.getOrElse((error) => 0);
final isSuccess = doubled.isRight();
final nullable = doubled.rightOrNull();
final hasTwenty = doubled.contains(20);

final saved = await doubled
    .tap((value) => print('Saving $value'))
    .flatMapAsync((value) async => Right<String, int>(value + 1));

Use Cases #

UseCase gives clean-architecture style boundaries for asynchronous operations that may fail.

import 'package:gmana/functional.dart';

class LoginParams {
  const LoginParams({
    required this.email,
    required this.password,
  });

  final String email;
  final String password;
}

class LoginUseCase implements UseCase<String, LoginParams> {
  @override
  FutureResult<String> call(LoginParams params) async {
    if (params.email.trim().isEmpty) {
      return const Left(Failure('Email is required'));
    }

    return const Right('auth-token');
  }
}

For operations with no meaningful success value, return unit:

FutureResultUnit saveSettings() async {
  return const Right(unit);
}

Use StreamUseCase when a use case emits fallible values over time:

class WatchCountUseCase implements StreamUseCase<int, NoParams> {
  @override
  StreamResult<int> call(NoParams params) async* {
    yield const Right<Failure, int>(1);
    yield const Left<Failure, int>(Failure('Stream stopped', 'counter_stopped'));
  }
}

String Extensions #

import 'package:gmana/extensions.dart';

void main() {
  print('hello world'.toTitleCase); // Hello World
  print('hello world'.toSentenceCase); // Hello world
  print('helloWorld'.toSnakeCase); // hello_world
  print('Hello World! 2026'.toSlug); // hello-world-2026

  print('42'.toIntOrNull); // 42
  print('bad'.toIntOrZero); // 0
  print('12.5'.toDoubleOrNull); // 12.5

  print('01:30'.toDuration()); // 0:01:30.000000
  print('A long article body'.readingTimeMinutes); // 1
  print('Hello World'.truncate(8)); // Hello...
}

Nullable string helpers:

String? name;

final safe = name.orEmpty; // ''
final normalized = '   '.blankToNull; // null
final displayName = name.mapNotBlank((value) => value.toTitleCase);

Number And Duration Extensions #

import 'package:gmana/extensions.dart';

final retryDelay = 500.ms;
final timeout = 30.seconds;
final cacheTtl = 2.hours;

await retryDelay.delay;

print(25.celsiusToFahrenheit); // 77.0
print(260.normalized(0, 300).roundTo(2)); // 0.87
print(27.roundToMultiple(5)); // 25
print(3.to(6).toList()); // [3, 4, 5, 6]

Duration formatting:

final duration = Duration(hours: 1, minutes: 2, seconds: 34);

print(duration.toHumanizedString()); // 1:02:34
print(duration.toPaddedString()); // 01:02:34
print(duration.toVerboseString()); // 1h 2m 34s
print(duration.toWordString()); // 1 hour, 2 minutes, 34 seconds
print(Duration(minutes: -5).toRelativeString()); // 5 minutes ago

Iterable, List, And Stream Extensions #

import 'package:gmana/extensions.dart';

final scores = [10, 20, 30, 40];

print(scores.sum()); // 100
print(scores.average); // 25.0
print(scores.median); // 25.0
print(scores.top(2)); // [40, 30]
print(scores.runningSum().toList()); // [10, 30, 60, 100]

print([1, 2, 3, 4, 5].chunked(2).toList()); // [[1, 2], [3, 4], [5]]
print(['a', 'bb', 'c'].groupBy((value) => value.length)); // {1: [a, c], 2: [bb]}
print([[1, 2], [3, 4]].flattenToList()); // [1, 2, 3, 4]
print([1, null, 2].whereNotNull.toList()); // [1, 2]

Stream helpers:

final values = Stream.fromIterable([1, 1, 2, 3]);

values
    .distinctUntilChanged()
    .scan(0, (total, value) => total + value)
    .listen(print); // 1, 3, 6

List-emitting streams:

Stream.value([1, 2, 3, 4])
    .filter((value) => value.isEven)
    .listen(print); // [2, 4]

Validation #

The canonical validators return Either<Issue, Value>. A Right contains the normalized value. A Left contains a typed validation issue.

import 'package:gmana/validation.dart';

final emailResult = const EmailValidator().validate(' User@Example.com ');

emailResult.fold(
  (issue) => print(resolveEmailValidationIssue(issue)),
  (email) => print(email), // user@example.com
);

Strict email validation can reject disposable domains:

final validator = EmailValidator(EmailValidationConfig.strict());
final result = validator.validate('person@mailinator.com');
print(result.leftOrNull()?.code); // email.disposableDomain

Email validation is designed for product forms and domain input, not SMTP delivery checks. It trims whitespace, lowercases the returned address, rejects invalid local-part dot placement, enforces RFC email length limits, and returns typed issues you can map to your own localized messages.

const validator = EmailValidator(
  EmailValidationConfig(
    blockedDomains: {'example.test', 'internal.company'},
    disposableDomains: {'mailinator.com', 'tempmail.com'},
    rejectDisposable: true,
  ),
);

final result = validator.validate(' Person@MAIL.Example.Test ');

result.fold(
  (issue) {
    print(issue.code); // email.blockedDomain
    print(resolveEmailValidationIssue(issue));
  },
  (email) => print(email),
);

Configured domains are trimmed and compared case-insensitively. By default a configured domain also matches subdomains, so example.com blocks mail.example.com. Set matchSubdomains: false when you only want exact domain matches.

const exactOnly = EmailValidator(
  EmailValidationConfig(
    blockedDomains: {'example.com'},
    matchSubdomains: false,
  ),
);

print(exactOnly.validate('user@mail.example.com').rightOrNull());
// user@mail.example.com

Password validation:

final password = const PasswordValidator().validate('StrongP@ssw0rd');
final message = password.fold(resolvePasswordValidationIssue, (_) => null);
print(message); // null

final lenient = PasswordValidator(PasswordValidationConfig.lenient());
print(lenient.validate('abcd').isRight()); // true

Number and text validation:

final number = const NumberValidator(
  NumberValidationConfig(allowNegative: false, integerOnly: true),
).validate('42');

final text = TextValidator(
  TextValidationConfig(trimWhitespace: true, minLength: 3),
).validate('  abc  ');

print(number.rightOrNull()); // 42
print(text.rightOrNull()); // abc

For Flutter FormField.validator adapters, use package:gmana_form/gmana_form.dart.

Instant String Validation #

Use validation.dart for format checks and regex helpers:

import 'package:gmana/validation.dart';

print('test@example.com'.isValidEmail); // true
print('Jane Doe'.isValidName); // true
print('550e8400-e29b-41d4-a716-446655440000'.isValidUuid); // true
print('#F57224'.isValidHexColor); // true

You can also call the lower-level functions directly:

print(isEmail('test@example.com')); // true
print(isInt('42')); // true
print(isBase64('YWJjZA==')); // true
print(isHexColor('#F57224')); // true
print(isLength('hello', 2, 10)); // true

Utilities #

Generate IDs and encoded values:

import 'package:gmana/utilities.dart';

final uuid = IdGenerator.uuidV4Like();
final nanoid = IdGenerator.nanoid(size: 10);
final timestampId = IdGenerator.timestampId();
final random = IdGenerator.randomString(length: 12);
final encoded = IdGenerator.encodeToBase64(['user', 123]);

IdGenerator uses non-cryptographic randomness. Do not use these helpers for secrets, reset tokens, or security-sensitive identifiers.

Debounce and throttle repeated work:

final debouncer = Debouncer(milliseconds: 300);

debouncer.run(() {
  print('Runs after the user stops triggering events');
});

final throttler = Throttler(milliseconds: 1000);

throttler.run(() {
  print('Runs at most once per interval');
});

Package Relationship #

  • Use gmana for pure Dart extensions, validation, functional results, and utilities.
  • Use gmana_form for Flutter form fields and validator adapters.
  • Use gmana_flutter for Flutter widgets, theme helpers, color helpers, and UI convenience APIs.
  • Use gmana_value_objects for typed domain values when that package is part of your project.
2
likes
160
points
410
downloads

Documentation

API reference

Publisher

verified publishergmana.co

Weekly Downloads

Convenience facade re-exporting gmana_functional, gmana_predicates, gmana_validation, gmana_utils, and gmana_extensions.

Repository (GitHub)
View/report issues

Topics

#extensions #utility #validator #functional-programming

License

MIT (license)

Dependencies

gmana_extensions, gmana_functional, gmana_predicates, gmana_utils, gmana_validation

More

Packages that depend on gmana