gmana

Core Dart utilities for production apps: functional result types, focused extensions, validation primitives, ID generation, debounce/throttle helpers, and small design-system constants.

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.1.5

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, Left, Right, Failure, Unit, UseCase, FutureEither
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 form-validator adapters
Utilities IdGenerator, Debouncer, Throttler, GSpacing, waveVerticalOffset

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();

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
  FutureEither<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:

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

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

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 = const TextValidator(
  TextValidationConfig(trimWhitespace: true, minLength: 3),
).validate('  abc  ');

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

Flutter form adapters:

final emailFormValidator = asFormValidator(
  validate: const EmailValidator().validate,
  resolve: resolveEmailValidationIssue,
);

print(emailFormValidator('invalid-email')); // Please enter a valid email address
print(emailFormValidator('user@example.com')); // null

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.uuidV1();
final nanoid = IdGenerator.nanoid(size: 10);
final timestampId = IdGenerator.timestampId();
final random = IdGenerator.randomString(length: 12);
final encoded = IdGenerator.encodeToBase64(['user', 123]);

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_flutter for Flutter widgets, form fields, theme helpers, color helpers, and UI convenience APIs.
  • Use gmana_value_objects for typed domain values when that package is part of your project.

Libraries

design_system/spacing
either/either
either/left
either/use_case
extensions
extensions/duration_ext
extensions/duration_natural_language_ext
extensions/is_ext
extensions/iterable_ext
extensions/list_ext
extensions/natural_duration_ext
extensions/num_duration_extension
extensions/num_ext
extensions/stream_ext
extensions/string_ext
functional
gmana
is/contains
is/equals
is/is_after
is/is_alpha
is/is_alpha_numeric
is/is_ascii
is/is_base64
is/is_before
is/is_byte_length
is/is_credit_card
is/is_date
is/is_divisible_by
is/is_email
is/is_f_q_d_n
is/is_float
is/is_full_width
is/is_half_width
is/is_hex_color
is/is_hexadecimal
is/is_i_s_b_n
is/is_int
is/is_json
is/is_length
is/is_lower_case
is/is_mongo_id
is/is_multi_byte
is/is_null
is/is_numeric
is/is_postal_code
is/is_surrogate_pair
is/is_upper_case
is/is_uuid
is/is_variable_width
is/matches
math/wave_vertical_offset
regex/alpha_numeric_reg
regex/alpha_reg
regex/ascii_reg
regex/base64_reg
regex/credit_card_reg
regex/digit_reg
regex/email_reg
regex/five_digit
regex/float_reg
regex/four_digit
regex/full_width_reg
regex/half_width_reg
regex/hex_color_reg
regex/hexadecimal_reg
regex/int_reg
regex/ipv4_maybe
regex/ipv6
regex/isbn10_maybe_reg
regex/isbn13_maybe_reg
regex/lower_case_reg
regex/multi_byte_reg
regex/numeric_reg
regex/phone_reg
regex/postal_code_patterns
regex/six_digit
regex/special_char_reg
regex/surrogate_pairs_reg
regex/three_digit
regex/upper_case_reg
regex/url_reg
regex/uuid_reg
regex/white_space_reg
services/id_generator_service
utilities
utils/debouncer
utils/id_generator
utils/id_generator_utils
utils/throttler
validation
validator/email_validator
validator/form_validator_adapter
validator/number_validator
validator/password_validator
validator/text_validator
validator/validation_issue