Repository Context Usage
You need to pass context data to repository methods that isnât part of the GraphQL request but is needed for internal filtering and business logic decisions.
Repository context patterns need team input for standardization and better type safety mechanisms.
Repository Context Pattern
Section titled âRepository Context PatternâRepository context allows passing data needed for layer logic that isnât tied to the GraphQL request:
typedef ChatContext = ({String currentUserId});
class ChatsRepository implements QueryableRepository< PaginatedChatsModel, ChatModel, ChatFilterCriteria, ChatSortCriteria >, WritableRepository<ChatModel, ChatUpsertVars>, SubscribableRepository<ChatModel, Object?> {
ChatsRepository({required this.datasource});
final ChatDatasource datasource;
@override Stream<PaginatedChatsModel> queryList({ int? skip, int? take, ChatFilterCriteria? filter, ChatSortCriteria? sort, Object? context, }) { if (context is! ChatContext) { throw ArgumentError( 'ChatContext with currentUserId is required for chat normalization', ); }
return datasource .queryList( skip: skip, take: take, filter: filter?.toGql, sort: sort?.toGql, ) .map((response) => response.data?.chat) .map((data) => convertToPaginatedChat(data, context.currentUserId)); }}Context-Based Data Normalization
Section titled âContext-Based Data NormalizationâUse context for data normalization that requires external information:
ChatModel _buildNormalizedChatModel({ required String id, required String? lastMessage, required DateTime createdAt, required DateTime updatedAt, required AttendeeModel user1, required AttendeeModel user2, required String currentUserId, required bool? isBlockedByUser1, required bool? isBlockedByUser2, int? childMessageCount,}) { final bool user1IsCurrent = user1.id == currentUserId;
if (!user1IsCurrent && user2.id != currentUserId) { throw StateError('currentUserId does not belong to this chat'); }
return ChatModel( id: id, lastMessage: lastMessage, createdAt: createdAt, updatedAt: updatedAt, currentUser: user1IsCurrent ? user1 : user2, otherUser: user1IsCurrent ? user2 : user1, childMessageCount: childMessageCount ?? 0, isBlockedByCurrentUser: user1IsCurrent ? isBlockedByUser1 ?? false : isBlockedByUser2 ?? false, isBlockedByOtherUser: user1IsCurrent ? isBlockedByUser2 ?? false : isBlockedByUser1 ?? false, );}Context Validation
Section titled âContext ValidationâImplement proper context validation:
@overrideStream<ChatModel?> queryOne({ required ChatFilterCriteria filter, Object? context,}) { if (context is! ChatContext) { throw ArgumentError( 'ChatContext with currentUserId is required for chat normalization', ); }
return datasource .queryOne(filter: filter.toGql) .map((response) => response.data?.chat) .map((data) => convertToChat(data, context.currentUserId));}Context in Mutations
Section titled âContext in MutationsâUse context for mutation operations that need additional data:
@overrideStream<ChatModel> upsert({required ChatUpsertVars vars, Object? context}) { if (context is! ChatContext) { throw ArgumentError( 'ChatContext with currentUserId is required for chat normalization', ); }
return datasource.upsert(vars: vars.toGql).map((response) { final result = convertFromMutationToChat( response.data?.chatSave, context.currentUserId, );
if (result == null) { throw ChatUpsertException( code: CRUDExceptionCode.NO_DATA, stackTrace: StackTrace.current.toString(), originalException: Exception( 'No data received from server $vars, $result', ), ); }
return result; });}Context in Subscriptions
Section titled âContext in SubscriptionsâPass context to subscription methods for filtering:
@overrideStream<ChatModel> subscribe(Object? context) { if (context is! ChatContext) { throw ArgumentError( 'ChatContext with currentUserId is required for chat normalization', ); }
return datasource.subscribe().map((response) { final result = convertFromSubscriptionToChat( response.data?.chatSubscription, context.currentUserId, );
if (result == null) { throw ChatUpsertException( code: CRUDExceptionCode.NO_DATA, stackTrace: StackTrace.current.toString(), originalException: Exception('No data received from server, $result'), ); }
return result; });}Service Layer Context Usage
Section titled âService Layer Context UsageâServices provide context when calling repository methods:
class ChatService { final ChatsRepository _repository; final AuthService _authService;
Stream<PaginatedChatsModel> getChats({ int? skip, int? take, ChatFilterCriteria? filter, ChatSortCriteria? sort, }) { final context = (currentUserId: _authService.currentUserId);
return _repository.queryList( skip: skip, take: take, filter: filter, sort: sort, context: context, ); }
Stream<ChatModel?> getChatById(String chatId) { final context = (currentUserId: _authService.currentUserId);
return _repository.queryOne( filter: ChatFilterCriteria(id: chatId), context: context, ); }}Complex Context Types
Section titled âComplex Context TypesâFor complex scenarios, use structured context types:
@freezedclass HotelBookingContext with _$HotelBookingContext { const factory HotelBookingContext({ required String userId, required UserRole userRole, String? organizationId, List<String>? permissions, Map<String, dynamic>? metadata, }) = _HotelBookingContext;}
class HotelBookingsRepository { Stream<PaginatedHotelBookingsEntity> queryList({ int? skip, int? take, HotelBookingFilterCriteria? filter, HotelBookingSortCriteria? sort, Object? context, }) { final bookingContext = context as HotelBookingContext?;
return datasource .queryList( skip: skip, take: take, filter: _applyContextFilters(filter, bookingContext), sort: sort?.toGql, ) .map((response) => response.data?.hotelbookings) .map((data) => convertToPaginatedHotelBookings(data, bookingContext)); }
HotelBookingFilterCriteria? _applyContextFilters( HotelBookingFilterCriteria? filter, HotelBookingContext? context, ) { if (context == null) return filter;
return (filter ?? HotelBookingFilterCriteria()).copyWith( userId: context.userRole == UserRole.admin ? null : context.userId, organizationId: context.organizationId, ); }}Context Use Cases
Section titled âContext Use CasesâCommon repository context scenarios:
- User Normalization - Determine current vs other user in relationships
- Permission Filtering - Filter data based on user permissions
- Organization Scoping - Scope data to userâs organization
- Feature Flags - Enable/disable features based on context
- Audit Information - Track who performed operations
- Localization - Format data based on user locale
Team Enhancement Areas
Section titled âTeam Enhancement AreasâCurrent Limitations:
- Type Safety - Context is
Object?without compile-time validation - Context Inheritance - No way to compose contexts
- Context Validation - Runtime validation only
- Context Documentation - No clear contract for required context
Proposed Improvements:
- Typed Context Interface - Define context contracts per repository
- Context Composition - Combine multiple context sources
- Context Middleware - Process context before repository methods
- Context Validation Framework - Compile-time context validation
Next Steps
Section titled âNext Stepsâ- Learn about service layer patterns