import { ConversationInboxSnapshot, ID, InboxViewId, UserInboxItemView } from './Conversation'; import { ConversationQueue } from './Queue'; import { ConversationAssignmentChanged } from './Transfer'; export type InboxProcessorName = 'inbox-audience-resolver' | 'inbox-item-projector' | 'inbox-counter-projector'; /** * Queue membership snapshot used while resolving an assignment change. * * Produced by: assignment-change consumer. * Consumed by: InboxAudienceResolver. * Used when: previous/current queue audiences must be expanded from groups * into concrete users without embedding membership in the integration event. */ export interface QueueAudienceResolutionContext { queue: ConversationQueue; memberUserIds: ID[]; activeMemberUserIds: ID[]; supervisorUserIds: ID[]; } /** * Complete input for calculating desired inbox visibility. * * Produced by: ConversationAssignmentChanged consumer. * Consumed by: InboxAudienceResolver. * Used when: an assignment/transfer changes queue, owner or status and the * desired per-user inbox audience must be recalculated. */ export interface InboxAudienceResolverInput { sourceEvent: ConversationAssignmentChanged; snapshot: ConversationInboxSnapshot; previousQueue?: QueueAudienceResolutionContext; currentQueue?: QueueAudienceResolutionContext; participantUserIds: ID[]; followerUserIds: ID[]; } /** * Visibility of one conversation inside one logical inbox view. * * Produced by: InboxAudienceResolver. * Consumed by: inbox item projection planner/projector. * Used when: multiple independent reasons can preserve the same view without * generating duplicate inbox items or duplicate counter increments. */ export type ResolvedInboxView = UserInboxItemView; /** * Desired inbox visibility for a single user after applying the source event. * * Produced by: InboxAudienceResolver. * Consumed by: inbox item projection planner/projector. * Used when: the projector compares desired visibility with the materialized * user_inbox_items state. */ export interface ResolvedInboxUserVisibility { userId: ID; views: ResolvedInboxView[]; } /** * Full audience resolution result for one conversation version. * * Produced by: InboxAudienceResolver. * Consumed by: inbox item projection planner/projector. * Used when: all affected users must be diffed against their existing * user_inbox_items documents. */ export interface ResolvedInboxAudience { sourceEventId: ID; spaceId: ID; conversationId: ID; conversationVersion: number; snapshot: ConversationInboxSnapshot; users: ResolvedInboxUserVisibility[]; resolvedAt: Date; } /** * Processor boundary for pure audience calculation. * * Called by: ConversationAssignmentChanged consumer. * Returns to: inbox item projection planner/projector. */ export interface InboxAudienceResolver { resolve(input: InboxAudienceResolverInput): Promise; } export type InboxProjectionOperation = 'insert' | 'update' | 'delete'; /** * Idempotent mutation plan for one user's materialized conversation item. * * Produced by: inbox item projection planner. * Consumed by: inbox item projector; after persistence it is also the input * used to derive InboxCounterDelta messages. * Used when: desired audience is compared with the existing projection. * * id must be deterministic from: * sourceEventId + userId + conversationId. */ export interface InboxProjectionDelta { id: ID; sourceEventId: ID; spaceId: ID; userId: ID; conversationId: ID; conversationVersion: number; operation: InboxProjectionOperation; previous?: ResolvedInboxUserVisibility; current?: ResolvedInboxUserVisibility; snapshot?: ConversationInboxSnapshot; createdAt: Date; } /** * Atomic counter change for one user/view caused by one projection delta. * * Produced by: inbox item projector after determining the actual persisted * before/after state. * Consumed by: InboxCounterProjector. * Used when: view membership, requiresAction or unread state changes. * * id must be deterministic from: * sourceEventId + userId + conversationId + viewId. */ export interface InboxCounterDelta { id: ID; sourceEventId: ID; spaceId: ID; userId: ID; conversationId: ID; viewId: InboxViewId; conversationVersion: number; totalDelta: -1 | 0 | 1; requiresActionDelta: -1 | 0 | 1; unreadDelta: -1 | 0 | 1; createdAt: Date; } /** * Durable idempotency receipt for a processor side effect. * * Produced and consumed by: inbox item projector or InboxCounterProjector. * Used when: the same outbox event/delta is delivered more than once. * * Unique key: * processor + sourceEventId + scopeKey. * * scopeKey examples: * - item projector: userId + conversationId * - counter projector: userId + conversationId + viewId */ export interface InboxProcessorReceipt { id: ID; processor: InboxProcessorName; sourceEventId: ID; scopeKey: string; spaceId: ID; conversationId: ID; conversationVersion: number; processedAt: Date; } /** * Processor boundary for applying counter deltas idempotently. * * Called by: inbox item projector. * Mutates: user_inbox_counters and InboxProcessorReceipt in one transaction. */ export interface InboxCounterProjector { apply(delta: InboxCounterDelta): Promise; }