Skip to main content

cool_bedrock 3.0.0 copy "cool_bedrock: ^3.0.0" to clipboard
cool_bedrock: ^3.0.0 copied to clipboard

A bedrock package providing the blueprints and abstract definitions necessary to build a scalable and maintainable Dart/Flutter application.

cool_bedrock #

A bedrock package providing the blueprints and abstract definitions necessary to build a scalable and maintainable Dart/Flutter application.

Pub Version Pub Likes Pub Points Pub Downloads Dart SDK Version License codecov


โœจ Features #

  • ๐Ÿ›๏ธ Formalized Architecture: Strict base contracts for all layers: Entity, Params, AppService, and Codable.
  • ๐ŸŽฏ Domain Logic (Use Cases): Typified UseCase hierarchy for commands and queries, including assisted flow management (UseCaseHandler).
  • ๐Ÿ›ก๏ธ Functional Error Handling: Leverages the functional types Either<Failure, T> and Option
  • ๐Ÿ›‘ Typed Errors: Coherent error structure using sealed base classes: Issue, Failure (business logic), RepositoryError, and DataSourceException (technical/infrastructure).
  • ๐Ÿ”„ Reactive Services: Base classes for creating services that manage state using BehaviorSubject, PublishSubject, and periodic execution logic (Timer).
  • ๐Ÿงช Immutability & Testability: All core domain structures (Entity, Params) are immutable and comparable (Equatable).

๐Ÿš€ Installation #

Requires Dart >=3.13.0.

Add the package to your pubspec.yaml:

dependencies:
  cool_bedrock: ^3.0.0

Then run:

dart pub get

๐Ÿ“† Usage #

Basic usage #

import 'package:cool_bedrock/cool_bedrock.dart';

1. Creating the Use Case #

This example demonstrates how to implement a UseCase, defining its specific Failure and parameter validation:

// 1. Define the domain types: params, entity and the raw remote model.
final class FetchUserParams extends Params {
  const FetchUserParams({required this.userId});

  final String userId;

  // Checked automatically before executing the usecase.
  @override
  bool get isValid => userId.isNotEmpty;

  @override
  List<Object?> get props => [userId];
}

final class UserRemote {
  const UserRemote({this.name});

  final String? name;

  UserEntity toEntity() => UserEntity(name: name ?? 'Unknown');
}

final class UserEntity extends Entity {
  const UserEntity({required this.name});

  final String name;

  @override
  List<Object?> get props => [name];
}

// In this example the repository returns a Future<Either<Failure, UserRemote>>.

// 2. Define the specific Failure for this domain
sealed class FetchUserFailure extends Failure {
  const FetchUserFailure({super.message});
}

final class InvalidUserFailure extends FetchUserFailure {
  const InvalidUserFailure() : super(message: 'Invalid User ID provided.');

  @override
  List<Object?> get props => [message];
}

final class InvalidParamsUserFailure extends FetchUserFailure {
  const InvalidParamsUserFailure()
      : super(message: 'Invalid parameters provided.');

  @override
  List<Object?> get props => [message];
}

// 3. Implement the UseCase contract
final class FetchUserUseCase
    extends UseCase<UserEntity, FetchUserParams, FetchUserFailure> {
  const FetchUserUseCase(this.repository);

  final UserRepository repository;

  // Called automatically if params.isNotValid is true.
  @override
  FetchUserFailure onInvalidParams() => const InvalidParamsUserFailure();

  @override
  Future<Either<FetchUserFailure, UserEntity>> execute(
    FetchUserParams params,
  ) async {
    // Core logic goes here. Mappers and Repositories are typically called here.
    try {
      final result = await repository.fetch(params.userId);
      return result.fold(
        // Map the repository failure to a domain failure
        (failure) => const Left(InvalidUserFailure()),
        // Map the remote model to the entity
        (remote) => Right(remote.toEntity()),
      ); // Success
    } catch (e) {
      // Map low-level errors to high-level domain failures
      return const Left(InvalidUserFailure()); // Failure
    }
  }
}

