data_shaft 1.0.1
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.
โจ 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
DataSourceexceptions toRepositoryerrors.
๐ 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 formatanddart testbefore submitting
๐งช Testing #
To run tests and see code coverage:
dart test
๐ License #
MIT ยฉ 2025 Coolosos