Skip to main content

smart_repository_generator 0.6.0 copy "smart_repository_generator: ^0.6.0" to clipboard
smart_repository_generator: ^0.6.0 copied to clipboard

Optional code generator for smart_repository data-layer adapters.

smart_repository_generator #

Optional declarative and adapter-mixin codegen for smart_repository. Generated code owns coordination boilerplate; application code owns source contracts, model mapping, domain errors, and mutations.

Installation #

dependencies:
  smart_repository: ^0.6.0

dev_dependencies:
  build_runner: ^2.0.0
  smart_repository_generator: ^0.6.0

Generator is optional. Manual SmartRepository, MappedRepository, and family construction remains supported.

lib/
  domain/
    user.dart
    user_repository.dart          # No smart_repository dependency
  data/
    user_models.dart              # UserDto, UserBox
    user_sources.dart             # Remote/local source ports
    user_repository_impl.dart     # Definition + concrete domain adapter
    user_repository_impl.g.dart   # Generated coordinator

Domain port stays ordinary:

abstract interface class UserRepository {
  Future<User> getUser(int id);
}

Data layer declares coordination:

part 'user_repository_impl.g.dart';

User userFromRemote(UserDto value) => value.toEntity();
User userFromLocal(UserBox value) => value.toEntity();
UserBox userToLocal(User value) => value.toBox();

@GenerateSmartRepository<UserRemoteDataSource, UserLocalDataSource>()
abstract class UserRepositoryDefinition {
  @CacheFirst(
    key: #id,
    mapRemote: #userFromRemote,
    mapLocal: #userFromLocal,
    mapToLocal: #userToLocal,
    maxAge: Duration(minutes: 10),
  )
  Future<User> getUser(int id);
}

Concrete domain adapter uses generated coordinator:

final class UserRepositoryImpl implements UserRepository {
  UserRepositoryImpl({required remote, required local})
      : _coordinator = _$UserRepositoryDefinition(
          remote: remote,
          local: local,
        );

  final _$UserRepositoryDefinition _coordinator;

  @override
  Future<User> getUser(int id) async =>
      (await _coordinator.getUser(id)).getOrThrow();

  Future<void> dispose() => _coordinator.dispose();
}

Use fold instead of getOrThrow when domain uses Either, Result, or a custom failure type. Full runnable version lives in the Clean Architecture example.

Policies #

Annotate each method with one policy:

@GenerateSmartRepository<UserRemoteDataSource, UserLocalDataSource>()
abstract class UserQueries {
  @NetworkFirst()
  Future<User> getCurrentUser();

  @CacheFirst(maxAge: Duration(minutes: 10))
  Future<List<User>> getUsers();

  @StaleWhileRevalidate()
  Future<Profile> getProfile();
}

@RepositoryMethod() uses definition's defaultPolicy. Source method names default to definition method name. Override with remote: #fetchUser or local: #readUser. Local writer defaults from getUser to saveUser; override with saveLocal: #putUser.

Mapper symbols are required only when source models differ from domain return type. Generated constructor accepts optional fallbackWhen and mapError. Generated coordinator exposes each core repository as <method>Repository and provides dispose().

Methods may use positional, named, optional, and default parameters. One parameter becomes family key. Multiple parameters become generated composite record key, then expand back into original source method call:

@CacheFirst(maxAge: Duration(minutes: 10))
Future<List<User>> getUsers(
  String name, {
  required DateTime date,
  int page = 1,
});

Every argument participates in cache identity and request deduplication. Local writer may accept value only, or same query parameters followed by value.

Sensitive one-shot operations should release their key after completion:

@NetworkOnly(
  mapRemote: #sessionFromRemote,
  persistRemote: false,
  retainKeyedRepository: false,
)
Future<Session> login(String email, String password);

Direct contract implementations #

Use @GenerateRepositoryImplementation when separate definition plus concrete adapter would duplicate every method. Annotated class becomes public contract; generated _$ClassName becomes redirecting-factory target.

