data_shaft 1.0.1 copy "data_shaft: ^1.0.1" to clipboard
data_shaft: ^1.0.1 copied to clipboard

High-level data layer for Clean Architecture with standardized Drivers, typed DataSources, and Repository mixins for caching, deduplication, and safe error handling.

Data Shaft ๐Ÿš€ #

Data Shaft is a modular, robust, and type-safe data layer framework for Dart and Flutter. Built on Clean Architecture principles, it provides a standardized way to manage remote and local data sources, repositories, and error handling.

This package eliminates boilerplate code and solves common challenges such as error mapping, memory caching, and request deduplication.

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


โœจ Key Features #

  • โœ… Standardized Remote Drivers: Decouple your app from HTTP clients (Dio, Http, etc.).
  • โœ… Typed DataSources: Specialized mixins for GET, POST, PUT, PATCH, and DELETE operations.
  • โœ… Advanced Repository Mixins: Built-in Memory Cache, Request Deduplication, and Safe Execution.
  • โœ… Full Observability: Lifecycle and network logging powered by dart:developer.
  • โœ… Structured Error Handling: Automatic mapping from DataSource exceptions to Repository errors.

๐Ÿš€ Implementation Examples #

1. Implementing the Driver (with Dio) #

The RemoteDriver acts as an adapter. Here is how you bridge Dio with Data Shaft:

import 'package:dio/dio.dart';
import 'package:data_shaft/data_shaft.dart';

class DioRemoteDriver implements RemoteDriver {
  final Dio dio;
  DioRemoteDriver(this.dio);

  @override
  Future<RequestResponse> get(Uri uri, {Map<String, String>? headers, Object? options}) async {
    final response = await dio.getUri(uri, options: options as Options);
    return RequestResponse(
      statusCode: response.statusCode ?? 500,
      body: response.data.toString(),
      headers: response.headers.map.map((k, v) => MapEntry(k, v.join(','))),
      originalResponse: response,
    );
  }

  // Implement post, put, patch, delete following the same pattern...
}

2. DataSource #

2.1 Datasource Pre-build class

DataSources are specialized for specific operations. Use the pre-built base classes to save time:

class GetUserDataSource extends DatasourceGetRemote<User, MyDriver> {
  GetUserDataSource({required super.driver});

  @override
  GetParams? generateCallRequirement({required Params params}) {
    return GetParams(urlParams: {'id': params.id});
  }
}

2.1 Datasource using Mixins

You can use mixins directly on a DatasourceRemote to define request behavior without deep inheritance:

class UpdateUserDataSource extends DatasourceRemote<User, DioRemoteDriver> 
    with PatchCall<User, DioRemoteDriver> {
  UpdateUserDataSource({required super.driver});

  @override
  PatchParams generateCallRequirement({required Params params}) {
    return PatchParams(
      encodeBody: () => json.encode({'name': params.name}),
    );
  }
}

3. Safe Repository Execution #

The SafeRepositoryDatasourceCallable catches all exceptions and converts them into Either types, shielding your Domain layer from crashes:

// The Repository handles safety, mapping, and deduplication
final class GetUserDetailRepository extends DeduplicationRepository<User, GetUserDataSource> {
  GetUserDetailRepository({required super.dataSource});
  
  // Calling this repository returns: Future<Either<RepositoryError, User>>
}

// Usage in a Bloc, Controller, or UseCase:
final result = await userRepository(repositoryParams: UserParams(id: '123'));

result.fold(
  (error) => print('Error: ${error.message}'), // Inadmissible, UnControl, etc.
  (user) => print('User loaded: ${user.name}'),
);

3.1 Repository using Mixins

You can use mixins directly on a Repository to define datasource reply without deep inheritance:

class GetUserDetailRepository extends RepositoryDataSourceCallable<User, GetUserDataSource>
    with DeduplicationManagement<User, GetUserDataSource>, SafeRepositoryHelper<User> {
  
  GetUserDetailRepository({required super.dataSource});

  @override
  Future<Either<RepositoryError, User>> call({
    required covariant Params repositoryParams,
  }) async {
    return safeCall(
      call: () => super.call(repositoryParams: repositoryParams),
    );
  }

  @override
  RepositoryError Function(InadmissibleDataSourceException, StackTrace)
      get onInadmissibleException => (exception, stackTrace) {
            return InadmissibleRepositoryError(
              message: 'User not found or unauthorized access',
            );
          };

  @override
  RepositoryError Function(UnControlDataSourceException, StackTrace)
      get onUnControlException => (exception, stackTrace) {
            return const UnControlRepositoryError(
              message: 'Communication error with the remote server',
            );
          };

  @override
  RepositoryError Function(Object, StackTrace) 
      get onException => (exception, stackTrace) {
            return const OnExceptionRepositoryError(
              message: 'Unexpected error during user data processing',
            );
          };
}

๐Ÿ›  Layered Architecture #

๐Ÿ›ก Safety First #

The framework manages three error levels to ensure your UI never receives an unhandled exception:

  • InadmissibleRepositoryError: Controlled business failures (e.g., 404 Not Found).
  • OnExceptionRepositoryError: Failures during data transformation or orchestration.
  • UnControlRepositoryError: Unexpected failures (Null pointers, unexpected types).

โฑ Smart Caching #

Use SafeMemoryCacheRepository to get an out-of-the-box memory cache with a configurable refreshDuration.

๐Ÿ‘ฏ Deduplication #

Prevent redundant requests. If two identical calls are triggered simultaneously, DeduplicationManagement ensures both wait for the same result, saving bandwidth and backend resources.


๐Ÿ“Š Observability #

Data Shaft features an integrated logging system using dart:developer tags. You can filter your console by DS.REMOTE to see network traffic or REPO to see business logic flow.

// Example console output:
// [DS.REMOTE.GetUserDataSource] ๐Ÿ”› CALLING: [https://api.com/user/1](https://api.com/user/1)
// [REPO.GetUserRepository] โœ… Safe Call Complete | Result: Instance of 'User'

Customize logging by implementing HttpDatasourceObserver or RepositoryObserver:

DatasourceObserverInstances.httpDatasourceObserver = MyCustomLogStrategy();

๐Ÿ“š 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

0
likes
0
points
460
downloads

Publisher

verified publisheryaminokishi.com

Weekly Downloads

High-level data layer for Clean Architecture with standardized Drivers, typed DataSources, and Repository mixins for caching, deduplication, and safe error handling.

Homepage
Repository (GitHub)
View/report issues

Topics

#data #clean-architecture #repository #abstraction #architecture

License

unknown (license)

Dependencies

cool_bedrock, meta

More

Packages that depend on data_shaft