Skip to content

Register Subscriptions with Context

Problem

You need to register GraphQL subscriptions with custom context data for filtering events and passing layer-specific information.

Enhancement Needed

Subscription patterns need team input for standardization and better context handling mechanisms.

Define context models for subscription-specific data:

@freezed
@JsonSerializable(includeIfNull: false)
class ChatMessageSubscriptionContext with _$ChatMessageSubscriptionContext {
const factory ChatMessageSubscriptionContext({
required String chatId,
required String currentUserId,
}) = _ChatMessageSubscriptionContext;
factory ChatMessageSubscriptionContext.fromJson(Map<String, dynamic> json) =>
_$ChatMessageSubscriptionContextFromJson(json);
Map<String, dynamic> toJson() => _$ChatMessageSubscriptionContextToJson(this);
}

Create subscription request with context handling:

class ChatMessageSubscriptionRequestStrategy
implements
RequestStrategy<
GSubscribeToChatMessagesReq,
SubscriptionRequestParams<ChatMessageSubscriptionContext>
>,
RequestHolder<GSubscribeToChatMessagesReq> {
@override
String get key => ChatMessageRequestStrategyKeys.messageSubscription.name;
@override
String get requestId => 'chat_message_subscription';
GSubscribeToChatMessagesReq? _request;
@override
GSubscribeToChatMessagesReq? get request => _request;
@override
GSubscribeToChatMessagesReq build(
SubscriptionRequestParams<ChatMessageSubscriptionContext> params,
) {
_request = GSubscribeToChatMessagesReq(
(b) => b
..requestId = '${requestId}_${params.context?.chatId ?? 'all'}'
..updateCacheHandlerKey = ChatMessageCacheHandlerStrategyKeys
.messageSubscriptionCacheHandler.name
..updateCacheHandlerContext = params.context?.toJson(),
);
return _request!;
}
}

Implement cache handler with context-based filtering:

class ChatMessageSubscriptionCacheHandlerStrategy
implements CacheHandlerStrategy<
GSubscribeToChatMessagesData,
GSubscribeToChatMessagesVars
> {
@override
UpdateCacheHandler build(RequestContext requestContext) {
return (proxy, response) {
final subscriptionItem = response.data?.messageSubscription;
final context = response.operationRequest.updateCacheHandlerContext;
if (context == null || subscriptionItem == null) {
return;
}
final subscriptionContext = ChatMessageSubscriptionContext.fromJson(context);
if (!_shouldProcessEvent(subscriptionContext, subscriptionItem)) {
return;
}
_updateMessageListCache(proxy, requestContext, subscriptionItem, subscriptionContext);
};
}
bool _shouldProcessEvent(
ChatMessageSubscriptionContext context,
GSubscribeToChatMessagesData_messageSubscription subscriptionItem,
) {
if (context.chatId != subscriptionItem.systemParentId) {
return false;
}
if (context.currentUserId == subscriptionItem.senderID) {
return false;
}
return true;
}
void _updateMessageListCache(
CacheProxy proxy,
RequestContext requestContext,
GSubscribeToChatMessagesData_messageSubscription subscriptionItem,
ChatMessageSubscriptionContext context,
) {
final filter = GmessageFilters(
(b) => b..systemParentId = subscriptionItem.systemParentId,
);
final listHolder = requestContext.getHolder<GQueryChatMessagesReq>(
ChatMessageRequestStrategyKeys.messageList.name,
);
final queryRequest = listHolder?.request ??
requestContext.execute<
GQueryChatMessagesReq,
ListRequestParams<GmessageFilters, GmessageOrder>
>(
ChatMessageRequestStrategyKeys.messageList.name,
ListRequestParams(filter: filter),
);
final prevData = proxy.readQuery(queryRequest);
if (prevData == null) return;
final parsedItem = GQueryChatMessagesData_message_messageItems.fromJson(
subscriptionItem.toJson(),
);
proxy.writeQuery(
queryRequest,
prevData.rebuild((b) {
return b
..message.messageItems.add(parsedItem)
..message.totalCount = (b.message.totalCount ?? 0) + 1;
}),
);
}
@override
String get key => ChatMessageCacheHandlerStrategyKeys.messageSubscriptionCacheHandler.name;
}

Implement subscription method in datasource:

class ChatMessageDatasource {
Stream<OperationResponse<GSubscribeToChatMessagesData, GSubscribeToChatMessagesVars>>
subscribeToMessages({
required String chatId,
required String currentUserId,
}) {
final context = ChatMessageSubscriptionContext(
chatId: chatId,
currentUserId: currentUserId,
);
final params = SubscriptionRequestParams<ChatMessageSubscriptionContext>(
context: context,
);
final subscription = requestContext.execute<
GSubscribeToChatMessagesReq,
SubscriptionRequestParams<ChatMessageSubscriptionContext>
>(ChatMessageRequestStrategyKeys.messageSubscription.name, params);
return client.request(subscription);
}
}

Use subscriptions in repository with proper context:

class ChatMessageRepository {
Stream<ChatMessageEntity> subscribeToMessages({
required String chatId,
required String currentUserId,
}) {
return datasource
.subscribeToMessages(
chatId: chatId,
currentUserId: currentUserId,
)
.map((response) => response.data?.messageSubscription)
.where((data) => data != null)
.map((data) => convertToChatMessage(data!));
}
}

Common subscription context scenarios:

  1. User Filtering - Skip events from current user
  2. Permission Checks - Filter based on user permissions
  3. Feature Flags - Enable/disable subscription features
  4. Rate Limiting - Control subscription frequency
  5. Data Enrichment - Add context-specific data

Current Limitations:

  1. Context Type Safety - Need better type enforcement
  2. Context Validation - Validate context data at runtime
  3. Context Inheritance - Share context between related subscriptions
  4. Context Caching - Cache context data for performance

Proposed Improvements:

  1. Typed Context Registry - Register context types with validation
  2. Context Middleware - Process context data before cache handlers
  3. Context Composition - Combine multiple context sources
  4. Context Debugging - Better debugging tools for context flow