Class-level options supply repeated mapping, error, result, persistence, and lifecycle behavior. remoteBody maps method parameters into API body keys. Unannotated inherited methods automatically delegate to equally named local source methods.

@GenerateRepositoryImplementation<AuthApi, AuthLocalSource>(
  defaultPolicy: RepositoryPolicy.networkOnly,
  resultType: Session,
  mapRemote: #sessionFromRemote,
  mapError: #mapFailure,
  resultAdapter: #toEither,
  persistRemote: false,
  retainKeyedRepository: false,
)
abstract class AuthRepositoryImpl implements AuthRepository {
  factory AuthRepositoryImpl({
    required AuthApi remote,
    required AuthLocalSource local,
  }) = _\$AuthRepositoryImpl;

  @RepositoryMethod(
    remoteBody: {'email': #email, 'password': #password},
  )
  @override
  Future<Either<Failure, Session>> login(String email, String password);

  Future<void> dispose();
}

resultAdapter receives RepositoryResult<resultType>. Package stays unaware of Either, app-specific failures, or other domain result abstractions.

Sync, CRUD, and database streams #

Use @RepositoryOperation when public result differs from remote response and persisted value. This is common in paginated repositories where sync returns bool, remote returns a page DTO, and local source stores entity boxes.

@GenerateRepositoryImplementation<
  VehicleRemoteDataSource,
  VehicleLocalDataSource
>(
  mapError: #mapFailure,
  resultAdapter: #toEither,
)
abstract class VehicleRepositoryImpl implements VehicleRepository {
  factory VehicleRepositoryImpl({
    required VehicleRemoteDataSource remote,
    required VehicleLocalDataSource local,
  }) = _$VehicleRepositoryImpl;

  @RepositoryOperation(
    resultType: bool,
    remote: #getVehicles,
    request: #vehicleQuery,
    commit: #saveVehiclePage,
    mapResult: #vehicleHasNext,
    recover: #recoverVehiclePage,
    keyBy: [#page],
  )
  Future<Either<Failure, bool>> syncVehicles({
    required int page,
    required int perPage,
  });

  @RepositoryOperation(
    remote: #deleteVehicle,
    request: #vehicleId,
    commit: #deleteLocalVehicle,
  )
  Future<Either<Failure, void>> deleteVehicle({required int id});

  @RepositoryLocalStream(map: #vehiclesFromBoxes)
  Stream<List<Vehicle>> streamVehicles(int page);
}

Callback conventions:

  • request: receives repository method arguments and returns sole remote argument.
  • mapResult: receives raw remote value followed by method arguments.
  • commit: receives local source, raw remote value, then method arguments. For Future<void> remote methods, raw value is omitted.
  • recover: receives error, stack trace, then method arguments and returns RepositoryRecovery<RemoteType>.
  • resultAdapter: converts RepositoryResult<T> into application result type.
  • keyBy: selects parameters used only for in-flight deduplication; completed keys are removed immediately.

@RepositoryLocalStream forwards local database streams and optionally maps each emitted local value to domain value. Full compiled example: example/lib/data/vehicle_repository_impl.dart.

Adapter mixins #

Use @SmartRepositoryAdapter<T, R, L>() for one value or @SmartRepositoryFamilyAdapter<K, T, R, L>() for keyed values. Adapter mixins remain useful when source selection or mapping cannot follow declarative method conventions.

Generated mixins provide smartGet, smartRefresh, smartSetLocal, smartWatch, invalidation, clearing, removal for families, and disposal. Override smartFallbackWhen or smartRepositoryConfig as needed.

Generation #

Generate once:

dart run build_runner build

Regenerate continuously:

dart run build_runner watch
0
likes
0
points
67
downloads

Publisher

verified publisherpinz.dev

Weekly Downloads

Optional code generator for smart_repository data-layer adapters.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

analyzer, build, smart_repository, source_gen

More

Packages that depend on smart_repository_generator