// 4. Or use the Handler: obtain -> transform -> map errors
final class FetchUserUseCaseHandle
    extends UseCaseHandler<
      UserEntity,
      FetchUserParams,
      FetchUserFailure,
      UserRemote
    > {
  const FetchUserUseCaseHandle({required this.repository});

  final UserRepository repository;

  // Called automatically if params.isNotValid is true.
  @override
  FetchUserFailure onInvalidParams() => const InvalidParamsUserFailure();

  // Obtain repository values. Multiple repositories can be called.
  @override
  FutureOr<UserRemote> obtainValues(
    Resolver<FetchUserFailure> $,
    FetchUserParams params,
  ) async {
    // getValue unwraps a Future<Either<Issue, VALUE>> and maps its errors.
    return await $(getValue(() => repository.fetch(params.userId)));
  }

  @override
  UserEntity transformation(UserRemote values) {
    if (values.name == null || values.name!.isEmpty) {
      throw const UsecaseException(InvalidUserFailure());
    }
    // It can throw any exception, it will be handled by [wrapError].
    return values.toEntity();
  }

  @override
  FetchUserFailure wrapError(Object error, StackTrace stackTrace) {
    return const InvalidUserFailure();
  }
}

2. Execution and Error Handling #

// Execution with valid parameters
const validParams = FetchUserParams(userId: 'user_123');
final validResult = await fetchUserUsecase.call(validParams);

validResult.fold(
  // LEFT side (Failure)
  (failure) => print('Error: ${failure.message}'),
  // RIGHT side (Success)
  (user) => print('Fetched User: ${user.name}'),
);

๐Ÿ’ก Reactive Services Example #

The base services provide lifecycle control and reactivity. Here's a service that periodically updates a counter:

import 'package:cool_bedrock/cool_bedrock.dart';
import 'dart:async';

final class HeartbeatService extends TimerAndBehaviorService<int> {
  HeartbeatService()
      : super(periodicDuration: const Duration(seconds: 10));

  int _counter = 0;

  @override
  Future<void> work() async {
    // Logic that runs every 10 seconds
    _counter++;
    // Emit the new value to all subscribers
    add(_counter);
  }

  // start(), stop(), and dispose() logic is inherited and controlled externally.
}


๐Ÿ—๏ธ Use Case Types Comparison #

Choosing the right base class ensures your business logic is expressive and safe. Use this table as a quick guide to decide which one fits your needs:

Use Case Type Success Return Error Return Best For...
UseCase Right(Entity) Left(Failure) Standard business logic with manual error mapping.
UseCaseHandler Right(Entity) Left(Failure) Business logic with data fetching, transformation and error mapping.
OneWayUseCase Some(Entity) None Queries where the absence of a value is a valid result (e.g., Search).
OneWayFailureUseCase None (Success) Some(Failure) Standalone validations and guard checks (e.g., Is email taken?).

๐Ÿ“š API Reference #

Check the full API reference, including all generic types and abstract classes, on pub.dev โ†’ cool_bedrock.


Authors & Maintainers #

This project was created and is primarily maintained by:

๐Ÿค Contributing #

Contributions are welcome!

  • Open issues for bugs or feature requests
  • Fork the repo and submit a PR
  • Run dart format and dart test before submitting

๐Ÿงช Testing #

To run tests and see code coverage:

dart test

๐Ÿ“„ License #

MIT ยฉ 2025 Coolosos

1
likes
160
points
754
downloads

Documentation

API reference

Publisher

verified publisheryaminokishi.com

Weekly Downloads

A bedrock package providing the blueprints and abstract definitions necessary to build a scalable and maintainable Dart/Flutter application.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#architecture #clean-architecture #domain-driven-design #functional-programming #core

License

MIT (license)

Dependencies

collection, equatable, fpdart, meta, rxdart

More

Packages that depend on cool_bedrock